Guía para desarrolladores sobre el acceso verificado de Chrome

Acerca de esta guía

La API de Chrome Verified Access permite que los servicios de red, como las VPN y las páginas de intranet, entre otros, verifiquen criptográficamente que sus clientes son genuinos y cumplen con la política corporativa. La mayoría de las grandes empresas tienen el requisito de permitir solo dispositivos administrados por empresas en sus redes WPA2 EAP-TLS, acceso de nivel superior en VPN y páginas de intranet mutua con TLS. Muchas soluciones existentes dependen de verificaciones heurísticas en el mismo cliente que pudo haberse comprometido. Esto presenta el desafío de que se hayan falsificado las señales que se utilizan para certificar el estado legítimo del dispositivo. En esta guía, se proporcionan garantías criptográficas respaldadas por hardware de la identidad del dispositivo y de que su estado no se modificó y cumple con las políticas durante el inicio, lo que se denomina Acceso verificado.

Público principal Administradores de dominios de TI empresariales
Componentes técnicos ChromeOS y API de Google Verified Access

Requisitos previos para el acceso verificado

Completa la siguiente configuración antes de implementar el proceso de acceso verificado.

Cómo habilitar la API

Configura un proyecto de la Consola de API de Google y habilita la API:

  1. Crea o usa un proyecto existente en la Consola de API de Google.
  2. Ve a la página APIs y servicios habilitados.
  3. Habilita la API de Chrome Verified Access.
  4. Crea una clave de API para tu aplicación según las instrucciones de la documentación de la API de Google Cloud.

Crea una cuenta de servicio

Para que el servicio de red acceda a la API de Verified Access de Chrome y verifique tu respuesta al desafío, crea una cuenta de servicio y una clave de cuenta de servicio (no es necesario que crees un proyecto de Cloud nuevo, puedes usar el mismo).

Una vez que crees la clave de la cuenta de servicio, deberías tener descargado un archivo de claves privadas de la cuenta de servicio. Esta es la única copia de la clave privada, así que asegúrate de almacenarla de forma segura.

Inscribe un dispositivo Chromebook administrado

Necesitas una configuración de dispositivo Chromebook correctamente administrada con tu extensión de Chrome para el Acceso verificado.

  1. El dispositivo Chromebook debe estar inscrito para la administración empresarial o educativa.
  2. El usuario del dispositivo debe ser un usuario registrado del mismo dominio.
  3. La extensión de Chrome para el Acceso verificado debe estar instalada en el dispositivo.
  4. Las políticas están configuradas para habilitar el acceso verificado, incluir la extensión de Chrome en la lista de entidades permitidas y otorgar acceso a la API a la cuenta de servicio que representa el servicio de red (consulta la documentación de ayuda de la Consola del administrador de Google).

Verifica el usuario y el dispositivo

Los desarrolladores pueden usar el Acceso verificado para la verificación de usuarios o dispositivos, o bien usar ambos a fin de aumentar la seguridad:

  • Verificación del dispositivo: Si se realiza correctamente, la verificación del dispositivo garantiza que el dispositivo Chrome esté inscrito en un dominio administrado y que cumpla con la política de dispositivo del modo de inicio verificado que especifique el administrador de dominio. Si al servicio de red se le otorga un permiso para ver la identidad del dispositivo (consulta la documentación de ayuda de la Consola del administrador de Google), también recibe un ID de dispositivo que se puede usar para auditar, hacer un seguimiento o llamar a la API de Directory.

  • Verificación del usuario: Si se realiza correctamente, la verificación del usuario garantiza que un usuario de Chrome que accedió sea un usuario administrado, que use un dispositivo inscrito y que cumpla con la política del usuario del modo de inicio verificado que especifique el administrador del dominio. Si al servicio de red se le otorga un permiso para recibir datos adicionales del usuario, también obtendrá una solicitud de firma de certificado emitida por el usuario (CSR en forma de clave pública y desafío firmado o SPKAC, también conocido como formato keygen).

Cómo verificar el usuario y el dispositivo

  1. Aceptar un desafío: La extensión de Chrome del dispositivo se comunica con la API de Verified Access para obtener un desafío. El desafío es una estructura de datos opaca (un BLOB firmado por Google) que dura 1 minuto, lo que significa que la verificación de respuesta al desafío (paso 3) falla si se usa un desafío inactivo.

    En el caso de uso más sencillo, el usuario inicia este flujo haciendo clic en el botón que genera la extensión (esto también es lo que hace la extensión de muestra proporcionada por Google).

    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 de ayuda para codificar el desafío: Si usas la versión 1 de la API, el desafío deberá estar 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. Generar una respuesta de desafío: La extensión de Chrome usa el desafío que recibió en el paso 1 para llamar a la API de Chrome enterprise.platformKeys. Esto genera una respuesta de verificación firmada y encriptada, que la extensión incluye en la solicitud de acceso que envía al servicio de red.

    En este paso, no se intenta definir un protocolo que la extensión y el servicio de red usen para comunicarse. Los desarrolladores externos implementan ambas entidades y no se les indica la forma en que se comunican entre sí. Un ejemplo sería enviar una respuesta de desafío (con codificación URL) como un parámetro de cadena de consulta, mediante HTTP POST o un encabezado HTTP especial.

    Este es un código de muestra para generar una respuesta de desafío:

    Generar una respuesta al desafío

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

    Función de devolución de llamada de desafío

      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 de ayuda para la conversión 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. Verifica la respuesta del desafío: Cuando recibe una respuesta de verificación de un dispositivo (quizás como una extensión de un protocolo de autenticación existente), el servicio de red debe llamar a la API de Verified Access para verificar la identidad del dispositivo y la postura de la política (consulta el código de ejemplo a continuación). Para combatir la falsificación de identidad, recomendamos que el servicio de red identifique al cliente con el que se comunica y que incluya la identidad esperada del cliente en su solicitud:

    • Para la verificación del dispositivo, se debe proporcionar el dominio del dispositivo esperado. Es probable que este sea un valor hard-coded en muchos casos, ya que el servicio de red protege los recursos de un dominio en particular. Si no se conoce con anticipación, se puede inferir a partir de la identidad del usuario.
    • Para la verificación del usuario, se debe proporcionar la dirección de correo electrónico del usuario esperado. Se espera que el servicio de red conozca a sus usuarios (por lo general, requeriría que los usuarios accedan).

    Cuando se llama a la API de Google, esta realiza una serie de verificaciones, como las siguientes:

    • Verifica que ChromeOS produzca la respuesta al desafío y que no se modifique durante el envío.
    • Verifica que el dispositivo o usuario esté administrado por una empresa.
    • Verifica que la identidad del dispositivo o usuario coincida con la identidad esperada (si se proporciona la última).
    • Verifica que el desafío que se está respondiendo esté actualizado (no tenga más de 1 minuto de antigüedad).
    • Verifica que el dispositivo o el usuario cumplan con la política que especificó el administrador de dominio.
    • Verifica que el llamador (servicio de red) tenga permiso para llamar a la API.
    • Si se le concede permiso al emisor para obtener datos adicionales del dispositivo o del usuario, incluye el ID del dispositivo o la solicitud de firma de certificado (CSR) del usuario en la respuesta.

    En este ejemplo, se usa la biblioteca de 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. Otorgar acceso: Este paso también es específico del servicio de red. Esta es una implementación sugerida (no prescrita). Estas son algunas de las acciones posibles:

    • Creación de una cookie de sesión
    • Emitir un certificado para el usuario o el dispositivo. En caso de que la verificación del usuario sea exitosa, y suponiendo que el servicio de red tiene acceso a datos adicionales del usuario (a través de la política de la Consola del administrador de Google), recibe una CSR firmada por el usuario, que luego se puede usar para obtener el certificado real de la autoridad certificadora. Cuando se integra con la CA de Microsoft, el servicio de red puede actuar como intermediario y usar la interfaz de ICertRequest.

Usa certificados de cliente con Acceso verificado

Cómo usar certificados de cliente con Acceso verificado.

En una organización grande, puede haber varios servicios de red (servidores VPN, puntos de acceso Wi-Fi, firewalls y sitios de intranet múltiples) que se beneficiarían del acceso verificado. Sin embargo, compilar la lógica de los pasos 2 a 4 (en la sección anterior) en cada uno de estos servicios de red puede no ser práctico. A menudo, muchos de estos servicios de red ya tienen la capacidad de requerir certificados de cliente como parte de sus autenticaciones (por ejemplo, páginas de intranet EAP-TLS o TLS mutua). Por lo tanto, si la autoridad certificadora empresarial que emite estos certificados de cliente puede implementar los pasos 2 a 4 y condicionar la emisión del certificado de cliente en la verificación de respuesta al desafío, la posesión del certificado podría ser la prueba de que el cliente es genuino y cumple con la política corporativa. Luego, cada punto de acceso Wi-Fi, servidor de VPN, etc., podría verificar este certificado de cliente en lugar de tener que seguir los pasos 2 a 4.

En otras palabras, aquí la CA (que emite el certificado de cliente a dispositivos empresariales) asume el rol del servicio de red en la Figura 1. Necesita invocar la API de Verified Access y, solo después de que se aprueba la verificación de la respuesta de desafío, debe proporcionar el certificado al cliente. Proporcionar el certificado al cliente equivale al paso 4: otorgar acceso, que aparece en la Figura 1.

El proceso para obtener certificados de cliente en Chromebooks de forma segura se describe en este artículo. Si se sigue el diseño que se describe en este párrafo, se puede combinar la extensión de acceso verificado y la extensión de integración del certificado de cliente en una sola. Obtén más información para escribir una extensión de integración de certificado de cliente.