Chrome 인증 액세스 개발자 가이드

이 가이드에 대한 정보

Chrome Verified Access API를 사용하면 VPN, 인트라넷 페이지와 같은 네트워크 서비스에서 클라이언트가 진짜이고 기업 정책을 준수하는지 암호화 방식으로 확인할 수 있습니다. 대부분의 대기업은 엔터프라이즈 관리 기기만 WPA2 EAP-TLS 네트워크로, VPN의 고계층 액세스, 상호 TLS 인트라넷 페이지를 허용해야 합니다. 많은 기존 솔루션이 손상되었을 수 있는 동일한 클라이언트에 대한 휴리스틱 검사를 사용합니다. 이는 기기의 적법한 상태를 증명하기 위해 의존하는 신호 자체가 위조되었을 수 있다는 문제를 나타냅니다. 이 가이드에서는 기기 ID에 관한 하드웨어 지원 암호화 보증과 기기 상태가 수정되지 않았으며 부팅 시 정책을 준수함을 확인합니다. 이를 검증 액세스라고 합니다.

주요 대상 기업 IT 도메인 관리자
기술 구성요소 ChromeOS, Google Verified Access API

인증 액세스 기본 요건

인증 액세스 프로세스를 구현하기 전에 다음 설정을 완료하세요.

API 사용 설정

Google API 콘솔 프로젝트를 설정하고 API를 사용 설정합니다.

  1. Google API 콘솔에서 프로젝트를 만들거나 기존 프로젝트를 사용합니다.
  2. 사용 설정된 API 및 서비스 페이지로 이동합니다.
  3. Chrome Verified Access API를 사용 설정합니다.
  4. Google Cloud API 문서에 따라 애플리케이션의 API 키를 만듭니다.

서비스 계정 만들기

네트워크 서비스가 Chrome Verified Access API에 액세스하여 보안 질문 응답을 확인할 수 있도록 서비스 계정과 서비스 계정 키를 만듭니다(새 Cloud 프로젝트를 만들 필요가 없으며 동일한 프로젝트를 사용해도 됨).

서비스 계정 키를 만들면 서비스 계정 비공개 키 파일이 다운로드됩니다. 이 사본은 비공개 키의 유일한 사본이므로 안전하게 저장해야 합니다.

관리 Chromebook 기기 등록하기

인증 액세스용 Chrome 확장 프로그램을 사용하여 제대로 관리되는 Chromebook 기기 설정이 필요합니다.

  1. Chromebook 기기가 엔터프라이즈 또는 교육용 관리에 등록되어 있어야 합니다.
  2. 기기 사용자는 동일한 도메인에 등록된 사용자여야 합니다.
  3. 인증 액세스용 Chrome 확장 프로그램이 기기에 설치되어야 합니다.
  4. 정책은 인증 액세스를 사용 설정하고, Chrome 확장 프로그램을 허용 목록에 추가하고, 네트워크 서비스를 나타내는 서비스 계정에 대해 API에 대한 액세스 권한을 부여하도록 구성됩니다 (Google 관리 콘솔 도움말 문서 참고).

사용자 및 기기 확인

개발자는 사용자 인증 또는 기기 인증을 위해 인증 액세스를 사용하거나 보안 강화를 위해 두 가지를 모두 사용할 수 있습니다.

  • 기기 확인 - 성공하면 기기 확인에서 Chrome 기기가 관리 도메인에 등록되어 있고 도메인 관리자가 지정한 자체 검사 부팅 모드 기기 정책을 준수함을 보장합니다. 네트워크 서비스에 기기 ID를 볼 수 있는 권한이 부여되면 (Google 관리 콘솔 도움말 참고) 감사, 추적, Directory API 호출에 사용할 수 있는 기기 ID도 수신됩니다.

  • 사용자 확인 - 사용자 확인이 성공하면 로그인한 Chrome 사용자가 관리 사용자이고, 등록된 기기를 사용하고 있으며, 도메인 관리자가 지정한 자체 검사 부팅 모드 사용자 정책을 준수함을 보장합니다. 추가 사용자 데이터를 수신할 수 있는 권한이 네트워크 서비스에 부여되면 사용자가 발급한 인증서 서명 요청 (서명된 공개 키 및 챌린지 형식의 CSR 또는 키 생성기 형식이라고도 함)을 받게 됩니다.

사용자 및 기기 확인 방법

  1. 보안 질문 받기: 기기의 Chrome 확장 프로그램에서 Verified Access API에 접속하여 본인 확인 요청을 받습니다. 챌린지는 1분 동안 유효한 불투명한 데이터 구조 (Google에서 서명한 blob)입니다. 즉, 오래된 챌린지를 사용하면 챌린지-응답 인증 (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단계에서 받은 보안 질문을 사용하여 enterprise.platformKeys Chrome API를 호출합니다. 이렇게 하면 서명되고 암호화된 챌린지 응답이 생성됩니다. 이 응답은 확장 프로그램이 네트워크 서비스에 전송하는 액세스 요청에 포함합니다.

    이 단계에서는 확장 프로그램과 네트워크 서비스가 통신에 사용하는 프로토콜을 정의하려고 시도하지 않습니다. 두 항목 모두 외부 개발자가 구현하며 서로 통신하는 방법은 규정되지 않습니다. 예를 들어 HTTP POST를 사용하거나 특수 HTTP 헤더를 사용하여 (URL로 인코딩된) 챌린지 응답을 쿼리 문자열 매개변수로 전송할 수 있습니다.

    다음은 보안 질문 응답을 생성하는 샘플 코드입니다.

    챌린지 응답 생성

      // 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. 챌린지 응답 확인 - 네트워크 서비스는 (기존 인증 프로토콜의 확장으로) 기기에서 챌린지 응답을 받으면 Verified Access API를 호출하여 기기 ID 및 정책 상태를 확인해야 합니다 (아래 예시 코드 참고). 스푸핑을 방지하려면 네트워크 서비스에서 통신하는 클라이언트를 식별하고 요청에 클라이언트의 예상 ID를 포함하는 것이 좋습니다.

    • 기기 확인의 경우 예상 기기 도메인을 제공해야 합니다. 네트워크 서비스가 특정 도메인의 리소스를 보호하므로 대부분의 경우 하드 코딩된 값일 수 있습니다. 미리 알 수 없는 경우 사용자 ID를 통해 추론할 수 있습니다.
    • 사용자 인증의 경우 예상 사용자의 이메일 주소를 제공해야 합니다. 네트워크 서비스는 사용자를 알아야 합니다 (일반적으로 사용자가 로그인해야 함).

    Google API가 호출되면 다음과 같은 여러 검사를 실행합니다.

    • 챌린지 응답이 ChromeOS에서 생성되고 전송 중에 수정되지 않는지 확인합니다.
    • 기기 또는 사용자가 기업에서 관리하는지 확인합니다.
    • 기기/사용자의 ID가 예상 ID와 일치하는지 확인합니다 (후자가 제공된 경우).
    • 응답 중인 챌린지가 최근 (1분 이내)인지 확인합니다.
    • 기기 또는 사용자가 도메인 관리자가 지정한 정책을 준수하는지 확인합니다.
    • 호출자 (네트워크 서비스)에게 API를 호출할 수 있는 권한이 부여되었는지 확인합니다.
    • 호출자에게 추가 기기 또는 사용자 데이터를 가져올 수 있는 권한이 부여되면 응답에 기기 ID 또는 사용자의 인증서 서명 요청 (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. 액세스 권한 부여: 이 단계는 네트워크 서비스에 따라 달라집니다. 이는 권장되는 구현 (규정되지 않음)입니다. 가능한 조치는 다음과 같습니다.

    • 세션 쿠키 만들기
    • 사용자 또는 기기에 대한 인증서 발급 사용자 확인이 완료되고 네트워크 서비스가 Google 관리 콘솔 정책을 통해 추가 사용자 데이터에 대한 액세스 권한을 부여받은 경우 사용자 서명 CSR을 받게 되며, 이 CSR을 사용하여 인증 기관에서 실제 인증서를 가져올 수 있습니다. Microsoft CA와 통합할 때 네트워크 서비스가 중개 역할을 하고 ICertRequest 인터페이스를 사용할 수 있습니다.

인증 액세스를 통해 클라이언트 인증서 사용

인증 액세스를 통해 클라이언트 인증서 사용

대규모 조직에는 인증 액세스를 활용할 수 있는 여러 네트워크 서비스(VPN 서버, Wi-Fi 액세스 포인트, 방화벽, 여러 인트라넷 사이트)가 있을 수 있습니다. 그러나 이러한 각 네트워크 서비스에서 2~4단계 (위 섹션)의 로직을 빌드하는 것은 실용적이지 않을 수 있습니다. 종종 이러한 네트워크 서비스에는 이미 인증의 일부로 클라이언트 인증서를 요구하는 기능이 있습니다 (예: EAP-TLS 또는 상호 TLS 인트라넷 페이지). 따라서 이러한 클라이언트 인증서를 발급하는 엔터프라이즈 인증 기관에서 2~4단계를 구현하고 챌린지-응답 인증에서 클라이언트 인증서의 발급 조건을 지정할 수 있는 경우, 인증서 소유는 클라이언트가 진짜이며 기업 정책을 준수한다는 증거가 될 수 있습니다. 이후 각 Wi-Fi 액세스 포인트, VPN 서버 등은 2~4단계를 따를 필요 없이 이 클라이언트 인증서를 확인할 수 있습니다.

즉, 여기서는 엔터프라이즈 기기에 클라이언트 인증서를 발급하는 CA가 그림 1에서 네트워크 서비스의 역할을 합니다. 또한 Verified Access API를 호출해야 하며 챌린지 응답 확인을 통과한 경우에만 클라이언트에 인증서를 제공해야 합니다. 클라이언트에 인증서를 제공하는 것은 그림 1의 4단계(액세스 권한 부여)와 같습니다.

클라이언트 인증서를 Chromebook으로 안전하게 가져오는 프로세스는 이 도움말에서 설명합니다. 이 단락에서 설명하는 설계를 따르는 경우 인증 액세스 확장 프로그램과 클라이언트 인증서 온보딩 확장 프로그램을 하나로 결합할 수 있습니다. 클라이언트 인증서 온보딩 확장 프로그램을 작성하는 방법을 자세히 알아보세요.