// Copyright 2022 Luca Casonato. All rights reserved. MIT license. /** * Chrome Verified Access API Client for Deno * ========================================== * * API for Verified Access chrome extension to provide credential verification for chrome devices connecting to an enterprise network * * Docs: https://developers.google.com/chrome/verified-access * Source: https://googleapis.deno.dev/v1/verifiedaccess:v2.ts */ import { auth, CredentialsClient, GoogleAuth, request } from "/_/base@v1/mod.ts"; export { auth, GoogleAuth }; export type { CredentialsClient }; /** * API for Verified Access chrome extension to provide credential verification * for chrome devices connecting to an enterprise network */ export class VerifiedAccess { #client: CredentialsClient | undefined; #baseUrl: string; constructor(client?: CredentialsClient, baseUrl: string = "https://verifiedaccess.googleapis.com/") { this.#client = client; this.#baseUrl = baseUrl; } /** * Generates a new challenge. * */ async challengeGenerate(req: Empty): Promise { const url = new URL(`${this.#baseUrl}v2/challenge:generate`); const body = JSON.stringify(req); const data = await request(url.href, { client: this.#client, method: "POST", body, }); return deserializeChallenge(data); } /** * Verifies the challenge response. * */ async challengeVerify(req: VerifyChallengeResponseRequest): Promise { req = serializeVerifyChallengeResponseRequest(req); const url = new URL(`${this.#baseUrl}v2/challenge:verify`); const body = JSON.stringify(req); const data = await request(url.href, { client: this.#client, method: "POST", body, }); return data as VerifyChallengeResponseResult; } } /** * Result message for VerifiedAccess.GenerateChallenge. */ export interface Challenge { /** * Generated challenge, the bytes representation of SignedData. */ challenge?: Uint8Array; } function serializeChallenge(data: any): Challenge { return { ...data, challenge: data["challenge"] !== undefined ? encodeBase64(data["challenge"]) : undefined, }; } function deserializeChallenge(data: any): Challenge { return { ...data, challenge: data["challenge"] !== undefined ? decodeBase64(data["challenge"] as string) : undefined, }; } /** * Properties of the CrowdStrike agent installed on a device. */ export interface CrowdStrikeAgent { /** * The Agent ID of the Crowdstrike agent. */ agentId?: string; /** * The Customer ID to which the agent belongs to. */ customerId?: string; } /** * The device signals as reported by Chrome. Unless otherwise specified, * signals are available on all platforms. */ export interface DeviceSignals { /** * Value of the AllowScreenLock policy on the device. See * https://chromeenterprise.google/policies/?policy=AllowScreenLock for more * details. Available on ChromeOS only. */ allowScreenLock?: boolean; /** * Current version of the Chrome browser which generated this set of signals. * Example value: "107.0.5286.0". */ browserVersion?: string; /** * Whether Chrome's built-in DNS client is used. The OS DNS client is * otherwise used. This value may be controlled by an enterprise policy: * https://chromeenterprise.google/policies/#BuiltInDnsClientEnabled. */ builtInDnsClientEnabled?: boolean; /** * Whether access to the Chrome Remote Desktop application is blocked via a * policy. */ chromeRemoteDesktopAppBlocked?: boolean; /** * Crowdstrike agent properties installed on the device, if any. Available on * Windows and MacOS only. */ crowdStrikeAgent?: CrowdStrikeAgent; /** * Affiliation IDs of the organizations that are affiliated with the * organization that is currently managing the device. When the sets of device * and profile affiliation IDs overlap, it means that the organizations * managing the device and user are affiliated. To learn more about user * affiliation, visit * https://support.google.com/chrome/a/answer/12801245?ref_topic=9027936. */ deviceAffiliationIds?: string[]; /** * Enrollment domain of the customer which is currently managing the device. */ deviceEnrollmentDomain?: string; /** * The name of the device's manufacturer. */ deviceManufacturer?: string; /** * The name of the device's model. */ deviceModel?: string; /** * The encryption state of the disk. On ChromeOS, the main disk is always * ENCRYPTED. */ diskEncryption?: | "DISK_ENCRYPTION_UNSPECIFIED" | "DISK_ENCRYPTION_UNKNOWN" | "DISK_ENCRYPTION_DISABLED" | "DISK_ENCRYPTION_ENCRYPTED"; /** * The display name of the device, as defined by the user. */ displayName?: string; /** * Hostname of the device. */ hostname?: string; /** * International Mobile Equipment Identity (IMEI) of the device. Available on * ChromeOS only. */ imei?: string[]; /** * MAC addresses of the device. */ macAddresses?: string[]; /** * Mobile Equipment Identifier (MEID) of the device. Available on ChromeOS * only. */ meid?: string[]; /** * The type of the Operating System currently running on the device. */ operatingSystem?: | "OPERATING_SYSTEM_UNSPECIFIED" | "CHROME_OS" | "CHROMIUM_OS" | "WINDOWS" | "MAC_OS_X" | "LINUX"; /** * The state of the OS level firewall. On ChromeOS, the value will always be * ENABLED on regular devices and UNKNOWN on devices in developer mode. * Support for MacOS 15 (Sequoia) and later has been introduced in Chrome * M131. */ osFirewall?: | "OS_FIREWALL_UNSPECIFIED" | "OS_FIREWALL_UNKNOWN" | "OS_FIREWALL_DISABLED" | "OS_FIREWALL_ENABLED"; /** * The current version of the Operating System. On Windows and linux, the * value will also include the security patch information. */ osVersion?: string; /** * Whether the Password Protection Warning feature is enabled or not. * Password protection alerts users when they reuse their protected password * on potentially suspicious sites. This setting is controlled by an * enterprise policy: * https://chromeenterprise.google/policies/#PasswordProtectionWarningTrigger. * Note that the policy unset does not have the same effects as having the * policy explicitly set to `PASSWORD_PROTECTION_OFF`. */ passwordProtectionWarningTrigger?: | "PASSWORD_PROTECTION_WARNING_TRIGGER_UNSPECIFIED" | "POLICY_UNSET" | "PASSWORD_PROTECTION_OFF" | "PASSWORD_REUSE" | "PHISHING_REUSE"; /** * Affiliation IDs of the organizations that are affiliated with the * organization that is currently managing the Chrome Profile’s user or * ChromeOS user. */ profileAffiliationIds?: string[]; /** * Enrollment domain of the customer which is currently managing the profile. */ profileEnrollmentDomain?: string; /** * Whether Enterprise-grade (i.e. custom) unsafe URL scanning is enabled or * not. This setting may be controlled by an enterprise policy: * https://chromeenterprise.google/policies/#EnterpriseRealTimeUrlCheckMode */ realtimeUrlCheckMode?: | "REALTIME_URL_CHECK_MODE_UNSPECIFIED" | "REALTIME_URL_CHECK_MODE_DISABLED" | "REALTIME_URL_CHECK_MODE_ENABLED_MAIN_FRAME"; /** * Safe Browsing Protection Level. That setting may be controlled by an * enterprise policy: * https://chromeenterprise.google/policies/#SafeBrowsingProtectionLevel. */ safeBrowsingProtectionLevel?: | "SAFE_BROWSING_PROTECTION_LEVEL_UNSPECIFIED" | "INACTIVE" | "STANDARD" | "ENHANCED"; /** * The state of the Screen Lock password protection. On ChromeOS, this value * will always be ENABLED as there is not way to disable requiring a password * or pin when unlocking the device. */ screenLockSecured?: | "SCREEN_LOCK_SECURED_UNSPECIFIED" | "SCREEN_LOCK_SECURED_UNKNOWN" | "SCREEN_LOCK_SECURED_DISABLED" | "SCREEN_LOCK_SECURED_ENABLED"; /** * Whether the device's startup software has its Secure Boot feature enabled. * Available on Windows only. */ secureBootMode?: | "SECURE_BOOT_MODE_UNSPECIFIED" | "SECURE_BOOT_MODE_UNKNOWN" | "SECURE_BOOT_MODE_DISABLED" | "SECURE_BOOT_MODE_ENABLED"; /** * The serial number of the device. On Windows, this represents the BIOS's * serial number. Not available on most Linux distributions. */ serialNumber?: string; /** * Whether the Site Isolation (a.k.a Site Per Process) setting is enabled. * That setting may be controlled by an enterprise policy: * https://chromeenterprise.google/policies/#SitePerProcess */ siteIsolationEnabled?: boolean; /** * List of the addesses of all OS level DNS servers configured in the * device's network settings. */ systemDnsServers?: string[]; /** * Whether Chrome is blocking third-party software injection or not. This * setting may be controlled by an enterprise policy: * https://chromeenterprise.google/policies/?policy=ThirdPartyBlockingEnabled. * Available on Windows only. */ thirdPartyBlockingEnabled?: boolean; /** * The trigger which generated this set of signals. */ trigger?: | "TRIGGER_UNSPECIFIED" | "TRIGGER_BROWSER_NAVIGATION" | "TRIGGER_LOGIN_SCREEN"; /** * Windows domain that the current machine has joined. Available on Windows * only. */ windowsMachineDomain?: string; /** * Windows domain for the current OS user. Available on Windows only. */ windowsUserDomain?: string; } /** * A generic empty message that you can re-use to avoid defining duplicated * empty messages in your APIs. A typical example is to use it as the request or * the response type of an API method. For instance: service Foo { rpc * Bar(google.protobuf.Empty) returns (google.protobuf.Empty); } */ export interface Empty { } /** * Signed ChallengeResponse. */ export interface VerifyChallengeResponseRequest { /** * Required. The generated response to the challenge, the bytes * representation of SignedData. */ challengeResponse?: Uint8Array; /** * Optional. Service can optionally provide identity information about the * device or user associated with the key. For an EMK, this value is the * enrolled domain. For an EUK, this value is the user's email address. If * present, this value will be checked against contents of the response, and * verification will fail if there is no match. */ expectedIdentity?: string; } function serializeVerifyChallengeResponseRequest(data: any): VerifyChallengeResponseRequest { return { ...data, challengeResponse: data["challengeResponse"] !== undefined ? encodeBase64(data["challengeResponse"]) : undefined, }; } function deserializeVerifyChallengeResponseRequest(data: any): VerifyChallengeResponseRequest { return { ...data, challengeResponse: data["challengeResponse"] !== undefined ? decodeBase64(data["challengeResponse"] as string) : undefined, }; } /** * Result message for VerifiedAccess.VerifyChallengeResponse. */ export interface VerifyChallengeResponseResult { /** * Attested device ID (ADID). */ attestedDeviceId?: string; /** * Unique customer id that this device belongs to, as defined by the Google * Admin SDK at * https://developers.google.com/admin-sdk/directory/v1/guides/manage-customers */ customerId?: string; /** * Device enrollment id for ChromeOS devices. */ deviceEnrollmentId?: string; /** * Device permanent id is returned in this field (for the machine response * only). */ devicePermanentId?: string; /** * Deprecated. Device signal in json string representation. Prefer using * `device_signals` instead. */ deviceSignal?: string; /** * Device signals. */ deviceSignals?: DeviceSignals; /** * Device attested key trust level. */ keyTrustLevel?: | "KEY_TRUST_LEVEL_UNSPECIFIED" | "CHROME_OS_VERIFIED_MODE" | "CHROME_OS_DEVELOPER_MODE" | "CHROME_BROWSER_HW_KEY" | "CHROME_BROWSER_OS_KEY" | "CHROME_BROWSER_NO_KEY"; /** * Unique customer id that this profile belongs to, as defined by the Google * Admin SDK at * https://developers.google.com/admin-sdk/directory/v1/guides/manage-customers */ profileCustomerId?: string; /** * Profile attested key trust level. */ profileKeyTrustLevel?: | "KEY_TRUST_LEVEL_UNSPECIFIED" | "CHROME_OS_VERIFIED_MODE" | "CHROME_OS_DEVELOPER_MODE" | "CHROME_BROWSER_HW_KEY" | "CHROME_BROWSER_OS_KEY" | "CHROME_BROWSER_NO_KEY"; /** * Certificate Signing Request (in the SPKAC format, base64 encoded) is * returned in this field. This field will be set only if device has included * CSR in its challenge response. (the option to include CSR is now available * for both user and machine responses) */ signedPublicKeyAndChallenge?: string; /** * Virtual device id of the device. The definition of virtual device id is * platform-specific. */ virtualDeviceId?: string; /** * The ID of a profile on the device. */ virtualProfileId?: string; } function decodeBase64(b64: string): Uint8Array { const binString = atob(b64); const size = binString.length; const bytes = new Uint8Array(size); for (let i = 0; i < size; i++) { bytes[i] = binString.charCodeAt(i); } return bytes; } const base64abc = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/"]; /** * CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 * Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation * @param data */ function encodeBase64(uint8: Uint8Array): string { let result = "", i; const l = uint8.length; for (i = 2; i < l; i += 3) { result += base64abc[uint8[i - 2] >> 2]; result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]; result += base64abc[uint8[i] & 0x3f]; } if (i === l + 1) { // 1 octet yet to write result += base64abc[uint8[i - 2] >> 2]; result += base64abc[(uint8[i - 2] & 0x03) << 4]; result += "=="; } if (i === l) { // 2 octets yet to write result += base64abc[uint8[i - 2] >> 2]; result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; result += base64abc[(uint8[i - 1] & 0x0f) << 2]; result += "="; } return result; }