// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/attestation/soft_bind_attestation_flow_impl.h"
#include <optional>
#include "base/containers/span.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/attestation/attestation_ca_client.h"
#include "chromeos/ash/components/attestation/attestation_flow_adaptive.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/attestation/attestation_client.h"
#include "chromeos/ash/components/dbus/constants/attestation_constants.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/openssl_util.h"
#include "crypto/random.h"
#include "crypto/rsa_private_key.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/err.h"
#include "third_party/boringssl/src/include/openssl/mem.h"
#include "third_party/boringssl/src/pki/pem.h"
#include "third_party/securemessage/proto/securemessage.pb.h"
namespace ash {
namespace attestation {
namespace {
// Adds a critical extension following the specification described at
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2 and
// the ASN.1 encoding defined at
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1:
// Extension ::= SEQUENCE {
// extnID OBJECT IDENTIFIER,
// critical BOOLEAN DEFAULT FALSE,
// extnValue OCTET STRING
// -- contains the DER encoding of an ASN.1 value
// -- corresponding to the extension type identified
// -- by extnID
// }
bool AddCriticalExtension(CBB* extensions,
const uint8_t* ext_oid,
size_t ext_oid_len,
const uint8_t* ext_value,
size_t ext_value_len) {
CBB extension, oid, value;
if (!CBB_add_asn1(extensions, &extension, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&extension, &oid, CBS_ASN1_OBJECT) ||
!CBB_add_bytes(&oid, ext_oid, ext_oid_len) ||
!CBB_add_asn1_bool(&extension, 1) ||
!CBB_add_asn1(&extension, &value, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&value, ext_value, ext_value_len) ||
!CBB_flush(extensions)) {
return false;
}
return true;
}
// The validity time of the leaf cert
constexpr base::TimeDelta kLeafCertValidityWindow = base::Hours(72);
// Trigger a new certificate if current certificate is nearing expiration.
constexpr base::TimeDelta kExpiryThresholdDays = base::Days(30);
// RSA SHA256 oid
const uint8_t kRsaSha256Oid[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
0x0d, 0x01, 0x01, 0x0b};
const uint8_t kBasicConstraintsOid[] = {0x55, 0x1d, 0x13};
const uint8_t kKeyUsageOid[] = {0x55, 0x1d, 0x0f};
// cA = FALSE
const uint8_t kBasicConstraintsContents[] = {0x30, 0x00};
// Tag 3 (bit string), length 2, 0 unused of 0x80 (0b10000000)
// Bit 0 specifies the digitalSignature usage.
const uint8_t kKeyUsageContents[] = {0x03, 0x02, 0x00, 0x80};
const char kLeafCertIssuerName[] =
"O=Chrome Device Soft Bind,CN=Local Authority";
const char kLeafCertSubjectName[] =
"O=Chrome Device Soft Bind,CN=Cryptauth User Key";
// If it takes more than 30 seconds to receive a response, it's not likely
// ever going to succeed.
constexpr base::TimeDelta kTimeout = base::Seconds(30);
} // namespace
SoftBindAttestationFlowImpl::Session::Session(Callback callback,
AccountId account_id,
const std::string& user_key)
: callback_(std::move(callback)),
account_id_(account_id),
user_key_(user_key) {
base::RepeatingClosure timeout_callback =
base::BindRepeating(&SoftBindAttestationFlowImpl::Session::OnTimeout,
weak_ptr_factory_.GetWeakPtr());
timer_.Start(FROM_HERE, kTimeout, std::move(timeout_callback));
}
SoftBindAttestationFlowImpl::Session::~Session() = default;
void SoftBindAttestationFlowImpl::Session::OnTimeout() {
LOG(WARNING) << "Timeout exceeded";
ReportFailure("timeout");
}
bool SoftBindAttestationFlowImpl::Session::IsTimerRunning() const {
return timer_.IsRunning();
}
void SoftBindAttestationFlowImpl::Session::StopTimer() {
timer_.Stop();
}
bool SoftBindAttestationFlowImpl::Session::ResetTimer() {
if (max_retries_-- > 0) {
timer_.Reset();
return true;
}
return false;
}
const AccountId& SoftBindAttestationFlowImpl::Session::GetAccountId() const {
return account_id_;
}
const std::string& SoftBindAttestationFlowImpl::Session::GetUserKey() const {
return user_key_;
}
void SoftBindAttestationFlowImpl::Session::ReportFailure(
const std::string& error_message) {
LOG(WARNING) << "Attestation session failure: " << error_message;
if (!callback_) {
LOG(WARNING) << "Callback is null";
base::debug::DumpWithoutCrashing();
return;
}
std::move(callback_).Run(std::vector<std::string>{"INVALID:" + error_message},
/*valid=*/false);
}
void SoftBindAttestationFlowImpl::Session::ReportSuccess(
const std::vector<std::string>& certificate_chain) {
if (!callback_) {
LOG(WARNING) << "Attestation session success but callback is null";
base::debug::DumpWithoutCrashing();
return;
}
std::move(callback_).Run(certificate_chain, /*valid=*/true);
}
SoftBindAttestationFlowImpl::SoftBindAttestationFlowImpl()
: attestation_client_(AttestationClient::Get()) {
std::unique_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient());
attestation_flow_ = std::make_unique<AttestationFlowAdaptive>(
std::move(attestation_ca_client));
}
SoftBindAttestationFlowImpl::~SoftBindAttestationFlowImpl() = default;
void SoftBindAttestationFlowImpl::SetAttestationFlowForTesting(
std::unique_ptr<AttestationFlow> attestation_flow) {
attestation_flow_ = std::move(attestation_flow);
}
void SoftBindAttestationFlowImpl::GetCertificate(Callback callback,
const AccountId& account_id,
const std::string& user_key) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!IsAttestationAllowedByPolicy()) {
LOG(ERROR) << "Attestation not allowed by device policy";
std::move(callback).Run(
std::vector<std::string>{"INVALID:attestationNotAllowed"},
/*valid=*/false);
return;
}
GetCertificateInternal(
/*force_new_key=*/false,
std::make_unique<Session>(std::move(callback), account_id, user_key));
}
void SoftBindAttestationFlowImpl::GetCertificateInternal(
bool force_new_key,
std::unique_ptr<Session> session) {
AccountId account_id(session->GetAccountId());
AttestationFlow::CertificateCallback certificate_callback =
base::BindOnce(&SoftBindAttestationFlowImpl::OnCertificateReady,
weak_ptr_factory_.GetWeakPtr(), std::move(session));
attestation_flow_->GetCertificate(
/*certificate_profile=*/PROFILE_SOFT_BIND_CERTIFICATE,
/*account_id=*/account_id,
/*request_origin=*/std::string(),
/*force_new_key=*/force_new_key,
/*key_crypto_type=*/::attestation::KEY_TYPE_RSA,
/*key_name=*/kSoftBindKey, /*profile_specific_data=*/std::nullopt,
/*callback=*/std::move(certificate_callback));
}
void SoftBindAttestationFlowImpl::OnCertificateReady(
std::unique_ptr<Session> session,
AttestationStatus operation_status,
const std::string& certificate_chain) {
if (!session->IsTimerRunning()) {
LOG(WARNING) << "Certificate ready but already timed out";
return;
}
session->StopTimer();
if (operation_status != ATTESTATION_SUCCESS) {
LOG(ERROR) << "Attestation unsuccessful, not verified";
session->ReportFailure("notVerified");
return;
}
CertificateExpiryStatus expiry_status = CheckExpiry(certificate_chain);
if (expiry_status == CertificateExpiryStatus::kExpired) {
if (session->ResetTimer()) {
GetCertificateInternal(/*force_new_key=*/true, std::move(session));
} else {
session->ReportFailure("tooManyRetries");
}
return;
}
VLOG(1) << "Intermediate certificate obtained successfully";
// Construct a short-lived leaf certificate for the given user key and
// sign with the intermediate soft-bind attestation key. This binding of
// a hardware-backed attestation key (the intermediate) to a software key
// (the user key), is the fundamental operation of the soft-bind
// attestation scheme.
std::string user_key(session->GetUserKey());
securemessage::GenericPublicKey generic_public_key;
generic_public_key.ParseFromString(user_key);
std::string raw_x(generic_public_key.ec_p256_public_key().x());
bssl::UniquePtr<BIGNUM> x(BN_new());
BN_bin2bn(reinterpret_cast<const uint8_t*>(raw_x.data()), raw_x.size(),
x.get());
std::string raw_y(generic_public_key.ec_p256_public_key().y());
bssl::UniquePtr<BIGNUM> y(BN_new());
BN_bin2bn(reinterpret_cast<const uint8_t*>(raw_y.data()), raw_y.size(),
y.get());
bssl::UniquePtr<EC_GROUP> ec_group(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> ec_point(EC_POINT_new(ec_group.get()));
if (!EC_POINT_set_affine_coordinates(ec_group.get(), ec_point.get(), x.get(),
y.get(), /* ctx= */ nullptr)) {
LOG(ERROR) << "SoftBindAttestation: Could not set user key coordinates: "
<< ERR_error_string(ERR_get_error(), nullptr);
session->ReportFailure("couldNotSetUserKeyCoordsNotOnCurve");
return;
}
bssl::UniquePtr<EC_KEY> ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
if (!EC_KEY_set_public_key(ec_key.get(), ec_point.get())) {
LOG(ERROR) << "SoftBindAttestation: Could not set user key public key: "
<< ERR_error_string(ERR_get_error(), nullptr);
session->ReportFailure("couldNotSetUserKeyPublicKey");
return;
}
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
if (!EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release())) {
LOG(ERROR) << "SoftBindAttestation: Could not assign user key pkey: "
<< ERR_error_string(ERR_get_error(), nullptr);
session->ReportFailure("couldNotAssignUserKeyPkey");
return;
}
base::Time now = base::Time::Now();
std::string leaf_cert;
GenerateLeafCert(pkey.get(), now, now + kLeafCertValidityWindow, &leaf_cert);
std::string tbs_cert(leaf_cert);
::attestation::SignRequest request;
request.set_username(
cryptohome::Identification(session->GetAccountId()).id());
std::string key_name(kSoftBindKey);
request.set_key_label(std::move(key_name));
request.set_data_to_sign(std::move(leaf_cert));
AttestationClient::Get()->Sign(
request,
base::BindOnce(&SoftBindAttestationFlowImpl::OnCertificateSigned,
weak_ptr_factory_.GetWeakPtr(), std::move(session),
tbs_cert, certificate_chain,
expiry_status == CertificateExpiryStatus::kExpiringSoon));
}
void SoftBindAttestationFlowImpl::OnCertificateSigned(
std::unique_ptr<Session> session,
const std::string& tbs_cert,
const std::string& certificate_chain,
bool should_renew,
const ::attestation::SignReply& reply) {
if (reply.status() != ::attestation::STATUS_SUCCESS) {
LOG(ERROR) << "Could not sign attestation certificate: " << reply.status();
session->ReportFailure("couldNotSignCert");
return;
}
bssl::ScopedCBB cbb;
CBB signed_cert, signature, alg, alg_oid, alg_null;
uint8_t* signed_cert_bytes;
size_t signed_cert_len;
if (!CBB_init(cbb.get(), 64) ||
!CBB_add_asn1(cbb.get(), &signed_cert, CBS_ASN1_SEQUENCE) ||
!CBB_add_bytes(&signed_cert,
reinterpret_cast<const uint8_t*>(tbs_cert.data()),
tbs_cert.size()) ||
!CBB_add_asn1(&signed_cert, &alg, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&alg, &alg_oid, CBS_ASN1_OBJECT) ||
!CBB_add_bytes(&alg_oid, kRsaSha256Oid, 9) ||
!CBB_add_asn1(&alg, &alg_null, CBS_ASN1_NULL) ||
!CBB_add_asn1(&signed_cert, &signature, CBS_ASN1_BITSTRING) ||
!CBB_add_u8(&signature, 0) ||
!CBB_add_bytes(&signature,
reinterpret_cast<const uint8_t*>(reply.signature().data()),
reply.signature().size()) ||
!CBB_flush(&signature) || !CBB_flush(&signed_cert) ||
!CBB_finish(cbb.get(), &signed_cert_bytes, &signed_cert_len)) {
LOG(ERROR) << "Could not sign attestation certificate";
session->ReportFailure("couldNotSignCertCbb");
return;
}
std::string der_encoded_cert;
der_encoded_cert.assign(reinterpret_cast<char*>(signed_cert_bytes),
signed_cert_len);
bssl::UniquePtr<uint8_t> delete_signed_cert_bytes(signed_cert_bytes);
std::string pem_encoded_cert;
net::X509Certificate::GetPEMEncodedFromDER(der_encoded_cert,
&pem_encoded_cert);
std::vector<std::string> cert_chain_with_leaf = {pem_encoded_cert};
bssl::PEMTokenizer pem_tokenizer(certificate_chain, {"CERTIFICATE"});
while (pem_tokenizer.GetNext()) {
std::string pem_encoded_intermediate_cert;
net::X509Certificate::GetPEMEncodedFromDER(pem_tokenizer.data(),
&pem_encoded_intermediate_cert);
cert_chain_with_leaf.push_back(pem_encoded_intermediate_cert);
}
session->ReportSuccess(cert_chain_with_leaf);
// If certificate is close to expiry, send a new request to ensure
// uninterrupted continuity.
if (should_renew && renewals_in_progress_.count(certificate_chain) == 0) {
renewals_in_progress_.insert(certificate_chain);
AttestationFlow::CertificateCallback renew_callback = base::BindOnce(
&SoftBindAttestationFlowImpl::RenewCertificateCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(certificate_chain));
attestation_flow_->GetCertificate(
/*certificate_profile=*/PROFILE_SOFT_BIND_CERTIFICATE,
/*account_id=*/session->GetAccountId(),
/*request_origin=*/std::string(), /*force_new_key=*/true,
/*key_crypto_type=*/::attestation::KEY_TYPE_RSA,
/*key_name=*/kSoftBindKey, /*profile_specific_data=*/std::nullopt,
/*callback=*/std::move(renew_callback));
}
}
bool SoftBindAttestationFlowImpl::IsAttestationAllowedByPolicy() const {
bool enabled_for_device = false;
if (!CrosSettings::Get()->GetBoolean(kAttestationForContentProtectionEnabled,
&enabled_for_device)) {
LOG(ERROR) << "Failed to get device attestation policy setting.";
return false;
}
if (!enabled_for_device) {
LOG(ERROR) << "Soft key bind attestation denied because Verified Access is "
<< "disabled for the device.";
return false;
}
return true;
}
// TODO(b/185520169): create utility method for both this and content protection
CertificateExpiryStatus SoftBindAttestationFlowImpl::CheckExpiry(
const std::string& certificate_chain) {
int num_certificates = 0;
bssl::PEMTokenizer pem_tokenizer(certificate_chain, {"CERTIFICATE"});
while (pem_tokenizer.GetNext()) {
++num_certificates;
scoped_refptr<net::X509Certificate> x509 =
net::X509Certificate::CreateFromBytes(
base::as_byte_span(pem_tokenizer.data()));
if (!x509.get() || x509->valid_expiry().is_null()) {
// This logic intentionally fails open. In theory this should not happen
// but in practice parsing X.509 can be brittle and there are a lot of
// factors including which underlying module is parsing the certificate,
// whether that module performs more checks than just ASN.1/DER format,
// and the server module that generated the certificate(s). Renewal is
// expensive so we only renew certificates with good evidence that they
// have expired or will soon expire; if we don't know, we don't renew.
LOG(WARNING) << "Failed to parse certificate, cannot check expiry";
return CertificateExpiryStatus::kInvalidX509;
}
if (base::Time::Now() > x509->valid_expiry()) {
return CertificateExpiryStatus::kExpired;
}
if ((x509->valid_expiry() - base::Time::Now()) < kExpiryThresholdDays) {
return CertificateExpiryStatus::kExpiringSoon;
}
}
if (num_certificates == 0) {
LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry";
return CertificateExpiryStatus::kInvalidPemChain;
}
return CertificateExpiryStatus::kValid;
}
void SoftBindAttestationFlowImpl::RenewCertificateCallback(
const std::string& old_certificate_chain,
AttestationStatus operation_status,
const std::string& certificate_chain) {
renewals_in_progress_.erase(old_certificate_chain);
if (operation_status != ATTESTATION_SUCCESS) {
LOG(WARNING) << "Failed to renew certificate";
return;
}
VLOG(1) << "Certificate successfully renewed";
}
bool SoftBindAttestationFlowImpl::GenerateLeafCert(
EVP_PKEY* key,
base::Time not_valid_before,
base::Time not_valid_after,
std::string* der_encoded_cert) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::ScopedCBB cbb;
CBB cert, version, validity, alg, alg_oid, alg_null;
uint8_t* cert_bytes;
size_t cert_len;
uint64_t serial_number;
crypto::RandBytes(base::byte_span_from_ref(serial_number));
if (!CBB_init(cbb.get(), 64) ||
!CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&cert, &version,
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
!CBB_add_asn1_uint64(&version, 2) ||
!CBB_add_asn1_uint64(&cert, serial_number) ||
!CBB_add_asn1(&cert, &alg, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&alg, &alg_oid, CBS_ASN1_OBJECT) ||
!CBB_add_bytes(&alg_oid, kRsaSha256Oid, 9) ||
!CBB_add_asn1(&alg, &alg_null, CBS_ASN1_NULL) ||
!net::x509_util::AddName(&cert, kLeafCertIssuerName) ||
!CBB_add_asn1(&cert, &validity, CBS_ASN1_SEQUENCE) ||
!net::x509_util::CBBAddTime(&validity, not_valid_before) ||
!net::x509_util::CBBAddTime(&validity, not_valid_after) ||
!net::x509_util::AddName(&cert, kLeafCertSubjectName) ||
!EVP_marshal_public_key(&cert, key)) { // subjectPublicKeyInfo
return false;
}
CBB outer_extensions, extensions;
if (!CBB_add_asn1(&cert, &outer_extensions,
3 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED) ||
!CBB_add_asn1(&outer_extensions, &extensions, CBS_ASN1_SEQUENCE)) {
return false;
}
if (!AddCriticalExtension(&extensions, kBasicConstraintsOid, 3,
kBasicConstraintsContents, 2) ||
!AddCriticalExtension(&extensions, kKeyUsageOid, 3, kKeyUsageContents,
4)) {
return false;
}
if (!CBB_flush(&cert)) {
return false;
}
if (!CBB_finish(cbb.get(), &cert_bytes, &cert_len)) {
return false;
}
der_encoded_cert->assign(reinterpret_cast<char*>(cert_bytes), cert_len);
bssl::UniquePtr<uint8_t> delete_cert_bytes(cert_bytes);
return true;
}
} // namespace attestation
} // namespace ash