關於這份指南
Chrome Verified Access API 可讓網路服務 (例如 VPN) 和內部網路 以加密方式 驗證客戶是否真實可靠 符合公司政策大多數大型企業都要求 只允許企業管理的裝置透過 WPA2 EAP-TLS 網路存取。 ,以及雙向傳輸層安全標準 (TLS) 內部網路的層級較高的存取權許多現有 會仰賴經驗法則檢查, 遭到入侵的漏洞這便是工具中的挑戰 本裝置的合法狀態, 偽造本指南提供硬體支援的加密編譯保證, 裝置識別資訊,以及裝置狀態未經修改且符合政策規定 執行時;稱為「已驗證存取權」
主要目標對象 | 企業 IT 網域管理員 |
技術元件 | ChromeOS、Google Verified Access API |
使用「已驗證存取權」的先決條件
實作「已驗證存取權」程序前,請先完成下列設定。
啟用 API
設定 Google API 控制台專案並啟用 API:
- 在以下位置建立或使用現有專案: Google API 控制台。
- 前往 已啟用的 API 與服務網頁。
- 啟用 Chrome Verified Access API。
- 按照 Google Cloud API 說明文件為應用程式建立 API 金鑰。
建立服務帳戶
為了讓網路服務存取 Chrome Verified Access API 以驗證 「挑戰/回應」 建立服務帳戶和服務帳戶金鑰 (您不需要建立新的 Cloud 專案,可以使用同一個專案)。
建立服務帳戶金鑰後,您應擁有服務帳戶 已下載私密金鑰檔案。這是私密金鑰的唯一副本,因此請 請務必妥善保存
註冊受管理的 Chromebook 裝置
必須使用經過妥善管理的 Chromebook 裝置設定 Chrome 「已驗證存取權」的擴充功能
- Chromebook 裝置必須註冊企業或教育管理服務。
- 裝置的使用者必須是來自相同網域的註冊使用者。
- 您必須在裝置上安裝「已驗證存取權」的 Chrome 擴充功能。
- 政策設為啟用「已驗證存取權」,請將 Chrome 加入許可清單 擴充功能,並將存取 API 存取權授予代表 網路服務 (請參閱 Google 管理控制台說明文件)。
驗證使用者和裝置
開發人員可以使用「已驗證存取權」向使用者或裝置驗證,或同時使用這兩種做法 ,為帳戶多添一層保障:
裝置驗證:如果成功,裝置驗證會提供 確保 Chrome 裝置已在受管理的網域中註冊,且該裝置 符合網域指定的驗證開機模式裝置政策 管理員。如果網路服務授予查看裝置的權限 身分 (請參閱 Google 管理控制台說明文件),也會收到 裝置 ID 可用於稽核、追蹤或呼叫 Directory API。
使用者驗證:如果成功,使用者驗證就能保證 已登入的 Chrome 使用者是受管理的使用者,且正在使用已註冊的裝置。 必須符合網域指定的驗證開機模式使用者政策 管理員。如果網路服務授予接收權限 額外的使用者資料時,也會取得已核發的憑證簽署要求 使用者 (CSR,格式為 Sign-public-key-and-challenge,或 SPKAC), 也稱為 Keygen 格式)。
如何驗證使用者和裝置
取得驗證問題:裝置上的 Chrome 擴充功能與「已驗證」連線 存取 API 來取得驗證。面臨的難題是在資料中 容器 (Google 簽署的 blob),有效期限為 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 第 1 版, 將需要編碼。
// 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); } };
產生驗證問題:Chrome 擴充功能會使用驗證方式 以便呼叫 enterprise.platformKeys Chrome API。這個 產生已簽署的加密挑戰回應,而 包含在傳送至網路服務的存取要求中。
在這個步驟中,系統不會嘗試定義擴充功能 以及網路服務使用的通訊方式這兩種實體都會 而並未主張自己彼此之間的溝通方式。一個 可能是以查詢字串形式傳送 (網址編碼) 驗證回應 參數,透過 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; }
驗證挑戰回應:收到來自 裝置 (也許可作為現有驗證通訊協定的擴充功能) 網路服務應呼叫 Verified Access API 來驗證裝置 身分和政策防護機制 (請見下方的程式碼範例)。為打擊詐騙,我們 建議網路服務辨識正在通訊的用戶端 在要求中加入用戶端的預期身分:
- 如要驗證裝置,請務必提供預期的裝置網域 ,直接在 Google Cloud 控制台實際操作。在許多情況下,這很可能是硬式編碼值,因為網路 服務可保護特定網域中的資源。如果不清楚 便可根據使用者身分推斷。
- 進行使用者驗證時,預期使用者的電子郵件地址應為 或更新提示我們希望網路服務能夠知道使用者 (一般為它) 將要求使用者登入)。
呼叫 Google API 時,系統會執行多項檢查,例如:
- 確認驗證回應是由 ChromeOS 產生,而不是 修改運送中
- 確認裝置或使用者受到企業管理。
- 確認裝置/使用者的身分與預期相符 身分 (如果提供後者)。
- 驗證目前回應的挑戰 即時 (不超過 1 分鐘)。
- 確認裝置或使用者符合 網域管理員。
- 確認呼叫端 (網路服務) 已獲得呼叫權限 並嚴謹測試及提升 API 的公平性後 我們才能放心地推出 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(); }
授予存取權:這個步驟也是網路服務專屬。這是 以下是可能的動作:
- 建立工作階段 Cookie
- 為使用者或裝置核發憑證。對於成功的使用者 並假設網路服務已取得存取權 至其他使用者資料 (透過 Google 管理控制台政策), 會收到使用者簽署的 CSR,然後透過這個 CSR 取得憑證授權單位核發的憑證整合 MicrosoftR 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 的程序說明瞭 這篇文章中。如果 依序描述此段落所述的設計,以及「已驗證存取權擴充功能」 和用戶端憑證登入擴充功能可以合併為一個擴充功能瞭解詳情 瞭解如何撰寫用戶端憑證註冊擴充功能。