[Device Trust] Generate challenge response
Chrome gets the challenge from the Idp, then it create the challenge
response and send it back in the proper header.
This CL includes
- Handle the HTTP response from the Idp which contains the challenge so
the client can generate the challenge response.
- Signing message method using the key pair created by client.
- Challenge response creation with the signature using the method
mentioned above.
- Use of VA protos for the handshake that were only part of Chrome OS.
This CL doesn't contain the full logic for the challenge response
creation, items that are missing:
- Encrypted key info in the challenge response.
- Verification of the challenge.
Following CLs are going to be created to implement those logic. I
decided to separated since those items need lot of code for the crypto
part.
Change-Id: Ie1d90f1007bb08ef48b0b168a200e22342530048
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2827913
Reviewed-by: Roger Tawa <rogerta@chromium.org>
Commit-Queue: Martin Rodriguez <rodmartin@google.com>
Cr-Commit-Position: refs/heads/master@{#878847}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index aaf1bf44..f0dbcef 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3557,6 +3557,8 @@
"enterprise/connectors/connectors_prefs.h",
"enterprise/connectors/connectors_service.cc",
"enterprise/connectors/connectors_service.h",
+ "enterprise/connectors/device_trust/attestation_service.cc",
+ "enterprise/connectors/device_trust/attestation_service.h",
"enterprise/connectors/device_trust/device_trust_factory.cc",
"enterprise/connectors/device_trust/device_trust_factory.h",
"enterprise/connectors/device_trust/device_trust_service.cc",
@@ -4177,6 +4179,10 @@
"//chrome/app/theme:chrome_unscaled_resources_grit",
"//chrome/app/vector_icons",
"//chrome/browser/cart:mojo_bindings",
+ "//chrome/browser/enterprise/connectors/device_trust:attestation_ca_proto",
+ "//chrome/browser/enterprise/connectors/device_trust:google_key_proto",
+ "//chrome/browser/enterprise/connectors/device_trust:interface_proto",
+ "//chrome/browser/enterprise/connectors/device_trust:keystore_proto",
"//chrome/browser/policy:path_parser",
"//chrome/browser/profile_resetter:profile_reset_report_proto",
"//chrome/browser/resource_coordinator:intervention_policy_database_proto",
diff --git a/chrome/browser/enterprise/connectors/device_trust/BUILD.gn b/chrome/browser/enterprise/connectors/device_trust/BUILD.gn
new file mode 100644
index 0000000..e717cc8
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("keystore_proto") {
+ proto_out_dir = "chrome/browser/enterprise/connectors/device_trust/"
+ sources = [ "//chrome/browser/enterprise/connectors/device_trust/attestation_protos/keystore.proto" ]
+ generate_python = false
+}
+
+proto_library("attestation_ca_proto") {
+ proto_out_dir = "chrome/browser/enterprise/connectors/device_trust/"
+ sources = [ "//chrome/browser/enterprise/connectors/device_trust/attestation_protos/attestation_ca.proto" ]
+ generate_python = false
+}
+
+proto_library("interface_proto") {
+ proto_out_dir = "chrome/browser/enterprise/connectors/device_trust/"
+ sources = [ "//chrome/browser/enterprise/connectors/device_trust/attestation_protos/interface.proto" ]
+ generate_python = false
+}
+
+proto_library("google_key_proto") {
+ proto_out_dir = "chrome/browser/enterprise/connectors/device_trust/"
+ sources = [ "//chrome/browser/enterprise/connectors/device_trust/attestation_protos/google_key.proto" ]
+ generate_python = false
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_protos/attestation_ca.proto b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/attestation_ca.proto
new file mode 100644
index 0000000..6e843f3
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/attestation_ca.proto
@@ -0,0 +1,345 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is an exact copy of
+// third_party/cros_system_api/dbus/attestation/attestation_ca.proto
+// third_party/cros_system_api is only for ChromeOS.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package attestation;
+option go_package = "chromiumos/system_api/attestation_proto";
+
+// Enumerates various certificate profiles supported by the Attestation CA.
+enum CertificateProfile {
+ // A certificate intended for enterprise-owned devices. It has the following
+ // subjectName fields:
+ // CN=<stable device identifier>
+ // OU=state:[verified|developer]
+ // O=Chrome Device Enterprise
+ ENTERPRISE_MACHINE_CERTIFICATE = 0;
+
+ // A certificate intended for enterprise-owned user accounts. It has the
+ // following subjectName fields:
+ // OU=state:[verified|developer]
+ // O=Chrome Device Enterprise
+ ENTERPRISE_USER_CERTIFICATE = 1;
+
+ // A certificate intended for platform verification by providers of protected
+ // content. It has the following subjectName fields:
+ // O=Chrome Device Content Protection
+ CONTENT_PROTECTION_CERTIFICATE = 2;
+
+ // Like above, but it also includes a stable ID and origin.
+ // CN=<origin-specific device identifier>
+ // OU=<origin>
+ // O=Chrome Device Content Protection
+ CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID = 3;
+
+ // A certificate intended for cast devices.
+ CAST_CERTIFICATE = 4;
+
+ GFSC_CERTIFICATE = 5;
+
+ JETSTREAM_CERTIFICATE = 6;
+
+ // A certificate for enterprise enrollment.
+ ENTERPRISE_ENROLLMENT_CERTIFICATE = 7;
+
+ // A certificate for signing Android Testsuite Results using CTS-in-a-box.
+ XTS_CERTIFICATE = 8;
+
+ // An EK certificate for vTPM
+ // CN=CROS VTPM PRD EK ROOT CA
+ ENTERPRISE_VTPM_EK_CERTIFICATE = 9;
+}
+
+enum TpmVersion {
+ TPM_1_2 = 1; // NOTE: This is the default. It must remain listed first.
+ TPM_2_0 = 2;
+}
+
+// Types of NVRAM quotes used for attestation.
+enum NVRAMQuoteType {
+ // Quote of the Cr50-backed BoardID.
+ BOARD_ID = 0;
+ // Quote of the Cr50-backed SN+RMA bits.
+ SN_BITS = 1;
+ // Quote of the Cr50-backed RSA public endorsement key certificate.
+ RSA_PUB_EK_CERT = 2;
+ // Quote of the Cr50-backed RSU device ID.
+ RSU_DEVICE_ID = 3;
+}
+
+// Holds information about a quote generated by the TPM.
+message Quote {
+ // The quote; a signature generated with the AIK.
+ optional bytes quote = 1;
+ // The serialized data that was quoted; this assists in verifying the quote.
+ optional bytes quoted_data = 2;
+ // The value of the PCR(s) at the time the quote was generated.
+ optional bytes quoted_pcr_value = 3;
+ // Source data which was originally used to extend the PCR. If this field
+ // exists it can be expected that SHA1(pcr_source_hint) was extended into the
+ // PCR.
+ optional bytes pcr_source_hint = 4;
+}
+
+// Holds encrypted data and information required to decrypt it.
+message EncryptedData {
+ // A key that has been sealed to the TPM or wrapped by another key.
+ optional bytes wrapped_key = 2;
+ // The initialization vector used during encryption.
+ optional bytes iv = 3;
+ // MAC of (iv + encrypted_data).
+ optional bytes mac = 4;
+ optional bytes encrypted_data = 5;
+ // An identifier for the wrapping key to assist in decryption.
+ optional bytes wrapping_key_id = 6;
+}
+
+// The wrapper message of any data and its signature.
+message SignedData {
+ // The data to be signed.
+ optional bytes data = 1;
+ // The signature of the data field.
+ optional bytes signature = 2;
+}
+
+// The first two fields are suitable for passing to Tspi_TPM_ActivateIdentity()
+// directly when using TPM 1.2. For TPM 2.0 the first two fields are not used.
+message EncryptedIdentityCredential {
+ // TPM_ASYM_CA_CONTENTS, encrypted with EK public key.
+ optional bytes asym_ca_contents = 1;
+ // TPM_SYM_CA_ATTESTATION, encrypted with the key in aysm_ca_contents.
+ optional bytes sym_ca_attestation = 2;
+
+ optional TpmVersion tpm_version = 3;
+
+ // The following fields are used only for TPM 2.0. For details see the TPM 2.0
+ // specification Part 1 Rev 1.16:
+ // - Section 9.5.3.3: General description of the scheme.
+ // - Section 24: More details including how to use the seed to compute the
+ // values for 'credential_mac' and 'wrapped_certificate->
+ // wrapped_key'
+ // - Section B.10.4: Encrypting the seed with a RSA EK.
+ // - Section C.7.4: Encrypting the seed with an EC EK.
+
+ // A seed encrypted with the EK public key. The TPM will use this seed to
+ // derive both an HMAC key to verify the 'credential_mac' field and an AES key
+ // to unwrap the 'wrapped_certificate->wrapped_key' field.
+ optional bytes encrypted_seed = 4;
+
+ // An integrity value computed using HMAC-SHA256 over the
+ // 'wrapped_certificate.wrapped_key' field and the 'Name' of the identity key.
+ optional bytes credential_mac = 5;
+
+ // A certificate encrypted with a 'credential' that is decrypted by the TPM.
+ // The 'wrapped_key' field contains the encrypted credential which is
+ // encrypted using AES-256-CFB with a zero IV. The encryption of the
+ // certificate itself uses AES-256-CBC with PKCS #5 padding and a random IV.
+ // The encryption key is derived from the 'credential' using:
+ // SHA256('ENCRYPT' + credential)
+ // The mac uses HMAC-SHA256 with a key derived using:
+ // SHA256('MAC' + credential)
+ optional EncryptedData wrapped_certificate = 6;
+}
+
+// This message holds all information to be sent to the attestation server in
+// order to complete enrollment.
+message AttestationEnrollmentRequest {
+ // The EK cert, in X.509 form, encrypted using the server's public key with
+ // the following parameters:
+ // Key encryption: RSA-OAEP with no custom parameters.
+ // Data encryption: 256-bit key, AES-CBC with PKCS5 padding.
+ // MAC: HMAC-SHA-512 using the AES key.
+ optional EncryptedData encrypted_endorsement_credential = 1;
+ // The AIK public key, the raw TPM format. (TPM_PUBKEY for TPM 1.2,
+ // TPMT_PUBLIC for TPM 2.0).
+ optional bytes identity_public_key = 2;
+ // PCR0 quoted by AIK.
+ optional Quote pcr0_quote = 3;
+ // PCR1 quoted by AIK.
+ optional Quote pcr1_quote = 4;
+ // DEN for enterprise zero-touch enrollment (crbug/624187).
+ optional bytes enterprise_enrollment_nonce = 5;
+ // The device TPM version.
+ optional TpmVersion tpm_version = 6;
+ // An encrypted quote of the RSA EK cert, in X.509 form, if the endorsement
+ // credential is not RSA.
+ optional EncryptedData encrypted_rsa_endorsement_quote = 7;
+}
+
+enum ResponseStatus {
+ OK = 0;
+ // Internal server error.
+ SERVER_ERROR = 1;
+ // The server cannot parse the request.
+ BAD_REQUEST = 2;
+ // The server rejects the request.
+ REJECT = 3;
+ // Only appears in enrollment response. The server returns the same generated
+ // id and reports the quota limit exceeded status when the number of reset
+ // action in a specified time window is more than self reset limitation.
+ QUOTA_LIMIT_EXCEEDED = 4;
+}
+
+// The response from the attestation server for the enrollment request.
+message AttestationEnrollmentResponse {
+ optional ResponseStatus status = 1;
+ // Short detail response message. Included when the result is not OK.
+ optional string detail = 2;
+ optional EncryptedIdentityCredential encrypted_identity_credential = 3;
+ // Extra details included when the result is not OK.
+ optional string extra_details = 4;
+}
+
+// The certificate request to be sent to the attestation server.
+message AttestationCertificateRequest {
+ // The AIK cert in X.509 format.
+ optional bytes identity_credential = 1;
+ // A certified public key in TPM_PUBKEY (TPMT_PUBLIC for TPM 2.0).
+ optional bytes certified_public_key = 3;
+ // The serialized TPM_CERTIFY_INFO (TPMS_ATTEST for TPM 2.0) for the
+ // certified key.
+ optional bytes certified_key_info = 4;
+ // The signature of the TPM_CERTIFY_INFO (TPMS_ATTEST for TPM 2.0) by the AIK.
+ optional bytes certified_key_proof = 5;
+ // A message identifier to be included in the response.
+ optional bytes message_id = 10;
+ // The certificate profile defines the type of certificate to issue.
+ optional CertificateProfile profile = 11;
+ // Information about the origin of the request which may be used depending on
+ // the certificate profile.
+ optional string origin = 12;
+ // The index of a temporal value. This may be used or ignored depending on
+ // the certificate profile.
+ optional int32 temporal_index = 13;
+ // The device TPM version.
+ optional TpmVersion tpm_version = 14;
+ // NVRAM quoted by AIK. Keys are values of the NVRAMQuoteType. This is only
+ // used for enrollment certificates.
+ map<int32, Quote> nvram_quotes = 15;
+}
+
+// The response from the attestation server for the certificate request.
+message AttestationCertificateResponse {
+ optional ResponseStatus status = 1;
+ // Short detail response message. Included when the result is not OK.
+ optional string detail = 2;
+ // The credential of the certified key in X.509 format.
+ optional bytes certified_key_credential = 3;
+ // The issuer intermediate CA certificate in X.509 format.
+ optional bytes intermediate_ca_cert = 5;
+ // A message identifier from the request this message is responding to.
+ optional bytes message_id = 6;
+ // Additional intermediate CA certificates that can help in validation.
+ // Certificate chaining order is from the leaf to the root. That is,
+ // |certified_key_credential| is signed by
+ // |intermediate_ca_cert|, which is signed by
+ // |additional_intermediate_ca_cert(0)|, which is signed by
+ // |additional_intermediate_ca_cert(1)|, ... and so on.
+ repeated bytes additional_intermediate_ca_cert = 7;
+ // Extra details included when the result is not OK.
+ optional string extra_details = 8;
+}
+
+// The reset request to be sent to the attestation server.
+message AttestationResetRequest {
+ // The AIK cert, in X.509 form, encrypted using the server's public key with
+ // the following parameters:
+ // Key encryption: RSA-OAEP with no custom parameters.
+ // Data encryption: 256-bit key, AES-CBC with PKCS5 padding.
+ // MAC: HMAC-SHA-512 using the AES key.
+ optional EncryptedData encrypted_identity_credential = 1;
+
+ // The one time token to make sure the reset process can be triggered only
+ // once.
+ optional bytes token = 2;
+
+ // The EK cert, in X.509 form, encrypted using the server's public key with
+ // the following parameters:
+ // Key encryption: RSA-OAEP with no custom parameters.
+ // Data encryption: 256-bit key, AES-CBC with PKCS5 padding.
+ // MAC: HMAC-SHA-512 using the AES key.
+ optional EncryptedData encrypted_endorsement_credential = 3;
+}
+
+// The response from the attestation server for the reset request.
+message AttestationResetResponse {
+ // The response status.
+ optional ResponseStatus status = 1;
+ // Short detail response message. Included when the result is not OK.
+ optional string detail = 2;
+ // Extra details included when the result is not OK.
+ optional string extra_details = 3;
+}
+
+// The challenge data (as in challenge-response) generated by the server.
+// Before transmitted to the client, this message will be wrapped as a
+// SignedData message, in which the data field is the serialized Challenge
+// message, and the signature field is the signature of the data field signed
+// by the enterprise server using a hard-coded key. The signature algorithm is
+// RSASSA-PKCS1-v1_5-SHA256.
+message Challenge {
+ // A string for the client to sanity check a legitimate challenge.
+ optional string prefix = 1;
+ // A 256-bit random value generated by the server.
+ optional bytes nonce = 2;
+ // A timestamp for a stateless server to limit the timeframe during which the
+ // challenge may be replayed.
+ optional int64 timestamp = 3;
+}
+
+// The response data (as in challenge-response) generated by the client.
+// Before transmitted to the server, this message will be wrapped as a
+// SignedData message, in which the data field is the serialized
+// ChallengeResponse message, and the signature field is the signature of the
+// data field signed by the client using the key being challenged. The
+// signature algorithm is RSASSA-PKCS1-v1_5-SHA256.
+message ChallengeResponse {
+ // The original challenge data.
+ optional SignedData challenge = 1;
+ // A 256-bit random value generated by the client. Mixing in this nonce
+ // prevents a caller from using a challenge to sign arbitrary data.
+ optional bytes nonce = 2;
+ // The KeyInfo message encrypted using a public encryption key, pushed via
+ // policy with the following parameters:
+ // Key encryption: RSA-OAEP with no custom parameters.
+ // Data encryption: 256-bit key, AES-CBC with PKCS5 padding.
+ // MAC: HMAC-SHA-512 using the AES key.
+ optional EncryptedData encrypted_key_info = 3;
+}
+
+// The data type of the message decrypted from
+// ChallengeResponse.encrypted_key_info.encrypted_data field. This message holds
+// information required by enterprise server to complete the verification.
+message KeyInfo {
+ // Indicates whether the key is an EMK or EUK.
+ optional KeyProfile key_type = 1;
+ // Domain information about the user associated with the key. For an
+ // EMK, this value is empty - customer_id is used instead.
+ // For an EUK, this value is the user's email address.
+ optional string domain = 2;
+ // The virtual device ID associated with the device or user.
+ optional bytes device_id = 3;
+ // If the key is an EUK, this value is the PCA-issued certificate for the key.
+ optional bytes certificate = 4;
+ // If the key is an EUK, this value may hold a SignedPublicKeyAndChallenge
+ // with a random challenge. The SignedPublicKeyAndChallenge specification is
+ // here: https://developer.mozilla.org/en-US/docs/HTML/Element/keygen.
+ optional bytes signed_public_key_and_challenge = 5;
+ // The identifier of the customer, as defined by the Google Admin SDK at
+ // https://developers.google.com/admin-sdk/directory/v1/guides/manage-customers
+ optional string customer_id = 6;
+}
+
+enum KeyProfile {
+ // Enterprise machine key.
+ EMK = 0;
+ // Enterprise user key.
+ EUK = 1;
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_protos/google_key.proto b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/google_key.proto
new file mode 100644
index 0000000..8fd316e
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/google_key.proto
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is an exact copy of
+// third_party/cros_system_api/dbus/attestation/google_key.proto
+// third_party/cros_system_api is only for ChromeOS.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package attestation;
+
+// The RSA public key of Google key used by attestation service. Only used
+// internally for attestation service, this message is specialized to contain a
+// RSA key modulus in hex with well known exponent 65537.
+message GoogleRsaPublicKey {
+ optional string modulus_in_hex = 1;
+ // The key id for the servers to look up the keys for decryption.
+ optional bytes key_id = 2;
+}
+
+// A key set used with |DEAULT_ACA| and |DEFAULT_VA|.
+message DefaultGoogleRsaPublicKeySet {
+ optional GoogleRsaPublicKey default_ca_encryption_key = 1;
+ optional GoogleRsaPublicKey default_va_signing_key = 2;
+ optional GoogleRsaPublicKey default_va_encryption_key = 3;
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_protos/interface.proto b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/interface.proto
new file mode 100644
index 0000000..150fa5b0
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/interface.proto
@@ -0,0 +1,500 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is an exact copy of
+// third_party/cros_system_api/dbus/attestation/interface.proto
+// third_party/cros_system_api is only for ChromeOS.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+import "attestation_ca.proto";
+import "keystore.proto";
+
+package attestation;
+option go_package = "chromiumos/system_api/attestation_proto";
+
+enum AttestationStatus {
+ STATUS_SUCCESS = 0;
+ STATUS_UNEXPECTED_DEVICE_ERROR = 1;
+ STATUS_NOT_AVAILABLE = 2;
+ STATUS_NOT_READY = 3;
+ STATUS_NOT_ALLOWED = 4;
+ STATUS_INVALID_PARAMETER = 5;
+ STATUS_REQUEST_DENIED_BY_CA = 6;
+ STATUS_CA_NOT_AVAILABLE = 7;
+ STATUS_NOT_SUPPORTED = 8;
+ // The error that is translated into by the client to indicate any kind of
+ // D-Bus error.
+ STATUS_DBUS_ERROR = 9;
+}
+
+enum ACAType {
+ DEFAULT_ACA = 0;
+ TEST_ACA = 1;
+}
+
+enum VAType {
+ DEFAULT_VA = 0;
+ TEST_VA = 1;
+}
+
+message GetKeyInfoRequest {
+ optional string key_label = 1;
+ optional string username = 2;
+}
+
+message GetKeyInfoReply {
+ optional AttestationStatus status = 1;
+ optional KeyType key_type = 2;
+ optional KeyUsage key_usage = 3;
+ // The public key (X.509/DER SubjectPublicKeyInfo).
+ optional bytes public_key = 4;
+ // The serialized TPM_CERTIFY_INFO or TPM2B_ATTEST for the new key.
+ optional bytes certify_info = 5;
+ // The signature of certify_info by the Attestation Key.
+ optional bytes certify_info_signature = 6;
+ // The certificate data associated with the key (if any).
+ optional bytes certificate = 7;
+ // The payload associated with the key.
+ optional bytes payload = 8;
+}
+
+message GetEndorsementInfoRequest {
+ reserved 1;
+}
+
+message GetEndorsementInfoReply {
+ optional AttestationStatus status = 1;
+ // The endorsement public key (X.509/DER SubjectPublicKeyInfo).
+ optional bytes ek_public_key = 2;
+ // The endorsement certificate (X.509/DER).
+ optional bytes ek_certificate = 3;
+ // Human-readable string with EK information. Contains EK certificate in PEM
+ // format and SHA256 hash of the raw DER encoded certificate.
+ optional string ek_info = 4;
+}
+
+message GetAttestationKeyInfoRequest {
+ reserved 1;
+ // What kind of ACA to use.
+ optional ACAType aca_type = 2;
+}
+
+message GetAttestationKeyInfoReply {
+ optional AttestationStatus status = 1;
+ // The attestation public key (X.509/DER SubjectPublicKeyInfo).
+ optional bytes public_key = 2;
+ // The attestation public key in TPM_PUBKEY form.
+ optional bytes public_key_tpm_format = 3;
+ // The attestation key certificate.
+ optional bytes certificate = 4;
+ // A quote of PCR0 at the time of attestation key creation.
+ optional Quote pcr0_quote = 5;
+ // A quote of PCR1 at the time of attestation key creation.
+ optional Quote pcr1_quote = 6;
+}
+
+message ActivateAttestationKeyRequest {
+ reserved 1;
+ optional EncryptedIdentityCredential encrypted_certificate = 2;
+ // Whether, on success, the decrypted certificate should be retained in
+ // association with the attestation key. When using an ACA, this is normally
+ // set to true. Other protocols may use a nonce as the 'certificate' and in
+ // these cases this field is normally set to false.
+ optional bool save_certificate = 3;
+ // What kind of ACA to use.
+ optional ACAType aca_type = 4;
+}
+
+message ActivateAttestationKeyReply {
+ optional AttestationStatus status = 1;
+ // The decrypted attestation key certificate.
+ optional bytes certificate = 2;
+}
+
+message CreateCertifiableKeyRequest {
+ // An arbitrary label which can be used to reference the key later.
+ optional string key_label = 1;
+ // Provided if the new key should be accessible only by a
+ // particular user. If this field is not set or is the empty
+ // string, the key will be accessible system-wide.
+ optional string username = 2;
+ optional KeyType key_type = 3;
+ optional KeyUsage key_usage = 4;
+}
+
+message CreateCertifiableKeyReply {
+ optional AttestationStatus status = 1;
+ // The new public key (X.509/DER SubjectPublicKeyInfo).
+ optional bytes public_key = 2;
+ // The serialized TPM_CERTIFY_INFO or TPM2B_ATTEST for the new key.
+ optional bytes certify_info = 3;
+ // The signature of certify_info by the Attestation Key.
+ optional bytes certify_info_signature = 4;
+}
+
+message DecryptRequest {
+ optional string key_label = 1;
+ optional string username = 2;
+ optional bytes encrypted_data = 3;
+}
+
+message DecryptReply {
+ optional AttestationStatus status = 1;
+ optional bytes decrypted_data = 2;
+}
+
+message SignRequest {
+ optional string key_label = 1;
+ optional string username = 2;
+ optional bytes data_to_sign = 3;
+}
+
+message SignReply {
+ optional AttestationStatus status = 1;
+ optional bytes signature = 2;
+}
+
+message RegisterKeyWithChapsTokenRequest {
+ optional string key_label = 1;
+ optional string username = 2;
+ optional bool include_certificates = 3;
+}
+
+message RegisterKeyWithChapsTokenReply {
+ optional AttestationStatus status = 1;
+}
+
+// Message to check whether attestation is prepared for enrollment or not.
+message GetEnrollmentPreparationsRequest {
+ optional ACAType aca_type = 1;
+}
+
+// Reply to a check of whether attestation is prepared for enrollment or not.
+message GetEnrollmentPreparationsReply {
+ optional AttestationStatus status = 1;
+ map<int32, bool> enrollment_preparations = 2;
+}
+
+message GetStatusRequest {
+ // Get extended status (see GetStatusReply below).
+ optional bool extended_status = 1;
+}
+
+message GetStatusReply {
+ message Identity {
+ // The identity features.
+ optional int32 features = 1;
+ }
+
+ message IdentityCertificate {
+ // The identity that is enrolled.
+ optional int32 identity = 1;
+ // The Privacy CA that the identity is enrolled with.
+ optional int32 aca = 2;
+ }
+
+ optional AttestationStatus status = 1;
+ // Is prepared for enrollment? True if prepared for *any* CA.
+ optional bool prepared_for_enrollment = 2;
+ // Is enrolled (AIK certificate created)? True if enrolled with *any* CA.
+ optional bool enrolled = 3;
+ // Is in verified boot mode (according to PCR0 quote)?
+ // [Only included in reply if extended_status is set]
+ optional bool verified_boot = 4;
+ // List of identities and their identity features.
+ repeated Identity identities = 5;
+ // List of identity certificates.
+ map<int32, IdentityCertificate> identity_certificates = 6;
+ // Map of CA types to whether we are prepared for enrollment with that CA.
+ map<int32, bool> enrollment_preparations = 7;
+}
+
+// Verify attestation data.
+message VerifyRequest {
+ // Use CrosCore CA to verify that the EK is endorsed.
+ optional bool cros_core = 1;
+ // Verify EK only.
+ // Otherwise, in addition to EK, verify all attestation data as a CA would.
+ optional bool ek_only = 2;
+}
+
+message VerifyReply {
+ optional AttestationStatus status = 1;
+ optional bool verified = 2;
+}
+
+// Create an enrollment request to be sent to the Privacy CA. This request
+// is a serialized AttestationEnrollmentRequest protobuf. Attestation
+// enrollment is a process by which the Privacy CA verifies the EK certificate
+// of a device and issues a certificate for an AIK. The enrollment process can
+// be finished by sending FinishEnrollRequest with the response from the CA.
+message CreateEnrollRequestRequest {
+ // What kind of ACA to use.
+ optional ACAType aca_type = 1;
+}
+
+message CreateEnrollRequestReply {
+ optional AttestationStatus status = 1;
+ // AttestationEnrollmentRequest to be sent to CA server.
+ optional bytes pca_request = 2;
+}
+
+// Finish the enrollment process.
+message FinishEnrollRequest {
+ // AttestationEnrollmentResponse received from CA server.
+ optional bytes pca_response = 1;
+ // What kind of ACA to use.
+ optional ACAType aca_type = 2;
+}
+
+message FinishEnrollReply {
+ optional AttestationStatus status = 1;
+}
+
+// Goes through the entire enrollment process, including creating enroll
+// request, sending the request to the corresponding PCA server, and storing the
+// response from PCA server if success. The message field is identical to
+// |CreateEnrollRequestRequest|.
+message EnrollRequest {
+ // What kind of ACA to use.
+ optional ACAType aca_type = 1;
+ // No matter is the device is enrolled, (re-)enroll the device.
+ optional bool forced = 2;
+}
+
+message EnrollReply {
+ optional AttestationStatus status = 1;
+}
+
+// Create an attestation certificate request to be sent to the Privacy CA.
+// The request is a serialized AttestationCertificateRequest protobuf. The
+// certificate request process generates and certifies a key in the TPM and
+// sends the AIK certificate along with information about the certified key to
+// the Privacy CA. The PCA verifies the information and issues a certificate
+// for the certified key. The certificate request process can be finished by
+// sending FinishCertificateRequestRequest with the response from the CA.
+message CreateCertificateRequestRequest {
+ // Type of certificate to be requested.
+ optional CertificateProfile certificate_profile = 1;
+ // The canonical username of the active user profile. Leave blank
+ // if not associated with the current user.
+ optional string username = 2;
+ // Some certificate requests require information about the origin
+ // of the request. If no origin is needed, this can be empty.
+ optional string request_origin = 3;
+ // What kind of ACA to use.
+ optional ACAType aca_type = 4;
+ // The key algorithm of certified key.
+ optional KeyType key_type = 5;
+}
+
+message CreateCertificateRequestReply {
+ optional AttestationStatus status = 1;
+ // The request to be sent to the Privacy CA.
+ optional bytes pca_request = 2;
+}
+
+// Finish the certificate request process. The |pca_response| from the PCA
+// is a serialized AttestationCertificateResponse protobuf. This final step
+// verifies the PCA operation succeeded and extracts the certificate for the
+// certified key. The certificate is stored with the key.
+message FinishCertificateRequestRequest {
+ // The Privacy CA's response to a certificate request.
+ optional bytes pca_response = 1;
+ // A name for the key. If a key already exists with this name it
+ // will be destroyed and replaced with this one.
+ optional string key_label = 2;
+ // This should match |username| in CreateCertificateRequestRequest.
+ optional string username = 3;
+}
+
+message FinishCertificateRequestReply {
+ optional AttestationStatus status = 1;
+ // The PCA issued certificate chain in PEM format. By
+ // convention the certified key certificate is listed
+ // first followed by intermediate CA certificate(s).
+ // The PCA root certificate is not included.
+ optional bytes certificate = 2;
+ // The public key (X.509/DER SubjectPublicKeyInfo).
+ optional bytes public_key = 3;
+}
+
+// Goes through the entire cert process, including creating cert, sending the
+// request to the corresponding PCA server, and storing the response from PCA
+// server if success. The message fields are basically the union of
+// |CreateCertificateRequestRequest| and |FinishCertificateRequestRequest|.
+message GetCertificateRequest {
+ // Type of certificate to be requested.
+ optional CertificateProfile certificate_profile = 1;
+ // The canonical username of the active user profile. Leave blank
+ // if not associated with the current user.
+ optional string username = 2;
+ // Some certificate requests require information about the origin
+ // of the request. If no origin is needed, this can be empty.
+ optional string request_origin = 3;
+ // What kind of ACA to use.
+ optional ACAType aca_type = 4;
+ // The key algorithm of certified key.
+ optional KeyType key_type = 5;
+ // A name for the key. If a key already exists with this name it
+ // will be destroyed and replaced with this one.
+ optional string key_label = 6;
+ // Ignores the current certificate if any and gets the new certificate.
+ optional bool forced = 7;
+ // If set to |true|, this request also triggers enrollment process if the
+ // device is not enrolled yet.
+ optional bool shall_trigger_enrollment = 8;
+}
+
+message GetCertificateReply {
+ optional AttestationStatus status = 1;
+ // The PCA issued certificate chain in PEM format. By
+ // convention the certified key certificate is listed
+ // first followed by intermediate CA certificate(s).
+ // The PCA root certificate is not included.
+ optional bytes certificate = 2;
+ // The public key (X.509/DER SubjectPublicKeyInfo).
+ optional bytes public_key = 3;
+}
+
+// Sign a challenge for an enterprise device / user. This challenge is
+// typically generated by and the response verified by the Enterprise Device
+// Verification Server (DVServer).
+message SignEnterpriseChallengeRequest {
+ // The key name. This is the same name previously passed to
+ // FinishCertificateRequestRequest.
+ optional string key_label = 1;
+ // The canonical username of the active user profile.
+ // When this is set, an EUK challenge will be performed.
+ // When this is unset or empty, an EMK challenge will be performed.
+ optional string username = 2;
+ // A domain value to be included in the challenge response.
+ // This is supposed to be the user's canonical e-mail address for an EUK
+ // challenge. Ignored for EMK challenges.
+ optional string domain = 3;
+ // A device identifier to be included in the challenge response.
+ optional bytes device_id = 4;
+ // Whether the challenge response should include
+ // a SignedPublicKeyAndChallenge.
+ optional bool include_signed_public_key = 5;
+ // The challenge to be signed.
+ optional bytes challenge = 6;
+ // The VA server that will handle the challenge.
+ optional VAType va_type = 7;
+ // The name of the key used for SignedPublicKeyAndChallenge. If left empty,
+ // the same key used to sign the challenge response (EMK or EUK) will be used
+ // for SignedPublicKeyAndChallenge.
+ optional string key_name_for_spkac = 8;
+}
+
+message SignEnterpriseChallengeReply {
+ optional AttestationStatus status = 1;
+ // The challenge response.
+ optional bytes challenge_response = 2;
+}
+
+// Sign a challenge outside of an enterprise context: generate a random nonce
+// and append it to challenge, then sign and return the signature in the
+// |challenge_response|.
+// This challenge is typically generated by some module running within Chrome.
+message SignSimpleChallengeRequest {
+ // The key name. This is the same name previously passed to
+ // FinishCertificateRequestRequest.
+ optional string key_label = 1;
+ // The canonical username of the active user profile. Leave blank
+ // if not associated with the current user.
+ optional string username = 2;
+ // The challenge to be signed.
+ optional bytes challenge = 3;
+}
+
+message SignSimpleChallengeReply {
+ optional AttestationStatus status = 1;
+ // The challenge response.
+ optional bytes challenge_response = 2;
+}
+
+// Set a payload for a key; any previous payload will be overwritten.
+message SetKeyPayloadRequest {
+ // The key name. This is the same name previously passed to
+ // FinishCertificateRequestRequest.
+ optional string key_label = 1;
+ // The canonical username of the active user profile. Leave blank
+ // if not associated with the current user.
+ optional string username = 2;
+ optional bytes payload = 3;
+}
+
+message SetKeyPayloadReply {
+ optional AttestationStatus status = 1;
+}
+
+// Delete keys either by key label prefix or by exact key label.
+message DeleteKeysRequest {
+ enum MatchBehavior {
+ // Match type not specified. Behavior defaults to |MATCH_TYPE_PREFIX|.
+ MATCH_BEHAVIOR_UNSPECIFIED = 0;
+
+ // Will delete all keys that start with |key_label_match|.
+ // If no such key exists, the operation still succeeds.
+ MATCH_BEHAVIOR_PREFIX = 1;
+
+ // Will delete the key which has a key_label exactly matching
+ // |key_label_match|.
+ // If no such key exists, the operation still succeeds.
+ MATCH_BEHAVIOR_EXACT = 2;
+ }
+ // The key label prefix or the exact key label (depends on |match_behavior|).
+ optional string key_label_match = 1;
+ // The canonical username of the active user profile. Leave blank
+ // if not associated with the current user.
+ optional string username = 2;
+ // The matching behavior - see comments on the enum values. If omitted,
+ // defaults to MATCH_BEHAVIOR_PREFIX for backwards compatibility.
+ optional MatchBehavior match_behavior = 3;
+}
+
+message DeleteKeysReply {
+ optional AttestationStatus status = 1;
+}
+
+// Create a request to be sent to the PCA which will reset the identity for
+// this device on future AIK enrollments. The |reset_token| is put in the
+// request protobuf verbatim.
+message ResetIdentityRequest {
+ optional string reset_token = 1;
+}
+
+message ResetIdentityReply {
+ optional AttestationStatus status = 1;
+ // Request to be sent to the CA.
+ optional bytes reset_request = 2;
+}
+
+message GetEnrollmentIdRequest {
+ optional bool ignore_cache = 1;
+}
+
+message GetEnrollmentIdReply {
+ optional AttestationStatus status = 1;
+ optional string enrollment_id = 2;
+}
+
+// Gets a copy of the specific NV data, signed using the key with the specified
+// label, eg "attest-ent-machine".
+message GetCertifiedNvIndexRequest {
+ optional int32 nv_index = 1;
+ optional int32 nv_size = 2;
+ optional string key_label = 3;
+}
+
+message GetCertifiedNvIndexReply {
+ optional AttestationStatus status = 1;
+ optional bytes certified_data = 2;
+ optional bytes signature = 3;
+ optional bytes key_certificate = 4;
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_protos/keystore.proto b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/keystore.proto
new file mode 100644
index 0000000..2caaedbf
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_protos/keystore.proto
@@ -0,0 +1,26 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is an exact copy of
+// third_party/cros_system_api/dbus/attestation/keystore.proto
+// third_party/cros_system_api is only for ChromeOS.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package attestation;
+option go_package = "chromiumos/system_api/attestation_proto";
+
+// Describes key type.
+enum KeyType {
+ KEY_TYPE_RSA = 1;
+ KEY_TYPE_ECC = 2;
+}
+
+// Describes allowed key usage.
+enum KeyUsage {
+ KEY_USAGE_SIGN = 1;
+ KEY_USAGE_DECRYPT = 2;
+}
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_service.cc b/chrome/browser/enterprise/connectors/device_trust/attestation_service.cc
new file mode 100644
index 0000000..854bb941
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_service.cc
@@ -0,0 +1,140 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/connectors/device_trust/attestation_service.h"
+
+#include "base/base64.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.h"
+
+namespace attestation {
+
+AttestationService::AttestationService() {
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ key_pair_ = std::make_unique<enterprise_connectors::DeviceTrustKeyPair>();
+ key_pair_->Init();
+#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+}
+
+AttestationService::~AttestationService() = default;
+
+std::string AttestationService::JsonChallengeToProtobufChallenge(
+ const std::string& challenge) {
+ attestation::SignedData signed_challenge;
+ // Get challenge and decode it.
+ base::Optional<base::Value> data = base::JSONReader::Read(
+ challenge, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+
+ // If json is malformed or it doesn't include the needed fields return
+ // an empty string.
+ if (!data || !data.value().FindPath("challenge.data") ||
+ !data.value().FindPath("challenge.signature"))
+ return std::string();
+
+ base::Base64Decode(data.value().FindPath("challenge.data")->GetString(),
+ signed_challenge.mutable_data());
+ base::Base64Decode(data.value().FindPath("challenge.signature")->GetString(),
+ signed_challenge.mutable_signature());
+
+ std::string serialized_signed_challenge;
+ if (!signed_challenge.SerializeToString(&serialized_signed_challenge)) {
+ LOG(ERROR) << __func__ << ": Failed to serialize signed data.";
+ return std::string();
+ }
+ return serialized_signed_challenge;
+}
+
+std::string AttestationService::ProtobufChallengeToJsonChallenge(
+ const std::string& challenge_response) {
+ base::Value signed_data(base::Value::Type::DICTIONARY);
+
+ std::string encoded;
+ base::Base64Encode(challenge_response, &encoded);
+ signed_data.SetKey("data", base::Value(encoded));
+
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ std::string signature;
+ key_pair_->GetSignatureInBase64(challenge_response, &signature);
+ signed_data.SetKey("signature", base::Value(signature));
+#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetKey("challengeResponse", std::move(signed_data));
+
+ std::string json;
+ base::JSONWriter::Write(dict, &json);
+ return json;
+}
+
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+std::string AttestationService::ExportPEMPublicKey() {
+ return key_pair_->ExportPEMPublicKey();
+}
+#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+
+void AttestationService::SignEnterpriseChallenge(
+ const SignEnterpriseChallengeRequest& request,
+ SignEnterpriseChallengeReply* result) {
+ SignEnterpriseChallengeTask(request, result);
+}
+
+void AttestationService::SignEnterpriseChallengeTask(
+ const SignEnterpriseChallengeRequest& request,
+ SignEnterpriseChallengeReply* result) {
+ // Validate that the challenge is coming from the expected source.
+ SignedData signed_challenge;
+ if (!signed_challenge.ParseFromString(request.challenge())) {
+ LOG(ERROR) << __func__ << ": Failed to parse signed challenge.";
+ result->set_status(STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ KeyInfo key_info;
+ // Set the public key so VA can verify the client.
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ key_info.set_signed_public_key_and_challenge(ExportPEMPublicKey());
+#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+
+ ChallengeResponse response_pb;
+ *response_pb.mutable_challenge() = signed_challenge;
+ // TODO(b/185459013): Encrypt `key_info` and add it to `response_pb`.
+
+ // Serialize and sign the response protobuf.
+ std::string serialized;
+ if (!response_pb.SerializeToString(&serialized)) {
+ result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR);
+ return;
+ }
+ // Sign data using the client generated key pair.
+ if (!SignChallengeData(serialized, result->mutable_challenge_response())) {
+ result->clear_challenge_response();
+ result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR);
+ return;
+ }
+}
+
+bool AttestationService::SignChallengeData(const std::string& data,
+ std::string* response) {
+ std::string signature;
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ if (!key_pair_->GetSignatureInBase64(data, &signature)) {
+ LOG(ERROR) << __func__ << ": Failed to sign data.";
+ return false;
+ }
+#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ SignedData signed_data;
+ signed_data.set_data(data);
+ signed_data.set_signature(signature);
+ if (!signed_data.SerializeToString(response)) {
+ LOG(ERROR) << __func__ << ": Failed to serialize signed data.";
+ return false;
+ }
+ return true;
+}
+
+} // namespace attestation
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_service.h b/chrome/browser/enterprise/connectors/device_trust/attestation_service.h
new file mode 100644
index 0000000..206a5b9
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_service.h
@@ -0,0 +1,84 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_SERVICE_H_
+#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_SERVICE_H_
+
+#include "build/build_config.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation_ca.pb.h"
+#include "chrome/browser/enterprise/connectors/device_trust/interface.pb.h"
+
+namespace enterprise_connectors {
+
+class DeviceTrustKeyPair;
+
+}
+
+namespace attestation {
+
+// This class is in charge of handling the key pair used for attestation. Also
+// provides the methods needed in the handshake between Chrome, an IdP and
+// Verified Access.
+// The main method is `SignEnterpriseChallenge` which take a challenge request
+// and builds a challenge response and wrap it into a
+// `SignEnterpriseChallengeReply`.
+class AttestationService {
+ public:
+ AttestationService();
+ ~AttestationService();
+
+ // Export the public key of `key_pair_`.
+ std::string ExportPEMPublicKey();
+
+ void SignEnterpriseChallenge(const SignEnterpriseChallengeRequest& request,
+ SignEnterpriseChallengeReply* result);
+
+ // Take the challenge that comes from the Idp in json format and generate a
+ // SignedData proto.
+ // The expected format of the challenge is the following:
+ // {
+ // "challenge": {
+ // object (SignedData)
+ // }
+ // }
+ // SignedData has the following scheme:
+ // {
+ // "data": string (base64 encoded string),
+ // "signature": string (base64 encoded string),
+ // }
+ std::string JsonChallengeToProtobufChallenge(const std::string& challenge);
+
+ // Take a challenge_response proto and return the json version of it.
+ // The format is the following:
+ // {
+ // "challengeResponse": {
+ // object (SignedData)
+ // }
+ // }
+ // SignedData has the following scheme:
+ // {
+ // "data": string (base64 encoded string),
+ // "signature": string (base64 encoded string),
+ // }
+ std::string ProtobufChallengeToJsonChallenge(
+ const std::string& challenge_response);
+
+ private:
+ // Sign the challenge and return the challenge response in
+ // `result.challenge_response`.
+ void SignEnterpriseChallengeTask(
+ const SignEnterpriseChallengeRequest& request,
+ SignEnterpriseChallengeReply* result);
+
+ // Sign `data` using `key_pair_` and store that value in `signature`.
+ bool SignChallengeData(const std::string& data, std::string* response);
+
+#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ std::unique_ptr<enterprise_connectors::DeviceTrustKeyPair> key_pair_;
+#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+};
+
+} // namespace attestation
+
+#endif // CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_ATTESTATION_SERVICE_H_
diff --git a/chrome/browser/enterprise/connectors/device_trust/attestation_service_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/attestation_service_unittest.cc
new file mode 100644
index 0000000..3ffb9bd
--- /dev/null
+++ b/chrome/browser/enterprise/connectors/device_trust/attestation_service_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/connectors/device_trust/attestation_service.h"
+
+#include "base/base64.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/os_crypt/os_crypt_mocker.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char challenge[] =
+ "{"
+ "\"challenge\": {"
+ " \"data\": "
+ "\"ChZFbnRlcnByaXNlS2V5Q2hhbGxlbmdlEiAXyp394cl5TtKo+yhlQPa+CQMPbFyIoY//"
+ "CXHxvDqVqhiBktSOgi8=\","
+ " \"signature\": "
+ "\"BRRaR9cKJe6NRBAUtPLjRujd0BawaYaPpHXzaWxSqCbFZcB2ZnDwFskh4qPeO0EganhwrPBj"
+ "bD1yLXY1uiPM38TjYQgj2LFXyq0RjGehZ7qLDv2zebiIn6TIDvRi4rhEoXDg3bpKczBTDgp9im"
+ "BQ6QjJS7Pbj0kxPwHzkoVq5UnF9mUUecOAHgKV6ONs4rVjNSpZAPSD/"
+ "jC39wDlIXR5YDKSPCs46u66koDyjM7DNVig+S8nTdr14sXEGFSiHyeFaZC5kXQo103bB9j+"
+ "tcSpwa0MfuZJ5QFJlB1HipjpaGSImZbJPfkjtoK3F1rn9AiHz+nIjLWPrg3KnQt2eaTNSw==\""
+ "}"
+ "}";
+
+} // namespace
+
+namespace attestation {
+
+class AttestationServiceTest : public testing::Test {
+ public:
+ AttestationServiceTest() : local_state_(TestingBrowserProcess::GetGlobal()) {}
+ void SetUp() override {
+ testing::Test::SetUp();
+ OSCryptMocker::SetUp();
+ }
+
+ void TearDown() override {
+ testing::Test::TearDown();
+ OSCryptMocker::TearDown();
+ }
+
+ private:
+ ScopedTestingLocalState local_state_;
+};
+
+TEST_F(AttestationServiceTest, BuildChallengeResponse) {
+ AttestationService attestation_service_;
+ SignEnterpriseChallengeRequest request;
+ SignEnterpriseChallengeReply result;
+ // Get the challenge from the SignedData json and create request.
+ request.set_challenge(
+ attestation_service_.JsonChallengeToProtobufChallenge(challenge));
+ // If challenge is equal to empty string, then
+ // `JsonChallengeToProtobufChallenge()` failed.
+ EXPECT_NE(request.challenge(), std::string());
+
+ attestation_service_.SignEnterpriseChallenge(request, &result);
+ // If challenge is equal to empty string, then
+ // `JsonChallengeToProtobufChallenge()` failed.
+ EXPECT_NE(result.challenge_response(), std::string());
+
+ base::Optional<base::Value> challenge_response = base::JSONReader::Read(
+ attestation_service_.ProtobufChallengeToJsonChallenge(
+ result.challenge_response()),
+ base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+
+ EXPECT_NE(challenge_response.value()
+ .FindPath("challengeResponse.data")
+ ->GetString(),
+ std::string());
+ EXPECT_NE(challenge_response.value()
+ .FindPath("challengeResponse.signature")
+ ->GetString(),
+ std::string());
+}
+
+} // namespace attestation
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc
index e0b8bca..634e8bf 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc
@@ -96,7 +96,7 @@
if (!base::Base64Decode(base64_encrypted_private_key_info, &decoded)) {
// TODO(crbug/1176175): Handle error for when it can't get the private
// key.
- LOG(ERROR) << "[DeviceTrust - Init] error while decoding the private key";
+ LOG(ERROR) << "[Init] error while decoding the private key";
return false;
}
@@ -105,7 +105,7 @@
if (!success) {
// TODO(crbug/1176175): Handle error for when it can't get the private
// key.
- LOG(ERROR) << "[DeviceTrust - Init] error while decrypting the private key";
+ LOG(ERROR) << "[Init] error while decrypting the private key";
return false;
}
@@ -117,7 +117,7 @@
bool DeviceTrustKeyPair::StoreKeyPair() {
if (!key_pair_) {
- LOG(ERROR) << "[DeviceTrust - StoreKeyPair] no `key_pair_` member";
+ LOG(ERROR) << "[StoreKeyPair] no `key_pair_` member";
return false;
}
@@ -131,8 +131,7 @@
std::string encrypted_key;
bool encrypted = OSCrypt::EncryptString(encoded_private_key, &encrypted_key);
if (!encrypted) {
- LOG(ERROR) << "[DeviceTrust - StoreKeyPair] error while encrypting the "
- "private key.";
+ LOG(ERROR) << "[StoreKeyPair] error while encrypting the private key.";
return false;
}
// The string must be encoded as base64 for storage in local state.
@@ -182,4 +181,20 @@
return public_key;
}
+bool DeviceTrustKeyPair::SignMessage(const std::string& message,
+ std::vector<uint8_t>& signature) {
+ std::unique_ptr<crypto::ECSignatureCreator> signer(
+ crypto::ECSignatureCreator::Create(key_pair_.get()));
+ return signer->Sign(reinterpret_cast<const uint8_t*>(message.c_str()),
+ message.size(), &signature);
+}
+bool DeviceTrustKeyPair::GetSignatureInBase64(const std::string& message,
+ std::string* signature) {
+ std::vector<uint8_t> signature_vector;
+ if (!SignMessage(message, signature_vector))
+ return false;
+ *signature = BytesToEncodedString(signature_vector);
+ return true;
+}
+
} // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.h b/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.h
index 82fd0d5..667ad7e 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.h
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.h
@@ -50,6 +50,13 @@
// Return strue on success.
bool Init();
+ // Sign `message` using elliptic curve (EC) private key.
+ bool SignMessage(const std::string& message, std::vector<uint8_t>& signature);
+
+ // Sign `message` using `SignMessage` method and return the signature as a
+ // base64 encode string.
+ bool GetSignatureInBase64(const std::string& message, std::string* signature);
+
private:
std::unique_ptr<crypto::ECPrivateKey> key_pair_;
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_service.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_service.cc
index 4441370e..d2c28d1 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_service.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_service.cc
@@ -4,9 +4,15 @@
#include "chrome/browser/enterprise/connectors/device_trust/device_trust_service.h"
+#include "base/base64.h"
+#include "base/base64url.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/connectors/connectors_prefs.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation_ca.pb.h"
+#include "chrome/browser/enterprise/connectors/device_trust/interface.pb.h"
#include "chrome/browser/enterprise/connectors/device_trust/signal_reporter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
@@ -25,6 +31,7 @@
key_pair_ = std::make_unique<DeviceTrustKeyPair>();
#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ attestation_service_ = std::make_unique<attestation::AttestationService>();
pref_observer_.Init(prefs_);
pref_observer_.Add(kContextAwareAccessSignalsAllowlistPref,
base::BindRepeating(&DeviceTrustService::OnPolicyUpdated,
@@ -125,4 +132,17 @@
}
#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+std::string DeviceTrustService::BuildChallengeResponse(
+ const std::string& challenge) {
+ ::attestation::SignEnterpriseChallengeRequest request;
+ ::attestation::SignEnterpriseChallengeReply result;
+ // Get the challenge from the SignedData json and create request.
+ request.set_challenge(
+ attestation_service_->JsonChallengeToProtobufChallenge(challenge));
+ attestation_service_->SignEnterpriseChallenge(request, &result);
+
+ return attestation_service_->ProtobufChallengeToJsonChallenge(
+ result.challenge_response());
+}
+
} // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_service.h b/chrome/browser/enterprise/connectors/device_trust/device_trust_service.h
index 2afb6b37..40a0698 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_service.h
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_service.h
@@ -6,8 +6,7 @@
#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_DEVICE_TRUST_SERVICE_H_
#include "build/build_config.h"
-#include "build/buildflag.h"
-#include "build/chromeos_buildflags.h"
+#include "chrome/browser/enterprise/connectors/device_trust/attestation_service.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/policy/core/browser/configuration_policy_handler.h"
#include "components/prefs/pref_change_registrar.h"
@@ -47,6 +46,10 @@
std::string GetAttestationCredentialForTesting() const;
#endif // defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MAC)
+ // Starts flow that actually builds a response. This method is called
+ // from a non_UI thread.
+ std::string BuildChallengeResponse(const std::string& challenge);
+
private:
friend class DeviceTrustFactory;
@@ -73,6 +76,7 @@
std::unique_ptr<enterprise_connectors::DeviceTrustSignalReporter> reporter_;
SignalReportCallback signal_report_callback_;
+ std::unique_ptr<attestation::AttestationService> attestation_service_;
base::WeakPtrFactory<DeviceTrustService> weak_factory_{this};
};
diff --git a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.cc b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.cc
index 834bb65..09e4182 100644
--- a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.cc
@@ -5,13 +5,18 @@
#include "chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h"
#include "base/memory/ptr_util.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/connectors/connectors_prefs.h"
#include "chrome/browser/enterprise/connectors/device_trust/device_trust_factory.h"
-#include "chrome/browser/enterprise/util/managed_browser_utils.h"
-#include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/browser/enterprise/connectors/device_trust/device_trust_service.h"
+#include "chrome/browser/enterprise/connectors/device_trust/interface.pb.h"
#include "chrome/browser/profiles/profile.h"
#include "components/policy/core/browser/url_util.h"
#include "components/prefs/pref_service.h"
+#include "components/url_matcher/url_matcher.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
@@ -20,6 +25,13 @@
namespace enterprise_connectors {
+// Const headers used in the handshake flow.
+constexpr char kDeviceTrustHeader[] = "X-Device-Trust";
+constexpr char kDeviceTrustHeaderValue[] = "VerifiedAccess";
+constexpr char kVerifiedAccessChallengeHeader[] = "X-Verified-Access-Challenge";
+constexpr char kVerifiedAccessResponseHeader[] =
+ "X-Verified-Access-Challenge-Response";
+
// static
std::unique_ptr<DeviceTrustNavigationThrottle>
DeviceTrustNavigationThrottle::MaybeCreateThrottleFor(
@@ -106,18 +118,57 @@
// If we are starting an attestation flow.
if (navigation_handle()->GetResponseHeaders() == nullptr) {
- navigation_handle()->SetRequestHeader("X-Device-Trust", "VerifiedAccess");
+ navigation_handle()->SetRequestHeader(kDeviceTrustHeader,
+ kDeviceTrustHeaderValue);
return PROCEED;
}
// If a challenge is coming from the Idp.
- if (!navigation_handle()->GetRequestHeaders().HasHeader(
- "x-verified-access-challenge")) {
- navigation_handle()->RemoveRequestHeader("X-Device-Trust");
- return PROCEED;
- }
+ if (navigation_handle()->GetResponseHeaders()->HasHeader(
+ kVerifiedAccessChallengeHeader)) {
+ // Remove request header since is not needed for challenge response.
+ navigation_handle()->RemoveRequestHeader(kDeviceTrustHeader);
+ // Get challenge.
+ const net::HttpResponseHeaders* headers =
+ navigation_handle()->GetResponseHeaders();
+ std::string challenge;
+ if (headers->GetNormalizedHeader(kVerifiedAccessChallengeHeader,
+ &challenge)) {
+ // Post a task to get the challenge response. It will defer the navigation
+ // and it will be resumed after it's built.
+ // `StartSignChallengeAndReplyWithResponse` won't run in the main thread,
+ // and then reply to `ReplyChallengeResponseAndResume` which makes use of
+ // `weak_ptr_factory_.GetWeakPtr()` so it won't run in case the object is
+ // destroyed.
+ AttestationCallback reply = base::BindOnce(
+ &DeviceTrustNavigationThrottle::ReplyChallengeResponseAndResume,
+ weak_ptr_factory_.GetWeakPtr());
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
+ base::BindOnce(&DeviceTrustNavigationThrottle::
+ StartSignChallengeAndReplyWithResponse,
+ base::Unretained(this), challenge),
+ std::move(reply));
+
+ return DEFER;
+ }
+ }
return PROCEED;
}
+std::string
+DeviceTrustNavigationThrottle::StartSignChallengeAndReplyWithResponse(
+ const std::string& challenge) {
+ return device_trust_service_->BuildChallengeResponse(challenge);
+}
+
+void DeviceTrustNavigationThrottle::ReplyChallengeResponseAndResume(
+ std::string challenge_response) {
+ navigation_handle()->SetRequestHeader(kVerifiedAccessResponseHeader,
+ challenge_response);
+ Resume();
+}
+
} // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h
index 5d17aa2..ddb946a 100644
--- a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h
+++ b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h
@@ -5,22 +5,37 @@
#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_NAVIGATION_THROTTLE_H_
#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_DEVICE_TRUST_NAVIGATION_THROTTLE_H_
-#include "chrome/browser/enterprise/connectors/device_trust/device_trust_service.h"
#include "components/prefs/pref_change_registrar.h"
-#include "components/url_matcher/url_matcher.h"
#include "content/public/browser/navigation_throttle.h"
class GURL;
+namespace url_matcher {
+
+class URLMatcher;
+
+}
+
namespace enterprise_connectors {
+class DeviceTrustService;
+
// DeviceTrustNavigationThrottle provides a simple way to start a handshake
// base on a list of allowed URLs.
+// Handshake is an exchange between Chrome and an IdP. This start when Chrome
+// visit one of the allowed list of URLs set in the policy
+// `ContextAwareAccessSignalsAllowlist`. Chrome will add a HTTP header
+// (X-Device-Trust: VerifiedAccess), when the IdP detect that header it should
+// reply with a challenge from Verified Access. Chrome will take this challenge
+// and build a challenge response that is sent back to the IdP using another
+// HTTP header (X-Verified-Access-Challenge-Response).
class DeviceTrustNavigationThrottle : public content::NavigationThrottle {
public:
static std::unique_ptr<DeviceTrustNavigationThrottle> MaybeCreateThrottleFor(
content::NavigationHandle* navigation_handle);
+ using AttestationCallback = base::OnceCallback<void(std::string)>;
+
explicit DeviceTrustNavigationThrottle(
content::NavigationHandle* navigation_handle);
DeviceTrustNavigationThrottle(const DeviceTrustNavigationThrottle&) = delete;
@@ -40,11 +55,26 @@
content::NavigationThrottle::ThrottleCheckResult AddHeadersIfNeeded();
+ // Not owned.
DeviceTrustService* device_trust_service_;
+
+ // Set `challege_response` into the header
+ // `X-Verified-Access-Challenge-Response` of the redirection request to the
+ // IdP and resume the navigation.
+ void ReplyChallengeResponseAndResume(std::string challenge_response);
+
+ // Call `DeviceTrustService::BuildChallengeResponse` which is the method that
+ // actually builds the challenge response, and return it as a string with the
+ // format described in `AttestationService::ProtobufChallengeToJsonChallenge`.
+ std::string StartSignChallengeAndReplyWithResponse(
+ const std::string& challenge);
+
// The URL matcher created from the ContextAwareAccessSignalsAllowlist policy.
std::unique_ptr<url_matcher::URLMatcher> matcher_;
PrefChangeRegistrar pref_change_registrar_;
+
+ base::WeakPtrFactory<DeviceTrustNavigationThrottle> weak_ptr_factory_{this};
};
} // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc
index d73f0b1..2546dfd8 100644
--- a/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -20,8 +20,8 @@
namespace {
-const base::Value origins[]{base::Value("https://www.example.com"),
- base::Value("example2.example.com")};
+const base::Value kOrigins[]{base::Value("https://www.example.com"),
+ base::Value("example2.example.com")};
} // namespace
@@ -47,7 +47,7 @@
Profile::FromBrowserContext(browser_context())
->GetPrefs()
->Set(kContextAwareAccessSignalsAllowlistPref,
- std::move(base::ListValue(origins)));
+ base::ListValue(kOrigins));
}
private:
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 509b098..4951cc34 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5896,6 +5896,7 @@
if (is_linux || is_mac || is_win) {
sources += [
+ "../browser/enterprise/connectors/device_trust/attestation_service_unittest.cc",
"../browser/enterprise/connectors/device_trust/device_trust_key_pair_unittest.cc",
"../browser/enterprise/connectors/device_trust/navigation_throttle_unittest.cc",
]