Hướng dẫn dành cho nhà phát triển về quyền truy cập được xác minh của Chrome

Giới thiệu về hướng dẫn này

API Quyền truy cập đã xác minh của Chrome cho phép các dịch vụ mạng, chẳng hạn như VPN, trang mạng nội bộ, v.v. xác minh bằng cách mã hoá rằng ứng dụng của họ là chính hãng và tuân thủ chính sách của công ty. Hầu hết các doanh nghiệp lớn đều yêu cầu chỉ cho phép các thiết bị do doanh nghiệp quản lý trên mạng WPA2 EAP-TLS, quyền truy cập cấp cao hơn trong VPN và các trang mạng nội bộ lẫn TLS. Nhiều giải pháp hiện tại dựa vào quy trình kiểm tra phỏng đoán trên cùng một ứng dụng có thể đã bị xâm phạm. Điều này đặt ra thách thức là các tín hiệu được dựa vào để chứng thực trạng thái hợp pháp của thiết bị có thể đã bị làm giả. Hướng dẫn này đưa ra sự đảm bảo bằng mật mã học dựa trên phần cứng về danh tính của thiết bị, cũng như trạng thái của thiết bị là chưa bị sửa đổi và tuân thủ chính sách khi khởi động (được gọi là Quyền truy cập đã xác minh).

Đối tượng chính Quản trị viên miền CNTT của doanh nghiệp
Các thành phần kỹ thuật ChromeOS, API Quyền truy cập đã xác minh của Google

Điều kiện tiên quyết để xác minh quyền truy cập

Hoàn tất quy trình thiết lập sau đây trước khi triển khai quy trình Quyền truy cập đã xác minh.

Bật API

Thiết lập dự án bảng điều khiển API của Google và bật API:

  1. Tạo hoặc sử dụng một dự án hiện có trong Bảng điều khiển API của Google.
  2. Chuyển đến trang API và dịch vụ đã bật.
  3. Bật ChromeVerify Access API (API Quyền truy cập đã xác minh của Chrome).
  4. Tạo khoá API cho ứng dụng của bạn bằng cách làm theo tài liệu về API của Google Cloud.

Tạo một tài khoản dịch vụ

Để dịch vụ mạng truy cập vào API Truy cập đã xác minh của Chrome để xác minh phản hồi của bạn, hãy tạo tài khoản dịch vụ và khoá tài khoản dịch vụ (bạn không cần tạo dự án Google Cloud mới mà có thể sử dụng cùng một dự án).

Sau khi tạo khoá tài khoản dịch vụ, bạn nên tải tệp khoá riêng tư của tài khoản dịch vụ xuống. Đây là bản sao duy nhất của khoá riêng tư, vì vậy, hãy nhớ lưu trữ khoá một cách an toàn.

Đăng ký thiết bị Chromebook được quản lý

Bạn cần thiết lập thiết bị Chromebook được quản lý đúng cách bằng tiện ích của Chrome cho Quyền truy cập đã xác minh.

  1. Thiết bị Chromebook phải được đăng ký để quản lý doanh nghiệp hoặc giáo dục.
  2. Người dùng thiết bị phải là người dùng đã đăng ký thuộc cùng một miền.
  3. Bạn phải cài đặt tiện ích của Chrome cho Quyền truy cập đã xác minh trên thiết bị.
  4. Các chính sách được định cấu hình để bật Quyền truy cập đã xác minh, đưa tiện ích của Chrome vào danh sách cho phép và cấp quyền truy cập vào API cho tài khoản dịch vụ đại diện cho dịch vụ mạng (xem Tài liệu trợ giúp về Bảng điều khiển dành cho quản trị viên của Google).

Xác minh người dùng và thiết bị

Nhà phát triển có thể sử dụng Quyền truy cập đã xác minh để xác minh người dùng hoặc thiết bị, hoặc sử dụng cả hai để tăng cường bảo mật:

  • Xác minh thiết bị – Nếu thành công, quá trình xác minh thiết bị sẽ đảm bảo rằng thiết bị Chrome được đăng ký trong một miền được quản lý và tuân thủ chính sách thiết bị về chế độ khởi động được xác minh mà quản trị viên miền chỉ định. Nếu dịch vụ mạng được cấp quyền xem danh tính thiết bị (xem Tài liệu trợ giúp về Bảng điều khiển dành cho quản trị viên của Google), thì dịch vụ mạng đó cũng nhận được một mã thiết bị có thể dùng để kiểm tra, theo dõi hoặc gọi API Thư mục.

  • Xác minh người dùng – Nếu thành công, quy trình xác minh người dùng sẽ đảm bảo rằng người dùng Chrome đã đăng nhập là người dùng được quản lý, đang sử dụng thiết bị đã đăng ký và tuân thủ chính sách người dùng về chế độ khởi động đã xác minh mà quản trị viên miền chỉ định. Nếu được cấp quyền nhận thêm dữ liệu người dùng, dịch vụ mạng cũng sẽ nhận được yêu cầu ký chứng chỉ do người dùng đưa ra (CSR ở dạng ký-public-key-and-Challenge hoặc SPKAC, còn gọi là định dạng chương trình tạo khoá).

Cách xác minh người dùng và thiết bị

  1. Nhận thử thách – Tiện ích của Chrome trên thiết bị sẽ liên hệ với API Quyền truy cập đã xác minh để nhận thử thách xác thực. Thách thức là một cấu trúc dữ liệu không rõ ràng (blob do Google ký) hoạt động trong 1 phút, nghĩa là xác minh phản hồi thách thức (bước 3) sẽ không thành công nếu xác thực lỗi thời được sử dụng.

    Trong trường hợp sử dụng đơn giản nhất, người dùng bắt đầu quy trình này bằng cách nhấp vào nút mà tiện ích tạo ra (đây cũng là thao tác mà tiện ích mẫu do Google cung cấp).

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

    Mã trình trợ giúp để mã hoá thử thách – Nếu bạn đang sử dụng phiên bản 1 của API, thì thách thức sẽ cần được mã hoá.

    // 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. Tạo phản hồi cho yêu cầu – Tiện ích của Chrome sử dụng thử thách đã nhận được ở bước 1 để gọi API Chrome enterprise.platformKeys. Thao tác này sẽ tạo ra một phản hồi thử thách đã ký và mã hoá. Tiện ích này có trong yêu cầu truy cập mà tiện ích gửi đến dịch vụ mạng.

    Trong bước này, bạn không cố gắng xác định giao thức mà tiện ích và dịch vụ mạng sử dụng để giao tiếp. Cả hai thực thể này đều do các nhà phát triển bên ngoài triển khai và không quy định cách chúng giao tiếp với nhau. Ví dụ: việc gửi phản hồi thử thách (được mã hoá URL) dưới dạng tham số chuỗi truy vấn, sử dụng yêu cầu POST qua HTTP hoặc sử dụng tiêu đề HTTP đặc biệt.

    Dưới đây là mã mẫu để tạo phản hồi xác thực:

    Tạo câu trả lời cho thử thách

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

    Hàm callback thách thức

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

    Mã trợ giúp để chuyển đổi 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. Xác minh phản hồi thử thách – Khi nhận được phản hồi thử thách từ một thiết bị (có thể là một tiện ích của giao thức xác thực hiện có), dịch vụ mạng sẽ gọi API Truy cập đã xác minh để xác minh danh tính và tình trạng của chính sách thiết bị (xem mã ví dụ dưới đây). Để chống giả mạo, dịch vụ mạng nên xác định ứng dụng đang giao tiếp và đưa danh tính dự kiến của ứng dụng đó vào yêu cầu:

    • Để xác minh thiết bị, bạn phải cung cấp miền thiết bị dự kiến. Đây có thể là một giá trị được mã hoá cứng trong nhiều trường hợp, vì dịch vụ mạng bảo vệ các tài nguyên cho một miền cụ thể. Nếu không biết trước thông tin này, người dùng có thể suy ra thông tin này từ danh tính của người dùng.
    • Để xác minh người dùng, bạn phải cung cấp địa chỉ email của người dùng dự kiến. Chúng tôi kỳ vọng dịch vụ mạng sẽ biết người dùng của mình (thông thường, dịch vụ này sẽ yêu cầu người dùng đăng nhập).

    Khi được gọi, Google API sẽ thực hiện một số bước kiểm tra, chẳng hạn như:

    • Kiểm tra để đảm bảo rằng phản hồi cho thử thách do ChromeOS tạo và không bị sửa đổi trong quá trình truyền dữ liệu
    • Xác minh rằng thiết bị hoặc người dùng do doanh nghiệp quản lý.
    • Xác minh rằng danh tính của thiết bị/người dùng khớp với danh tính dự kiến (nếu danh tính đó được cung cấp).
    • Xác minh rằng thử thách được phản hồi là thử thách mới (không quá 1 phút).
    • Xác minh rằng thiết bị hoặc người dùng tuân thủ chính sách mà quản trị viên miền chỉ định.
    • Xác minh rằng phương thức gọi (dịch vụ mạng) được cấp quyền để gọi API.
    • Nếu phương thức gọi được cấp quyền lấy thêm dữ liệu người dùng hoặc thiết bị, hãy đưa mã thiết bị hoặc yêu cầu ký chứng chỉ (CSR) của người dùng vào phản hồi.

    Ví dụ này sử dụng thư viện 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. Cấp quyền truy cập—Bước này cũng dành riêng cho từng dịch vụ mạng. Đây là cách triển khai được đề xuất (không theo quy định). Các thao tác có thể thực hiện có thể là:

    • Tạo cookie phiên
    • Cấp chứng chỉ cho người dùng hoặc thiết bị. Trong trường hợp xác minh người dùng thành công và giả sử dịch vụ mạng đã được cấp quyền truy cập vào dữ liệu bổ sung về người dùng (thông qua chính sách trên Bảng điều khiển dành cho quản trị viên của Google), dịch vụ sẽ nhận được CSR do người dùng ký. Sau đó, dịch vụ này có thể được dùng để lấy chứng chỉ thực từ tổ chức phát hành chứng chỉ. Khi tích hợp với Microsoft CA, dịch vụ mạng có thể đóng vai trò trung gian và sử dụng giao diện ICertRequest.

Sử dụng chứng chỉ ứng dụng với Quyền truy cập đã xác minh

Sử dụng chứng chỉ máy khách với Quyền truy cập đã xác minh.

Trong một tổ chức lớn, có thể có nhiều dịch vụ mạng (máy chủ VPN, điểm truy cập Wi-Fi, tường lửa và nhiều trang web nội bộ) sẽ hưởng lợi từ tính năng Quyền truy cập đã xác minh. Tuy nhiên, việc xây dựng logic của các bước 2–4 (trong phần trên) trong từng dịch vụ mạng này có thể không thực tế. Thông thường, nhiều dịch vụ mạng trong số này đã có khả năng yêu cầu chứng chỉ ứng dụng trong quá trình xác thực (ví dụ: các trang nội bộ EAP-TLS hoặc TLS tương hỗ). Vì vậy, nếu Tổ chức phát hành Chứng chỉ doanh nghiệp cấp các chứng chỉ ứng dụng này có thể triển khai các bước 2–4 và điều kiện cấp chứng chỉ ứng dụng trong quá trình xác minh phản hồi xác minh, thì việc sở hữu chứng chỉ có thể là bằng chứng cho thấy ứng dụng đó là thật và tuân thủ chính sách của công ty. Sau đó, mỗi điểm truy cập Wi-Fi, máy chủ VPN, v.v. có thể kiểm tra chứng chỉ ứng dụng này thay vì cần làm theo các bước 2–4.

Nói cách khác, ở đây, CA (cấp chứng chỉ máy khách cho các thiết bị của doanh nghiệp) đóng vai trò của Dịch vụ mạng như trong Hình 1. Trình phân tích cú pháp cần gọi API Quyền truy cập đã xác minh và chỉ cung cấp chứng chỉ cho ứng dụng sau khi xác minh phản hồi thử thách vượt qua quy trình xác minh. Việc cung cấp chứng chỉ cho ứng dụng tương đương với Bước 4 – Cấp quyền truy cập ở Hình 1.

Quy trình nhận chứng chỉ ứng dụng khách một cách an toàn cho Chromebook được mô tả trong bài viết này. Nếu tuân theo thiết kế mô tả trong đoạn này, thì bạn có thể kết hợp Tiện ích truy cập đã xác minh và Tiện ích giới thiệu chứng chỉ ứng dụng thành một. Tìm hiểu thêm về cách viết tiện ích giới thiệu chứng chỉ ứng dụng.