| // Copyright 2021 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/cryptorecovery/recovery_crypto.h" |
| |
| #include "cryptohome/crypto/big_num_util.h" |
| #include "cryptohome/crypto/elliptic_curve.h" |
| #include "cryptohome/crypto/secure_blob_util.h" |
| #include "cryptohome/cryptorecovery/fake_recovery_mediator_crypto.h" |
| #include "cryptohome/cryptorecovery/recovery_crypto_hsm_cbor_serialization.h" |
| |
| #include <gtest/gtest.h> |
| |
| using brillo::SecureBlob; |
| |
| namespace cryptohome { |
| namespace cryptorecovery { |
| |
| namespace { |
| |
| constexpr EllipticCurve::CurveType kCurve = EllipticCurve::CurveType::kPrime256; |
| const char kFakeEnrollmentMetaData[] = "fake_enrollment_metadata"; |
| const char kFakeRequestMetaData[] = "fake_request_metadata"; |
| |
| SecureBlob GeneratePublicKey() { |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context) { |
| ADD_FAILURE() << "CreateBigNumContext failed"; |
| return SecureBlob(); |
| } |
| base::Optional<EllipticCurve> ec = |
| EllipticCurve::Create(kCurve, context.get()); |
| if (!ec) { |
| ADD_FAILURE() << "EllipticCurve::Create failed"; |
| return SecureBlob(); |
| } |
| crypto::ScopedEC_KEY key = ec->GenerateKey(context.get()); |
| if (!key) { |
| ADD_FAILURE() << "GenerateKey failed"; |
| return SecureBlob(); |
| } |
| SecureBlob result; |
| if (!ec->PointToSecureBlob(*EC_KEY_get0_public_key(key.get()), &result, |
| context.get())) { |
| ADD_FAILURE() << "PointToSecureBlob failed"; |
| return SecureBlob(); |
| } |
| return result; |
| } |
| |
| SecureBlob GenerateScalar() { |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context) { |
| ADD_FAILURE() << "CreateBigNumContext failed"; |
| return SecureBlob(); |
| } |
| base::Optional<EllipticCurve> ec = |
| EllipticCurve::Create(kCurve, context.get()); |
| if (!ec) { |
| ADD_FAILURE() << "EllipticCurve::Create failed"; |
| return SecureBlob(); |
| } |
| crypto::ScopedBIGNUM random_bn = ec->RandomNonZeroScalar(context.get()); |
| if (!random_bn) { |
| ADD_FAILURE() << "RandomNonZeroScalar failed"; |
| return SecureBlob(); |
| } |
| SecureBlob result; |
| if (!BigNumToSecureBlob(*random_bn, ec->ScalarSizeInBytes(), &result)) { |
| ADD_FAILURE() << "BigNumToSecureBlob failed"; |
| return SecureBlob(); |
| } |
| return result; |
| } |
| |
| } // namespace |
| |
| class RecoveryCryptoTest : public testing::Test { |
| public: |
| void SetUp() override { |
| ASSERT_TRUE(FakeRecoveryMediatorCrypto::GetFakeMediatorPublicKey( |
| &mediator_pub_key_)); |
| ASSERT_TRUE(FakeRecoveryMediatorCrypto::GetFakeMediatorPrivateKey( |
| &mediator_priv_key_)); |
| ASSERT_TRUE( |
| FakeRecoveryMediatorCrypto::GetFakeEpochPublicKey(&epoch_pub_key_)); |
| ASSERT_TRUE( |
| FakeRecoveryMediatorCrypto::GetFakeEpochPrivateKey(&epoch_priv_key_)); |
| |
| recovery_ = RecoveryCrypto::Create(); |
| ASSERT_TRUE(recovery_); |
| mediator_ = FakeRecoveryMediatorCrypto::Create(); |
| ASSERT_TRUE(mediator_); |
| } |
| |
| protected: |
| void GenerateSecretsAndMediate(SecureBlob* recovery_key, |
| SecureBlob* destination_share, |
| SecureBlob* channel_priv_key, |
| SecureBlob* ephemeral_pub_key, |
| SecureBlob* response_cbor) { |
| // Generates HSM payload that would be persisted on a chromebook. |
| HsmPayload hsm_payload; |
| SecureBlob channel_pub_key; |
| EXPECT_TRUE(recovery_->GenerateHsmPayload( |
| mediator_pub_key_, rsa_pub_key_, enrollment_metadata_, &hsm_payload, |
| destination_share, recovery_key, &channel_pub_key, channel_priv_key)); |
| |
| // Start recovery process. |
| SecureBlob recovery_request_cbor; |
| EXPECT_TRUE(recovery_->GenerateRecoveryRequest( |
| hsm_payload, request_metadata_, *channel_priv_key, channel_pub_key, |
| epoch_pub_key_, &recovery_request_cbor, ephemeral_pub_key)); |
| |
| // Simulates mediation performed by HSM. |
| EXPECT_TRUE(mediator_->MediateRequestPayload( |
| epoch_pub_key_, epoch_priv_key_, mediator_priv_key_, |
| recovery_request_cbor, response_cbor)); |
| } |
| |
| SecureBlob rsa_pub_key_; |
| SecureBlob enrollment_metadata_ = SecureBlob(kFakeEnrollmentMetaData); |
| SecureBlob request_metadata_ = SecureBlob(kFakeRequestMetaData); |
| |
| SecureBlob mediator_pub_key_; |
| SecureBlob mediator_priv_key_; |
| SecureBlob epoch_pub_key_; |
| SecureBlob epoch_priv_key_; |
| std::unique_ptr<RecoveryCrypto> recovery_; |
| std::unique_ptr<FakeRecoveryMediatorCrypto> mediator_; |
| }; |
| |
| TEST_F(RecoveryCryptoTest, RecoveryTestSuccess) { |
| // Generates HSM payload that would be persisted on a chromebook. |
| HsmPayload hsm_payload; |
| SecureBlob destination_share, recovery_key, channel_pub_key, channel_priv_key; |
| EXPECT_TRUE(recovery_->GenerateHsmPayload( |
| mediator_pub_key_, rsa_pub_key_, enrollment_metadata_, &hsm_payload, |
| &destination_share, &recovery_key, &channel_pub_key, &channel_priv_key)); |
| |
| // Start recovery process. |
| SecureBlob ephemeral_pub_key, recovery_request_cbor; |
| EXPECT_TRUE(recovery_->GenerateRecoveryRequest( |
| hsm_payload, request_metadata_, channel_priv_key, channel_pub_key, |
| epoch_pub_key_, &recovery_request_cbor, &ephemeral_pub_key)); |
| |
| // Simulates mediation performed by HSM. |
| SecureBlob response_cbor; |
| EXPECT_TRUE(mediator_->MediateRequestPayload( |
| epoch_pub_key_, epoch_priv_key_, mediator_priv_key_, |
| recovery_request_cbor, &response_cbor)); |
| |
| HsmResponsePlainText response_plain_text; |
| EXPECT_TRUE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| |
| SecureBlob mediated_recovery_key; |
| EXPECT_TRUE(recovery_->RecoverDestination( |
| response_plain_text.dealer_pub_key, destination_share, ephemeral_pub_key, |
| response_plain_text.mediated_point, &mediated_recovery_key)); |
| |
| // Checks that cryptohome encryption key generated at enrollment and the |
| // one obtained after migration are identical. |
| EXPECT_EQ(recovery_key, mediated_recovery_key); |
| } |
| |
| TEST_F(RecoveryCryptoTest, GenerateHsmPayloadInvalidMediatorKey) { |
| HsmPayload hsm_payload; |
| SecureBlob destination_share, recovery_key, channel_pub_key, channel_priv_key; |
| EXPECT_FALSE(recovery_->GenerateHsmPayload( |
| /*mediator_pub_key=*/SecureBlob("not a key"), rsa_pub_key_, |
| enrollment_metadata_, &hsm_payload, &destination_share, &recovery_key, |
| &channel_pub_key, &channel_priv_key)); |
| } |
| |
| TEST_F(RecoveryCryptoTest, MediateWithInvalidEpochPublicKey) { |
| // Generates HSM payload that would be persisted on a chromebook. |
| HsmPayload hsm_payload; |
| SecureBlob destination_share, recovery_key, channel_pub_key, channel_priv_key; |
| EXPECT_TRUE(recovery_->GenerateHsmPayload( |
| mediator_pub_key_, rsa_pub_key_, enrollment_metadata_, &hsm_payload, |
| &destination_share, &recovery_key, &channel_pub_key, &channel_priv_key)); |
| |
| // Start recovery process. |
| SecureBlob ephemeral_pub_key, recovery_request_cbor; |
| EXPECT_TRUE(recovery_->GenerateRecoveryRequest( |
| hsm_payload, request_metadata_, channel_priv_key, channel_pub_key, |
| epoch_pub_key_, &recovery_request_cbor, &ephemeral_pub_key)); |
| |
| SecureBlob random_key = GeneratePublicKey(); |
| |
| // Simulates mediation performed by HSM. |
| SecureBlob response_cbor; |
| EXPECT_TRUE(mediator_->MediateRequestPayload( |
| /*epoch_pub_key=*/random_key, epoch_priv_key_, mediator_priv_key_, |
| recovery_request_cbor, &response_cbor)); |
| |
| // `DecryptResponsePayload` fails if invalid epoch value was used for |
| // `MediateRequestPayload`. |
| HsmResponsePlainText response_plain_text; |
| EXPECT_FALSE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| } |
| |
| TEST_F(RecoveryCryptoTest, RecoverDestinationInvalidDealerPublicKey) { |
| SecureBlob recovery_key, destination_share, channel_priv_key, |
| ephemeral_pub_key, response_cbor; |
| GenerateSecretsAndMediate(&recovery_key, &destination_share, |
| &channel_priv_key, &ephemeral_pub_key, |
| &response_cbor); |
| |
| HsmResponsePlainText response_plain_text; |
| EXPECT_TRUE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| |
| SecureBlob random_key = GeneratePublicKey(); |
| |
| SecureBlob mediated_recovery_key; |
| EXPECT_TRUE(recovery_->RecoverDestination( |
| /*dealer_pub_key=*/random_key, destination_share, ephemeral_pub_key, |
| response_plain_text.mediated_point, &mediated_recovery_key)); |
| |
| // `mediated_recovery_key` is different from `recovery_key` when |
| // `dealer_pub_key` is set to a wrong value. |
| EXPECT_NE(recovery_key, mediated_recovery_key); |
| } |
| |
| TEST_F(RecoveryCryptoTest, RecoverDestinationInvalidDestinationShare) { |
| SecureBlob recovery_key, destination_share, channel_priv_key, |
| ephemeral_pub_key, response_cbor; |
| GenerateSecretsAndMediate(&recovery_key, &destination_share, |
| &channel_priv_key, &ephemeral_pub_key, |
| &response_cbor); |
| |
| HsmResponsePlainText response_plain_text; |
| EXPECT_TRUE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| |
| SecureBlob random_scalar = GenerateScalar(); |
| |
| SecureBlob mediated_recovery_key; |
| EXPECT_TRUE(recovery_->RecoverDestination( |
| response_plain_text.dealer_pub_key, /*destination_share=*/random_scalar, |
| ephemeral_pub_key, response_plain_text.mediated_point, |
| &mediated_recovery_key)); |
| |
| // `mediated_recovery_key` is different from `recovery_key` when |
| // `destination_share` is set to a wrong value. |
| EXPECT_NE(recovery_key, mediated_recovery_key); |
| } |
| |
| TEST_F(RecoveryCryptoTest, RecoverDestinationInvalidEphemeralKey) { |
| SecureBlob recovery_key, destination_share, channel_priv_key, |
| ephemeral_pub_key, response_cbor; |
| GenerateSecretsAndMediate(&recovery_key, &destination_share, |
| &channel_priv_key, &ephemeral_pub_key, |
| &response_cbor); |
| |
| HsmResponsePlainText response_plain_text; |
| EXPECT_TRUE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| |
| SecureBlob random_key = GeneratePublicKey(); |
| |
| SecureBlob mediated_recovery_key; |
| EXPECT_TRUE(recovery_->RecoverDestination( |
| response_plain_text.dealer_pub_key, destination_share, |
| /*ephemeral_pub_key=*/random_key, response_plain_text.mediated_point, |
| &mediated_recovery_key)); |
| |
| // `mediated_recovery_key` is different from `recovery_key` when |
| // `ephemeral_pub_key` is set to a wrong value. |
| EXPECT_NE(recovery_key, mediated_recovery_key); |
| } |
| |
| TEST_F(RecoveryCryptoTest, RecoverDestinationInvalidMediatedPointValue) { |
| SecureBlob recovery_key, destination_share, channel_priv_key, |
| ephemeral_pub_key, response_cbor; |
| GenerateSecretsAndMediate(&recovery_key, &destination_share, |
| &channel_priv_key, &ephemeral_pub_key, |
| &response_cbor); |
| |
| HsmResponsePlainText response_plain_text; |
| EXPECT_TRUE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| |
| SecureBlob random_key = GeneratePublicKey(); |
| |
| SecureBlob mediated_recovery_key; |
| EXPECT_TRUE(recovery_->RecoverDestination( |
| response_plain_text.dealer_pub_key, destination_share, ephemeral_pub_key, |
| /*mediated_point=*/random_key, &mediated_recovery_key)); |
| |
| // `mediated_recovery_key` is different from `recovery_key` when |
| // `mediated_point` is set to a wrong point. |
| EXPECT_NE(recovery_key, mediated_recovery_key); |
| } |
| |
| TEST_F(RecoveryCryptoTest, RecoverDestinationInvalidMediatedPoint) { |
| SecureBlob recovery_key, destination_share, channel_priv_key, |
| ephemeral_pub_key, response_cbor; |
| GenerateSecretsAndMediate(&recovery_key, &destination_share, |
| &channel_priv_key, &ephemeral_pub_key, |
| &response_cbor); |
| |
| HsmResponsePlainText response_plain_text; |
| EXPECT_TRUE(recovery_->DecryptResponsePayload( |
| channel_priv_key, epoch_pub_key_, response_cbor, &response_plain_text)); |
| |
| // `RecoverDestination` fails when `mediated_point` is not a point. |
| SecureBlob mediated_recovery_key; |
| EXPECT_FALSE(recovery_->RecoverDestination( |
| response_plain_text.dealer_pub_key, destination_share, ephemeral_pub_key, |
| /*mediated_point=*/SecureBlob("not a point"), &mediated_recovery_key)); |
| } |
| |
| } // namespace cryptorecovery |
| } // namespace cryptohome |