Руководство разработчика с подтвержденным доступом к Chrome

Об этом руководстве

API проверенного доступа Chrome позволяет сетевым службам, таким как VPN, страницы интрасети и т. д., криптографически проверять подлинность своих клиентов и соответствие корпоративной политике. Большинству крупных предприятий требуется разрешать доступ только устройствам, управляемым предприятием, к своим сетям WPA2 EAP-TLS, доступу более высокого уровня в VPN и страницам интрасети с взаимным TLS. Многие существующие решения полагаются на эвристические проверки одного и того же клиента, который мог быть скомпрометирован. Это создает проблему, заключающуюся в том, что сигналы, используемые для подтверждения законного статуса устройства, сами по себе могут быть фальсифицированы. В этом руководстве предоставляются аппаратные криптографические гарантии идентификации устройства, а также того, что его состояние не было изменено и соответствовало политике при загрузке; называется проверенным доступом.

Основная аудитория Администраторы корпоративных ИТ-доменов
Технические компоненты ChromeOS, API проверенного доступа Google

Предварительные условия для подтвержденного доступа

Прежде чем приступать к процессу проверенного доступа, выполните следующую настройку.

Включить API

Настройте проект консоли Google API и включите API:

  1. Создайте или используйте существующий проект в консоли Google API .
  2. Перейдите на страницу «Включенные API и службы» .
  3. Включите API проверенного доступа Chrome .
  4. Создайте ключ API для своего приложения, следуя документации Google Cloud API .

Создать учетную запись службы

Чтобы сетевая служба могла получить доступ к API Chrome Verified Access для проверки вашего запроса-ответа, создайте учетную запись службы и ключ учетной записи службы (вам не нужно создавать новый облачный проект, вы можете использовать тот же).

После создания ключа учетной записи службы вам необходимо загрузить файл закрытого ключа учетной записи службы. Это единственная копия закрытого ключа, поэтому обязательно сохраните ее в надежном месте.

Зарегистрируйте управляемое устройство Chromebook

Для подтвержденного доступа вам потребуется правильно управляемое устройство Chromebook с расширением Chrome.

  1. Устройство Chromebook должно быть зарегистрировано для управления предприятием или образованием .
  2. Пользователь устройства должен быть зарегистрированным пользователем из того же домена.
  3. На устройстве должно быть установлено расширение Chrome для проверенного доступа.
  4. Политики настроены на включение проверенного доступа, добавление расширения Chrome в белый список и предоставление доступа к API для сервисного аккаунта, представляющего сетевую службу (см. справочную документацию консоли администратора Google ).

Подтвердить пользователя и устройство

Разработчики могут использовать проверенный доступ для проверки пользователя или устройства или использовать и то, и другое для дополнительной безопасности:

  • Проверка устройства . В случае успеха проверка устройства обеспечивает гарантию того, что устройство Chrome зарегистрировано в управляемом домене и соответствует политике устройства проверенного режима загрузки, указанной администратором домена. Если сетевой службе предоставлено разрешение на просмотр идентификатора устройства (см. справочную документацию консоли администратора Google ), она также получает идентификатор устройства, который можно использовать для аудита, отслеживания или вызова API каталога .

  • Проверка пользователя . В случае успеха проверка пользователя гарантирует, что вошедший в систему пользователь Chrome является управляемым пользователем, использует зарегистрированное устройство и соответствует политике пользователя в режиме проверенной загрузки, указанной администратором домена. Если сетевой службе предоставлено разрешение на получение дополнительных пользовательских данных, она также получит запрос на подпись сертификата, выданный пользователем (CSR в форме подписанного открытого ключа и запроса, или SPKAC, также известного как формат генератора ключей). ).

Как подтвердить пользователя и устройство

  1. Получить вызов . Расширение Chrome на устройстве обращается к API проверенного доступа, чтобы получить запрос. Запрос представляет собой непрозрачную структуру данных (BLOB-объект, подписанный Google), который действителен в течение 1 минуты. Это означает, что проверка запроса и ответа (шаг 3) не удалась, если используется устаревший запрос.

    В простейшем случае пользователь инициирует этот процесс, нажимая кнопку, которую генерирует расширение (это также то, что делает образец расширения, предоставленный 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
      }
    };
    

    Вспомогательный код для кодирования запроса . Если вы используете версию API v1, вызов необходимо будет закодировать.

    // 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. Создайте ответ на запрос . Расширение Chrome использует запрос, полученный на шаге 1, для вызова API Chrome Enterprise.platformKeys. При этом генерируется подписанный и зашифрованный ответ на запрос, который расширение включает в запрос доступа, отправляемый сетевой службе.

    На этом этапе не предпринимается попытка определить протокол, который расширение и сетевая служба используют для связи. Обе эти сущности реализованы внешними разработчиками, и их способ взаимодействия друг с другом не указан. Примером может быть отправка ответа на вызов (в URL-кодировке) в качестве параметра строки запроса с использованием HTTP POST или специального заголовка HTTP.

    Вот пример кода для генерации ответа на запрос:

    Создать ответ на вызов

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

    Функция обратного вызова вызова

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

    Вспомогательный код для преобразования 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. Проверка ответа на запрос . После получения ответа на запрос от устройства (возможно, в качестве расширения существующего протокола аутентификации) сетевая служба должна вызвать API проверенного доступа, чтобы проверить идентичность устройства и состояние политики (см. пример кода ниже). Для борьбы со спуфингом мы рекомендуем сетевой службе идентифицировать клиента, с которым она разговаривает, и включить ожидаемую личность клиента в свой запрос:

    • Для проверки устройства необходимо указать ожидаемый домен устройства. Вероятно, во многих случаях это значение жестко запрограммировано, поскольку сетевая служба защищает ресурсы определенного домена. Если это заранее не известно, это можно сделать на основании личности пользователя.
    • Для проверки пользователя необходимо указать адрес электронной почты ожидаемого пользователя. Мы ожидаем, что сетевая служба будет знать своих пользователей (обычно она требует от пользователей входа в систему).

    При вызове API Google он выполняет ряд проверок, таких как:

    • Убедитесь, что ответ на запрос создается ChromeOS и не изменяется при передаче.
    • Убедитесь, что устройство или пользователь управляются предприятием.
    • Убедитесь, что идентификатор устройства/пользователя соответствует ожидаемому идентификатору (если последний указан).
    • Убедитесь, что запрос, на который получен ответ, является новым (не старше 1 минуты).
    • Убедитесь, что устройство или пользователь соответствует политике, указанной администратором домена.
    • Убедитесь, что вызывающей стороне (сетевой службе) предоставлено разрешение на вызов API.
    • Если вызывающему абоненту предоставлено разрешение на получение дополнительных данных устройства или пользователя, включите в ответ идентификатор устройства или запрос на подпись сертификата пользователя (CSR).

    В этом примере используется библиотека 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. Предоставить доступ . Этот шаг также зависит от сетевой службы. Это предлагаемая (не предписанная) реализация. Возможными действиями могут быть:

    • Создание сеансового файла cookie
    • Выдача сертификата для пользователя или устройства. В случае успешной проверки пользователя и при условии, что сетевой службе предоставлен доступ к дополнительным данным пользователя (через политику консоли администратора Google), она получает подписанный пользователем CSR, который затем можно использовать для получения фактического сертификата из сертификации. власть. При интеграции с Microsoft CA сетевая служба может выступать в качестве посредника и использовать интерфейс ICertRequest.

Использование клиентских сертификатов с проверенным доступом

Использование клиентских сертификатов с проверенным доступом.

В крупной организации может существовать несколько сетевых служб (VPN-серверы, точки доступа Wi-Fi, межсетевые экраны и несколько сайтов интрасети), которым будет полезен проверенный доступ. Однако построение логики шагов 2–4 (в разделе выше) в каждом из этих сетевых сервисов может быть нецелесообразным. Зачастую многие из этих сетевых служб уже имеют возможность запрашивать клиентские сертификаты в рамках своей аутентификации (например, EAP-TLS или взаимные страницы интрасети TLS). Таким образом, если центр сертификации предприятия, выдающий эти клиентские сертификаты, мог бы реализовать шаги 2–4 и обусловить выдачу клиентского сертификата проверкой запрос-ответ, тогда обладание сертификатом могло бы стать доказательством того, что клиент является подлинным и соответствует корпоративная политика. После этого каждая точка доступа Wi-Fi, VPN-сервер и т. д. смогут проверять наличие этого сертификата клиента вместо необходимости выполнять шаги 2–4.

Другими словами, здесь центр сертификации (который выдает клиентский сертификат корпоративным устройствам) берет на себя роль сетевой службы на рисунке 1. Ему необходимо вызвать API проверенного доступа и только после прохождения проверки ответа на запрос предоставить сертификат клиент. Предоставление сертификата клиенту эквивалентно шагу 4 — Предоставление доступа на рис. 1.

Процесс безопасного получения клиентских сертификатов на Chromebook описан в этой статье . Если следовать схеме, описанной в этом параграфе, то расширение проверенного доступа и расширение подключения сертификата клиента можно объединить в одно. Узнайте больше о том, как написать расширение для подключения клиентского сертификата .