Guia do desenvolvedor do Acesso verificado do Chrome

Sobre este guia

A API Verified Access do Chrome permite que serviços de rede, como VPNs, páginas de intranet, e assim por diante, verifiquem criptograficamente se os clientes são genuínos e estão em conformidade com a política corporativa. A maioria das grandes empresas precisa permitir que apenas dispositivos gerenciados pela empresa acessem as redes WPA2 EAP-TLS, acesso de nível mais alto em VPNs e páginas de intranet mútua TLS. Muitas soluções atuais dependem de verificações heurísticas no mesmo cliente que podem ter sido comprometidos. Isso apresenta o desafio de que os indicadores usados para atestar o status legítimo do dispositivo possam ter sido falsificados. Este guia fornece garantias criptográficas baseadas em hardware da identidade do dispositivo e de que o estado não foi modificado e está em conformidade com a política na inicialização (chamada de "Acesso verificado").

Público principal Administradores de domínios de TI corporativos
Componentes técnicos ChromeOS, API Google Verified Access

Pré-requisitos para o acesso verificado

Conclua a configuração a seguir antes de implementar o processo de Acesso verificado.

Ativar a API

Configure um projeto do Console de APIs do Google e ative a API:

  1. Crie ou use um projeto no Console de APIs do Google.
  2. Acesse a página APIs e serviços ativados.
  3. Ative a API Verified Access do Chrome.
  4. Crie uma chave de API para seu aplicativo seguindo a documentação da API do Google Cloud.

Crie uma conta de serviço

Para que o serviço de rede acesse a API Verified Access do Chrome para verificar sua resposta de desafio, crie uma conta de serviço e uma chave de conta de serviço. Não é necessário criar um novo projeto do Cloud, você pode usar o mesmo.

Depois de criar a chave da conta de serviço, faça o download de um arquivo de chave privada da conta de serviço. Essa é a única cópia da chave privada, portanto, armazene-a com segurança.

Registrar um dispositivo Chromebook gerenciado

Você precisa de uma configuração de dispositivo Chromebook gerenciado corretamente com sua extensão do Chrome para o Acesso verificado.

  1. O dispositivo Chromebook precisa estar registrado no gerenciamento empresarial ou educacional.
  2. O usuário do dispositivo precisa ser registrado no mesmo domínio.
  3. A extensão do Chrome para o acesso verificado precisa estar instalada no dispositivo.
  4. As políticas são configuradas para ativar o acesso verificado, colocar a extensão do Chrome na lista de permissões e conceder acesso à API para a conta de serviço que representa o serviço de rede. Consulte a documentação de ajuda do Google Admin Console.

Verificar usuário e dispositivo

Os desenvolvedores podem usar o "Acesso verificado" para a verificação do usuário ou do dispositivo ou ambos para aumentar a segurança:

  • Verificação do dispositivo: se bem-sucedida, a verificação do dispositivo fornece uma garantia de que o dispositivo Chrome está registrado em um domínio gerenciado e que está em conformidade com a política do dispositivo do modo de inicialização verificada, conforme especificado pelo administrador do domínio. Se o serviço de rede receber uma permissão para ver a identidade do dispositivo (consulte a documentação de Ajuda do Google Admin Console), ele também receberá um ID de dispositivo que poderá ser usado para auditoria, rastreamento ou chamada da API Directory.

  • Verificação do usuário: se bem-sucedida, a verificação do usuário fornecerá uma garantia de que um usuário conectado do Chrome é um usuário gerenciado, está usando um dispositivo registrado e está em conformidade com a política do usuário do modo de inicialização verificada, conforme especificado pelo administrador do domínio. Se o serviço de rede receber uma permissão para receber outros dados do usuário, ele também vai receber uma solicitação de assinatura de certificado emitida pelo usuário (CSR na forma de chave pública e desafio assinado, ou SPKAC, também conhecido como formato keygen).

Como verificar usuário e dispositivo

  1. Receber um desafio: a extensão do Chrome no dispositivo entra em contato com a API Verified Access para receber um desafio. O desafio é uma estrutura de dados opaca (um blob assinado pelo Google) que é boa por 1 minuto, o que significa que a verificação de resposta ao desafio (etapa 3) falhará se um desafio desatualizado for usado.

    No caso de uso mais simples, o usuário inicia esse fluxo clicando em um botão gerado pela extensão (isso também é o que a extensão de amostra fornecida pelo Google faz).

    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
      }
    };
    

    Código auxiliar para codificar o desafio: se você estiver usando a v1 da API, o desafio precisará ser codificado.

    // 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. Gerar uma resposta de desafio: a extensão do Chrome usa o desafio recebido na etapa 1 para chamar a API enterprise.platformKeys do Chrome. Isso gera uma resposta de desafio assinada e criptografada, que a extensão inclui na solicitação de acesso enviada ao serviço de rede.

    Nesta etapa, não há tentativa de definir um protocolo que a extensão e o serviço de rede vão usar para se comunicar. As duas entidades são implementadas por desenvolvedores externos e não definem a forma como elas se comunicam. Um exemplo seria enviar uma resposta de desafio (codificada em URL) como parâmetro de string de consulta usando POST HTTP ou um cabeçalho HTTP especial.

    Este é um exemplo de código para gerar uma resposta de desafio:

    Gerar resposta do desafio

      // 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);
      }
    

    Função de callback de desafio

      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
       };
      }
    

    Código auxiliar para conversão de ArrayBuffer

      /**
       * 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. Verificar resposta do desafio: ao receber uma resposta de desafio de um dispositivo, talvez como uma extensão de um protocolo de autenticação já existente, o serviço de rede precisa chamar a API Verified Access para verificar a identidade do dispositivo e a postura da política (veja o exemplo de código abaixo). Para combater o spoofing, recomendamos que o serviço de rede identifique o cliente com quem está se comunicando e inclua a identidade esperada na solicitação:

    • Para a verificação do dispositivo, o domínio esperado do dispositivo deve ser fornecido. Em muitos casos, esse é provavelmente um valor codificado, porque o serviço de rede protege os recursos de um domínio específico. Se isso não for conhecido com antecedência, ele poderá ser inferido com base na identidade do usuário.
    • Para a verificação do usuário, o endereço de e-mail do usuário esperado precisa ser fornecido. Esperamos que o serviço de rede conheça os usuários (normalmente, isso exige que os usuários façam login).

    Quando a API do Google é chamada, ela executa uma série de verificações, como:

    • Verifique se a resposta do desafio é produzida pelo ChromeOS e não é modificada em trânsito
    • Verifique se o dispositivo ou usuário é gerenciado pela empresa.
    • Verifique se a identidade do dispositivo/usuário corresponde à identidade esperada (se a última for fornecida).
    • Verifique se o desafio que está sendo respondido é recente (não tem mais de um minuto).
    • Verifique se o dispositivo ou usuário está em conformidade com a política especificada pelo administrador do domínio.
    • Verifique se o autor da chamada (serviço de rede) tem permissão para chamar a API.
    • Se o autor da chamada receber permissão para coletar outros dados do dispositivo ou do usuário, inclua o ID do dispositivo ou a solicitação de assinatura de certificado (CSR, na sigla em inglês) do usuário na resposta.

    Este exemplo usa a biblioteca gRPC

    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. Conceder acesso: esta etapa também é específica ao serviço de rede. Essa é uma implementação sugerida (não prescrita. As ações possíveis podem ser:

    • Criação de um cookie de sessão
    • Emissão de um certificado para o usuário ou dispositivo. Se a verificação do usuário for bem-sucedida, e presumindo que o serviço de rede tenha recebido acesso a outros dados do usuário (por meio da política do Google Admin Console), ele receberá uma CSR assinada pelo usuário, que poderá então ser usada para obter o certificado real da autoridade certificadora. Ao fazer a integração com a CA da Microsoft, o serviço de rede pode atuar como intermediário e usar a interface ICertRequest.

Como usar certificados do cliente com o acesso verificado

Como usar certificados do cliente com o acesso verificado.

Em uma grande organização, pode haver vários serviços de rede (servidores VPN, pontos de acesso Wi-Fi, firewalls e vários sites de intranet) que se beneficiariam do Acesso verificado. No entanto, criar a lógica das etapas de 2 a 4 (na seção acima) em cada um desses serviços de rede pode não ser prático. Muitas vezes, muitos desses serviços de rede já podem exigir certificados do cliente como parte das autenticações (por exemplo, EAP-TLS ou páginas de intranet mútuas de TLS). Portanto, se a autoridade certificadora corporativa que emite esses certificados do cliente puder implementar as etapas de 2 a 4 e condicionar a emissão do certificado na verificação de contestação/resposta, a posse do certificado poderá ser a prova de que o cliente é genuíno e está em conformidade com a política corporativa. Depois disso, cada ponto de acesso Wi-Fi, servidor de VPN e assim por diante poderão verificar esse certificado de cliente em vez de seguir as etapas de 2 a 4.

Em outras palavras, aqui a CA (que emite o certificado do cliente para dispositivos corporativos) assume o papel do serviço de rede na Figura 1. Ele precisa invocar a API Verified Access e, somente após a aprovação da verificação da resposta do desafio, fornecer o certificado ao cliente. Fornecer o certificado ao cliente é o equivalente à Etapa 4: conceder acesso na Figura 1.

O processo de recebimento de certificados do cliente para Chromebooks com segurança é descrito neste artigo. Se o design descrito neste parágrafo for seguido, a extensão de acesso verificado e a extensão de integração do certificado do cliente poderão ser combinadas em uma só. Saiba mais sobre como gravar uma extensão de integração do certificado do cliente.