Przewodnik dla programistów dotyczący zweryfikowanego dostępu do Chrome

Informacje o tym przewodniku

Interfejs Chrome Verified Access API umożliwia usługom sieciowym, takim jak sieci VPN czy strony intranetowe, kryptograficznie potwierdzanie oryginalności klientów i ich zgodności z zasadami firmy. Większość dużych przedsiębiorstw wymaga, aby do sieci WPA2 EAP-TLS, sieci VPN i stron intranetowych z wspólnym protokołem TLS mogły korzystać tylko urządzenia zarządzane przez firmę. Wiele istniejących rozwiązań bazuje na kontroli heurystycznej na tym samym kliencie, który mógł zostać przejęty. Stanowi to problem, ponieważ sygnały, na których podstawie poświadczasz prawdziwy stan urządzenia, mogły zostać sfałszowane. Ten przewodnik zapewnia sprzętowe gwarancje kryptograficzne dotyczące tożsamości urządzenia oraz tego, że jego stan był niezmodyfikowany i zgodny z zasadami w trakcie uruchamiania. Jest to tak zwany zweryfikowany dostęp.

Główni odbiorcy Firmowi administratorzy domen IT
Komponenty techniczne ChromeOS, interfejs Google Verified Access API

Wymagania wstępne dotyczące zweryfikowanego dostępu

Zanim wdrożysz proces Zweryfikowanego dostępu, przeprowadź konfigurację poniżej.

Włącz API

Skonfiguruj projekt w konsoli interfejsów API Google i włącz interfejs API:

  1. Utwórz projekt lub użyj istniejącego projektu w konsoli interfejsów API Google.
  2. Otwórz stronę Włączone interfejsy API i usługi.
  3. Włącz Chrome Verified Access API.
  4. Utwórz klucz interfejsu API dla swojej aplikacji, postępując zgodnie z dokumentacją interfejsu Google Cloud API.

Tworzenie konta usługi

Aby usługa sieciowa mogła uzyskać dostęp do interfejsu Chrome Verified Access API i zweryfikować odpowiedź na pytanie, utwórz konto usługi i klucz konta usługi (nie musisz tworzyć nowego projektu Cloud – możesz użyć tego samego projektu).

Po utworzeniu klucza konta usługi należy pobrać plik klucza prywatnego konta usługi. Jest to jedyna kopia klucza prywatnego, więc pamiętaj, by przechowywać go w bezpiecznym miejscu.

Rejestrowanie zarządzanego Chromebooka

Do korzystania z funkcji Zweryfikowany dostęp potrzebujesz prawidłowo zarządzanego urządzenia Chromebooka z rozszerzeniem do Chrome.

  1. Urządzenie Chromebook musi być zarejestrowane w systemie zarządzania w firmie lub instytucji edukacyjnej.
  2. Użytkownik urządzenia musi być zarejestrowanym użytkownikiem z tej samej domeny.
  3. Na urządzeniu musi być zainstalowane rozszerzenie do Chrome Zweryfikowany dostęp.
  4. Zasady są skonfigurowane tak, aby włączać Zweryfikowany dostęp, dodawać rozszerzenie do Chrome do listy dozwolonych i przyznawać dostęp do interfejsu API kontu usługi reprezentującym usługę sieci (zobacz dokumentację pomocy konsoli administracyjnej Google).

Weryfikacja użytkownika i urządzenia

Deweloperzy mogą używać zweryfikowanego dostępu do weryfikacji użytkowników lub urządzenia albo obu tych metod, aby zwiększyć bezpieczeństwo:

  • Weryfikacja urządzenia – weryfikacja urządzenia daje pewność, że urządzenie z Chrome jest zarejestrowane w domenie zarządzanej oraz że jest zgodne z zasadami dotyczącymi urządzeń w trybie zweryfikowanym, które zostały określone przez administratora domeny. Jeśli usługa sieciowa ma uprawnienia do wyświetlania tożsamości urządzenia (zobacz dokumentację pomocy konsoli administracyjnej Google), otrzymuje też identyfikator urządzenia, który może służyć do kontroli, śledzenia lub wywoływania interfejsu Directory API.

  • Weryfikacja użytkownika – weryfikacja użytkownika daje gwarancję, że zalogowany użytkownik Chrome jest użytkownikiem zarządzanym, używa zarejestrowanego urządzenia i jest zgodny z zasadami dotyczącymi użytkowników w trybie uruchamiania zweryfikowanym, które są określone przez administratora domeny. Jeśli usługa sieciowa otrzyma uprawnienia do odbierania dodatkowych danych użytkownika, otrzyma również żądanie podpisania certyfikatu wysłane przez użytkownika (podpisanie certyfikatu w postaci podpisanego klucza publicznego i wyzwania SPKAC, znanego również jako format generatora kluczy).

Jak zweryfikować użytkownika i urządzenie

  1. Pobranie testu zabezpieczającego – rozszerzenie do Chrome na urządzeniu kontaktuje się z interfejsem Verified Access API, aby uzyskać test. Zadanie to nieprzejrzysta struktura danych (obiekt blob podpisany przez Google), która wystarcza na 1 minutę. Oznacza to, że weryfikacja odpowiedzi typu test (krok 3) kończy się niepowodzeniem, jeśli użyto nieaktualnego testu zabezpieczającego.

    W najprostszym przypadku użytkownik inicjuje tę procedurę, klikając wygenerowany przez rozszerzenie przycisk (podobnie jak działa przykładowe rozszerzenie dostarczone przez 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
      }
    };
    

    Kod pomocniczy do kodowania wyzwania – jeśli korzystasz z interfejsu API w wersji 1, wyzwanie musi być zakodowane.

    // 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. Wygeneruj odpowiedź na żądanie – rozszerzenie do Chrome używa wyzwania otrzymanego w kroku 1, aby wywołać interfejs Chrome API enterprise.platformKeys. Generuje to podpisaną i zaszyfrowaną odpowiedź na wyzwanie, którą rozszerzenie umieszcza w żądaniu dostępu wysyłanym do usługi sieciowej.

    Na tym etapie nie jest podejmowana próba zdefiniowania protokołu używanego przez rozszerzenie i usługę sieciową do komunikacji. Oba te elementy są wdrażane przez zewnętrznych programistów i nie mają określonego sposobu ich współdziałania. Przykładem może być wysłanie (zakodowanej w formacie URL) odpowiedzi zabezpieczającej w postaci parametru ciągu zapytania, użycie metody POST HTTP lub specjalnego nagłówka HTTP.

    Oto przykładowy kod, który pozwala wygenerować odpowiedź na wyzwanie:

    Generowanie odpowiedzi na wyzwanie

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

    Funkcja wywołania zwrotnego wyzwania

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

    Kod pomocniczy konwersji SlateBuffer

      /**
       * 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. Weryfikacja odpowiedzi na test zabezpieczający – po otrzymaniu odpowiedzi zabezpieczającej z urządzenia (np. jako rozszerzenia istniejącego protokołu uwierzytelniania) usługa sieciowa powinna wywołać interfejs Verified Access API, aby zweryfikować tożsamość i stan zasad urządzenia (zobacz przykładowy kod poniżej). Aby zwalczać podszywania się, usługa sieciowa powinna zidentyfikować klienta, z którym się komunikuje, i podać jego oczekiwaną tożsamość w żądaniu:

    • Na potrzeby weryfikacji urządzenia należy podać oczekiwaną domenę urządzenia. W wielu przypadkach jest to wartość zakodowana na stałe, ponieważ usługa sieciowa chroni zasoby określonej domeny. Jeśli nie wiemy tego z wyprzedzeniem, można ją wywnioskować na podstawie tożsamości użytkownika.
    • Na potrzeby weryfikacji użytkownika musisz podać oczekiwany adres e-mail użytkownika. Oczekujemy, że usługa sieciowa będzie znać swoich użytkowników (zwykle wymaga to od nich zalogowania się).

    Po wywołaniu interfejsu Google API wykonuje on szereg testów, na przykład:

    • Sprawdź, czy odpowiedź na pytanie zabezpieczające jest generowana przez ChromeOS i nie jest modyfikowana podczas przesyłania
    • Sprawdź, czy urządzenie lub użytkownik są zarządzane przez firmę.
    • Sprawdź, czy tożsamość urządzenia lub użytkownika jest zgodna z oczekiwaną tożsamością (jeśli ta druga została podana).
    • Sprawdź, czy wyzwanie, na które pojawia się odpowiedź, jest aktualne (nie starsze niż minutę).
    • Sprawdź, czy urządzenie lub użytkownik spełniają warunki określone przez administratora domeny.
    • Sprawdź, czy element wywołujący (usługa sieciowa) ma uprawnienia do wywoływania interfejsu API.
    • Jeśli element wywołujący otrzymał uprawnienia do uzyskania dodatkowych danych urządzenia lub użytkownika, w odpowiedzi umieść identyfikator urządzenia lub żądanie podpisania certyfikatu użytkownika.

    W tym przykładzie użyto biblioteki 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. Przyznaj dostęp – ten krok dotyczy też poszczególnych usług sieciowych. Jest to implementacja sugerowana, ale nie rekomendowana. Możliwe działania:

    • Tworzenie pliku cookie sesji
    • Wystawianie certyfikatu dla użytkownika lub urządzenia. Jeśli weryfikacja użytkownika zakończy się powodzeniem i usługa sieciowa ma dostęp do dodatkowych danych użytkownika (za pomocą zasad konsoli administracyjnej Google), otrzymuje żądanie podpisania certyfikatu podpisane przez użytkownika, którego można użyć do uzyskania rzeczywistego certyfikatu od urzędu certyfikacji. W przypadku integracji z urząd certyfikacji Microsoft usługa sieciowa może działać jako pośrednik i korzystać z interfejsu ICertRequest.

Używanie certyfikatów klienta z zweryfikowanym dostępem

Używanie certyfikatów klienta z zweryfikowanym dostępem.

W dużej organizacji może istnieć wiele usług sieciowych (serwery VPN, punkty dostępu Wi-Fi, zapory sieciowe i wiele witryn intranetowych), dla których zweryfikowany dostęp może być korzystny. Skonstruowanie logiki działań 2–4 (w sekcji powyżej) w każdej z tych usług sieciowych może być jednak niepraktyczne. Często wiele z tych usług sieciowych może już wymagać certyfikatów klienta do uwierzytelniania (np. w przypadku EAP-TLS lub wzajemnego stron intranetowych TLS). Jeśli więc urząd certyfikacji firmy, który wystawia te certyfikaty klienta, może wdrożyć kroki 2–4 i uzależnić wystawienie certyfikatu klienta w ramach weryfikacji odpowiedzi na próbę logowania, wówczas posiadanie takiego certyfikatu może być dowodem na autentyczność klienta i zgodność z zasadami firmy. Później każdy punkt dostępu Wi-Fi, serwer VPN itd. może sprawdzać ten certyfikat klienta, zamiast wykonywać kroki 2–4.

Innymi słowy, tutaj urząd certyfikacji (który wydaje certyfikat klienta urządzeniom dla przedsiębiorstw) ma rolę usługi sieciowej (patrz: Rysunek 1). Musi wywołać interfejs Verified Access API i, tylko w przypadku pomyślnego wyniku weryfikacji odpowiedzi, dostarczyć certyfikat klientowi. Udostępnienie certyfikatu klientowi jest równoważne z etapu 4 (udzielanie dostępu) opisanego na ilustracji 1.

Proces bezpiecznego uzyskiwania certyfikatów klienta na Chromebookach został opisany w tym artykule. Jeśli zastosowano projekt opisany w tym akapicie, można połączyć rozszerzenia zweryfikowanego dostępu i do wprowadzania certyfikatów klienta. Dowiedz się więcej o tym, jak napisać rozszerzenie do wprowadzania certyfikatów klienta.