| // Copyright 2017 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 <string> |
| #include <tuple> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <chaps/attributes.h> |
| #include <chaps/chaps_proxy_mock.h> |
| #include <crypto/scoped_openssl_types.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <openssl/rsa.h> |
| #include <openssl/sha.h> |
| #include <openssl/x509.h> |
| |
| #include "cert/cert_provision.pb.h" |
| #include "cryptohome/cert/cert_provision_cryptohome.h" |
| #include "cryptohome/cert/cert_provision_keystore.h" |
| #include "cryptohome/cert/cert_provision_pca.h" |
| #include "cryptohome/cert/cert_provision_util.h" |
| #include "cryptohome/cert/mock_cert_provision_cryptohome.h" |
| #include "cryptohome/cert/mock_cert_provision_keystore.h" |
| #include "cryptohome/cert/mock_cert_provision_pca.h" |
| #include "cryptohome/cert_provision.h" |
| |
| using ::brillo::SecureBlob; |
| using ::google::protobuf::MessageLite; |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::ReturnRef; |
| using ::testing::SetArgPointee; |
| |
| namespace { |
| |
| // Some arbitrary certificate label used for testing. |
| const char kCertLabel[] = "test"; |
| const char kWrongLabel[] = "some wrong label"; |
| |
| const char kBegCertificate[] = "-----BEGIN CERTIFICATE-----"; |
| const char kEndCertificate[] = "-----END CERTIFICATE-----"; |
| |
| // Format for storing captured progress by the callback. |
| struct Progress { |
| cert_provision::Status status; |
| int progress; |
| std::string message; |
| }; |
| |
| // Matchers for the captured progress vector. |
| MATCHER_P(ResultsIn, status, "") { |
| return arg.back().status == status && arg.back().progress == 100; |
| } |
| MATCHER_P(ResultsNotIn, status, "") { |
| return arg.back().status != status && arg.back().progress == 100; |
| } |
| |
| } // namespace |
| |
| namespace cert_provision { |
| |
| // Test class for testing top-level functions. |
| class CertProvisionTest : public testing::Test { |
| public: |
| CertProvisionTest() { |
| CryptohomeProxy::subst_obj = &c_proxy_; |
| PCAProxy::subst_obj = &pca_proxy_; |
| KeyStore::subst_obj = &key_store_; |
| } |
| ~CertProvisionTest() { |
| CryptohomeProxy::subst_obj = nullptr; |
| PCAProxy::subst_obj = nullptr; |
| KeyStore::subst_obj = nullptr; |
| } |
| |
| void SetUp() { |
| ON_CALL(c_proxy_, Init()).WillByDefault(Return(OpResult())); |
| ON_CALL(c_proxy_, CheckIfPrepared(_)) |
| .WillByDefault(Invoke([this](bool* prepared) { |
| *prepared = prepared_; |
| return OpResult(); |
| })); |
| ON_CALL(c_proxy_, CheckIfEnrolled(_)) |
| .WillByDefault(Invoke([this](bool* enrolled) { |
| *enrolled = enrolled_; |
| return OpResult(); |
| })); |
| ON_CALL(c_proxy_, CreateEnrollRequest(_, _)) |
| .WillByDefault(Return(OpResult())); |
| ON_CALL(c_proxy_, ProcessEnrollResponse(_, _)) |
| .WillByDefault(Return(OpResult())); |
| ON_CALL(c_proxy_, CreateCertRequest(_, _, _)) |
| .WillByDefault(Return(OpResult())); |
| ON_CALL(c_proxy_, ProcessCertResponse(_, _, _)) |
| .WillByDefault(Return(OpResult())); |
| ON_CALL(c_proxy_, GetPublicKey(_, _)) |
| .WillByDefault( |
| Invoke([this](const std::string& /* label */, SecureBlob* key) { |
| *key = GetTestPublicKey(); |
| return OpResult(); |
| })); |
| ON_CALL(c_proxy_, Register(_)).WillByDefault(Return(OpResult())); |
| |
| ON_CALL(pca_proxy_, MakeRequest(_, _, _)).WillByDefault(Return(OpResult())); |
| |
| ON_CALL(key_store_, Init()).WillByDefault(Return(OpResult())); |
| ON_CALL(key_store_, Sign(_, _, _, _, _)).WillByDefault(Return(OpResult())); |
| ON_CALL(key_store_, ReadProvisionStatus(_, _)) |
| .WillByDefault( |
| Invoke([this](const std::string& /* label */, MessageLite* proto) { |
| proto->ParseFromString(provision_status_.SerializeAsString()); |
| return OpResult(); |
| })); |
| ON_CALL(key_store_, WriteProvisionStatus(_, _)) |
| .WillByDefault(Invoke( |
| [this](const std::string& /* label */, const MessageLite& proto) { |
| provision_status_.ParseFromString(proto.SerializeAsString()); |
| return OpResult(); |
| })); |
| ON_CALL(key_store_, DeleteKeys(_, _)).WillByDefault(Return(OpResult())); |
| } |
| |
| OpResult TestError(Status status) { return {status, "Test error"}; } |
| |
| // Resets the captured progress and returns the progress callback to |
| // be passed to ProvisionCertificate() for capturing new progress. |
| ProgressCallback GetProgressCallback() { |
| progress_.clear(); |
| return base::Bind(&CertProvisionTest::CaptureProgress, |
| base::Unretained(this)); |
| } |
| |
| // Successfully provisions and checks results/ |
| void Provision() { |
| EXPECT_EQ(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| ExpectProvisioned(true); |
| EXPECT_EQ(GetTestKeyID(), provision_status_.key_id()); |
| } |
| |
| // Verifies that a cert is provisioned/not provisioned. |
| // Does so by checking the stored ProvisionStatus and the result of |
| // GetCertificate(). |
| void ExpectProvisioned(bool provisioned) { |
| EXPECT_EQ(provisioned, provision_status_.provisioned()); |
| std::string certificate; |
| EXPECT_EQ(provisioned ? Status::Success : Status::NotProvisioned, |
| GetCertificate(kCertLabel, true, &certificate)); |
| } |
| |
| protected: |
| // Returns the current test RSA key. Generates a new random one, if empty. |
| RSA* rsa() { |
| if (!rsa_) { |
| crypto::ScopedBIGNUM e(BN_new()); |
| CHECK(e); |
| EXPECT_TRUE(BN_set_word(e.get(), 65537)); |
| rsa_.reset(RSA_new()); |
| CHECK(rsa_); |
| EXPECT_TRUE(RSA_generate_key_ex(rsa_.get(), 2048, e.get(), nullptr)); |
| } |
| return rsa_.get(); |
| } |
| // Resets the current test RSA key. Next time it is requested through |
| // GetTestPublicKey(), a new random key will be returned. |
| void ResetObtainedTestKey() { rsa_.reset(); } |
| // Returns the current test public key in X.509 format. |
| SecureBlob GetTestPublicKey() { |
| 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; |
| } |
| // Calculates the id for the current test public key. |
| std::string GetTestKeyID() { return GetKeyID(GetTestPublicKey()); } |
| |
| // Captures progress reported through callback. |
| std::vector<Progress> progress_; |
| |
| // IsPreparedForEnrollment and IsEnrolled status. |
| bool prepared_ = true; |
| bool enrolled_ = false; |
| |
| // Emulated storage for ProvisionStatus in keystore. |
| ProvisionStatus provision_status_; |
| |
| NiceMock<MockCryptohomeProxy> c_proxy_; |
| NiceMock<MockPCAProxy> pca_proxy_; |
| NiceMock<MockKeyStore> key_store_; |
| |
| private: |
| void CaptureProgress(Status status, |
| int progress, |
| const std::string& message) { |
| progress_.push_back({status, progress, message}); |
| } |
| |
| crypto::ScopedRSA rsa_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CertProvisionTest); |
| }; |
| |
| // Checks that provisioning succeeds after sending EnrollRequest if not |
| // enrolled yet. Checks that the reported progress is not decreasing and |
| // ends with 100%, and success is reported to the callback on all steps. |
| TEST_F(CertProvisionTest, ProvisionCertificateSuccessEnroll) { |
| ExpectProvisioned(false); |
| prepared_ = true; |
| enrolled_ = false; |
| EXPECT_CALL(c_proxy_, CreateEnrollRequest(_, _)); |
| EXPECT_CALL(c_proxy_, ProcessEnrollResponse(_, _)); |
| EXPECT_CALL(pca_proxy_, MakeRequest(_, _, _)).Times(2); |
| EXPECT_EQ(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| int last_progress = 0; |
| for (auto p : progress_) { |
| EXPECT_EQ(Status::Success, p.status); |
| EXPECT_LE(last_progress, p.progress); |
| last_progress = p.progress; |
| } |
| EXPECT_EQ(100, last_progress); |
| ExpectProvisioned(true); |
| } |
| |
| // Checks that provisioning succeeds w/o sending EnrollRequest if already |
| // enrolled. Checks that the reported progress is not decreasing and |
| // ends with 100%, and success is reported to the callback on all steps. |
| TEST_F(CertProvisionTest, ProvisionCertificateSuccessAlreadyEnrolled) { |
| ExpectProvisioned(false); |
| prepared_ = true; |
| enrolled_ = true; |
| EXPECT_CALL(c_proxy_, CreateEnrollRequest(_, _)).Times(0); |
| EXPECT_CALL(c_proxy_, ProcessEnrollResponse(_, _)).Times(0); |
| EXPECT_CALL(pca_proxy_, MakeRequest(_, _, _)).Times(1); |
| EXPECT_EQ(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| int last_progress = 0; |
| for (auto p : progress_) { |
| EXPECT_EQ(Status::Success, p.status); |
| EXPECT_LE(last_progress, p.progress); |
| last_progress = p.progress; |
| } |
| EXPECT_EQ(100, last_progress); |
| ExpectProvisioned(true); |
| } |
| |
| // Checks that if enrollment is not prepared, provisioning fails. |
| TEST_F(CertProvisionTest, ProvisionCertificateNotPrepared) { |
| ExpectProvisioned(false); |
| prepared_ = false; |
| EXPECT_EQ(Status::NotPrepared, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsIn(Status::NotPrepared)); |
| ExpectProvisioned(false); |
| } |
| |
| // Checks that a failure in EnrollRequest is handled properly. |
| TEST_F(CertProvisionTest, ProvisionCertificateFailureEnroll) { |
| ExpectProvisioned(false); |
| enrolled_ = false; |
| EXPECT_CALL(c_proxy_, ProcessEnrollResponse(_, _)) |
| .WillOnce(Return(TestError(Status::CryptohomeError))); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| EXPECT_EQ("Test error", progress_.back().message); |
| ExpectProvisioned(false); |
| } |
| |
| // Checks that a failure in CertRequest is handled properly. |
| TEST_F(CertProvisionTest, ProvisionCertificateFailureCert) { |
| ExpectProvisioned(false); |
| EXPECT_CALL(c_proxy_, ProcessCertResponse(kCertLabel, _, _)) |
| .WillOnce(Return(TestError(Status::CryptohomeError))); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| EXPECT_EQ("Test error", progress_.back().message); |
| ExpectProvisioned(false); |
| } |
| |
| // Checks that a failure to access PCA is handled properly. |
| TEST_F(CertProvisionTest, ProvisionCertificateFailurePCA) { |
| ExpectProvisioned(false); |
| EXPECT_CALL(pca_proxy_, MakeRequest(_, _, _)) |
| .WillOnce(Return(TestError(Status::ServerError))); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| EXPECT_EQ("Test error", progress_.back().message); |
| ExpectProvisioned(false); |
| } |
| |
| // Checks that a failure when registering the keys is handled properly. |
| TEST_F(CertProvisionTest, ProvisionCertificateFailureRegister) { |
| ExpectProvisioned(false); |
| EXPECT_CALL(c_proxy_, Register(kCertLabel)) |
| .WillOnce(Return(TestError(Status::CryptohomeError))); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| EXPECT_EQ("Test error", progress_.back().message); |
| ExpectProvisioned(false); |
| } |
| |
| // Checks that a failure when accessing the keystore is handled properly. |
| TEST_F(CertProvisionTest, ProvisionCertificateFailureKeyStore) { |
| ExpectProvisioned(false); |
| EXPECT_CALL(key_store_, Init()) |
| .WillOnce(Return(TestError(Status::KeyStoreError))) |
| .WillRepeatedly(Return(OpResult())); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| EXPECT_EQ("Test error", progress_.back().message); |
| ExpectProvisioned(false); |
| } |
| |
| // Checks that re-provisioning the certificate deletes the old keys and |
| // replaces the cert with the new one. |
| TEST_F(CertProvisionTest, ReProvisionCertificateSuccess) { |
| Provision(); |
| std::string old_key_id = provision_status_.key_id(); |
| ResetObtainedTestKey(); |
| |
| EXPECT_CALL(key_store_, DeleteKeys(old_key_id, kCertLabel)); |
| Provision(); |
| EXPECT_NE(old_key_id, provision_status_.key_id()); |
| } |
| |
| // Checks that registration failure upon re-provisioning keeps the old cert |
| // in place. |
| TEST_F(CertProvisionTest, ReProvisionCertificateFailureRegister) { |
| Provision(); |
| std::string old_key_id = provision_status_.key_id(); |
| ResetObtainedTestKey(); |
| |
| EXPECT_CALL(c_proxy_, Register(kCertLabel)) |
| .WillOnce(Return(TestError(Status::CryptohomeError))); |
| EXPECT_CALL(key_store_, DeleteKeys(_, _)).Times(0); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| ExpectProvisioned(true); |
| EXPECT_EQ(old_key_id, provision_status_.key_id()); |
| } |
| |
| // Checks that a failure when deleting the old keys is reported even |
| // though the new cert is stored. Checks that the new cert is usable, |
| // if the old keys were not deleted. |
| TEST_F(CertProvisionTest, ReProvisionCertificateFailureDeleteKeys) { |
| Provision(); |
| std::string old_key_id = provision_status_.key_id(); |
| ResetObtainedTestKey(); |
| |
| EXPECT_CALL(key_store_, DeleteKeys(old_key_id, kCertLabel)) |
| .WillOnce(Return(TestError(Status::KeyStoreError))); |
| EXPECT_NE(Status::Success, |
| ProvisionCertificate( |
| PCAType::kDefaultPCA, std::string(), kCertLabel, |
| CertificateProfile::CAST_CERTIFICATE, GetProgressCallback())); |
| EXPECT_THAT(progress_, ResultsNotIn(Status::Success)); |
| ExpectProvisioned(true); |
| EXPECT_NE(old_key_id, provision_status_.key_id()); |
| } |
| |
| // Checks that GetCertificate returns the provisioned certificate. |
| TEST_F(CertProvisionTest, GetCertificateSuccess) { |
| std::string cert[] = { |
| std::string(kBegCertificate) + "first" + kEndCertificate, |
| std::string(kBegCertificate) + "second" + kEndCertificate}; |
| std::string cert_chain = cert[0] + cert[1]; |
| SecureBlob cert_chain_blob(cert_chain); |
| EXPECT_CALL(c_proxy_, ProcessCertResponse(kCertLabel, _, _)) |
| .WillOnce(DoAll(SetArgPointee<2>(cert_chain_blob), Return(OpResult()))); |
| Provision(); |
| std::string result_cert; |
| EXPECT_EQ(Status::Success, GetCertificate(kCertLabel, true, &result_cert)); |
| EXPECT_EQ(cert_chain, result_cert); |
| EXPECT_EQ(Status::Success, GetCertificate(kCertLabel, false, &result_cert)); |
| EXPECT_EQ(cert[0], result_cert); |
| } |
| |
| // Checks that getting a certificate if it is not provisioned returns an |
| // error. |
| TEST_F(CertProvisionTest, GetCertificateNotProvisioned) { |
| ExpectProvisioned(false); |
| std::string certificate; |
| EXPECT_EQ(Status::NotProvisioned, |
| GetCertificate(kCertLabel, true, &certificate)); |
| EXPECT_TRUE(certificate.empty()); |
| } |
| |
| // Checks that signing succeeds and returns the requested data. |
| TEST_F(CertProvisionTest, SignSuccess) { |
| Provision(); |
| |
| std::string data = "some data"; |
| std::string keystore_sig("signature"); |
| |
| std::string sig; |
| EXPECT_CALL(key_store_, Sign(GetTestKeyID(), kCertLabel, SHA1_RSA_PKCS, _, _)) |
| .WillOnce(DoAll(SetArgPointee<4>(keystore_sig), Return(OpResult()))); |
| EXPECT_EQ(Status::Success, Sign(kCertLabel, SHA1_RSA_PKCS, data, &sig)); |
| EXPECT_EQ("signature", sig); |
| |
| sig.clear(); |
| EXPECT_CALL(key_store_, |
| Sign(GetTestKeyID(), kCertLabel, SHA256_RSA_PKCS, _, _)) |
| .WillOnce(DoAll(SetArgPointee<4>(keystore_sig), Return(OpResult()))); |
| EXPECT_EQ(Status::Success, Sign(kCertLabel, SHA256_RSA_PKCS, data, &sig)); |
| EXPECT_EQ("signature", sig); |
| } |
| |
| // Checks that signing fails if there is no provisioned certificate. |
| TEST_F(CertProvisionTest, SignNotProvisioned) { |
| ExpectProvisioned(false); |
| std::string data = "some data"; |
| std::string sig; |
| EXPECT_EQ(Status::NotProvisioned, |
| Sign(kCertLabel, SHA1_RSA_PKCS, data, &sig)); |
| EXPECT_TRUE(sig.empty()); |
| } |
| |
| // Checks that signing fails if keystore Sign operation fails. |
| TEST_F(CertProvisionTest, SignFailure) { |
| Provision(); |
| std::string data = "some data"; |
| std::string sig; |
| EXPECT_CALL(key_store_, Sign(GetTestKeyID(), kCertLabel, SHA1_RSA_PKCS, _, _)) |
| .WillOnce(Return(TestError(Status::KeyStoreError))); |
| EXPECT_NE(Status::Success, Sign(kCertLabel, SHA1_RSA_PKCS, data, &sig)); |
| EXPECT_TRUE(sig.empty()); |
| } |
| |
| // Checks that if a cert is provisioned for one label, it doesn't affect |
| // other labels. |
| TEST_F(CertProvisionTest, WrongLabel) { |
| Provision(); |
| EXPECT_CALL(key_store_, ReadProvisionStatus(kWrongLabel, _)) |
| .WillRepeatedly(Return(OpResult())); |
| EXPECT_CALL(key_store_, ReadProvisionStatus(kCertLabel, _)).Times(0); |
| std::string certificate; |
| EXPECT_EQ(Status::NotProvisioned, |
| GetCertificate(kWrongLabel, true, &certificate)); |
| EXPECT_TRUE(certificate.empty()); |
| std::string data = "some data"; |
| std::string sig; |
| EXPECT_EQ(Status::NotProvisioned, |
| Sign(kWrongLabel, SHA1_RSA_PKCS, data, &sig)); |
| EXPECT_TRUE(sig.empty()); |
| } |
| |
| } // namespace cert_provision |