# Chrome Verified Access Developer&#39;s Guide

## About this guide

The Chrome Verified Access API allows network services, such as VPNs, intranet
pages, and so on to cryptographically verify that their clients are genuine and
conform to corporate policy. Most large enterprises have the requirement to
allow only enterprise-managed devices onto their WPA2 EAP-TLS networks,
higher-tier access in VPNs, and mutual-TLS intranet pages. Many existing
solutions rely on heuristic checks on the same client that may have been
compromised. This presents the challenge that the signals being relied on to
attest to the legitimate status of the device may themselves have been
falsified. This guide provides hardware-backed cryptographic guarantees of the
identity of the device and that its state was unmodified and policy compliant
at boot; called Verified Access.

|---|---|
| **Primary audience** | Enterprise IT domain administrators |
| **Technical components** | ChromeOS, Google Verified Access API |

## Prerequisites to Verified Access

Complete the following setup before implementing the Verified Access process.

### Enable the API

Setup a Google API console project and enable the API:

1. Create or use an existing project in the [Google API console](https://console.developers.google.com/).
2. Go to the [Enabled APIs \& services](https://console.cloud.google.com/apis/dashboard) page.
3. Enable the [Chrome Verified Access API](https://console.cloud.google.com/apis/library/verifiedaccess.googleapis.com).
4. Create an API key for your application by following the [Google Cloud API documentation](https://cloud.google.com/docs/authentication/api-keys?ref_topic=6262490&visit_id=636989986401753289-2059290229&rd=1).

### Create a service account

For the network service to access the Chrome Verified Access API to verify your
challenge-response,
[create a service account and a service account key](https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount)
(you don't need to create a new Cloud project, you may use the same one).

Once you create the service account key, you should then have a service account
private key file downloaded. This is the only copy of the private key, so make
sure to store it securely.

### Enroll a managed Chromebook device

You need a properly managed Chromebook device setup with your Chrome
extension for Verified Access.

1. The Chromebook device must be [enrolled for enterprise or education management](https://support.google.com/chrome/a/answer/1360534).
2. The user of the device must be a registered user from the same domain.
3. The Chrome extension for Verified Access must be [installed on the device](https://support.google.com/chrome/a/answer/6306504).
4. Policies are configured to enable Verified Access, allowlist the Chrome extension, and grant access to the API for the service account representing the network service (see the [Google Admin console Help documentation](https://support.google.com/chrome/a/answer/7156268)).

## Verify user and device

Developers can use Verified Access for user or device verification, or use both
for the added security:

- **Device verification** ---If successful, device verification provides a
  guarantee that the Chrome device is enrolled in a managed domain and that it
  conforms to the verified boot mode device policy as specified by the domain
  administrator. If the network service is granted a permission to see the device
  identity (see [Google Admin console Help documentation](https://support.google.com/chrome/a/answer/7156268)), then it also receives a
  device ID that can be used for auditing, tracking, or calling the [Directory API](https://developers.google.com/admin-sdk/directory/v1/guides/manage-chrome-devices).

- **User verification**---If successful, user verification provides a guarantee
  that a signed-in Chrome user is a managed user, is using an enrolled device, and
  conforms to the verified boot mode user policy as specified by the domain
  administrator. If the network service is granted a permission to receive
  additional user data, it would also obtain a certificate signing request issued
  by the user (CSR in the form of signed-public-key-and-challenge, or SPKAC,
  also known as keygen format).

### How to verify user and device

1. **Get a challenge**---The Chrome extension on the device contacts the Verified
   Access API to obtain a challenge. The challenge is an opaque data
   structure (a Google-signed blob) that's good for 1 minute, meaning the
   challenge-response verification (step 3) fails if a stale challenge is used.

   In the simplest use case, the user initiates this flow by clicking a button that
   the extension generates (this is also what the Google-provided sample extension
   does).

       var apiKey = 'YOUR_API_KEY_HERE';
       var challengeUrlString =
         'https://verifiedaccess.googleapis.com/v2/challenge:generate?key=' + apiKey;

       // Request challenge from URL
       var xmlhttp = new XMLHttpRequest();
       xmlhttp.open('POST', challengeUrlString, true);
       xmlhttp.send();
       xmlhttp.onreadystatechange = function() {
         if (xmlhttp.readyState == 4) {
           var challenge = xmlhttp.responseText;
           console.log('challenge: ' + challenge);
           // v2 of the API returns an encoded challenge so no further challenge processing is needed
         }
       };

   **Helper code to encode challenge**---If you are using v1 of the API, the
   challenge will need to be encoded.

       // This can be replaced by using a third-party library such as
       // [https://github.com/dcodeIO/ProtoBuf.js/wiki](https://github.com/dcodeIO/ProtoBuf.js/wiki)
       /**
        * encodeChallenge convert JSON challenge into base64 encoded byte array
        * @param {string} challenge JSON encoded challenge protobuf
        * @return {string} base64 encoded challenge protobuf
        */
       var encodeChallenge = function(challenge) {
         var jsonChallenge = JSON.parse(challenge);
         var challengeData = jsonChallenge.challenge.data;
         var challengeSignature = jsonChallenge.challenge.signature;

         var protobufBinary = protoEncodeChallenge(
             window.atob(challengeData), window.atob(challengeSignature));

         return window.btoa(protobufBinary);
       };

       /**
        * protoEncodeChallenge produce binary encoding of the challenge protobuf
        * @param {string} dataBinary binary data field
        * @param {string} signatureBinary binary signature field
        * @return {string} binary encoded challenge protobuf
        */
       var protoEncodeChallenge = function(dataBinary, signatureBinary) {
         var protoEncoded = '';

         // See https://developers.google.com/protocol-buffers/docs/encoding
         // for encoding details.

         // 0x0A (00001 010, field number 1, wire type 2 [length-delimited])
         protoEncoded += '\u000A';

         // encode length of the data
         protoEncoded += varintEncode(dataBinary.length);
         // add data
         protoEncoded += dataBinary;

         // 0x12 (00010 010, field number 2, wire type 2 [length-delimited])
         protoEncoded += '\u0012';
         // encode length of the signature
         protoEncoded += varintEncode(signatureBinary.length);
         // add signature
         protoEncoded += signatureBinary;

         return protoEncoded;
       };

       /**
        * varintEncode produce binary encoding of the integer number
        * @param {number} number integer number
        * @return {string} binary varint-encoded number
        */
       var varintEncode = function(number) {
         // This only works correctly for numbers 0 through 16383 (0x3FFF)
         if (number <= 127) {
           return String.fromCharCode(number);
         } else {
           return String.fromCharCode(128 + (number & 0x7f), number >>> 7);
         }
       };

2. **Generate a challenge response**---The Chrome extension uses the challenge it
   received in step 1 to call the enterprise.platformKeys Chrome API. This
   generates a signed and encrypted challenge response, which the extension
   includes in the access request that it sends to the network service.

   In this step, there's no attempt to define a protocol that the extension and
   network service use for communicating. Both of these entities are implemented
   by external developers and aren't prescribed how they talk to each other. An
   example would be sending a (URL-encoded) challenge response as a query string
   parameter, using HTTP POST, or using a special HTTP header.

   Here's a sample code to generate a challenge response:

   **Generate challenge response**

         // Generate challenge response
         var encodedChallenge; // obtained by generate challenge API call
         try {
           if (isDeviceVerification) { // isDeviceVerification set by external logic
             chrome.enterprise.platformKeys.challengeKey(
                 {
                   scope: 'MACHINE',
                   challenge: decodestr2ab(encodedChallenge),
                 },
                 ChallengeCallback);
           } else {
             chrome.enterprise.platformKeys.challengeKey(
                 {
                   scope: 'USER',
                   challenge: decodestr2ab(encodedChallenge),
                   registerKey: { 'RSA' }, // can also specify 'ECDSA'
                 },
                 ChallengeCallback);
           }
         } catch (error) {
           console.log('ERROR: ' + error);
         }

   **Challenge callback function**

         var ChallengeCallback = function(response) {
           if (chrome.runtime.lastError) {
             console.log(chrome.runtime.lastError.message);
           } else {
             var responseAsString = ab2base64str(response);
             console.log('resp: ' + responseAsString);
           ... // send on to network service
          };
         }

   **Helper code for ArrayBuffer conversion**

         /**
          * ab2base64str convert an ArrayBuffer to base64 string
          * @param {ArrayBuffer} buf ArrayBuffer instance
          * @return {string} base64 encoded string representation
          * of the ArrayBuffer
          */
         var ab2base64str = function(buf) {
           var binary = '';
           var bytes = new Uint8Array(buf);
           var len = bytes.byteLength;
           for (var i = 0; i < len; i++) {
             binary += String.fromCharCode(bytes[i]);
           }
           return window.btoa(binary);
         }

         /**
          * decodestr2ab convert a base64 encoded string to ArrayBuffer
          * @param {string} str string instance
          * @return {ArrayBuffer} ArrayBuffer representation of the string
          */
         var decodestr2ab = function(str) {
           var binary_string =  window.atob(str);
           var len = binary_string.length;
           var bytes = new Uint8Array(len);
           for (var i = 0; i < len; i++)        {
               bytes[i] = binary_string.charCodeAt(i);
           }
           return bytes.buffer;
         }

3. **Verify challenge response**---Upon receiving a challenge response from a
   device (perhaps as an extension to an existing authentication protocol), the
   network service should call the Verified Access API to verify the device
   identity and policy posture (see example code below). To combat spoofing, we
   recommend that the network service identify the client it's talking to and
   include the expected identity of the client in its request:

   - *For device verification*, the expected device domain should be provided . This is likely a hard-coded value in many cases, because the network service protects resources for a particular domain. If this isn't known ahead of time, it can be inferred from user identity.
   - *For user verification*, the expected user's email address should be provided. We expect the network service to know its users (normally it would require users to sign in).

   When the Google API is called, it performs a number of checks, such as:
   - Verify that the challenge response is produced by ChromeOS and isn't modified in transit
   - Verify that the device or user is enterprise-managed.
   - Verify that the identity of the device/user matches the expected identity (if the latter is provided).
   - Verify that the challenge that's being responded to is fresh (no more than 1 minute old).
   - Verify that the device or user conforms to the policy as specified by the domain administrator.
   - Verify that the caller (network service) is granted permission to call the API.
   - If the caller is granted permission to obtain additional device or user data, include the device ID or the user's certificate signing request (CSR) in the response.

   **This example uses gRPC library**

       import com.google.auth.oauth2.GoogleCredentials;
       import com.google.auth.oauth2.ServiceAccountCredentials;
       import com.google.chrome.verifiedaccess.v2.VerifiedAccessGrpc;
       import com.google.chrome.verifiedaccess.v2.VerifyChallengeResponseRequest;
       import com.google.chrome.verifiedaccess.v2.VerifyChallengeResponseResult;
       import com.google.protobuf.ByteString;

       import io.grpc.ClientInterceptor;
       import io.grpc.ClientInterceptors;
       import io.grpc.ManagedChannel;
       import io.grpc.auth.ClientAuthInterceptor;
       import io.grpc.netty.GrpcSslContexts;
       import io.grpc.netty.NettyChannelBuilder;

       import java.io.File;
       import java.io.FileInputStream;
       import java.util.Arrays;
       import java.util.concurrent.Executors;

       // https://cloud.google.com/storage/docs/authentication#generating-a-private-key
       private final String clientSecretFile = "PATH_TO_GENERATED_JSON_SECRET_FILE";

       private ManagedChannel channel;
       private VerifiedAccessGrpc.VerifiedAccessBlockingStub client;

       void setup() {

          channel = NettyChannelBuilder.forAddress("verifiedaccess.googleapis.com", 443)
             .sslContext(GrpcSslContexts.forClient().ciphers(null).build())
             .build();

          List<ClientInterceptor> interceptors = Lists.newArrayList();
          // Attach a credential for my service account and scope it for the API.
          GoogleCredentials credentials =
              ServiceAccountCredentials.class.cast(
                  GoogleCredentials.fromStream(
                      new FileInputStream(new File(clientSecretFile))));
         credentials = credentials.createScoped(
             Arrays.<String>asList("https://www.googleapis.com/auth/verifiedaccess"));
         interceptors.add(
              new ClientAuthInterceptor(credentials, Executors.newSingleThreadExecutor()));

         // Create a stub bound to the channel with the interceptors applied
         client = VerifiedAccessGrpc.newBlockingStub(
             ClientInterceptors.intercept(channel, interceptors));
       }

       /**
        * Invokes the synchronous RPC call that verifies the device response.
        * Returns the result protobuf as a string.
        *
        * @param signedData base64 encoded signedData blob (this is a response from device)
        * @param expectedIdentity expected identity (domain name or user email)
        * @return the verification result protobuf as string
        */
       public String verifyChallengeResponse(String signedData, String expectedIdentity)
         throws IOException, io.grpc.StatusRuntimeException {
         VerifyChallengeResponseResult result =
           client.verifyChallengeResponse(newVerificationRequest(signedData,
               expectedIdentity)); // will throw StatusRuntimeException on error.

         return result.toString();
       }

       private VerifyChallengeResponseRequest newVerificationRequest(
         String signedData, String expectedIdentity) throws IOException {
         return VerifyChallengeResponseRequest.newBuilder()
           .setChallengeResponse(
               ByteString.copyFrom(BaseEncoding.base64().decode(signedData)))
           .setExpectedIdentity(expectedIdentity == null ? "" : expectedIdentity)
           .build();
       }

4. **Grant access**---This step is also network-service specific. This is a
   suggested (not prescribed) implementation. Possible actions could be:

   - Creation of a session cookie
   - Issuing a certificate for the user or device. In case of successful user verification, and assuming the network service has been granted access to additional user data (via the Google Admin console policy), it receives a user-signed CSR, which can then be used to obtain the actual certificate from the certification authority. When integrating with Microsoft^Ⓡ^ CA, the network service could act as an [intermediary](https://msdn.microsoft.com/en-us/library/aa386311.aspx) and make use of the ICertRequest interface.

## Using client certificates with Verified Access

![](https://developers.google.com/static/chrome/verified-access/images/client-certificate.svg) Using client certificates with Verified Access.

In a large organization, there may be multiple network services
(VPN servers, Wi-Fi access points, firewalls, and multiple intranet sites) that
would benefit from Verified Access. However, building the logic of steps
2--4 (in the section above) in each of these network services may not be
practical. Often, many of these network services already have the capability to
require client certificates as part of their authentications (for example,
EAP-TLS or mutual TLS intranet pages). So if the Enterprise Certificate
Authority that issues these client certificates could implement steps 2--4 and
condition the issuance of the client certificate on the challenge-response
verification, then the possession of the certificate could be the proof that
the client is genuine and conforms to corporate policy. Thereafter each Wi-Fi
access point, VPN server, and so on could check for this client certificate
instead of needing to follow steps 2--4.

In other words, here the CA (that issues the client certificate to enterprise
devices) takes the role of the Network Service in Figure 1. It needs to invoke
the Verified Access API and, only upon the challenge response verification
passing, provide the certificate to the client. Providing the certificate to
the client is the equivalent of Step 4 - Grant Access in Figure 1.

The process of getting client certificates securely to Chromebooks is described
in [this article](https://support.google.com/chrome/a/answer/6080885). If the
design described in this paragraph is followed, then Verified Access Extension
and Client certificate onboarding extension can be combined into one. Learn more
about how to [write a client certificate onboarding extension](http://www.chromium.org/administrators/certificate-management-extension-api-on-chrome-os).