Okta FastPass is Okta’s phishing-resistant authenticator which is available on iOS, macOS, Windows, and Android devices. A few years ago, some of our customers reported that FastPass was not functioning properly in certain Windows virtual environments. So, we set out to identify and resolve the issues. It proved to be an interesting challenge.
In this blog post, we’ll focus on Okta Verify and FastPass for Windows. We’ll explore why it didn’t work in certain virtual environments and how Okta’s engineers creatively solved the technical challenges to bring our authenticator to virtual environments.
For more information about FastPass, take a look at our FastPass deep dive and our FastPass technical whitepaper.
Why didn’t FastPass work in virtual environments?
There are two main reasons why FastPass didn’t work well in virtual environments. The first had to do with some of its dependencies on the device and the second had to do with user verification with Windows Hello. We’ll explore each of these problems in detail, but before we do, let’s look at the types of virtual environments and how they differ.
What are VDIs?
VDI stands for “virtual desktop infrastructure.” It is a term that encompasses any virtualization infrastructure designed to give users access to a virtual machine in desktop form. The term VDI can refer to both the infrastructure and to the desktop to which a user connects.
Types of VDIs
There are three main types of VDIs:
- Persistent
- Non-persistent
- Layered
Persistent VDIs
A persistent VDI is one where the VDI’s state and all of the user’s data are maintained between sessions. For example, when a user creates a virtual machine on their laptop, it functions like a persistent VDI. Each time the user connects, their desktop state and all of their data are preserved.
In an environment where there are multiple users and multiple virtual machines, the user is typically statically assigned to the machine. This means that a user connects to the same virtual machine each time they log on. For this reason, persistent VDIs are also sometimes called static VDIs.
Non-persistent VDIs
In a non-persistent VDI, the state of the virtual machine, including all of the user’s data, does not persist between sessions. The virtual machine is wiped or restored from a clean master image between sessions. In most environments, there is a pool of fresh virtual machines from which the user's machine is randomly selected. In a non-persistent VDI environment, the user may be connecting to a different virtual machine each time they log on.
Layered VDIs
Some infrastructure providers offer a third VDI type which is a hybrid of persistent and non-persistent VDI. These environments are typically divided into distinct layers. There is a user layer, an application layer, a machine layer, etc. For this reason, we call these environments “layered VDIs”. In a layered VDI environment, the user connects to a non-persistent virtual machine, but a user profile service syncs the user’s data so that it is preserved between sessions. Like in a non-persistent VDI environment, the user may be connecting to a different virtual machine each time they log on. But like a persistent VDI, their data is preserved between sessions.
Layered VDI environments are the most difficult type of virtual environment to support since the user is effectively roaming between virtual machines. We’ll focus on the technical challenges of supporting layered VDIs, then briefly discuss which challenges and solutions were also used to support static VDIs.
Supporting layered VDIs
In order to support layered VDI environments, there were four components of Okta Verify and FastPass that needed to be addressed:
- The service account used to protect the proof-of-possession cryptographic key pair
- TPM-protected cryptographic keys
- Okta Verify’s locally persisted credential
- The identifying device information reported to the Okta backend
We’ll take a look at each of these components in detail.
The Okta Verify service account
One of the cryptographic key pairs that Okta Verify uses for authentication is the proof-of-possession (PoP) key pair. The PoP key can be used for silent single-factor authentication where there is no user interaction. Therefore, it must be protected to ensure that another process on the machine cannot access and use it without the user’s knowledge. In the past, Okta Verify created a local Windows account that was used to protect the (PoP) key. We call this account the Okta Verify service, or sandbox account. Okta Verify impersonates the service account during PoP key operations. Since this account is a local user account, it cannot roam with the user between virtual machines in a layered VDI environment.
Our solution: To protect the PoP key in virtual environments, we utilized passcode key protection. On application start, Okta Verify derives a key protection passcode. When Okta Verify creates a PoP key using Win32 APIs, we use the NCryptSetProperty function with the NCRYPT_PIN_PROPERTY identifier to set the PIN property on the key handle, and we provide our key protection passcode. Setting this property instructs the operating system to create a key that is protected by our key protection passcode. Post-creation, when the key is used, the correct passcode must be provided. This prevents other processes on the machine from loading and using the PoP key since they do not possess our key protection passcode.
TPM-protected cryptographic keys
Okta Verify protects the cryptographic keys it creates with the machine’s Trusted Platform Module (TPM), if one is available. Even in many virtual environments, Okta Verify will protect the keys with the TPM since many virtual environments have virtual TPMs. However, in a layered VDI environment Okta Verify can’t use the TPM to protect its keys. When Okta Verify protects its keys with a TPM, those keys are cryptographically bound to that TPM. If Okta Verify moves to a different machine with a different TPM, as is the case in a layered VDI environment, it is unable to access the keys that it created and is unable to authenticate the user.
Our solution: In layered VDI environments, Okta Verify stores its cryptographic keys in a software key storage provider in a non-exportable format. The software key storage provider is provided and implemented by the Windows operating system that stores the keys as a file in the user’s directory. When the layered VDI’s user profile syncing service syncs the user’s directory, all of the user’s keys stored in the software key store will be synced. See Microsoft's key storage documentation for more information.
The Okta Verify credential
Okta Verify stores an application credential in the Windows Credential Manager which is used to protect application resources. Okta Verify used to always set the Persist property on the credential to CRED_PERSIST_LOCAL_MACHINE, which would persist the credential on the local machine. A locally persisted credential cannot be accessed by Okta Verify on a different virtual machine.
Our solution: To allow the credential to roam with the user profile between virtual machines in a layered VDI environment, we set the Persist property to CRED_PERSIST_ENTERPRISE.
Device Information
Okta Verify collects two identifying pieces of device information that are reported to the Okta backend:
- The device name
- The security identifier (SID) of the device
In layered VDI environments, both of these device identifiers could change between sessions, which could cause unexpected behavior such as failed authentication.
Our solution: To report consistent device identifiers in layered VDI environments, we use the user’s SID as the base identifier since it will not change between sessions. From it, we derive a device name and we report it directly as the SID of the device.
After we addressed each of these items, Okta Verify was ready for virtual environments. But FastPass wasn’t quite ready. Users were still unable to complete user verification, so we still had work to do.
User Verification (UV) in virtual environments
User verification is the process by which an authenticator (e.g., Okta FastPass) verifies that the user is present and is who they claim to be.
FastPass uses Windows Hello for user verification, which allows the user to verify their identity with either a PIN or biometric verification. However, most virtual machines do not have Windows Hello. To enable users to complete two-factor authentication with user verification in virtual environments, we introduced the Okta Verify Passcode feature.
FastPass Enrollment
During FastPass enrollment, the user is prompted to create an eight-digit passcode within Okta Verify.
When Okta Verify creates the cryptographic user verification key pair, we use the same Win32 APIs to set the NCRYPT_PIN_PROPERTY on the key handle, providing the user’s passcode as the value. This means the user verification key can only be accessed when the user’s passcode is provided. Okta Verify does not store the user’s passcode. It simply passes it to the operating system via the Win32 APIs.
FastPass Authentication
During authentication, Okta Verify prompts the user to enter their passcode to complete user verification.
When Okta Verify loads the user verification key to sign the authentication challenge JWT, we once again set the NCRYPT_PIN_PROPERTY on the key handle, providing the passcode that the user provided. The operating system handles the verification to ensure that the user has provided the correct passcode.
Incorrect Passcode
If the user enters an incorrect passcode, they are allowed a maximum of two more attempts before the authentication request fails.
Since Okta Verify doesn’t store the user’s passcode, it can’t directly detect an incorrect passcode. Instead, it handles errors from the key storage provider to determine when the passcode entered by the user is incorrect, and then shows the “incorrect passcode” error to the user. The challenge is that the error returned varies based on the key storage provider.
When the key is stored in the software provider, the error code NTE_INCORRECT_PASSWORD (0x80090033) is returned when we open the key and set the NCRYPT_PIN_PROPERTY on the key handle to an incorrect value. This allows us to easily detect when the user has entered an incorrect passcode.
However, the behavior of the TPM is different. First, no error code is returned when the key is opened and the NCRYPT_PIN_PROPERTY is set on the key handle to an incorrect value. Instead, an error code is returned later when the key handle is used, for example to hash some data. Second, the error code returned is not NTE_INCORRECT_PASSWORD, but is instead NTE_PERM (i.e. access denied, 0x80090010).
Since the access denied error is a generic error that can be raised in a variety of other contexts, we cannot simply handle all access denied errors as incorrect passcode attempts by the user. Rather, we have to handle access denied errors based on the context of the operation. If we receive the access denied error while attempting to use a user verification key that we know is protected by a user-provided passcode, then we handle the error as an incorrect passcode error and allow the user to re-enter their passcode.
Supporting static VDIs
Static VDIs were much easier to support. They function very similarly to physical machines. Once we solved the challenges in supporting layered VDIs, we were able to reuse some of the solutions to support static VDIs. The main challenge in supporting a static VDI is user verification. To solve this, we use the Okta Verify Passcode for user verification on static VDIs.
A note about support for non-persistent VDIs
Okta Verify is not supported in non-persistent VDIs since no user data persists between sessions, including the user’s Okta Verify data. If a user attempted to enroll in FastPass on a non-persistent VDI, their enrollment would be lost after logging off and back onto the VDI.
Configuring Okta Verify for virtual environments
In order to allow admins to configure Okta Verify for virtual environments, we added a new configuration flag called AuthenticatorOperationMode. It has three values:
- Normal
- VirtualDesktopStatic
- VirtualDesktopLayered
See our documentation for more information about configuring Okta Verify for virtual environments.
In order to allow admins to configure Okta Verify to use Okta Verify Passcode for user verification, we added a new installer flag called UserVerificationType. It has two values:
- WindowsHello
- OktaVerifyPasscode
To make the admin's life easier, we adjust the default value of the UserVerificationType flag based on the value provided for the AuthenticatorOperationMode flag.
- When the mode is Normal, the UserVerificationType defaults to WindowsHello
- When the mode is either VirtualDesktopStatic or VirtualDesktopLayered, the UserVerificationType defaults to OktaVerifyPasscode
To configure Okta Verify to run in a virtual environment and to use Okta Verify Passcode for user verification, the admin need only set the AuthenticatorOperationMode properly, and the UserVerificationType will automatically be set to Okta Verify Passcode.
See our documentation for more information about configuring the user verification type in Okta Verify.
A note about the new installer flags
Since the AuthenticatorOperationMode and UserVerificationType installer flags modify core Okta Verify functionality, their values are set once at installation. Any change to their values after installation does not take effect. This is necessary to prevent Okta Verify from functioning improperly. However, some customers have recently expressed interest in migrating their users from one user verification type to another after installation.
Okta Verify Passcode in place of Windows Hello on physical machines
Sometimes admins don’t want their users to use Windows Hello, or sometimes users can’t use Windows Hello, even on physical machines. So we designed the Okta Verify Passcode feature to work on physical machines too. The UserVerificationType can be configured independently of the AuthenticatorOperationMode to enable user verification with Okta Verify Passcode on physical machines.
See our documentation for more information.
Try it out
Interested? Try it out. You can install the latest version of Windows Okta Verify by downloading it from the admin dashboard. Be sure to follow our instructions for configuring Okta Verify for virtual environments and for configuring the user verification type.
Have questions about this blog post? Reach out to us at eng_blogs@okta.com.
Explore more insightful Engineering Blogs from Okta to expand your knowledge.
Ready to join our passionate team of exceptional engineers? Visit our career page.
Unlock the potential of modern and sophisticated identity management for your organization. Contact Sales for more information.