blob: 4d8ffee3998fbc5c3b885eb78b7e87298930df33 [file] [log] [blame]
// Copyright 2022 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 <memory>
#include <optional>
#include <string>
#include <brillo/secure_blob.h>
#include <crypto/scoped_openssl_types.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libhwsec-foundation/crypto/big_num_util.h>
#include <libhwsec-foundation/crypto/elliptic_curve.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <libhwsec-foundation/error/testing_helper.h>
#include <openssl/ec.h>
#include "cryptohome/cryptorecovery/recovery_crypto_tpm1_backend_impl.h"
#include "cryptohome/mock_tpm.h"
#include "cryptohome/tpm_impl.h"
using hwsec_foundation::BigNumToSecureBlob;
using hwsec_foundation::CreateBigNumContext;
using hwsec_foundation::EllipticCurve;
using hwsec_foundation::ScopedBN_CTX;
using hwsec_foundation::error::testing::ReturnError;
using testing::_;
using testing::DoAll;
using testing::Exactly;
using testing::NiceMock;
using testing::Return;
using testing::SetArgPointee;
namespace cryptohome {
namespace cryptorecovery {
namespace {
// Size of the auth_value blob to be randomly generated.
//
// The choice of this constant is dictated by the desire to provide sufficient
// amount of entropy as the authorization secret for the TPM_Seal command (but
// with taking into account that this authorization value is hashed by SHA-1
// by Trousers anyway).
constexpr int kAuthValueSizeBytes = 32;
} // namespace
class RecoveryCryptoTpm1BackendTest : public testing::Test {
public:
RecoveryCryptoTpm1BackendTest() {}
~RecoveryCryptoTpm1BackendTest() = default;
void SetUp() override {
context_ = CreateBigNumContext();
ASSERT_TRUE(context_);
ec_256_ = EllipticCurve::Create(EllipticCurve::CurveType::kPrime256,
context_.get());
ASSERT_TRUE(ec_256_);
ec_521_ = EllipticCurve::Create(EllipticCurve::CurveType::kPrime521,
context_.get());
ASSERT_TRUE(ec_521_);
}
protected:
NiceMock<MockTpm> tpm_;
cryptorecovery::RecoveryCryptoTpm1BackendImpl recovery_crypto_tpm1_backend_{
&tpm_};
ScopedBN_CTX context_;
std::optional<EllipticCurve> ec_256_;
std::optional<EllipticCurve> ec_521_;
};
TEST_F(RecoveryCryptoTpm1BackendTest, GenerateKeyAuthValue) {
EXPECT_EQ(recovery_crypto_tpm1_backend_.GenerateKeyAuthValue().size(),
kAuthValueSizeBytes);
}
TEST_F(RecoveryCryptoTpm1BackendTest, EncryptEccPrivateKeySuccess) {
// Set up inputs to the test.
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const brillo::SecureBlob auth_value(kAuthValueSizeBytes, 'a');
// Set up mock expectations
const brillo::SecureBlob expected_encrypted_own_priv_key(256, 'b');
EXPECT_CALL(tpm_, SealToPcrWithAuthorization(_, auth_value, _, _))
.WillOnce(DoAll(SetArgPointee<3>(expected_encrypted_own_priv_key),
ReturnError<hwsec::TPMErrorBase>()));
brillo::SecureBlob encrypted_privated_key;
EXPECT_TRUE(recovery_crypto_tpm1_backend_.EncryptEccPrivateKey(
ec_256_.value(), own_key_pair, auth_value, &encrypted_privated_key));
EXPECT_EQ(encrypted_privated_key.to_string(),
expected_encrypted_own_priv_key.to_string());
}
TEST_F(RecoveryCryptoTpm1BackendTest,
EncryptEccPrivateKeyWithInvalidOwnKeyPair) {
// Set up inputs to the test.
crypto::ScopedEC_KEY wrong_curve_key_pair =
ec_521_->GenerateKey(context_.get());
ASSERT_TRUE(wrong_curve_key_pair);
const brillo::SecureBlob auth_value(kAuthValueSizeBytes, 'a');
// Set up mock expectations
EXPECT_CALL(tpm_, SealToPcrWithAuthorization(_, auth_value, _, _))
.Times(Exactly(0));
brillo::SecureBlob encrypted_privated_key;
// the input key pair is not on the elliptic curve 256
EXPECT_FALSE(recovery_crypto_tpm1_backend_.EncryptEccPrivateKey(
ec_256_.value(), wrong_curve_key_pair, auth_value,
&encrypted_privated_key));
EXPECT_EQ(encrypted_privated_key.to_string(), "");
}
TEST_F(RecoveryCryptoTpm1BackendTest,
EncryptEccPrivateKeyWithInvalidAuthValue) {
// Set up inputs to the test.
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const BIGNUM* own_priv_key_bn = EC_KEY_get0_private_key(own_key_pair.get());
ASSERT_TRUE(own_priv_key_bn);
brillo::SecureBlob expected_privated_key;
ASSERT_TRUE(BigNumToSecureBlob(*own_priv_key_bn, ec_256_->ScalarSizeInBytes(),
&expected_privated_key));
// Set up mock expectations
EXPECT_CALL(tpm_, SealToPcrWithAuthorization(_, _, _, _)).Times(Exactly(0));
brillo::SecureBlob encrypted_privated_key;
EXPECT_TRUE(recovery_crypto_tpm1_backend_.EncryptEccPrivateKey(
ec_256_.value(), own_key_pair,
/*auth_value=*/std::nullopt, &encrypted_privated_key));
EXPECT_EQ(encrypted_privated_key.to_string(),
expected_privated_key.to_string());
}
TEST_F(RecoveryCryptoTpm1BackendTest,
GenerateDiffieHellmanSharedSecretSuccess) {
// Set up inputs to the test.
const brillo::SecureBlob auth_value(kAuthValueSizeBytes, 'a');
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const BIGNUM* own_priv_key = EC_KEY_get0_private_key(own_key_pair.get());
ASSERT_TRUE(own_priv_key);
brillo::SecureBlob own_priv_point_blob;
ASSERT_TRUE(BigNumToSecureBlob(*own_priv_key,
ec_256_->AffineCoordinateSizeInBytes(),
&own_priv_point_blob));
crypto::ScopedEC_KEY others_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(others_key_pair);
const EC_POINT* others_pub_key =
EC_KEY_get0_public_key(others_key_pair.get());
ASSERT_TRUE(others_pub_key);
// Calculate expected shared point
crypto::ScopedEC_POINT expected_shared_secret = ComputeEcdhSharedSecretPoint(
ec_256_.value(), *others_pub_key, *own_priv_key);
ASSERT_TRUE(expected_shared_secret);
// Set up mock expectations
EXPECT_CALL(tpm_, UnsealWithAuthorization(_, _, auth_value, _, _))
.WillOnce(DoAll(SetArgPointee<4>(own_priv_point_blob),
ReturnError<hwsec::TPMErrorBase>()));
crypto::ScopedEC_POINT shared_secret_point =
recovery_crypto_tpm1_backend_.GenerateDiffieHellmanSharedSecret(
ec_256_.value(), own_priv_point_blob, auth_value, *others_pub_key);
EXPECT_NE(shared_secret_point, nullptr);
EXPECT_TRUE(ec_256_->AreEqual(*shared_secret_point, *expected_shared_secret,
context_.get()));
}
TEST_F(RecoveryCryptoTpm1BackendTest,
GenerateDiffieHellmanSharedSecretWithInvalidOthersPublicPoint) {
// Set up inputs to the test.
const brillo::SecureBlob auth_value(kAuthValueSizeBytes, 'a');
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const BIGNUM* own_priv_key = EC_KEY_get0_private_key(own_key_pair.get());
ASSERT_TRUE(own_priv_key);
brillo::SecureBlob own_priv_point_blob;
ASSERT_TRUE(BigNumToSecureBlob(*own_priv_key,
ec_256_->AffineCoordinateSizeInBytes(),
&own_priv_point_blob));
// other's public point is from ec_521 and is not valid for ec_256
crypto::ScopedEC_KEY others_key_pair = ec_521_->GenerateKey(context_.get());
ASSERT_TRUE(others_key_pair);
const EC_POINT* others_pub_key =
EC_KEY_get0_public_key(others_key_pair.get());
ASSERT_TRUE(others_pub_key);
// Set up mock expectations
EXPECT_CALL(tpm_, UnsealWithAuthorization(_, _, auth_value, _, _))
.WillOnce(DoAll(SetArgPointee<4>(own_priv_point_blob),
ReturnError<hwsec::TPMErrorBase>()));
crypto::ScopedEC_POINT shared_secret_point =
recovery_crypto_tpm1_backend_.GenerateDiffieHellmanSharedSecret(
ec_256_.value(), own_priv_point_blob, auth_value, *others_pub_key);
EXPECT_EQ(nullptr, shared_secret_point);
}
TEST_F(RecoveryCryptoTpm1BackendTest,
GenerateDiffieHellmanSharedSecretWithErrorNoRetry) {
// Set up inputs to the test.
const brillo::SecureBlob auth_value(kAuthValueSizeBytes, 'a');
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const BIGNUM* own_priv_key = EC_KEY_get0_private_key(own_key_pair.get());
ASSERT_TRUE(own_priv_key);
brillo::SecureBlob own_priv_point_blob;
ASSERT_TRUE(BigNumToSecureBlob(*own_priv_key,
ec_256_->AffineCoordinateSizeInBytes(),
&own_priv_point_blob));
crypto::ScopedEC_KEY others_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(others_key_pair);
const EC_POINT* others_pub_key =
EC_KEY_get0_public_key(others_key_pair.get());
ASSERT_TRUE(others_pub_key);
// Set up mock expectations
EXPECT_CALL(tpm_, UnsealWithAuthorization(_, _, auth_value, _, _))
.WillOnce(ReturnError<hwsec::TPMError>("fake",
hwsec::TPMRetryAction::kNoRetry));
crypto::ScopedEC_POINT shared_secret_point =
recovery_crypto_tpm1_backend_.GenerateDiffieHellmanSharedSecret(
ec_256_.value(), own_priv_point_blob, auth_value, *others_pub_key);
EXPECT_EQ(nullptr, shared_secret_point);
}
TEST_F(RecoveryCryptoTpm1BackendTest,
GenerateDiffieHellmanSharedSecretWithCommunicationErrorRetry) {
// Set up inputs to the test.
const brillo::SecureBlob auth_value(kAuthValueSizeBytes, 'a');
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const BIGNUM* own_priv_key = EC_KEY_get0_private_key(own_key_pair.get());
ASSERT_TRUE(own_priv_key);
brillo::SecureBlob own_priv_point_blob;
ASSERT_TRUE(BigNumToSecureBlob(*own_priv_key,
ec_256_->AffineCoordinateSizeInBytes(),
&own_priv_point_blob));
crypto::ScopedEC_KEY others_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(others_key_pair);
const EC_POINT* others_pub_key =
EC_KEY_get0_public_key(others_key_pair.get());
ASSERT_TRUE(others_pub_key);
// Set up mock expectations
EXPECT_CALL(tpm_, UnsealWithAuthorization(_, _, auth_value, _, _))
.WillOnce(ReturnError<hwsec::TPMError>(
"fake", hwsec::TPMRetryAction::kCommunication));
crypto::ScopedEC_POINT shared_secret_point =
recovery_crypto_tpm1_backend_.GenerateDiffieHellmanSharedSecret(
ec_256_.value(), own_priv_point_blob, auth_value, *others_pub_key);
EXPECT_EQ(nullptr, shared_secret_point);
}
TEST_F(RecoveryCryptoTpm1BackendTest,
GenerateDiffieHellmanSharedSecretWithInvalidAuthValue) {
// Set up inputs to the test.
crypto::ScopedEC_KEY own_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(own_key_pair);
const BIGNUM* own_priv_key = EC_KEY_get0_private_key(own_key_pair.get());
ASSERT_TRUE(own_priv_key);
brillo::SecureBlob own_priv_point_blob;
ASSERT_TRUE(BigNumToSecureBlob(*own_priv_key,
ec_256_->AffineCoordinateSizeInBytes(),
&own_priv_point_blob));
crypto::ScopedEC_KEY others_key_pair = ec_256_->GenerateKey(context_.get());
ASSERT_TRUE(others_key_pair);
const EC_POINT* others_pub_key =
EC_KEY_get0_public_key(others_key_pair.get());
ASSERT_TRUE(others_pub_key);
// Calculate expected shared point
crypto::ScopedEC_POINT expected_shared_secret = ComputeEcdhSharedSecretPoint(
ec_256_.value(), *others_pub_key, *own_priv_key);
ASSERT_TRUE(expected_shared_secret);
// Set up mock expectations
EXPECT_CALL(tpm_, UnsealWithAuthorization(_, _, _, _, _)).Times(Exactly(0));
crypto::ScopedEC_POINT shared_secret_point =
recovery_crypto_tpm1_backend_.GenerateDiffieHellmanSharedSecret(
ec_256_.value(), own_priv_point_blob,
/*auth_value=*/std::nullopt, *others_pub_key);
EXPECT_NE(nullptr, shared_secret_point);
EXPECT_TRUE(ec_256_->AreEqual(*shared_secret_point, *expected_shared_secret,
context_.get()));
}
} // namespace cryptorecovery
} // namespace cryptohome