blob: bebc0693a56701a696eeb565746b82c3835d1346 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.h"
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/apple/bridging.h"
#include "base/containers/span.h"
#include "base/mac/scoped_cftyperef.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/shared_command_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using testing::_;
using ::testing::InSequence;
namespace enterprise_connectors {
using test::MockSecureEnclaveClient;
class SecureEnclaveSigningKeyTest : public testing::Test {
public:
SecureEnclaveSigningKeyTest() {
CreateTestKey();
auto mock_secure_enclave_client =
std::make_unique<MockSecureEnclaveClient>();
mock_secure_enclave_client_ = mock_secure_enclave_client.get();
SecureEnclaveClient::SetInstanceForTesting(
std::move(mock_secure_enclave_client));
}
protected:
// Creates a test key.
void CreateTestKey() {
base::ScopedCFTypeRef<CFMutableDictionaryRef> test_attributes(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(test_attributes, kSecAttrLabel, CFSTR("fake-label"));
CFDictionarySetValue(test_attributes, kSecAttrKeyType,
kSecAttrKeyTypeECSECPrimeRandom);
CFDictionarySetValue(test_attributes, kSecAttrKeySizeInBits,
base::apple::NSToCFPtrCast(@256));
base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(private_key_params, kSecAttrIsPermanent,
kCFBooleanFalse);
CFDictionarySetValue(test_attributes, kSecPrivateKeyAttrs,
private_key_params);
test_key_ = base::ScopedCFTypeRef<SecKeyRef>(
SecKeyCreateRandomKey(test_attributes, nullptr));
}
// Sets the unexportable key using the test key.
void SetUnexportableKey() {
auto provider = SecureEnclaveSigningKeyProvider(
SecureEnclaveClient::KeyType::kPermanent);
EXPECT_CALL(*mock_secure_enclave_client_, CreatePermanentKey())
.Times(1)
.WillOnce([this]() { return test_key_; });
auto acceptable_algorithms = {crypto::SignatureVerifier::ECDSA_SHA256};
key_ = provider.GenerateSigningKeySlowly(acceptable_algorithms);
}
MockSecureEnclaveClient* mock_secure_enclave_client_ = nullptr;
std::unique_ptr<crypto::UnexportableSigningKey> key_;
base::ScopedCFTypeRef<SecKeyRef> test_key_;
};
// Tests that the GenerateSigningKeySlowly method invokes the SE client's
// CreatePermanentKey method only when the provider is a permanent key provider.
TEST_F(SecureEnclaveSigningKeyTest, GenerateSigningKeySlowly) {
auto acceptable_algorithms = {crypto::SignatureVerifier::ECDSA_SHA256};
InSequence s;
auto provider =
SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kPermanent);
EXPECT_CALL(*mock_secure_enclave_client_, CreatePermanentKey())
.Times(1)
.WillOnce([this]() { return test_key_; });
auto unexportable_key =
provider.GenerateSigningKeySlowly(acceptable_algorithms);
EXPECT_TRUE(unexportable_key);
EXPECT_EQ(crypto::SignatureVerifier::ECDSA_SHA256,
unexportable_key->Algorithm());
provider =
SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kTemporary);
EXPECT_CALL(*mock_secure_enclave_client_, CreatePermanentKey()).Times(0);
unexportable_key = provider.GenerateSigningKeySlowly(acceptable_algorithms);
EXPECT_FALSE(unexportable_key);
}
// Tests that the FromWrappedSigningKeySlowly returns a nullptr when the wrapped
// key label is empty.
TEST_F(SecureEnclaveSigningKeyTest,
FromWrappedSigningKeySlowly_EmptyWrappedKey) {
auto permanent_key_provider = SecureEnclaveSigningKeyProvider(
(SecureEnclaveClient::KeyType::kPermanent));
base::span<const uint8_t> empty_span;
EXPECT_FALSE(permanent_key_provider.FromWrappedSigningKeySlowly(empty_span));
}
// Tests that the FromWrappedSigningKeySlowly returns a nullptr when the wrapped
// key label is invalid and does not match the provider key type.
TEST_F(SecureEnclaveSigningKeyTest,
FromWrappedSigningKeySlowly_InvalidWrappedKeyLabel) {
auto permanent_key_provider = SecureEnclaveSigningKeyProvider(
(SecureEnclaveClient::KeyType::kPermanent));
EXPECT_FALSE(permanent_key_provider.FromWrappedSigningKeySlowly(
base::as_bytes(base::make_span(
std::string(constants::kTemporaryDeviceTrustSigningKeyLabel)))));
auto temp_key_provider = SecureEnclaveSigningKeyProvider(
(SecureEnclaveClient::KeyType::kTemporary));
EXPECT_FALSE(temp_key_provider.FromWrappedSigningKeySlowly(base::as_bytes(
base::make_span(std::string(constants::kDeviceTrustSigningKeyLabel)))));
}
// Tests that the FromWrappedSigningKeySlowly invokes the SE client's
// CopyStoredKey method only when the wrapped key label matches the key
// provider type and the provider is a permanent key provider.
TEST_F(SecureEnclaveSigningKeyTest,
FromWrappedSigningKeySlowly_PermanentKeyProvider) {
auto permanent_key_provider = SecureEnclaveSigningKeyProvider(
(SecureEnclaveClient::KeyType::kPermanent));
std::unique_ptr<crypto::UnexportableSigningKey> unexportable_key;
InSequence s;
// Permanent provider key type with a wrapped permanent key label.
EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_))
.Times(1)
.WillOnce([this](SecureEnclaveClient::KeyType type) {
EXPECT_EQ(SecureEnclaveClient::KeyType::kPermanent, type);
return test_key_;
});
unexportable_key = permanent_key_provider.FromWrappedSigningKeySlowly(
base::as_bytes(base::make_span(
std::string(constants::kDeviceTrustSigningKeyLabel))));
EXPECT_TRUE(unexportable_key);
EXPECT_EQ(crypto::SignatureVerifier::ECDSA_SHA256,
unexportable_key->Algorithm());
// Permanent key provider with a wrapped temporary key label.
EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_)).Times(0);
unexportable_key = permanent_key_provider.FromWrappedSigningKeySlowly(
base::as_bytes(base::make_span(
std::string(constants::kTemporaryDeviceTrustSigningKeyLabel))));
EXPECT_FALSE(unexportable_key);
}
// Tests that the FromWrappedSigningKeySlowly invokes the SE client's
// CopyStoredKey method only when the wrapped key label matches the key
// provider type and the provider is a temporary key provider.
TEST_F(SecureEnclaveSigningKeyTest,
FromWrappedSigningKeySlowly_TemporaryKeyProvider) {
auto temp_key_provider = SecureEnclaveSigningKeyProvider(
(SecureEnclaveClient::KeyType::kTemporary));
std::unique_ptr<crypto::UnexportableSigningKey> unexportable_key;
InSequence s;
// Temporary key provider with a wrapped temporary key label.
EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_))
.Times(1)
.WillOnce([this](SecureEnclaveClient::KeyType type) {
EXPECT_EQ(SecureEnclaveClient::KeyType::kTemporary, type);
return test_key_;
});
unexportable_key = temp_key_provider.FromWrappedSigningKeySlowly(
base::as_bytes(base::make_span(
std::string(constants::kTemporaryDeviceTrustSigningKeyLabel))));
EXPECT_TRUE(unexportable_key);
EXPECT_EQ(crypto::SignatureVerifier::ECDSA_SHA256,
unexportable_key->Algorithm());
// Temporary provider key type with a wrapped permanent key label.
EXPECT_CALL(*mock_secure_enclave_client_, CopyStoredKey(_)).Times(0);
unexportable_key = temp_key_provider.FromWrappedSigningKeySlowly(
base::as_bytes(base::make_span(
std::string(constants::kDeviceTrustSigningKeyLabel))));
EXPECT_FALSE(unexportable_key);
}
// Tests that the GetSubjectPublicKeyInfo method invokes the SE client's
// ExportPublicKey method and that the public key information gotten from
// this method is correct.
TEST_F(SecureEnclaveSigningKeyTest, GetSubjectPublicKeyInfo) {
SetUnexportableKey();
std::string test_data = "data";
EXPECT_CALL(*mock_secure_enclave_client_, ExportPublicKey(_, _))
.WillOnce([&test_data](SecKeyRef key, std::vector<uint8_t>& output) {
output.assign(test_data.begin(), test_data.end());
return true;
});
EXPECT_EQ(std::vector<uint8_t>(test_data.begin(), test_data.end()),
key_->GetSubjectPublicKeyInfo());
}
// Tests that the GetWrappedKey method invokes the SE client's
// GetStoredKeyLabel method and that the wrapped key label is the permanent
// key label since the SecureEnclaveSigningKey is currently a permanent key.
TEST_F(SecureEnclaveSigningKeyTest, GetWrappedKey) {
SetUnexportableKey();
EXPECT_CALL(*mock_secure_enclave_client_,
GetStoredKeyLabel(SecureEnclaveClient::KeyType::kPermanent, _))
.WillOnce(
[](SecureEnclaveClient::KeyType type, std::vector<uint8_t>& output) {
std::string label =
(type == SecureEnclaveClient::KeyType::kTemporary)
? constants::kTemporaryDeviceTrustSigningKeyLabel
: constants::kDeviceTrustSigningKeyLabel;
output.assign(label.begin(), label.end());
return true;
});
auto wrapped = key_->GetWrappedKey();
EXPECT_EQ(constants::kDeviceTrustSigningKeyLabel,
std::string(wrapped.begin(), wrapped.end()));
}
// Tests that the SignSlowly method invokes the SE client's SignDataWithKey
// method and that the signature is correct.
TEST_F(SecureEnclaveSigningKeyTest, SignSlowly) {
SetUnexportableKey();
std::string test_data = "data";
EXPECT_CALL(*mock_secure_enclave_client_, SignDataWithKey(_, _, _))
.Times(1)
.WillOnce([&test_data](SecKeyRef key, base::span<const uint8_t> data,
std::vector<uint8_t>& output) {
output.assign(test_data.begin(), test_data.end());
return true;
});
EXPECT_EQ(
std::vector<uint8_t>(test_data.begin(), test_data.end()),
key_->SignSlowly(base::as_bytes(base::make_span("data to be sign"))));
}
} // namespace enterprise_connectors