| // Copyright 2020 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. |
| |
| #include "hwsec-test-utils/fake_pca_agent/pca_enroll_v2.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <crypto/sha2.h> |
| #include <openssl/ec.h> |
| #include <openssl/evp.h> |
| #include <trunks/error_codes.h> |
| #include <trunks/tpm_generated.h> |
| |
| #include "hwsec-test-utils/common/attestation_crypto.h" |
| #include "hwsec-test-utils/common/openssl_utility.h" |
| #include "hwsec-test-utils/fake_pca_agent/issue_certificate.h" |
| #include "hwsec-test-utils/fake_pca_agent/kdf.h" |
| #include "hwsec-test-utils/fake_pca_agent/tpm2_struct_utils.h" |
| #include "hwsec-test-utils/well_known_key_pairs/well_known_key_pairs.h" |
| |
| #include <base/check_op.h> |
| #include <base/logging.h> |
| |
| namespace hwsec_test_utils { |
| namespace fake_pca_agent { |
| |
| namespace { |
| |
| constexpr int kEcPointModulusLength = 32; |
| constexpr int kExpectedPcrLength = 32; |
| |
| std::string BignumToString(const crypto::ScopedBIGNUM& bn) { |
| size_t sz = BN_num_bytes(bn.get()); |
| std::unique_ptr<unsigned char[]> buffer = |
| std::make_unique<unsigned char[]>(sz); |
| BN_bn2bin(bn.get(), buffer.get()); |
| return std::string(buffer.get(), buffer.get() + sz); |
| } |
| |
| bool GetEcPointInStrings(const crypto::ScopedEVP_PKEY& key, |
| std::string* x_str, |
| std::string* y_str) { |
| CHECK_EQ(EVP_PKEY_base_id(key.get()), EVP_PKEY_EC); |
| crypto::ScopedEC_KEY ec_key(EVP_PKEY_get1_EC_KEY(key.get())); |
| const EC_POINT* ec_point = EC_KEY_get0_public_key(ec_key.get()); |
| const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get()); |
| crypto::ScopedBIGNUM x(BN_new()), y(BN_new()); |
| if (!x || !y) { |
| LOG(ERROR) << __func__ << ": Failed to call BN_new: " << GetOpenSSLError(); |
| return false; |
| } |
| if (EC_POINT_get_affine_coordinates_GFp(ec_group, ec_point, x.get(), y.get(), |
| nullptr) != 1) { |
| LOG(ERROR) << __func__ |
| << ": Failed to call EC_POINT_get_affine_coordinates_GFp: " |
| << GetOpenSSLError(); |
| return false; |
| } |
| if (x_str != nullptr) { |
| *x_str = BignumToString(x); |
| } |
| if (y_str != nullptr) { |
| *y_str = BignumToString(y); |
| } |
| return true; |
| } |
| |
| std::string ToSizedEccParameter(const std::string& s) { |
| CHECK_LE(s.size(), kEcPointModulusLength); |
| std::string size_str; |
| CHECK_EQ(trunks::Serialize_UINT16(s.size(), &size_str), |
| trunks::TPM_RC_SUCCESS); |
| return size_str + s; |
| } |
| |
| // Generates an new EC key and accept the result only if X and Y of the public |
| // key is in 32 bytes due to cr50's limitation. In case of any error, the |
| // returned object contains nullptr; otherwise, |x| and |y| are set to the |
| // public keys of the returned ECC key. |
| crypto::ScopedEVP_PKEY CreateNewEcKeyWithFullSize(std::string* x, |
| std::string* y) { |
| crypto::ScopedEVP_PKEY key = CreateNewEcKey(); |
| if (!key) { |
| LOG(ERROR) << __func__ << ": Failed to craete an EC key."; |
| return nullptr; |
| } |
| // Validate the size of the public key. |
| if (!GetEcPointInStrings(key, x, y)) { |
| LOG(ERROR) << __func__ << ": Failed to check key size."; |
| return nullptr; |
| } |
| CHECK_LE(x->size(), kEcPointModulusLength); |
| CHECK_LE(y->size(), kEcPointModulusLength); |
| if (x->size() == kEcPointModulusLength && |
| y->size() == kEcPointModulusLength) { |
| return key; |
| } |
| // Probabilistically impossible to fall into infinite loop; just invoke |
| // recursive call. |
| LOG(WARNING) << __func__ << ": Public key length too short; retry."; |
| return CreateNewEcKeyWithFullSize(x, y); |
| } |
| |
| } // namespace |
| |
| bool PcaEnrollV2::Preprocess() { |
| identity_key_ = |
| TpmtPublicToEVP(request_.identity_public_key(), &identity_key_name_); |
| if (!identity_key_) { |
| LOG(ERROR) << __func__ << ": Failed to parse identity key."; |
| return false; |
| } |
| |
| crypto::ScopedEVP_PKEY ca_encryption_key = |
| well_known_key_pairs::GetCaEncryptionkey(); |
| |
| std::string decrypted_data; |
| attestation_crypto::ReturnStatus decrypt_status = attestation_crypto::Decrypt( |
| request_.encrypted_endorsement_credential(), ca_encryption_key, |
| attestation_crypto::KeyDeriverDirect(), &decrypted_data); |
| if (decrypt_status != attestation_crypto::ReturnStatus::kSuccess) { |
| LOG(ERROR) << __func__ |
| << ": Failed to decrypt serialized key info; status code: " |
| << static_cast<int>(decrypt_status); |
| return {}; |
| } |
| |
| const unsigned char* asn1_ptr = |
| reinterpret_cast<const unsigned char*>(decrypted_data.data()); |
| crypto::ScopedX509 x509( |
| d2i_X509(nullptr, &asn1_ptr, decrypted_data.length())); |
| if (!x509) { |
| LOG(ERROR) << __func__ |
| << ": Failed to call d2i_X509: " << GetOpenSSLError(); |
| return false; |
| } |
| endorsement_key_.reset(X509_get_pubkey(x509.get())); |
| if (!endorsement_key_) { |
| LOG(ERROR) << __func__ |
| << ": Failed to call X509_get_pubkey: " << GetOpenSSLError(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool PcaEnrollV2::Verify() { |
| const std::vector<const attestation::Quote*> quotes = { |
| &request_.pcr0_quote(), &request_.pcr1_quote()}; |
| // Don't early return; instead, run through all pcr quotes to get more |
| // information for debugging. |
| bool any_failed_check = false; |
| for (int i = 0; i < quotes.size(); ++i) { |
| if (!EVPDigestVerify(identity_key_, EVP_sha256(), quotes[i]->quoted_data(), |
| quotes[i]->quote())) { |
| LOG(ERROR) << __func__ << "Failed to verify quote of PCR" << i << '.'; |
| any_failed_check = true; |
| } |
| if (quotes[i]->quoted_pcr_value().size() != kExpectedPcrLength) { |
| LOG(ERROR) << __func__ << ": Unexpected PCR" << i |
| << " value length: " << quotes[i]->quoted_pcr_value().size(); |
| any_failed_check = true; |
| } else { |
| trunks::TPMS_ATTEST tpms_attest; |
| trunks::TPM_RC result = trunks::Parse_TPMS_ATTEST( |
| std::make_unique<std::string>(quotes[i]->quoted_data()).get(), |
| &tpms_attest, nullptr); |
| if (result != trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << __func__ << ": Failed to parse quoted data."; |
| any_failed_check = true; |
| } else if (tpms_attest.type != trunks::TPM_ST_ATTEST_QUOTE) { |
| LOG(ERROR) << __func__ << ": Wrong attesting type."; |
| any_failed_check = true; |
| } else { |
| // Verify PCR digest. |
| if (StringFrom_TPM2B_DIGEST(tpms_attest.attested.quote.pcr_digest) != |
| crypto::SHA256HashString(quotes[i]->quoted_pcr_value())) { |
| LOG(ERROR) << __func__ << ": Mismatched pcr digest."; |
| any_failed_check = true; |
| } |
| // Verify PCR index. |
| if (tpms_attest.attested.quote.pcr_select.count != 1) { |
| LOG(ERROR) << __func__ << ": Mismatched pcr selection count: " |
| << tpms_attest.attested.quote.pcr_select.count; |
| any_failed_check = true; |
| } |
| const trunks::TPMS_PCR_SELECTION& pcr_selection = |
| tpms_attest.attested.quote.pcr_select.pcr_selections[0]; |
| if (pcr_selection.hash != trunks::TPM_ALG_SHA256) { |
| LOG(ERROR) << __func__ << ": Unexpected pcr digest algorithm: " |
| << pcr_selection.hash; |
| any_failed_check = true; |
| } |
| if (pcr_selection.sizeof_select < 1) { |
| LOG(ERROR) << __func__ << ": Unexpected size_of_select: " |
| << static_cast<int>(pcr_selection.sizeof_select); |
| any_failed_check = true; |
| } else { |
| if (pcr_selection.pcr_select[0] != (1 << i)) { |
| LOG(ERROR) << __func__ << ": Unexpected pcr_select[0]: " |
| << static_cast<int>(pcr_selection.pcr_select[0]); |
| any_failed_check = true; |
| } |
| for (int j = 1; j < pcr_selection.sizeof_select; ++j) { |
| if (pcr_selection.pcr_select[j] != 0) { |
| LOG(ERROR) << __func__ << ": Unexpected pcr_select[" << j << "]: " |
| << static_cast<int>(pcr_selection.pcr_select[j]); |
| any_failed_check = true; |
| } |
| } |
| } |
| } |
| } |
| } |
| return !any_failed_check; |
| } |
| |
| bool PcaEnrollV2::Generate() { |
| if (EVP_PKEY_base_id(endorsement_key_.get()) != EVP_PKEY_EC) { |
| LOG(ERROR) << __func__ << ": Only ECC EK is supported."; |
| return false; |
| } |
| base::Optional<std::string> cert_der = IssueTestCertificateDer(identity_key_); |
| if (!cert_der) { |
| LOG(ERROR) << __func__ |
| << ": Failed to issue a test certificate for the identity key."; |
| return false; |
| } |
| |
| // Create an ephemeral key and perform ECDH. |
| std::string ephemeral_x, ephemeral_y; |
| crypto::ScopedEVP_PKEY ephemeral_key = |
| CreateNewEcKeyWithFullSize(&ephemeral_x, &ephemeral_y); |
| if (!ephemeral_key) { |
| LOG(ERROR) << __func__ << ": Failed to create ephemeral key."; |
| return false; |
| } |
| CHECK_EQ(ephemeral_x.size(), kEcPointModulusLength); |
| CHECK_EQ(ephemeral_y.size(), kEcPointModulusLength); |
| base::Optional<std::string> z = EVPDerive(ephemeral_key, endorsement_key_); |
| if (!z) { |
| LOG(ERROR) << __func__ << ": Failed to create shared secret."; |
| return false; |
| } |
| std::string tpm_static_x; |
| if (!GetEcPointInStrings(endorsement_key_, &tpm_static_x, |
| /*y_str=*/nullptr)) { |
| LOG(ERROR) << __func__ << ": Failed to dump endorsement key."; |
| return false; |
| } |
| CHECK_LE(tpm_static_x.size(), 32); |
| |
| // TPM2.0 spec part I, C.8 ECC Point Padding -- Use the padded input to feed |
| // DKFe. |
| tpm_static_x.insert(0, 32 - tpm_static_x.size(), '\x00'); |
| |
| // TPM2.0 spec Part I, 11.4.10.3 KDFe for ECDH. |
| std::string ecdh_seed = KDFe(*z, "IDENTITY", ephemeral_x, tpm_static_x); |
| const std::string sized_ephemeral_x = ToSizedEccParameter(ephemeral_x); |
| const std::string sized_ephemeral_y = ToSizedEccParameter(ephemeral_y); |
| |
| base::Optional<std::string> secret = GetRandom(32); |
| if (!secret) { |
| LOG(ERROR) << __func__ << ": Failed to get random secret."; |
| return false; |
| } |
| // TPM2.0 spec Part I, 24.4 Symmetric Encryption. |
| base::Optional<std::string> aes_key = |
| KDFa(ecdh_seed, "STORAGE", identity_key_name_, "", 128); |
| if (!aes_key) { |
| LOG(ERROR) << __func__ << ": Failed to derive aes key."; |
| return false; |
| } |
| |
| std::string serialized_secret; |
| trunks::TPM2B_DIGEST tpm2b_digest; |
| tpm2b_digest.size = secret->size(); |
| std::copy(secret->begin(), secret->end(), tpm2b_digest.buffer); |
| CHECK_EQ(trunks::Serialize_TPM2B_DIGEST(tpm2b_digest, &serialized_secret), |
| trunks::TPM_RC_SUCCESS); |
| |
| base::Optional<std::string> encrypted_secret = EVPAesEncrypt( |
| serialized_secret, EVP_aes_128_cfb(), *aes_key, std::string(16, '\x00')); |
| if (!encrypted_secret) { |
| LOG(ERROR) << __func__ << ": Failed to encrypt secret."; |
| return false; |
| } |
| |
| // TPM2.0 spec Part I, 24.5 HMAC. |
| base::Optional<std::string> hmac_key_str = |
| KDFa(ecdh_seed, "INTEGRITY", "", "", 256); |
| if (!hmac_key_str) { |
| LOG(ERROR) << __func__ << ": Failed to derive hmac key."; |
| return false; |
| } |
| crypto::ScopedEVP_PKEY hmac_key(EVP_PKEY_new_mac_key( |
| EVP_PKEY_HMAC, nullptr, |
| reinterpret_cast<const unsigned char*>(hmac_key_str->data()), |
| hmac_key_str->length())); |
| if (!hmac_key) { |
| LOG(ERROR) << __func__ << ": Failed to call EVP_PKEY_new_mac_key: " |
| << GetOpenSSLError(); |
| return false; |
| } |
| base::Optional<std::string> encrypted_secret_hmac = EVPDigestSign( |
| hmac_key, EVP_sha256(), *encrypted_secret + identity_key_name_); |
| if (!encrypted_secret_hmac) { |
| LOG(ERROR) << __func__ |
| << ": Failed to calculate HMAC for encrypted secret."; |
| return false; |
| } |
| |
| attestation::EncryptedData encrypted_credential; |
| encrypted_credential.set_wrapped_key(*encrypted_secret); |
| attestation_crypto::KeyDeriverSha256WithHeader key_deriver; |
| base::Optional<std::string> iv = GetRandom(16); |
| if (!iv) { |
| LOG(ERROR) << __func__ << ": Failed to generate IV."; |
| return false; |
| } |
| base::Optional<std::string> encrypted_cert_der = EVPAesEncrypt( |
| *cert_der, EVP_aes_256_cbc(), key_deriver.ToAesKey(*secret), *iv); |
| if (!encrypted_cert_der) { |
| LOG(ERROR) << __func__ << ": Failed to encrypt cert."; |
| return false; |
| } |
| const std::string encrypted_cert_der_hmac_key_str = |
| key_deriver.ToHmacKey(*secret); |
| crypto::ScopedEVP_PKEY encrypted_cert_der_hmac_key( |
| EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nullptr, |
| reinterpret_cast<const unsigned char*>( |
| encrypted_cert_der_hmac_key_str.data()), |
| encrypted_cert_der_hmac_key_str.length())); |
| base::Optional<std::string> encrypted_cert_der_hmac = EVPDigestSign( |
| encrypted_cert_der_hmac_key, EVP_sha512(), *iv + *encrypted_cert_der); |
| if (!encrypted_cert_der_hmac) { |
| LOG(ERROR) << __func__ |
| << ": Failed to calculate HMAC for encrypted certificate."; |
| return false; |
| } |
| |
| encrypted_credential.set_encrypted_data(*encrypted_cert_der); |
| encrypted_credential.set_wrapped_key(*encrypted_secret); |
| encrypted_credential.set_iv(*iv); |
| encrypted_credential.set_mac(*encrypted_cert_der_hmac); |
| |
| // Last, generate the output. |
| encrypted_identity_credential_ = attestation::EncryptedIdentityCredential(); |
| encrypted_identity_credential_->set_tpm_version(attestation::TPM_2_0); |
| encrypted_identity_credential_->set_encrypted_seed(sized_ephemeral_x + |
| sized_ephemeral_y); |
| *encrypted_identity_credential_->mutable_wrapped_certificate() = |
| encrypted_credential; |
| encrypted_identity_credential_->set_credential_mac(*encrypted_secret_hmac); |
| return true; |
| } |
| |
| bool PcaEnrollV2::Write(attestation::AttestationEnrollmentResponse* response) { |
| if (!encrypted_identity_credential_) { |
| return false; |
| } |
| *response->mutable_encrypted_identity_credential() = |
| *encrypted_identity_credential_; |
| return true; |
| } |
| |
| } // namespace fake_pca_agent |
| } // namespace hwsec_test_utils |