| // Copyright (c) 2012 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 "cryptohome/attestation.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/data_encoding.h> |
| #include <brillo/http/http_transport_fake.h> |
| #include <brillo/mime_utils.h> |
| #include <brillo/secure_blob.h> |
| #include <gmock/gmock.h> |
| #include <google/protobuf/util/message_differencer.h> |
| #include <gtest/gtest.h> |
| #include <openssl/rsa.h> |
| #include <openssl/x509.h> |
| |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/mock_install_attributes.h" |
| #include "cryptohome/mock_keystore.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| #include "cryptohome/mock_tpm_init.h" |
| |
| #include "attestation.pb.h" // NOLINT(build/include) |
| |
| using base::FilePath; |
| using brillo::Blob; |
| using brillo::SecureBlob; |
| using google::protobuf::util::MessageDifferencer; |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SetArgPointee; |
| using ::testing::StartsWith; |
| using ::testing::WithArgs; |
| |
| namespace cryptohome { |
| |
| static const char* kTestPath = "/tmp/attestation_test.epb"; |
| static const char* kTestUser = "test_user"; |
| |
| static const char kABEData[] = |
| "2eac34fa74994262b907c15a3a1462e349e5108ca0d0e807f4b1a3ee741a5594"; |
| static const char kDEN[] = |
| "865cc962ffe14b3638b2d1f860e77b531644a3aba67e52e49e1f6c0a31d81daf"; |
| static const char kEID[] = |
| "809c6c9f425c59b86e551f4e8fdccd5a2200b08fe3250c4971f40d8bbcf7820a"; |
| |
| class AttestationBaseTest : public testing::Test { |
| public: |
| AttestationBaseTest() : crypto_(&platform_), attestation_() { |
| crypto_.set_tpm(&tpm_); |
| crypto_.set_use_tpm(true); |
| } |
| virtual ~AttestationBaseTest() { |
| if (rsa_) |
| RSA_free(rsa_); |
| } |
| |
| void SetUpAttestation(Attestation* attestation) { |
| attestation->set_database_path(kTestPath); |
| attestation->set_key_store(&key_store_); |
| } |
| |
| void SetUp() override { |
| SetUpAttestation(&attestation_); |
| http_transport_ = std::make_shared<brillo::http::fake::Transport>(); |
| attestation_.set_http_transport(http_transport_); |
| // Fake up a single file by default. |
| ON_CALL(platform_, |
| WriteStringToFileAtomicDurable( |
| Property(&FilePath::value, StartsWith(kTestPath)), _, _)) |
| .WillByDefault( |
| WithArgs<1>(Invoke(this, &AttestationBaseTest::WriteDB))); |
| ON_CALL( |
| platform_, |
| ReadFileToString(Property(&FilePath::value, StartsWith(kTestPath)), _)) |
| .WillByDefault(WithArgs<1>(Invoke(this, &AttestationBaseTest::ReadDB))); |
| // Configure a TPM that is ready. |
| ON_CALL(tpm_, IsEnabled()).WillByDefault(Return(true)); |
| ON_CALL(tpm_, IsOwned()).WillByDefault(Return(true)); |
| ON_CALL(tpm_, IsBeingOwned()).WillByDefault(Return(false)); |
| Initialize(); |
| } |
| |
| virtual void Initialize() { |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, |
| brillo::SecureBlob() /* abe_data */, |
| false /* retain_endorsement_data */); |
| } |
| |
| virtual bool WriteDB(const std::string& db) { |
| serialized_db_.assign(db); |
| return true; |
| } |
| |
| virtual bool ReadDB(std::string* db) { |
| if (serialized_db_.empty()) { |
| return false; |
| } |
| db->assign(serialized_db_); |
| return true; |
| } |
| |
| virtual bool ComputeEnterpriseEnrollmentId(brillo::SecureBlob* blob) { |
| return attestation_.ComputeEnterpriseEnrollmentId(blob); |
| } |
| |
| virtual std::string GetValidEndorsementKey() { |
| const std::string hex_ek = |
| "3082010A0282010100D3EE9D14FAC4C42B35FEDC87363CC29807A3F39D3E45D2" |
| "49586F620C6425CE981E8619DCE50D964E934A1F1FD2C1066418DD75D8916D85" |
| "DD9E82C27C82A8C2C9BC76BA914B5A43F7535AEAA2F7BD985F46A46C92334643" |
| "C89F5598ABD191AA5439088778774DB3B07FD08F019997893BEC1A87571AC95F" |
| "66ADE2F3631A2C9CF8EF0B94D2CA62E81F1FF9CC71339838E229E63CA59E0BB6" |
| "4D2134C3AF705BCF0F614E58DF848897454FFA2FA42073F80174C1D3D0C54D5B" |
| "DC45747FE662D6D321AEA5375F0AE489DF6ABB018D5D11707E546E8487641290" |
| "F9F9B3CC3A1F8631FB0F3486A875F6005D3539A5823F7618B007779FB31CFB7F" |
| "E36A1C2D9DEFD8F5030203010001"; |
| std::vector<uint8_t> ek_bytes; |
| base::HexStringToBytes(hex_ek, &ek_bytes); |
| return std::string(ek_bytes.begin(), ek_bytes.end()); |
| } |
| |
| protected: |
| std::string serialized_db_; |
| NiceMock<MockTpm> tpm_; |
| NiceMock<MockTpmInit> tpm_init_; |
| NiceMock<MockPlatform> platform_; |
| Crypto crypto_; |
| NiceMock<MockKeyStore> key_store_; |
| NiceMock<MockInstallAttributes> install_attributes_; |
| std::shared_ptr<brillo::http::fake::Transport> http_transport_; |
| Attestation attestation_; |
| RSA* rsa_ = nullptr; // Access with rsa(). |
| bool is_enterprise_setup_ = false; |
| |
| RSA* rsa() { |
| if (!rsa_) { |
| rsa_ = RSA_generate_key(2048, 65537, nullptr, nullptr); |
| CHECK(rsa_); |
| } |
| return rsa_; |
| } |
| |
| virtual void SetUpEnterprise() { |
| if (!is_enterprise_setup_) { |
| attestation_.set_enterprise_test_keys(Attestation::kDefaultVA, |
| RSA_generate_key(2048, 65537, nullptr, nullptr), |
| RSA_generate_key(2048, 65537, nullptr, nullptr)); |
| attestation_.set_enterprise_test_keys(Attestation::kTestVA, |
| RSA_generate_key(2048, 65537, nullptr, nullptr), |
| RSA_generate_key(2048, 65537, nullptr, nullptr)); |
| is_enterprise_setup_ = true; |
| } |
| } |
| |
| RSA* enterprise_signing_rsa(Attestation::VAType va_type) { |
| SetUpEnterprise(); |
| return attestation_.GetEnterpriseSigningKey(va_type); |
| } |
| |
| RSA* enterprise_encryption_rsa(Attestation::VAType va_type) { |
| SetUpEnterprise(); |
| return attestation_.GetEnterpriseEncryptionKey(va_type); |
| } |
| |
| SecureBlob GetEnrollBlob() { |
| AttestationEnrollmentResponse pb; |
| pb.set_status(OK); |
| pb.set_detail(""); |
| pb.mutable_encrypted_identity_credential()->set_asym_ca_contents("1234"); |
| pb.mutable_encrypted_identity_credential()->set_sym_ca_attestation("5678"); |
| std::string tmp; |
| pb.SerializeToString(&tmp); |
| return SecureBlob(tmp.begin(), tmp.end()); |
| } |
| |
| SecureBlob GetCertRequestBlob(const SecureBlob& request) { |
| AttestationCertificateRequest request_pb; |
| CHECK(request_pb.ParseFromArray(request.data(), request.size())); |
| AttestationCertificateResponse pb; |
| pb.set_message_id(request_pb.message_id()); |
| pb.set_status(OK); |
| pb.set_detail(""); |
| pb.set_certified_key_credential("response_cert"); |
| pb.set_intermediate_ca_cert("response_ca_cert"); |
| std::string tmp; |
| pb.SerializeToString(&tmp); |
| return SecureBlob(tmp.begin(), tmp.end()); |
| } |
| |
| SecureBlob GetCertifiedKeyBlob(const std::string& payload, |
| bool include_ca_cert) { |
| CertifiedKey pb; |
| pb.set_certified_key_credential("stored_cert"); |
| if (include_ca_cert) |
| pb.set_intermediate_ca_cert("stored_ca_cert"); |
| pb.set_public_key(GetPKCS1PublicKey().to_string()); |
| pb.set_payload(payload); |
| std::string tmp; |
| pb.SerializeToString(&tmp); |
| return SecureBlob(tmp.begin(), tmp.end()); |
| } |
| |
| bool CompareBlob(const SecureBlob& blob, const std::string& str) { |
| return (blob.to_string() == str); |
| } |
| |
| std::string EncodeCertChain(const std::string& cert1, |
| const std::string& cert2) { |
| std::string chain = "-----BEGIN CERTIFICATE-----\n"; |
| chain += brillo::data_encoding::Base64EncodeWrapLines(cert1); |
| chain += "-----END CERTIFICATE-----"; |
| if (!cert2.empty()) { |
| chain += "\n-----BEGIN CERTIFICATE-----\n"; |
| chain += brillo::data_encoding::Base64EncodeWrapLines(cert2); |
| chain += "-----END CERTIFICATE-----"; |
| } |
| return chain; |
| } |
| |
| SecureBlob GetPKCS1PublicKey() { |
| unsigned char* buffer = nullptr; |
| int length = i2d_RSAPublicKey(rsa(), &buffer); |
| if (length <= 0) |
| return SecureBlob(); |
| SecureBlob tmp(buffer, buffer + length); |
| OPENSSL_free(buffer); |
| return tmp; |
| } |
| |
| SecureBlob GetX509PublicKey() { |
| unsigned char* buffer = nullptr; |
| int length = i2d_RSA_PUBKEY(rsa(), &buffer); |
| if (length <= 0) |
| return SecureBlob(); |
| SecureBlob tmp(buffer, buffer + length); |
| OPENSSL_free(buffer); |
| return tmp; |
| } |
| |
| bool VerifySimpleChallenge(const SecureBlob& response, |
| const std::string& challenge, |
| const std::string& signature) { |
| SignedData signed_data; |
| if (!signed_data.ParseFromArray(response.data(), response.size())) |
| return false; |
| if (signed_data.data().find(challenge) != 0 || |
| signed_data.data() == challenge) |
| return false; |
| if (signed_data.signature() != signature) |
| return false; |
| return true; |
| } |
| |
| bool VerifyEnterpriseVaChallenge(Attestation::VAType va_type, |
| const SecureBlob& response, |
| KeyType key_type, |
| const std::string& domain, |
| const std::string& device_id, |
| const std::string& cert_chain, |
| const std::string& signature) { |
| SignedData signed_data; |
| if (!signed_data.ParseFromArray(response.data(), response.size())) |
| return false; |
| ChallengeResponse response_pb; |
| if (!response_pb.ParseFromString(signed_data.data())) |
| return false; |
| std::string expected_challenge = GetEnterpriseVaChallenge( |
| va_type, "EnterpriseKeyChallenge", false).to_string(); |
| if (response_pb.challenge().data() != expected_challenge) |
| return false; |
| std::string key_info; |
| if (!DecryptEnterpriseData(va_type, response_pb.encrypted_key_info(), |
| &key_info)) |
| return false; |
| KeyInfo key_info_pb; |
| if (!key_info_pb.ParseFromString(key_info)) |
| return false; |
| if (key_info_pb.domain() != domain || |
| key_info_pb.device_id() != device_id || |
| key_info_pb.key_type() != key_type || |
| key_info_pb.certificate() != cert_chain) |
| return false; |
| if (signed_data.signature() != signature) |
| return false; |
| return true; |
| } |
| |
| SecureBlob GetEnterpriseVaChallenge(Attestation::VAType va_type, |
| const std::string& prefix, |
| bool sign) { |
| Challenge challenge; |
| challenge.set_prefix(prefix); |
| challenge.set_nonce("nonce"); |
| challenge.set_timestamp(123456789); |
| std::string serialized; |
| challenge.SerializeToString(&serialized); |
| if (sign) { |
| unsigned char buffer[256]; |
| unsigned int length = 0; |
| SecureBlob digest = CryptoLib::Sha256(SecureBlob(serialized)); |
| RSA_sign(NID_sha256, |
| digest.data(), digest.size(), |
| buffer, &length, |
| enterprise_signing_rsa(va_type)); |
| SignedData signed_challenge; |
| signed_challenge.set_data(serialized); |
| signed_challenge.set_signature(buffer, length); |
| signed_challenge.SerializeToString(&serialized); |
| } |
| return SecureBlob(serialized); |
| } |
| |
| bool DecryptEnterpriseData(Attestation::VAType va_type, |
| const EncryptedData& input, |
| std::string* output) { |
| // Unwrap the AES key. |
| unsigned char* wrapped_key_buffer = reinterpret_cast<unsigned char*>( |
| const_cast<char*>(input.wrapped_key().data())); |
| unsigned char buffer[256]; |
| int length = RSA_private_decrypt(input.wrapped_key().size(), |
| wrapped_key_buffer, |
| buffer, |
| enterprise_encryption_rsa(va_type), |
| RSA_PKCS1_OAEP_PADDING); |
| if (length != 32) |
| return false; |
| SecureBlob aes_key(buffer, buffer + length); |
| // Decrypt the blob. |
| SecureBlob decrypted; |
| brillo::Blob encrypted = brillo::BlobFromString(input.encrypted_data()); |
| SecureBlob aes_iv = SecureBlob(input.iv()); |
| if (!CryptoLib::AesDecryptSpecifyBlockMode(encrypted, 0, encrypted.size(), |
| aes_key, aes_iv, |
| CryptoLib::kPaddingStandard, |
| CryptoLib::kCbc, |
| &decrypted)) |
| return false; |
| *output = decrypted.to_string(); |
| return true; |
| } |
| |
| // Returns a copy of the database as it exists on 'disk'. |
| AttestationDatabase GetPersistentDatabase() { |
| AttestationDatabase db; |
| attestation_.DecryptDatabase(serialized_db_, &db); |
| return db; |
| } |
| |
| // Returns a mutable reference to the current database instance. If a test is |
| // verifying database changes it should use GetPersistentDatabase() so it will |
| // also verify that the changes are written to disk correctly. |
| AttestationDatabase& GetMutableDatabase() { |
| return attestation_.database_pb_; |
| } |
| |
| // Verify Privacy CA-related data, including the default CA's identity |
| // credential. |
| void VerifyPCAData(const AttestationDatabase& db, |
| const char* default_identity_credential) { |
| ASSERT_EQ(default_identity_credential ? 1 : 0, |
| db.identity_certificates().size()); |
| for (int pca = 0; pca < db.identity_certificates().size(); ++pca) { |
| const AttestationDatabase::IdentityCertificate identity_certificate = |
| db.identity_certificates().at(pca); |
| ASSERT_EQ(0, identity_certificate.identity()); |
| ASSERT_EQ(pca, identity_certificate.aca()); |
| if (default_identity_credential && pca == Attestation::kDefaultPCA) { |
| ASSERT_EQ(default_identity_credential, |
| identity_certificate.identity_credential()); |
| } else { |
| ASSERT_FALSE(identity_certificate.has_identity_credential()); |
| } |
| } |
| // All PCAs have encrypted credentials. |
| for (int pca = Attestation::kDefaultPCA; pca < Attestation::kMaxPCAType; |
| ++pca) { |
| ASSERT_TRUE( |
| db.credentials().encrypted_endorsement_credentials().count(pca)); |
| } |
| } |
| |
| // Verify Privacy CA-related data, including the lack of default CA's identity |
| // credential. |
| void VerifyPCAData(const AttestationDatabase& db) { |
| VerifyPCAData(db, nullptr); |
| } |
| |
| // Gets the Google Privacy CA web-origin -- this can change depending on |
| // whether the test server is being targeted. |
| std::string GetPCAWebOrigin(Attestation::PCAType pca_type) const { |
| switch (pca_type) { |
| default: |
| case Attestation::kDefaultPCA: |
| return attestation_.kDefaultPCAWebOrigin; |
| case Attestation::kTestPCA: |
| return attestation_.kTestPCAWebOrigin; |
| } |
| } |
| |
| size_t GetDigestSize() const { return Attestation::kDigestSize; } |
| }; |
| |
| TEST_F(AttestationBaseTest, NotPreparedForEnrollment) { |
| ASSERT_FALSE(attestation_.IsPreparedForEnrollment()); |
| } |
| |
| TEST_F(AttestationBaseTest, PrepareForEnrollment) { |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.IsPreparedForEnrollment()); |
| AttestationDatabase db = GetPersistentDatabase(); |
| // One identity has been created. |
| EXPECT_EQ(1, db.identities().size()); |
| const AttestationDatabase::Identity& identity_data = db.identities().Get(0); |
| EXPECT_TRUE(identity_data.has_identity_binding()); |
| EXPECT_TRUE(identity_data.has_identity_key()); |
| EXPECT_EQ(1, identity_data.pcr_quotes().count(0)); |
| EXPECT_EQ(1, identity_data.pcr_quotes().count(1)); |
| EXPECT_EQ(IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID, |
| identity_data.features()); |
| // Deprecated identity-related values have not been set. |
| EXPECT_FALSE(db.has_identity_binding()); |
| EXPECT_FALSE(db.has_identity_key()); |
| EXPECT_FALSE(db.has_pcr0_quote()); |
| EXPECT_FALSE(db.has_pcr1_quote()); |
| // We have a delegate to activate the AIK. |
| EXPECT_TRUE(db.has_delegate()); |
| // Verify Privacy CA-related data. |
| VerifyPCAData(db); |
| // These deprecated fields have not been set either. |
| EXPECT_TRUE(db.has_credentials()); |
| EXPECT_FALSE(db.credentials().has_default_encrypted_endorsement_credential()); |
| } |
| |
| TEST_F(AttestationBaseTest, PrepareForEnrollmentNoIdentityFeatures) { |
| attestation_.set_default_identity_features_for_test(NO_IDENTITY_FEATURES); |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.IsPreparedForEnrollment()); |
| AttestationDatabase db = GetPersistentDatabase(); |
| // One identity has been created. |
| EXPECT_EQ(1, db.identities().size()); |
| const AttestationDatabase::Identity& identity_data = |
| db.identities().Get(0); |
| EXPECT_TRUE(identity_data.has_identity_binding()); |
| EXPECT_TRUE(identity_data.has_identity_key()); |
| EXPECT_EQ(1, identity_data.pcr_quotes().count(0)); |
| EXPECT_EQ(1, identity_data.pcr_quotes().count(1)); |
| EXPECT_EQ(NO_IDENTITY_FEATURES, identity_data.features()); |
| // Deprecated identity-related values have not been set. |
| EXPECT_FALSE(db.has_identity_binding()); |
| EXPECT_FALSE(db.has_identity_key()); |
| EXPECT_FALSE(db.has_pcr0_quote()); |
| EXPECT_FALSE(db.has_pcr1_quote()); |
| // We have a delegate to activate the AIK. |
| EXPECT_TRUE(db.has_delegate()); |
| // Verify Privacy CA-related data. |
| VerifyPCAData(db); |
| // These deprecated fields have not been set either. |
| EXPECT_TRUE(db.has_credentials()); |
| EXPECT_FALSE(db.credentials().has_default_encrypted_endorsement_credential()); |
| } |
| |
| TEST_F(AttestationBaseTest, IdentityCertificateMapIsDeepCopied) { |
| SecureBlob blob; |
| EXPECT_FALSE( |
| attestation_.CreateEnrollRequest(Attestation::kDefaultPCA, &blob)); |
| attestation_.PrepareForEnrollment(); |
| EXPECT_FALSE(attestation_.HasIdentityCertificate(Attestation::kFirstIdentity, |
| Attestation::kDefaultPCA)); |
| EXPECT_TRUE( |
| attestation_.CreateEnrollRequest(Attestation::kDefaultPCA, &blob)); |
| EXPECT_TRUE(attestation_.Enroll(Attestation::kDefaultPCA, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.HasIdentityCertificate(Attestation::kFirstIdentity, |
| Attestation::kDefaultPCA)); |
| // Check that the identity certificate map is not the same as what the |
| // database contains. |
| Attestation::IdentityCertificateMap map = |
| attestation_.GetIdentityCertificateMap(); |
| AttestationDatabase db = GetPersistentDatabase(); |
| db.mutable_identity_certificates()->at(Attestation::kDefaultPCA).set_aca( |
| Attestation::kMaxPCAType); |
| EXPECT_EQ(Attestation::kDefaultPCA, map.at(Attestation::kDefaultPCA).aca()); |
| } |
| |
| // Tests DeleteKeysByPrefix with device-wide keys stored in the attestation db. |
| TEST_F(AttestationBaseTest, DeleteByPrefixDevice) { |
| // Test with an empty db. |
| ASSERT_TRUE(attestation_.DeleteKeysByPrefix(false, "", "prefix")); |
| |
| // Test with a single matching key. |
| AttestationDatabase& db = GetMutableDatabase(); |
| db.add_device_keys()->set_key_name("prefix1"); |
| ASSERT_TRUE(attestation_.DeleteKeysByPrefix(false, "", "prefix")); |
| EXPECT_EQ(0, db.device_keys_size()); |
| |
| // Test with a single non-matching key. |
| db.add_device_keys()->set_key_name("other"); |
| ASSERT_TRUE(attestation_.DeleteKeysByPrefix(false, "", "prefix")); |
| EXPECT_EQ(1, db.device_keys_size()); |
| |
| // Test with an empty prefix. |
| ASSERT_TRUE(attestation_.DeleteKeysByPrefix(false, "", "")); |
| EXPECT_EQ(0, db.device_keys_size()); |
| |
| // Test with multiple matching / non-matching keys. |
| db.add_device_keys()->set_key_name("prefix1"); |
| db.add_device_keys()->set_key_name("other1"); |
| db.add_device_keys()->set_key_name("prefix2"); |
| db.add_device_keys()->set_key_name("other2"); |
| db.add_device_keys()->set_key_name("prefix3"); |
| db.add_device_keys()->set_key_name("other3"); |
| db.add_device_keys()->set_key_name("prefix4"); |
| |
| ASSERT_TRUE(attestation_.DeleteKeysByPrefix(false, "", "prefix")); |
| |
| EXPECT_EQ(3, db.device_keys_size()); |
| for (int i = 0; i < db.device_keys_size(); ++i) { |
| EXPECT_EQ(0, db.device_keys(i).key_name().find("other")); |
| } |
| } |
| |
| // Tests DeleteKeysByPrefix with user-owned keys. This object does not manage |
| // user-owned keys so the test is trivial. |
| TEST_F(AttestationBaseTest, DeleteByPrefixUser) { |
| EXPECT_TRUE(attestation_.DeleteKeysByPrefix(true, kTestUser, "prefix")); |
| } |
| |
| TEST_F(AttestationBaseTest, GetEKInfo) { |
| std::string info; |
| EXPECT_TRUE(attestation_.GetEKInfo(&info)); |
| EXPECT_TRUE(base::IsStringASCII(info)); |
| |
| // Simulate owner password not available. |
| EXPECT_CALL(tpm_, GetEndorsementCredential(_)) |
| .WillRepeatedly(Return(false)); |
| info.clear(); |
| EXPECT_FALSE(attestation_.GetEKInfo(&info)); |
| EXPECT_EQ(0, info.size()); |
| } |
| |
| TEST_F(AttestationBaseTest, FinalizeEndorsementData) { |
| // Simulate first login. |
| attestation_.PrepareForEnrollment(); |
| // Expect endorsement data to be available. |
| AttestationDatabase db = GetPersistentDatabase(); |
| EXPECT_TRUE(db.has_credentials() && |
| db.credentials().has_endorsement_public_key() && |
| db.credentials().has_endorsement_credential()); |
| |
| // Simulate second login. |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, brillo::SecureBlob() /* abe_data */, |
| false /* retain_endorsement_data */); |
| // Expect endorsement data to be no longer available. |
| db = GetPersistentDatabase(); |
| EXPECT_TRUE(db.has_credentials() && |
| !db.credentials().has_endorsement_public_key() && |
| !db.credentials().has_endorsement_credential()); |
| } |
| |
| TEST_F(AttestationBaseTest, RetainEndorsementData) { |
| // Simulate first login. |
| attestation_.PrepareForEnrollment(); |
| // Expect endorsement data to be available. |
| AttestationDatabase db = GetPersistentDatabase(); |
| EXPECT_TRUE(db.has_credentials() && |
| db.credentials().has_endorsement_public_key() && |
| db.credentials().has_endorsement_credential()); |
| |
| // Simulate second login. |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, brillo::SecureBlob() /* abe_data */, |
| true /* retain_endorsement_data */); |
| // Expect endorsement data to be still available. |
| db = GetPersistentDatabase(); |
| EXPECT_TRUE(db.has_credentials() && |
| db.credentials().has_endorsement_public_key() && |
| db.credentials().has_endorsement_credential()); |
| |
| attestation_.FinalizeEndorsementData(); |
| // Expect endorsement data to be still available. |
| db = GetPersistentDatabase(); |
| EXPECT_TRUE(db.has_credentials() && |
| db.credentials().has_endorsement_public_key() && |
| db.credentials().has_endorsement_credential()); |
| } |
| |
| TEST_F(AttestationBaseTest, MigrateAttestationDatabase) { |
| // Simulate first login. |
| attestation_.PrepareForEnrollment(); |
| |
| // Simulate an older database. |
| AttestationDatabase db = GetPersistentDatabase(); |
| db.mutable_credentials()->clear_encrypted_endorsement_credentials(); |
| db.mutable_credentials()->set_endorsement_credential("endorsement_cred"); |
| EncryptedData default_encrypted_endorsement_credential; |
| default_encrypted_endorsement_credential.set_wrapped_key("default_key"); |
| db.mutable_credentials() |
| ->mutable_default_encrypted_endorsement_credential() |
| ->CopyFrom(default_encrypted_endorsement_credential); |
| db.clear_identities(); |
| db.clear_identity_certificates(); |
| db.mutable_identity_binding()->set_identity_binding("identity_binding"); |
| db.mutable_identity_binding()->set_identity_public_key("identity_public_key"); |
| db.mutable_identity_key()->set_identity_credential("identity_cred"); |
| db.mutable_pcr0_quote()->set_quote("pcr0_quote"); |
| db.mutable_pcr1_quote()->set_quote("pcr1_quote"); |
| // Persist that older database. |
| attestation_.PersistDatabase(db); |
| |
| // Simulate second login. |
| Initialize(); |
| attestation_.PrepareForEnrollment(); |
| db = GetPersistentDatabase(); |
| |
| // The default encrypted endorsement credential has been migrated. |
| // The deprecated field has not been cleared so that older code can still |
| // use the database. |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| default_encrypted_endorsement_credential, |
| db.credentials().encrypted_endorsement_credentials().at( |
| Attestation::kDefaultPCA))); |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| default_encrypted_endorsement_credential, |
| db.credentials().default_encrypted_endorsement_credential())); |
| |
| // The default identity has data copied from the deprecated database fields. |
| // The deprecated fields have not been cleared so that older code can still |
| // use the database. |
| const AttestationDatabase::Identity& default_identity_data = |
| db.identities().Get(Attestation::kDefaultPCA); |
| EXPECT_EQ(IDENTITY_FEATURE_ENTERPRISE_ENROLLMENT_ID, |
| default_identity_data.features()); |
| ASSERT_EQ("identity_binding", |
| default_identity_data.identity_binding().identity_binding()); |
| ASSERT_EQ("identity_public_key", |
| default_identity_data.identity_binding().identity_public_key()); |
| ASSERT_EQ("identity_binding", db.identity_binding().identity_binding()); |
| ASSERT_EQ("identity_public_key", db.identity_binding().identity_public_key()); |
| ASSERT_EQ("pcr0_quote", default_identity_data.pcr_quotes().at(0).quote()); |
| EXPECT_EQ("pcr0_quote", db.pcr0_quote().quote()); |
| ASSERT_EQ("pcr1_quote", default_identity_data.pcr_quotes().at(1).quote()); |
| EXPECT_EQ("pcr1_quote", db.pcr1_quote().quote()); |
| |
| // No other identity has been created. |
| ASSERT_EQ(1, db.identities().size()); |
| |
| // The identity credential was migrated into an identity certificate. |
| // As a result, identity data does not use the identity credential. The |
| // deprecated field has not been cleared so that older code can still |
| // use the database. |
| ASSERT_FALSE(default_identity_data.identity_key().has_identity_credential()); |
| ASSERT_EQ("identity_cred", db.identity_key().identity_credential()); |
| VerifyPCAData(db, "identity_cred"); |
| } |
| |
| TEST_F(AttestationBaseTest, MigrateAttestationDatabaseWithCorruptedFields) { |
| // Simulate first login. |
| attestation_.PrepareForEnrollment(); |
| |
| // Simulate an older database. |
| AttestationDatabase db = GetPersistentDatabase(); |
| db.mutable_credentials()->clear_encrypted_endorsement_credentials(); |
| db.mutable_credentials()->set_endorsement_credential("endorsement_cred"); |
| EncryptedData default_encrypted_endorsement_credential; |
| default_encrypted_endorsement_credential.set_wrapped_key("default_key"); |
| db.mutable_credentials() |
| ->mutable_default_encrypted_endorsement_credential() |
| ->CopyFrom(default_encrypted_endorsement_credential); |
| db.clear_identities(); |
| db.clear_identity_certificates(); |
| db.mutable_identity_binding()->set_identity_binding("identity_binding"); |
| db.mutable_identity_binding()->set_identity_public_key("identity_public_key"); |
| db.mutable_identity_key()->set_identity_credential("identity_cred"); |
| // Note that we are missing a PCR0 quote. |
| db.mutable_pcr1_quote()->set_quote("pcr1_quote"); |
| // Persist that older database. |
| attestation_.PersistDatabase(db); |
| |
| // Simulate second login. |
| Initialize(); |
| attestation_.PrepareForEnrollment(); |
| db = GetPersistentDatabase(); |
| |
| // The default encrypted endorsement credential has been migrated. |
| // The deprecated field has not been cleared so that older code can still |
| // use the database. |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| default_encrypted_endorsement_credential, |
| db.credentials().encrypted_endorsement_credentials().at( |
| Attestation::kDefaultPCA))); |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| default_encrypted_endorsement_credential, |
| db.credentials().default_encrypted_endorsement_credential())); |
| |
| // The default identity could not be copied from the deprecated database. |
| // The deprecated fields have not been cleared so that older code can still |
| // use the database. |
| ASSERT_TRUE(db.identities().empty()); |
| ASSERT_EQ("identity_binding", db.identity_binding().identity_binding()); |
| ASSERT_EQ("identity_public_key", db.identity_binding().identity_public_key()); |
| EXPECT_EQ("pcr1_quote", db.pcr1_quote().quote()); |
| |
| // There is no identity certificate since there is no identity. |
| ASSERT_TRUE(db.identity_certificates().empty()); |
| } |
| |
| TEST_F(AttestationBaseTest, |
| MigrateAttestationDatabaseAllEndorsementCredentials) { |
| // Simulate first login. |
| attestation_.PrepareForEnrollment(); |
| |
| // Simulate an older database. |
| AttestationDatabase db = GetPersistentDatabase(); |
| db.mutable_credentials()->clear_encrypted_endorsement_credentials(); |
| db.mutable_credentials()->set_endorsement_credential("endorsement_cred"); |
| EncryptedData default_encrypted_endorsement_credential; |
| default_encrypted_endorsement_credential.set_wrapped_key("default_key"); |
| db.mutable_credentials() |
| ->mutable_default_encrypted_endorsement_credential() |
| ->CopyFrom(default_encrypted_endorsement_credential); |
| EncryptedData test_encrypted_endorsement_credential; |
| test_encrypted_endorsement_credential.set_wrapped_key("test_key"); |
| db.mutable_credentials() |
| ->mutable_test_encrypted_endorsement_credential() |
| ->CopyFrom(test_encrypted_endorsement_credential); |
| db.clear_identities(); |
| db.clear_identity_certificates(); |
| db.mutable_identity_binding()->set_identity_binding("identity_binding"); |
| db.mutable_identity_binding()->set_identity_public_key("identity_public_key"); |
| db.mutable_identity_key()->set_identity_credential("identity_cred"); |
| db.mutable_pcr0_quote()->set_quote("pcr0_quote"); |
| db.mutable_pcr1_quote()->set_quote("pcr1_quote"); |
| // Persist that older database. |
| attestation_.PersistDatabase(db); |
| |
| // Simulate second login. |
| Initialize(); |
| attestation_.PrepareForEnrollment(); |
| db = GetPersistentDatabase(); |
| |
| // The encrypted endorsement credentials have both been migrated. |
| // The deprecated fields have not been cleared so that older code can still |
| // use the database. |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| default_encrypted_endorsement_credential, |
| db.credentials().encrypted_endorsement_credentials().at( |
| Attestation::kDefaultPCA))); |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| default_encrypted_endorsement_credential, |
| db.credentials().default_encrypted_endorsement_credential())); |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| test_encrypted_endorsement_credential, |
| db.credentials().encrypted_endorsement_credentials().at( |
| Attestation::kTestPCA))); |
| ASSERT_TRUE(MessageDifferencer::Equals( |
| test_encrypted_endorsement_credential, |
| db.credentials().test_encrypted_endorsement_credential())); |
| } |
| |
| TEST_F(AttestationBaseTest, CertChainWithNoIntermediateCA) { |
| EXPECT_CALL(key_store_, Read(true, kTestUser, "test", _)) |
| .WillRepeatedly(DoAll( |
| SetArgPointee<3>(GetCertifiedKeyBlob("", false)), |
| Return(true))); |
| SecureBlob blob; |
| EXPECT_TRUE(attestation_.GetCertificateChain(true, kTestUser, "test", |
| &blob)); |
| EXPECT_TRUE(CompareBlob(blob, EncodeCertChain("stored_cert", ""))); |
| } |
| |
| TEST_F(AttestationBaseTest, IdentityResetRequest) { |
| SecureBlob blob; |
| EXPECT_TRUE(attestation_.GetIdentityResetRequest("token", &blob)); |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.GetIdentityResetRequest("token", &blob)); |
| } |
| |
| // A test class which does not initialize the Attestation instance. |
| class AttestationBaseTestNoInitialize : public AttestationBaseTest { |
| public: |
| void Initialize() override {} |
| }; |
| |
| TEST_F(AttestationBaseTestNoInitialize, AutoExtendPCR1) { |
| Blob default_pcr(GetDigestSize()); |
| EXPECT_CALL(tpm_, ReadPCR(1, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(default_pcr), Return(true))); |
| std::string fake_hwid = "hwid"; |
| Blob fake_hwid_expected_extension; |
| ASSERT_TRUE(base::HexStringToBytes( |
| "bc45e91a086497cd817cb3024ac5c0d733111a74378257b11991e1e435b7e71e", |
| &fake_hwid_expected_extension)); |
| fake_hwid_expected_extension.resize(GetDigestSize()); |
| EXPECT_CALL(tpm_, ExtendPCR(1, fake_hwid_expected_extension)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, GetHardwareID()).WillRepeatedly(Return(fake_hwid)); |
| // Now initialize and the mocks will complain if PCR1 is not extended. |
| AttestationBaseTest::Initialize(); |
| } |
| |
| TEST_F(AttestationBaseTestNoInitialize, AutoExtendPCR1NoHwID) { |
| Blob default_pcr(GetDigestSize()); |
| EXPECT_CALL(tpm_, ReadPCR(1, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(default_pcr), Return(true))); |
| std::string no_hwid = ""; |
| EXPECT_CALL(tpm_, ExtendPCR(_, _)).Times(0); |
| EXPECT_CALL(platform_, GetHardwareID()).WillRepeatedly(Return(no_hwid)); |
| // Now initialize and the mocks will complain if PCR1 is extended. |
| AttestationBaseTest::Initialize(); |
| } |
| |
| class AttestationEnrollmentIdTest : public AttestationBaseTest { |
| public: |
| void Initialize() override { |
| SecureBlob abe_data; |
| EXPECT_TRUE(base::HexStringToBytes(kABEData, &abe_data)); |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, abe_data, |
| false /* retain_endorsement_data */); |
| } |
| }; |
| |
| TEST_F(AttestationEnrollmentIdTest, GetEnterpriseEnrollmentId) { |
| brillo::SecureBlob pubek(GetValidEndorsementKey()); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKey(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(attestation_.GetEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ(kEID, |
| base::ToLowerASCII(base::HexEncode(blob.data(), blob.size()))); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, GetEnterpriseEnrollmentIdCached) { |
| brillo::SecureBlob pubek(GetValidEndorsementKey()); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKey(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| attestation_.PrepareForEnrollment(); |
| SecureBlob enroll_blob; |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(Attestation::kDefaultPCA, |
| &enroll_blob)); |
| attestation_.Enroll(Attestation::kDefaultPCA, GetEnrollBlob()); |
| // Change abe_data. |
| attestation_.Initialize( |
| &tpm_, &tpm_init_, &platform_, &crypto_, &install_attributes_, |
| brillo::SecureBlob("new_abe_data"), false /* retain_endorsement_data */); |
| // GetEnterpriseEnrollmentId should return a cached EID. |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(attestation_.GetEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ(kEID, |
| base::ToLowerASCII(base::HexEncode(blob.data(), blob.size()))); |
| // The EID should be different if recomputed since the abe_data has changed. |
| EXPECT_CALL(tpm_, GetEndorsementPublicKeyWithDelegate(_, _, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| EXPECT_TRUE(attestation_.ComputeEnterpriseEnrollmentId(&blob)); |
| EXPECT_NE(kEID, |
| base::ToLowerASCII(base::HexEncode(blob.data(), blob.size()))); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, ComputeEnterpriseEnrollmentId) { |
| brillo::SecureBlob pubek(GetValidEndorsementKey()); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKey(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(ComputeEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ(kEID, |
| base::ToLowerASCII(base::HexEncode(blob.data(), blob.size()))); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, ComputeEnterpriseEnrollmentIdHasDelegate) { |
| attestation_.PrepareForEnrollment(); |
| brillo::SecureBlob pubek(GetValidEndorsementKey()); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKeyWithDelegate(_, _, _)) |
| .WillOnce(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(ComputeEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ(kEID, |
| base::ToLowerASCII(base::HexEncode(blob.data(), blob.size()))); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, ComputeEnterpriseEnrollmentIdEmptyAbeData) { |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, brillo::SecureBlob(""), |
| false /* retain_endorsement_data */); |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(ComputeEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ("", base::HexEncode(blob.data(), blob.size())); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, ComputeEnterpriseEnrollmentIdEmptyEkm) { |
| brillo::SecureBlob pubek(""); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKey(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(ComputeEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ("", base::HexEncode(blob.data(), blob.size())); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, ComputeEnterpriseEnrollmentIdFailToGetEkm) { |
| brillo::SecureBlob pubek("ek"); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKey(_)) |
| .WillOnce(DoAll(SetArgPointee<0>(pubek), Return(false))); |
| brillo::SecureBlob blob; |
| EXPECT_FALSE(ComputeEnterpriseEnrollmentId(&blob)); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, CreateEnrollRequestCheckNonce) { |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.IsPreparedForEnrollment()); |
| brillo::SecureBlob enroll_request; |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(Attestation::kDefaultPCA, |
| &enroll_request)); |
| AttestationEnrollmentRequest request_pb; |
| ASSERT_TRUE(request_pb.ParseFromString(enroll_request.to_string())); |
| ASSERT_TRUE(request_pb.has_enterprise_enrollment_nonce()); |
| } |
| |
| TEST_F(AttestationEnrollmentIdTest, |
| CreateEnrollRequestNoIdentityFeaturesCheckNonce) { |
| attestation_.set_default_identity_features_for_test(NO_IDENTITY_FEATURES); |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.IsPreparedForEnrollment()); |
| brillo::SecureBlob enroll_request; |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(Attestation::kDefaultPCA, |
| &enroll_request)); |
| AttestationEnrollmentRequest request_pb; |
| ASSERT_TRUE(request_pb.ParseFromString(enroll_request.to_string())); |
| ASSERT_FALSE(request_pb.has_enterprise_enrollment_nonce()); |
| } |
| |
| class AttestationTest |
| : public AttestationBaseTest, |
| public testing::WithParamInterface<Attestation::PCAType> { |
| public: |
| void SetUp() override { |
| AttestationBaseTest::SetUp(); |
| pca_type_ = GetParam(); |
| } |
| |
| protected: |
| Attestation::PCAType pca_type_; |
| }; |
| |
| TEST_P(AttestationTest, FirstIdentityNotEnrolled) { |
| ASSERT_FALSE(attestation_.HasIdentityCertificate( |
| Attestation::kFirstIdentity, pca_type_)); |
| } |
| |
| TEST_P(AttestationTest, NullTpm) { |
| Crypto crypto(nullptr); |
| InstallAttributes install_attributes(nullptr); |
| Attestation without_tpm; |
| without_tpm.Initialize(NULL, NULL, NULL, &crypto, &install_attributes, |
| brillo::SecureBlob() /* abe_data */, |
| false /* retain_endorsement_data */); |
| without_tpm.PrepareForEnrollment(); |
| EXPECT_FALSE(without_tpm.IsPreparedForEnrollment()); |
| EXPECT_FALSE(without_tpm.Verify(false)); |
| EXPECT_FALSE(without_tpm.VerifyEK(false)); |
| EXPECT_FALSE(without_tpm.CreateEnrollRequest(pca_type_, NULL)); |
| EXPECT_FALSE(without_tpm.Enroll(pca_type_, SecureBlob())); |
| EXPECT_FALSE(without_tpm.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", nullptr)); |
| EXPECT_FALSE( |
| without_tpm.FinishCertRequest(SecureBlob(), false, "", "", nullptr)); |
| EXPECT_FALSE(without_tpm.SignEnterpriseChallenge( |
| false, "", "", "", SecureBlob(), false, SecureBlob(), nullptr)); |
| EXPECT_FALSE( |
| without_tpm.SignSimpleChallenge(false, "", "", SecureBlob(), nullptr)); |
| EXPECT_FALSE(without_tpm.GetEKInfo(nullptr)); |
| } |
| |
| TEST_P(AttestationTest, PCARequest_Enroll) { |
| std::string expected_url = GetPCAWebOrigin(pca_type_) + "/enroll"; |
| http_transport_->AddSimpleReplyHandler( |
| expected_url, brillo::http::request_type::kPost, |
| brillo::http::status_code::Ok, "response", |
| brillo::mime::application::kOctet_stream); |
| SecureBlob response; |
| EXPECT_TRUE(attestation_.SendPCARequestAndBlock( |
| pca_type_, Attestation::kEnroll, SecureBlob("request"), &response)); |
| EXPECT_TRUE(CompareBlob(response, "response")); |
| } |
| |
| TEST_P(AttestationTest, PCARequest_GetCertificate) { |
| std::string expected_url = GetPCAWebOrigin(pca_type_) + "/sign"; |
| http_transport_->AddSimpleReplyHandler( |
| expected_url, brillo::http::request_type::kPost, |
| brillo::http::status_code::Ok, "response", |
| brillo::mime::application::kOctet_stream); |
| SecureBlob response; |
| EXPECT_TRUE(attestation_.SendPCARequestAndBlock( |
| pca_type_, Attestation::kGetCertificate, SecureBlob("request"), |
| &response)); |
| EXPECT_TRUE(CompareBlob(response, "response")); |
| } |
| |
| TEST_P(AttestationTest, PCARequestWithServerError) { |
| std::string expected_url = GetPCAWebOrigin(pca_type_) + "/enroll"; |
| http_transport_->AddSimpleReplyHandler( |
| expected_url, brillo::http::request_type::kPost, |
| brillo::http::status_code::BadRequest, "response", |
| brillo::mime::application::kOctet_stream); |
| SecureBlob response; |
| EXPECT_FALSE(attestation_.SendPCARequestAndBlock( |
| pca_type_, Attestation::kEnroll, SecureBlob("request"), &response)); |
| EXPECT_FALSE(CompareBlob(response, "response")); |
| } |
| |
| struct AbeDataParam { |
| const char* data; |
| const char* enterprise_enrollment_nonce; |
| const char* enterprise_enrollment_id; |
| |
| AbeDataParam(const char* data, |
| const char* enterprise_enrollment_nonce, |
| const char* enterprise_enrollment_id) |
| : data(data), |
| enterprise_enrollment_nonce(enterprise_enrollment_nonce), |
| enterprise_enrollment_id(enterprise_enrollment_id) {} |
| }; |
| |
| struct AbeDataTestParam { |
| AbeDataParam abe_data; |
| Attestation::PCAType pca_type; |
| |
| AbeDataTestParam(AbeDataParam abe_data, Attestation::PCAType pca_type) |
| : abe_data(abe_data), pca_type(pca_type) {} |
| }; |
| |
| class AttestationWithAbeDataTest |
| : public AttestationBaseTest, |
| public testing::WithParamInterface<AbeDataTestParam> { |
| public: |
| void SetUp() override { |
| AttestationBaseTest::SetUp(); |
| pca_type_ = GetParam().pca_type; |
| } |
| |
| void Initialize() override { |
| SecureBlob abe_data; |
| const char* data = GetParam().abe_data.data; |
| if (data != NULL) { |
| if (!base::HexStringToBytes(data, &abe_data)) { |
| abe_data.clear(); |
| } |
| } |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, abe_data, |
| false /* retain_endorsement_data */); |
| } |
| |
| protected: |
| bool VerifyAttestationEnrollmentRequest(const SecureBlob& request) { |
| AttestationEnrollmentRequest request_pb; |
| if (!request_pb.ParseFromArray(request.data(), request.size())) { |
| return false; |
| } |
| const AbeDataParam& param = GetParam().abe_data; |
| if (param.data == nullptr) { |
| return !request_pb.has_enterprise_enrollment_nonce(); |
| } |
| SecureBlob expected; |
| EXPECT_TRUE( |
| base::HexStringToBytes(param.enterprise_enrollment_nonce, &expected)); |
| std::string nonce = request_pb.enterprise_enrollment_nonce(); |
| return expected == SecureBlob(nonce.begin(), nonce.end()); |
| } |
| |
| Attestation::PCAType pca_type_; |
| }; |
| |
| TEST_P(AttestationWithAbeDataTest, |
| PrepareForEnrollmentInstallAttributesNotReady) { |
| EXPECT_CALL(install_attributes_, is_first_install()) |
| .WillRepeatedly(Return(true)); |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.IsPreparedForEnrollment()); |
| AttestationDatabase db = GetPersistentDatabase(); |
| EXPECT_TRUE(db.has_credentials()); |
| // One identity has been created. |
| EXPECT_EQ(1, db.identities().size()); |
| const AttestationDatabase::Identity& identity_data = db.identities().Get(0); |
| EXPECT_TRUE(identity_data.has_identity_binding()); |
| EXPECT_TRUE(identity_data.has_identity_key()); |
| EXPECT_EQ(1, identity_data.pcr_quotes().count(0)); |
| EXPECT_EQ(1, identity_data.pcr_quotes().count(1)); |
| // Deprecated identity-related values have not been set. |
| EXPECT_FALSE(db.has_identity_binding()); |
| EXPECT_FALSE(db.has_identity_key()); |
| EXPECT_FALSE(db.has_pcr0_quote()); |
| EXPECT_FALSE(db.has_pcr1_quote()); |
| // We have a delegate to activate the AIK. |
| EXPECT_TRUE(db.has_delegate()); |
| // Verify Privacy CA-related data. |
| VerifyPCAData(db); |
| // These deprecated fields have not been set either. |
| EXPECT_TRUE(db.has_credentials()); |
| EXPECT_FALSE(db.credentials().has_default_encrypted_endorsement_credential()); |
| } |
| |
| TEST_P(AttestationWithAbeDataTest, Enroll) { |
| SecureBlob blob; |
| EXPECT_FALSE(attestation_.CreateEnrollRequest(pca_type_, &blob)); |
| attestation_.PrepareForEnrollment(); |
| EXPECT_FALSE(attestation_.HasIdentityCertificate(Attestation::kFirstIdentity, |
| pca_type_)); |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(pca_type_, &blob)); |
| EXPECT_TRUE(VerifyAttestationEnrollmentRequest(blob)); |
| EXPECT_TRUE(attestation_.Enroll(pca_type_, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.HasIdentityCertificate( |
| Attestation::kFirstIdentity, pca_type_)); |
| // Check that the database is only using the new fields. |
| AttestationDatabase db = GetPersistentDatabase(); |
| EXPECT_FALSE(db.mutable_identity_key()->has_identity_credential()); |
| } |
| |
| TEST_P(AttestationWithAbeDataTest, GetEnterpriseEnrollmentIdCached) { |
| brillo::SecureBlob pubek(GetValidEndorsementKey()); |
| EXPECT_CALL(tpm_, GetEndorsementPublicKey(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(pubek), Return(true))); |
| attestation_.PrepareForEnrollment(); |
| SecureBlob enroll_blob; |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(Attestation::kDefaultPCA, |
| &enroll_blob)); |
| attestation_.Enroll(Attestation::kDefaultPCA, GetEnrollBlob()); |
| // Change abe_data. |
| attestation_.Initialize(&tpm_, &tpm_init_, &platform_, &crypto_, |
| &install_attributes_, |
| brillo::SecureBlob("new_abe_data"), |
| false /* retain_endorsement_data */); |
| // GetEnterpriseEnrollmentId should return a cached EID. |
| brillo::SecureBlob blob; |
| EXPECT_TRUE(attestation_.GetEnterpriseEnrollmentId(&blob)); |
| EXPECT_EQ(GetParam().abe_data.enterprise_enrollment_id, |
| base::ToLowerASCII(base::HexEncode(blob.data(), blob.size()))); |
| } |
| |
| TEST_P(AttestationTest, CertRequest) { |
| EXPECT_CALL(tpm_, CreateCertifiedKey(_, _, _, _, _, _, _)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<3>(GetPKCS1PublicKey()), Return(true))); |
| SecureBlob blob; |
| attestation_.PrepareForEnrollment(); |
| EXPECT_FALSE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| EXPECT_TRUE(attestation_.Enroll(pca_type_, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| EXPECT_FALSE(attestation_.DoesKeyExist(false, kTestUser, "test")); |
| EXPECT_TRUE(attestation_.FinishCertRequest(GetCertRequestBlob(blob), false, |
| kTestUser, "test", &blob)); |
| EXPECT_TRUE(CompareBlob( |
| blob, EncodeCertChain("response_cert", "response_ca_cert"))); |
| EXPECT_TRUE(attestation_.DoesKeyExist(false, kTestUser, "test")); |
| EXPECT_TRUE( |
| attestation_.GetCertificateChain(false, kTestUser, "test", &blob)); |
| EXPECT_TRUE(CompareBlob( |
| blob, EncodeCertChain("response_cert", "response_ca_cert"))); |
| EXPECT_TRUE(attestation_.GetPublicKey(false, kTestUser, "test", &blob)); |
| EXPECT_TRUE(blob == GetX509PublicKey()); |
| } |
| |
| TEST_P(AttestationTest, CertRequestStorageFailure) { |
| EXPECT_CALL(key_store_, Write(true, kTestUser, "test", _)) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(key_store_, Read(true, kTestUser, "test", _)) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(DoAll(SetArgPointee<3>(GetCertifiedKeyBlob("", true)), |
| Return(true))); |
| SecureBlob blob; |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.Enroll(pca_type_, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| // Expect storage failure here. |
| EXPECT_FALSE(attestation_.FinishCertRequest(GetCertRequestBlob(blob), true, |
| kTestUser, "test", &blob)); |
| EXPECT_TRUE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| EXPECT_TRUE(attestation_.FinishCertRequest(GetCertRequestBlob(blob), true, |
| kTestUser, "test", &blob)); |
| EXPECT_TRUE(CompareBlob( |
| blob, EncodeCertChain("response_cert", "response_ca_cert"))); |
| // Expect storage failure here. |
| EXPECT_FALSE( |
| attestation_.GetCertificateChain(true, kTestUser, "test", &blob)); |
| EXPECT_TRUE(attestation_.DoesKeyExist(true, kTestUser, "test")); |
| EXPECT_TRUE( |
| attestation_.GetCertificateChain(true, kTestUser, "test", &blob)); |
| EXPECT_TRUE( |
| CompareBlob(blob, EncodeCertChain("stored_cert", "stored_ca_cert"))); |
| EXPECT_TRUE(attestation_.GetPublicKey(true, kTestUser, "test", &blob)); |
| EXPECT_TRUE(blob == GetX509PublicKey()); |
| } |
| |
| TEST_P(AttestationTest, SimpleChallenge) { |
| EXPECT_CALL(tpm_, Sign(_, _, _, _)) |
| .WillOnce(Return(false)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<3>(SecureBlob("signature")), Return(true))); |
| brillo::SecureBlob blob; |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(pca_type_, &blob)); |
| EXPECT_TRUE(attestation_.Enroll(pca_type_, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| EXPECT_TRUE(attestation_.FinishCertRequest(GetCertRequestBlob(blob), false, |
| kTestUser, "test", &blob)); |
| // Expect tpm_.Sign() failure the first attempt. |
| EXPECT_FALSE(attestation_.SignSimpleChallenge( |
| false, kTestUser, "test", SecureBlob("challenge"), &blob)); |
| EXPECT_TRUE(attestation_.SignSimpleChallenge( |
| false, kTestUser, "test", SecureBlob("challenge"), &blob)); |
| EXPECT_TRUE(VerifySimpleChallenge(blob, "challenge", "signature")); |
| } |
| |
| TEST_P(AttestationTest, EMKChallenge) { |
| EXPECT_CALL(tpm_, Sign(_, _, _, _)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<3>(SecureBlob("signature")), Return(true))); |
| brillo::SecureBlob blob; |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.CreateEnrollRequest(pca_type_, &blob)); |
| EXPECT_TRUE(attestation_.Enroll(pca_type_, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| EXPECT_TRUE(attestation_.FinishCertRequest(GetCertRequestBlob(blob), false, |
| kTestUser, "test", &blob)); |
| // Try all the VA servers in turn. We don't parameterize the test because |
| // not doing so allows us to verify that the attestation code uses the |
| // proper key when it has more than one. |
| for (int t = Attestation::kDefaultVA; t < Attestation::kMaxVAType; ++t) { |
| Attestation::VAType va_type = static_cast<Attestation::VAType>(t); |
| SecureBlob bad_prefix_challenge = |
| GetEnterpriseVaChallenge(va_type, "bad", true); |
| EXPECT_FALSE(attestation_.SignEnterpriseVaChallenge( |
| va_type, false, kTestUser, "test", "test_domain", |
| SecureBlob("test_id"), false, bad_prefix_challenge, &blob)); |
| SecureBlob challenge = |
| GetEnterpriseVaChallenge(va_type, "EnterpriseKeyChallenge", true); |
| EXPECT_TRUE(attestation_.SignEnterpriseVaChallenge( |
| va_type, false, kTestUser, "test", "test_domain", |
| SecureBlob("test_id"), false, challenge, &blob)); |
| EXPECT_TRUE(VerifyEnterpriseVaChallenge(va_type, blob, EMK, "test_domain", |
| "test_id", "", "signature")); |
| } |
| // Try the default VA server. |
| SecureBlob bad_prefix_challenge = |
| GetEnterpriseVaChallenge(Attestation::kDefaultVA, "bad", true); |
| EXPECT_FALSE(attestation_.SignEnterpriseChallenge( |
| false, kTestUser, "test", "test_domain", SecureBlob("test_id"), false, |
| bad_prefix_challenge, &blob)); |
| SecureBlob challenge = GetEnterpriseVaChallenge( |
| Attestation::kDefaultVA, "EnterpriseKeyChallenge", true); |
| EXPECT_TRUE(attestation_.SignEnterpriseChallenge( |
| false, kTestUser, "test", "test_domain", SecureBlob("test_id"), false, |
| challenge, &blob)); |
| EXPECT_TRUE(VerifyEnterpriseVaChallenge(Attestation::kDefaultVA, blob, EMK, |
| "test_domain", "test_id", "", |
| "signature")); |
| } |
| |
| TEST_P(AttestationTest, Payload) { |
| EXPECT_CALL(key_store_, Write(true, kTestUser, "test", |
| GetCertifiedKeyBlob("test_payload", true))) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(key_store_, Read(true, kTestUser, "test", _)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<3>(GetCertifiedKeyBlob("stored_payload", true)), |
| Return(true))); |
| EXPECT_CALL(tpm_, CreateCertifiedKey(_, _, _, _, _, _, _)) |
| .WillRepeatedly( |
| DoAll(SetArgPointee<3>(GetPKCS1PublicKey()), Return(true))); |
| SecureBlob blob; |
| attestation_.PrepareForEnrollment(); |
| EXPECT_TRUE(attestation_.Enroll(pca_type_, GetEnrollBlob())); |
| EXPECT_TRUE(attestation_.CreateCertRequest( |
| pca_type_, ENTERPRISE_USER_CERTIFICATE, "", "", &blob)); |
| EXPECT_TRUE(attestation_.FinishCertRequest(GetCertRequestBlob(blob), false, |
| kTestUser, "test", &blob)); |
| attestation_.GetKeyPayload(false, kTestUser, "test", &blob); |
| EXPECT_EQ(0, blob.size()); |
| attestation_.SetKeyPayload(false, kTestUser, "test", |
| SecureBlob("test_payload")); |
| attestation_.GetKeyPayload(false, kTestUser, "test", &blob); |
| EXPECT_TRUE(CompareBlob(blob, "test_payload")); |
| |
| attestation_.SetKeyPayload(true, kTestUser, "test", |
| SecureBlob("test_payload")); |
| attestation_.GetKeyPayload(true, kTestUser, "test", &blob); |
| EXPECT_TRUE(CompareBlob(blob, "stored_payload")); |
| } |
| |
| INSTANTIATE_TEST_CASE_P( |
| PcaType, AttestationTest, |
| ::testing::Values(Attestation::kDefaultPCA, Attestation::kTestPCA)); |
| |
| INSTANTIATE_TEST_CASE_P( |
| AbeData, AttestationWithAbeDataTest, |
| ::testing::Values(AbeDataTestParam(AbeDataParam(nullptr, nullptr, ""), |
| Attestation::kDefaultPCA), |
| AbeDataTestParam(AbeDataParam(nullptr, nullptr, ""), |
| Attestation::kTestPCA), |
| AbeDataTestParam(AbeDataParam(kABEData, kDEN, kEID), |
| Attestation::kDefaultPCA), |
| AbeDataTestParam(AbeDataParam(kABEData, kDEN, kEID), |
| Attestation::kTestPCA))); |
| |
| } // namespace cryptohome |