คู่มือนักพัฒนาซอฟต์แวร์การเข้าถึงที่ได้รับการยืนยันของ Chrome

ข้อมูลเกี่ยวกับคู่มือนี้

Chrome Verified Access API ช่วยให้บริการต่างๆ ของเครือข่าย เช่น VPN, หน้าอินทราเน็ต และอื่นๆ สามารถยืนยันแบบเข้ารหัสได้ว่าไคลเอ็นต์ของตนเป็นของแท้และเป็นไปตามนโยบายของบริษัท องค์กรขนาดใหญ่ส่วนใหญ่กำหนดให้อนุญาตเฉพาะอุปกรณ์ที่จัดการโดยองค์กรในเครือข่าย WPA2 EAP-TLS การเข้าถึง VPN ในระดับที่สูงขึ้น และหน้าอินทราเน็ตแบบ TLS ร่วมกัน โซลูชันจำนวนมากที่มีอยู่ต้องพึ่งพาการตรวจสอบระบบการเรียนรู้ในไคลเอ็นต์เดียวกันซึ่งอาจถูกบุกรุก กรณีนี้แสดงถึงความท้าทายที่ว่าอุปกรณ์อาจมีการปลอมแปลงสัญญาณที่ต้องใช้ในการรับรองสถานะที่ถูกต้องของอุปกรณ์ คู่มือนี้จะรับรองข้อมูลประจำตัวของอุปกรณ์โดยใช้ฮาร์ดแวร์ พร้อมการรับประกันสถานะอุปกรณ์โดยไม่มีการแก้ไขและเป็นไปตามนโยบายขณะเปิดเครื่อง ซึ่งเรียกว่า "การเข้าถึงที่ยืนยันแล้ว"

กลุ่มเป้าหมายหลัก ผู้ดูแลระบบโดเมนด้านไอทีขององค์กร
องค์ประกอบทางเทคนิค ChromeOS, API การเข้าถึงที่ยืนยันของ Google

ข้อกำหนดเบื้องต้นในการเข้าถึง Verified Access

โปรดตั้งค่าต่อไปนี้ให้เสร็จก่อนที่จะใช้ขั้นตอนการเข้าถึงที่ยืนยัน

เปิดใช้ API

สร้างโปรเจ็กต์คอนโซล Google API และเปิดใช้ API โดยทำดังนี้

  1. สร้างหรือใช้โปรเจ็กต์ที่มีอยู่ในคอนโซล Google API
  2. ไปที่หน้า API และบริการที่เปิดใช้
  3. เปิดใช้ Chrome Verified Access API
  4. สร้างคีย์ API สำหรับแอปพลิเคชันโดยทำตามเอกสารประกอบของ Google Cloud API

สร้างบัญชีบริการ

หากต้องการให้บริการเครือข่ายเข้าถึง Chrome Verified Access API เพื่อยืนยันการตอบกลับของภารกิจ ให้สร้างบัญชีบริการและคีย์บัญชีบริการ (คุณไม่จำเป็นต้องสร้างโปรเจ็กต์ระบบคลาวด์ใหม่ หรืออาจใช้โปรเจ็กต์เดิมก็ได้)

เมื่อสร้างคีย์บัญชีบริการแล้ว คุณควรดาวน์โหลดไฟล์คีย์ส่วนตัวของบัญชีบริการไว้ นี่เป็นสำเนาเพียงรายการเดียวของคีย์ส่วนตัว ดังนั้นโปรดเก็บคีย์ให้ปลอดภัย

ลงทะเบียนอุปกรณ์ Chromebook ที่มีการจัดการ

คุณต้องตั้งค่าอุปกรณ์ Chromebook ที่มีการจัดการอย่างเหมาะสมด้วยส่วนขยาย Chrome สำหรับการเข้าถึงที่ยืนยัน

  1. อุปกรณ์ Chromebook ต้องลงทะเบียนสำหรับการจัดการขององค์กรหรือการศึกษา
  2. ผู้ใช้อุปกรณ์ต้องเป็นผู้ใช้ที่ลงทะเบียนจากโดเมนเดียวกัน
  3. ต้องติดตั้งส่วนขยาย Chrome สำหรับการเข้าถึงที่ยืนยันในอุปกรณ์
  4. นโยบายจะได้รับการกำหนดค่าเพื่อเปิดใช้การเข้าถึงที่ยืนยันแล้ว อนุญาตส่วนขยาย Chrome ในรายการที่อนุญาต และให้สิทธิ์เข้าถึง API สำหรับบัญชีบริการที่เป็นตัวแทนของบริการเครือข่าย (ดูเอกสารประกอบความช่วยเหลือเกี่ยวกับคอนโซลผู้ดูแลระบบของ Google)

ยืนยันผู้ใช้และอุปกรณ์

นักพัฒนาแอปสามารถใช้การเข้าถึงที่ยืนยันสำหรับการยืนยันผู้ใช้หรืออุปกรณ์ หรือใช้ทั้ง 2 อย่างเพื่อความปลอดภัยที่เพิ่มขึ้น

  • การยืนยันอุปกรณ์ หากการยืนยันอุปกรณ์สำเร็จ การยืนยันอุปกรณ์จะให้การรับประกันว่าอุปกรณ์ Chrome นั้นได้ลงทะเบียนในโดเมนที่มีการจัดการแล้ว และเป็นไปตามนโยบายด้านอุปกรณ์ในโหมดการเปิดเครื่องที่ได้รับการยืนยันตามที่ผู้ดูแลระบบโดเมนระบุไว้ หากบริการเครือข่ายได้รับสิทธิ์ในการดูข้อมูลประจำตัวของอุปกรณ์ (ดูเอกสารความช่วยเหลือสำหรับคอนโซลผู้ดูแลระบบของ Google) บริการเครือข่ายจะได้รับรหัสอุปกรณ์ที่สามารถใช้เพื่อการตรวจสอบ ติดตาม หรือเรียกใช้ Directory API

  • การยืนยันผู้ใช้ หากการยืนยันผู้ใช้สำเร็จ การยืนยันผู้ใช้เป็นการรับประกันว่าผู้ใช้ Chrome ที่ลงชื่อเข้าใช้เป็นผู้ใช้ที่มีการจัดการ ใช้อุปกรณ์ที่ลงทะเบียน และเป็นไปตามนโยบายผู้ใช้ในโหมดการเปิดเครื่องที่ได้รับการยืนยันตามที่ผู้ดูแลระบบโดเมนระบุไว้ หากบริการเครือข่ายได้รับสิทธิ์ให้รับข้อมูลผู้ใช้เพิ่มเติม บริการดังกล่าวจะได้รับคำขอลงชื่อใบรับรองที่ออกโดยผู้ใช้ (CSR ในรูปแบบคีย์สาธารณะและคีย์ที่ลงชื่อแล้ว หรือ SPKAC หรือที่เรียกว่ารูปแบบคีย์เจน)

วิธียืนยันผู้ใช้และอุปกรณ์

  1. รับคำท้า - ส่วนขยาย Chrome ในอุปกรณ์จะติดต่อกับ Verified Access 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
      }
    };
    

    การทดสอบโค้ดตัวช่วยในการเข้ารหัส - หากคุณใช้ v1 ของ API ชาเลนจ์นั้นจะต้องเข้ารหัส

    // 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 การดำเนินการนี้จะสร้างคำตอบสำหรับทดสอบที่ลงชื่อและเข้ารหัสไว้ ซึ่งส่วนขยายจะรวมไว้ในคำขอเข้าถึงที่ส่งไปยังบริการเครือข่าย

    ในขั้นตอนนี้ คุณจะไม่ต้องกำหนดโปรโตคอลที่ส่วนขยายและบริการเครือข่ายใช้สื่อสาร ทั้งสองเอนทิตีนี้มีการใช้งานโดยนักพัฒนาซอฟต์แวร์ภายนอกและไม่ได้กำหนดไว้ว่าจะพูดคุยกันอย่างไร ตัวอย่างเช่น การส่งคำตอบทดสอบ (เข้ารหัส 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. ยืนยันการตอบกลับคำถาม - เมื่อได้รับการตอบกลับจากอุปกรณ์ (อาจเป็นส่วนขยายของโปรโตคอลการตรวจสอบสิทธิ์ที่มีอยู่) บริการเครือข่ายควรเรียกใช้ Verified Access API เพื่อยืนยันตัวตนและท่านโยบายของอุปกรณ์ (ดูตัวอย่างโค้ดด้านล่าง) ในการต่อสู้กับการปลอมแปลง เราขอแนะนำว่าบริการเครือข่ายจะระบุไคลเอ็นต์ที่ตัวไคลเอ็นต์กำลังคุยด้วย และรวมข้อมูลประจำตัวของลูกค้า (ตามที่คาดหวัง) ในคำขอ

    • สำหรับการยืนยันอุปกรณ์ คุณควรระบุโดเมนอุปกรณ์ที่คาดไว้ ซึ่งมักจะเป็นแบบฮาร์ดโค้ดในหลายๆ กรณี เนื่องจากบริการเครือข่ายจะปกป้องทรัพยากรสำหรับโดเมนหนึ่งๆ หากไม่ทราบล่วงหน้า อาจมีการอนุมานจากข้อมูลระบุตัวตนของผู้ใช้ได้
    • สำหรับการยืนยันผู้ใช้ คุณควรระบุอีเมลของผู้ใช้ที่คาดหวังไว้ เราคาดว่าบริการเครือข่ายจะทราบผู้ใช้ของตน (โดยปกติจะต้องให้ผู้ใช้ลงชื่อเข้าใช้)

    เมื่อมีการเรียก Google API ระบบจะทำการตรวจสอบหลายอย่าง เช่น

    • ตรวจสอบว่า 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. ให้สิทธิ์เข้าถึง - ขั้นตอนนี้ใช้ได้เฉพาะกับบริการเครือข่ายเท่านั้น ซึ่งเป็นการใช้งานที่แนะนำ (ไม่ได้กำหนดไว้) การดำเนินการที่เป็นไปได้มีดังนี้

    • การสร้างคุกกี้เซสชัน
    • การออกใบรับรองให้กับผู้ใช้หรืออุปกรณ์ ในกรณีที่ยืนยันผู้ใช้สำเร็จแล้ว และสมมติว่าบริการเครือข่ายได้รับสิทธิ์เข้าถึงข้อมูลผู้ใช้เพิ่มเติม (ผ่านนโยบายคอนโซลผู้ดูแลระบบของ Google) บริการเครือข่ายจะได้รับ CSR ที่ผู้ใช้ลงนาม ซึ่งสามารถใช้เพื่อขอรับใบรับรองจริงจากผู้ออกใบรับรอง เมื่อผสานรวมกับ Microsoft CA บริการเครือข่ายอาจทำหน้าที่เป็นตัวกลาง และใช้ประโยชน์จากอินเทอร์เฟซ ICertRequest

การใช้ใบรับรองไคลเอ็นต์กับการเข้าถึงที่ยืนยันแล้ว

การใช้ใบรับรองไคลเอ็นต์กับการเข้าถึงที่ยืนยันแล้ว

ในองค์กรขนาดใหญ่ อาจมีบริการเครือข่ายหลายอย่าง (เซิร์ฟเวอร์ VPN, จุดเข้าใช้งาน Wi-Fi, ไฟร์วอลล์ และเว็บไซต์อินทราเน็ตหลายแห่ง) ที่จะได้รับประโยชน์จากการเข้าถึงที่ยืนยันแล้ว แต่การสร้างตรรกะของขั้นตอนที่ 2-4 (ในส่วนด้านบน) ในบริการเครือข่ายแต่ละรายการเหล่านี้อาจไม่ใช่วิธีการปฏิบัติ บ่อยครั้งที่บริการเครือข่ายเหล่านี้จำนวนมากสามารถขอใบรับรองไคลเอ็นต์ในการตรวจสอบสิทธิ์ได้อยู่แล้ว (เช่น หน้า EAP-TLS หรือหน้าอินทราเน็ต TLS ร่วม) ดังนั้น หากหน่วยงานที่ออกใบรับรองไคลเอ็นต์เหล่านี้สามารถใช้ขั้นตอนที่ 2-4 และกำหนดเงื่อนไขในการออกใบรับรองไคลเอ็นต์ในการยืนยันเพื่อตอบคำถาม การครอบครองใบรับรองอาจเป็นหลักฐานว่าไคลเอ็นต์เป็นของแท้และสอดคล้องกับนโยบายขององค์กร หลังจากนั้น จุดเข้าใช้งาน Wi-Fi แต่ละจุด เซิร์ฟเวอร์ VPN และอื่นๆ สามารถตรวจสอบใบรับรองไคลเอ็นต์นี้ได้โดยไม่ต้องทำตามขั้นตอนที่ 2-4

กล่าวคือ CA (ที่ออกใบรับรองไคลเอ็นต์ไปยังอุปกรณ์ขององค์กร) จะรับบทบาทของบริการเครือข่ายในรูปที่ 1 จะต้องเรียกใช้ Verified Access API และต้องส่งใบรับรองแก่ไคลเอ็นต์เฉพาะเมื่อผ่านการตรวจสอบการตอบสนองคำถาม การส่งใบรับรองให้กับไคลเอ็นต์เทียบเท่ากับขั้นตอนที่ 4 - ให้สิทธิ์การเข้าถึงในรูปที่ 1

โปรดดูคำอธิบายขั้นตอนการรับใบรับรองไคลเอ็นต์ไปยัง Chromebook อย่างปลอดภัยในบทความนี้ หากเป็นไปตามการออกแบบที่อธิบายไว้ในย่อหน้านี้ ส่วนขยายการเข้าถึงที่ยืนยันแล้วและส่วนขยายการเริ่มต้นใช้งานใบรับรองไคลเอ็นต์รวมกันเป็นรายการเดียวได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเขียนส่วนขยายการเริ่มต้นใช้งานใบรับรองไคลเอ็นต์