| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <attestation/proto_bindings/interface.pb.h> |
| #include <libarc-attestation/lib/interface.h> |
| #include <libarc-attestation/lib/manager.h> |
| #include <libhwsec/factory/mock_factory.h> |
| #include <libhwsec/frontend/arc_attestation/mock_frontend.h> |
| #include <libhwsec-foundation/error/testing_helper.h> |
| |
| // This needs to be after interface.pb.h due to protobuf dependency. |
| #include <attestation/dbus-proxy-mocks.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include <base/strings/string_number_conversions.h> |
| |
| using attestation::ACAType; |
| using attestation::AttestationStatus; |
| using attestation::CertificateProfile; |
| using attestation::GetCertificateReply; |
| using attestation::GetCertificateRequest; |
| using attestation::KeyType; |
| using attestation::SignReply; |
| using attestation::SignRequest; |
| using brillo::Blob; |
| using brillo::BlobFromString; |
| using brillo::BlobToString; |
| using hwsec::TPMError; |
| using hwsec::TPMRetryAction; |
| using hwsec_foundation::error::testing::ReturnError; |
| using hwsec_foundation::error::testing::ReturnValue; |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Ge; |
| using ::testing::Invoke; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SetArgPointee; |
| using ::testing::SetArgReferee; |
| |
| namespace arc_attestation { |
| |
| namespace { |
| |
| // gmock matcher for protobufs, allowing to check protobuf arguments in mocks. |
| MATCHER_P(ProtobufEquals, expected_message, "") { |
| std::string arg_dumped; |
| arg.SerializeToString(&arg_dumped); |
| std::string expected_message_dumped; |
| expected_message.SerializeToString(&expected_message_dumped); |
| return arg_dumped == expected_message_dumped; |
| } |
| |
| } // namespace |
| |
| class ArcAttestationThreadedTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| singleton_ = ArcAttestationManagerSingleton::CreateForTesting(); |
| std::unique_ptr<ArcAttestationManager> manager = |
| std::make_unique<ArcAttestationManager>(); |
| manager_ = manager.get(); |
| singleton_->SetManagerForTesting(std::move(manager)); |
| |
| manager_->Setup(); |
| |
| std::unique_ptr<org::chromium::AttestationProxyMock> attestation_proxy = |
| std::make_unique<org::chromium::AttestationProxyMock>(); |
| attestation_proxy_ = attestation_proxy.get(); |
| provisioner_ = manager_->GetProvisionerForTesting(); |
| provisioner_->SetAttestationProxyForTesting(std::move(attestation_proxy)); |
| version_attester_ = manager_->GetVersionAttesterForTesting(); |
| version_attester_->SetHwsecFactoryForTesting(&hwsec_factory_); |
| |
| default_hwsec_ = std::make_unique<hwsec::MockArcAttestationFrontend>(); |
| hwsec_ = default_hwsec_.get(); |
| ON_CALL(hwsec_factory_, GetArcAttestationFrontend()) |
| .WillByDefault( |
| Invoke([this]() -> std::unique_ptr<hwsec::ArcAttestationFrontend> { |
| CHECK(default_hwsec_); |
| return std::move(default_hwsec_); |
| })); |
| } |
| |
| void TearDown() override { |
| ArcAttestationManagerSingleton::DestroyForTesting(); |
| singleton_ = nullptr; |
| manager_ = nullptr; |
| attestation_proxy_ = nullptr; |
| provisioner_ = nullptr; |
| version_attester_ = nullptr; |
| hwsec_ = nullptr; |
| default_hwsec_.reset(); |
| } |
| |
| protected: |
| void ExpectGetCertificateSuccess(const GetCertificateReply& reply, |
| const GetCertificateRequest& request) { |
| EXPECT_CALL(*attestation_proxy_, |
| GetCertificate(ProtobufEquals(request), _, _, |
| Ge(kGetCertificateMinTimeout.InMilliseconds()))) |
| .WillOnce(DoAll(SetArgPointee<1>(reply), Return(true))); |
| } |
| |
| void ExpectSignSuccess(const SignReply& reply, const SignRequest& request) { |
| EXPECT_CALL(*attestation_proxy_, Sign(ProtobufEquals(request), _, _, |
| Ge(kSignMinTimeout.InMilliseconds()))) |
| .WillOnce(DoAll(SetArgPointee<1>(reply), Return(true))); |
| } |
| |
| void SetupSuccessfulProvision() { |
| // Setup the correct Android Device Key provision request expectation. |
| GetCertificateRequest aadk_request; |
| aadk_request.set_certificate_profile( |
| CertificateProfile::ARC_ATTESTATION_DEVICE_KEY_CERTIFICATE); |
| aadk_request.set_aca_type(ACAType::DEFAULT_ACA); |
| aadk_request.set_key_type(KeyType::KEY_TYPE_ECC); |
| aadk_request.set_key_label(kArcAttestationDeviceKeyLabel); |
| aadk_request.set_shall_trigger_enrollment(true); |
| |
| GetCertificateReply aadk_reply; |
| aadk_reply.set_status(AttestationStatus::STATUS_SUCCESS); |
| aadk_reply.set_certificate(std::string(kFakeCert1Part1) + |
| std::string(kFakeCert1Part2)); |
| aadk_reply.set_public_key(kFakePublicKey1); |
| aadk_reply.set_key_blob(kFakeKeyBlob1); |
| |
| ExpectGetCertificateSuccess(aadk_reply, aadk_request); |
| |
| // Setup the correct TPM Certifying Key provision request expectation. |
| GetCertificateRequest tck_request; |
| tck_request.set_certificate_profile( |
| CertificateProfile::ARC_TPM_CERTIFYING_KEY_CERTIFICATE); |
| tck_request.set_aca_type(ACAType::DEFAULT_ACA); |
| tck_request.set_key_type(KeyType::KEY_TYPE_ECC); |
| tck_request.set_key_label(kTpmCertifyingKeyLabel); |
| tck_request.set_shall_trigger_enrollment(true); |
| |
| GetCertificateReply tck_reply; |
| tck_reply.set_status(AttestationStatus::STATUS_SUCCESS); |
| tck_reply.set_certificate(std::string(kFakeCert2Part1) + |
| std::string(kFakeCert2Part2)); |
| tck_reply.set_public_key(kFakePublicKey2); |
| tck_reply.set_key_blob(kFakeKeyBlob2); |
| |
| ExpectGetCertificateSuccess(tck_reply, tck_request); |
| } |
| |
| ArcAttestationManagerSingleton* singleton_; |
| ArcAttestationManager* manager_; |
| org::chromium::AttestationProxyMock* attestation_proxy_; |
| Provisioner* provisioner_; |
| VersionAttester* version_attester_; |
| hwsec::MockFactory hwsec_factory_; |
| std::unique_ptr<hwsec::MockArcAttestationFrontend> default_hwsec_; |
| hwsec::MockArcAttestationFrontend* hwsec_; |
| |
| static constexpr base::TimeDelta kGetCertificateMinTimeout = |
| base::Seconds(60); |
| static constexpr base::TimeDelta kSignMinTimeout = base::Seconds(15); |
| |
| static constexpr char kArcAttestationDeviceKeyLabel[] = |
| "arc-attestation-device-key"; |
| static constexpr char kTpmCertifyingKeyLabel[] = "tpm-certifying-key"; |
| |
| static constexpr char kFakeCert1Part1[] = |
| "-----BEGIN CERTIFICATE-----\n" |
| "part1-of-fake-cert-1\n" |
| "-----END CERTIFICATE-----\n"; |
| static constexpr char kFakeCert1Part2[] = |
| "-----BEGIN CERTIFICATE-----\n" |
| "fake-cert-1-last-part\n" |
| "-----END CERTIFICATE-----\n"; |
| static constexpr char kFakePublicKey1[] = "this-is-a-fake-public-key"; |
| static constexpr char kFakeKeyBlob1[] = "this-is-a-fake-key-blob"; |
| |
| static constexpr char kFakeCert2Part1[] = |
| "-----BEGIN CERTIFICATE-----\n" |
| "ca-of-another-cert\n" |
| "-----END CERTIFICATE-----\n"; |
| static constexpr char kFakeCert2Part2[] = |
| "-----BEGIN CERTIFICATE-----\n" |
| "the-last-part-of-another-cert\n" |
| "-----END CERTIFICATE-----\n"; |
| static constexpr char kFakePublicKey2[] = "some-fake-ecc-public-key"; |
| static constexpr char kFakeKeyBlob2[] = "yet-another-fake-key-blob"; |
| |
| static constexpr char kFakeSignData1[] = "to-be-signed"; |
| static constexpr char kFakeSignature1[] = "already-signed"; |
| |
| static constexpr char kFakeChallenge[] = "can-you-answer-this?"; |
| static constexpr char kFakeLsbRelease[] = "SOME_VERSION=1.2.3"; |
| static constexpr char kFakeCmdline[] = "lsb_hash=AABBCC1234"; |
| static constexpr char kFakePcrQuote[] = "pcr-quoted"; |
| static constexpr char kFakePcrSignature[] = "pcr-signed"; |
| }; |
| |
| TEST_F(ArcAttestationThreadedTest, ProvisionValidityTest) { |
| SetupSuccessfulProvision(); |
| |
| // Make the API call for provisioning. |
| AndroidStatus result = ProvisionDkCert(true); |
| ASSERT_TRUE(result.is_ok()); |
| |
| // Test the resulting certificate. |
| std::vector<Blob> cert_out; |
| result = GetDkCertChain(cert_out); |
| ASSERT_TRUE(result.is_ok()); |
| |
| ASSERT_EQ(cert_out.size(), 2); |
| EXPECT_EQ(BlobToString(cert_out[0]), kFakeCert1Part1); |
| EXPECT_EQ(BlobToString(cert_out[1]), kFakeCert1Part2); |
| |
| // Test the signing. |
| SignRequest sign_request; |
| sign_request.set_key_label(kArcAttestationDeviceKeyLabel); |
| sign_request.set_data_to_sign(kFakeSignData1); |
| |
| SignReply sign_reply; |
| sign_reply.set_status(AttestationStatus::STATUS_SUCCESS); |
| sign_reply.set_signature(kFakeSignature1); |
| |
| ExpectSignSuccess(sign_reply, sign_request); |
| |
| Blob data_to_sign = BlobFromString(kFakeSignData1); |
| Blob result_signature; |
| result = SignWithP256Dk(data_to_sign, result_signature); |
| ASSERT_TRUE(result.is_ok()); |
| |
| EXPECT_EQ(BlobToString(result_signature), kFakeSignature1); |
| |
| // Test the version attestation. |
| arc_attestation::CrOSVersionAttestationBlob blob_to_return; |
| blob_to_return.set_version(arc_attestation::CrOSVersionAttestationVersion:: |
| CROS_BLOB_VERSION_TPM2_FORMAT_1); |
| blob_to_return.set_tpm_certifying_key_cert(std::string(kFakeCert2Part1) + |
| std::string(kFakeCert2Part2)); |
| blob_to_return.set_lsb_release_content(kFakeLsbRelease); |
| blob_to_return.set_kernel_cmdline_content(kFakeCmdline); |
| blob_to_return.set_kernel_cmdline_quote(kFakePcrQuote); |
| blob_to_return.set_kernel_cmdline_quote_signature(kFakePcrSignature); |
| EXPECT_CALL(*hwsec_, AttestVersion(BlobFromString(kFakeKeyBlob2), |
| std::string(kFakeCert2Part1) + |
| std::string(kFakeCert2Part2), |
| BlobFromString(kFakeChallenge))) |
| .WillOnce(Return(blob_to_return)); |
| |
| Blob quoted_blob; |
| result = QuoteCrOSBlob(BlobFromString(kFakeChallenge), quoted_blob); |
| ASSERT_TRUE(result.is_ok()); |
| arc_attestation::CrOSSpecificBlob quoted_data; |
| quoted_data.ParseFromString(BlobToString(quoted_blob)); |
| EXPECT_EQ(quoted_data.version_attestation().version(), |
| arc_attestation::CrOSVersionAttestationVersion:: |
| CROS_BLOB_VERSION_TPM2_FORMAT_1); |
| EXPECT_EQ(quoted_data.version_attestation().tpm_certifying_key_cert(), |
| std::string(kFakeCert2Part1) + std::string(kFakeCert2Part2)); |
| EXPECT_EQ(quoted_data.version_attestation().lsb_release_content(), |
| kFakeLsbRelease); |
| EXPECT_EQ(quoted_data.version_attestation().kernel_cmdline_content(), |
| kFakeCmdline); |
| EXPECT_EQ(quoted_data.version_attestation().kernel_cmdline_quote(), |
| kFakePcrQuote); |
| EXPECT_EQ(quoted_data.version_attestation().kernel_cmdline_quote_signature(), |
| kFakePcrSignature); |
| } |
| |
| TEST_F(ArcAttestationThreadedTest, ProvisionTCKFailed) { |
| // Provisioning the TPM Certifying Key should fail. |
| GetCertificateRequest tck_request; |
| tck_request.set_certificate_profile( |
| CertificateProfile::ARC_TPM_CERTIFYING_KEY_CERTIFICATE); |
| tck_request.set_aca_type(ACAType::DEFAULT_ACA); |
| tck_request.set_key_type(KeyType::KEY_TYPE_ECC); |
| tck_request.set_key_label(kTpmCertifyingKeyLabel); |
| tck_request.set_shall_trigger_enrollment(true); |
| |
| GetCertificateReply tck_reply; |
| tck_reply.set_status(AttestationStatus::STATUS_UNEXPECTED_DEVICE_ERROR); |
| |
| ExpectGetCertificateSuccess(tck_reply, tck_request); |
| |
| // Make the API call for provisioning, and it should fail. |
| AndroidStatus result = ProvisionDkCert(true); |
| EXPECT_FALSE(result.is_ok()); |
| } |
| |
| TEST_F(ArcAttestationThreadedTest, ProvisionDKFailed) { |
| // Setup the correct TPM Certifying Key provision request expectation. |
| GetCertificateRequest tck_request; |
| tck_request.set_certificate_profile( |
| CertificateProfile::ARC_TPM_CERTIFYING_KEY_CERTIFICATE); |
| tck_request.set_aca_type(ACAType::DEFAULT_ACA); |
| tck_request.set_key_type(KeyType::KEY_TYPE_ECC); |
| tck_request.set_key_label(kTpmCertifyingKeyLabel); |
| tck_request.set_shall_trigger_enrollment(true); |
| |
| GetCertificateReply tck_reply; |
| tck_reply.set_status(AttestationStatus::STATUS_SUCCESS); |
| tck_reply.set_certificate(std::string(kFakeCert2Part1) + |
| std::string(kFakeCert2Part2)); |
| tck_reply.set_public_key(kFakePublicKey2); |
| tck_reply.set_key_blob(kFakeKeyBlob2); |
| |
| ExpectGetCertificateSuccess(tck_reply, tck_request); |
| |
| // Provisioning the Device Key should fail. |
| GetCertificateRequest aadk_request; |
| aadk_request.set_certificate_profile( |
| CertificateProfile::ARC_ATTESTATION_DEVICE_KEY_CERTIFICATE); |
| aadk_request.set_aca_type(ACAType::DEFAULT_ACA); |
| aadk_request.set_key_type(KeyType::KEY_TYPE_ECC); |
| aadk_request.set_key_label(kArcAttestationDeviceKeyLabel); |
| aadk_request.set_shall_trigger_enrollment(true); |
| |
| GetCertificateReply aadk_reply; |
| aadk_reply.set_status(AttestationStatus::STATUS_UNEXPECTED_DEVICE_ERROR); |
| |
| ExpectGetCertificateSuccess(aadk_reply, aadk_request); |
| |
| // Make the API call for provisioning, and it should fail. |
| AndroidStatus result = ProvisionDkCert(true); |
| EXPECT_FALSE(result.is_ok()); |
| } |
| |
| TEST_F(ArcAttestationThreadedTest, NoCertWithoutProvision) { |
| std::vector<Blob> cert_out; |
| AndroidStatus result = GetDkCertChain(cert_out); |
| EXPECT_FALSE(result.is_ok()); |
| } |
| |
| TEST_F(ArcAttestationThreadedTest, SignFailed) { |
| // We need to be provisioned to test this. |
| SetupSuccessfulProvision(); |
| AndroidStatus result = ProvisionDkCert(true); |
| ASSERT_TRUE(result.is_ok()); |
| |
| // Setup signing failure. |
| SignRequest sign_request; |
| sign_request.set_key_label(kArcAttestationDeviceKeyLabel); |
| sign_request.set_data_to_sign(kFakeSignData1); |
| |
| SignReply sign_reply; |
| sign_reply.set_status(AttestationStatus::STATUS_UNEXPECTED_DEVICE_ERROR); |
| |
| ExpectSignSuccess(sign_reply, sign_request); |
| |
| // Call and it should fail. |
| Blob data_to_sign = BlobFromString(kFakeSignData1); |
| Blob result_signature; |
| result = SignWithP256Dk(data_to_sign, result_signature); |
| EXPECT_FALSE(result.is_ok()); |
| } |
| |
| TEST_F(ArcAttestationThreadedTest, AttestVersionFailed) { |
| // We need to be provisioned to test this. |
| SetupSuccessfulProvision(); |
| AndroidStatus result = ProvisionDkCert(true); |
| ASSERT_TRUE(result.is_ok()); |
| |
| // AttestVersion should fail. |
| EXPECT_CALL(*hwsec_, AttestVersion(BlobFromString(kFakeKeyBlob2), |
| std::string(kFakeCert2Part1) + |
| std::string(kFakeCert2Part2), |
| BlobFromString(kFakeChallenge))) |
| .WillOnce(ReturnError<TPMError>("fake", TPMRetryAction::kNoRetry)); |
| |
| // Call to QuoteCrOSBlob should fail. |
| Blob quoted_blob; |
| result = QuoteCrOSBlob(BlobFromString(kFakeChallenge), quoted_blob); |
| EXPECT_FALSE(result.is_ok()); |
| } |
| |
| } // namespace arc_attestation |