mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 22:04:26 +00:00
Merge changes I2f5187bf,Icb79e1e0,I833894d3,I54dcaa61,I47a810f2 am: b39baeaa92 am: 319cf92322 am: ab9655adc7
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1650253 Change-Id: I50251136a8ad421e3526bc44b81b195473f3de6e
This commit is contained in:
@@ -202,7 +202,7 @@ interface IRemotelyProvisionedComponent {
|
||||
* 2 : bstr // KID : EEK ID
|
||||
* 3 : -25, // Algorithm : ECDH-ES + HKDF-256
|
||||
* -1 : 4, // Curve : X25519
|
||||
* -2 : bstr // Ed25519 public key
|
||||
* -2 : bstr // X25519 public key
|
||||
* }
|
||||
*
|
||||
* EekSignatureInput = [
|
||||
@@ -221,7 +221,7 @@ interface IRemotelyProvisionedComponent {
|
||||
* in the chain, which implies that it must not attempt to validate the signature.
|
||||
*
|
||||
* If testMode is false, the method must validate the chain signatures, and must verify
|
||||
* that the public key in the root certifictate is in its pre-configured set of
|
||||
* that the public key in the root certificate is in its pre-configured set of
|
||||
* authorized EEK root keys. If the public key is not in the database, or if signature
|
||||
* verification fails, the method must return STATUS_INVALID_EEK.
|
||||
*
|
||||
|
||||
@@ -26,7 +26,7 @@ parcelable MacedPublicKey {
|
||||
/**
|
||||
* key is a COSE_Mac0 structure containing the new public key. It's MACed by a key available
|
||||
* only to the secure environment, as proof that the public key was generated by that
|
||||
* environment. In CDDL, assuming the contained key is an Ed25519 public key:
|
||||
* environment. In CDDL, assuming the contained key is a P-256 public key:
|
||||
*
|
||||
* MacedPublicKey = [ // COSE_Mac0
|
||||
* protected: bstr .cbor { 1 : 5}, // Algorithm : HMAC-256
|
||||
@@ -36,10 +36,11 @@ parcelable MacedPublicKey {
|
||||
* ]
|
||||
*
|
||||
* PublicKey = { // COSE_Key
|
||||
* 1 : 1, // Key type : octet key pair
|
||||
* 3 : -8 // Algorithm : EdDSA
|
||||
* -1 : 6, // Curve : Ed25519
|
||||
* 1 : 2, // Key type : EC2
|
||||
* 3 : -8 // Algorithm : ES256
|
||||
* -1 : 6, // Curve : P256
|
||||
* -2 : bstr // X coordinate, little-endian
|
||||
* -3 : bstr // Y coordinate, little-endian
|
||||
* ? -70000 : nil // Presence indicates this is a test key. If set, K_mac is
|
||||
* // all zeros.
|
||||
* },
|
||||
@@ -51,7 +52,7 @@ parcelable MacedPublicKey {
|
||||
* payload : bstr .cbor PublicKey
|
||||
* ]
|
||||
*
|
||||
* if a non-Ed25519 public key were contained, the contents of the PublicKey map would change a
|
||||
* if a non-P256 public key were contained, the contents of the PublicKey map would change a
|
||||
* little; see RFC 8152 for details.
|
||||
*/
|
||||
byte[] macedKey;
|
||||
|
||||
@@ -33,7 +33,7 @@ parcelable ProtectedData {
|
||||
* unprotected: {
|
||||
* 5 : bstr .size 12 // IV
|
||||
* },
|
||||
* ciphertext: bstr, // AES-GCM-128(K, .cbor ProtectedDataPayload)
|
||||
* ciphertext: bstr, // AES-GCM-256(K, .cbor ProtectedDataPayload)
|
||||
* recipients : [
|
||||
* [ // COSE_Recipient
|
||||
* protected : bstr .cbor {
|
||||
|
||||
@@ -46,6 +46,14 @@ using namespace keymaster;
|
||||
|
||||
namespace {
|
||||
|
||||
// Hard-coded set of acceptable public keys that can act as roots of EEK chains.
|
||||
inline const vector<bytevec> kAuthorizedEekRoots = {
|
||||
// TODO(drysdale): replace this random value with real root pubkey(s).
|
||||
{0x5c, 0xea, 0x4b, 0xd2, 0x31, 0x27, 0x15, 0x5e, 0x62, 0x94, 0x70,
|
||||
0x53, 0x94, 0x43, 0x0f, 0x9a, 0x89, 0xd5, 0xc5, 0x0f, 0x82, 0x9b,
|
||||
0xcd, 0x10, 0xe0, 0x79, 0xef, 0xf3, 0xfa, 0x40, 0xeb, 0x0a},
|
||||
};
|
||||
|
||||
constexpr auto STATUS_FAILED = RemotelyProvisionedComponent::STATUS_FAILED;
|
||||
constexpr auto STATUS_INVALID_EEK = RemotelyProvisionedComponent::STATUS_INVALID_EEK;
|
||||
constexpr auto STATUS_INVALID_MAC = RemotelyProvisionedComponent::STATUS_INVALID_MAC;
|
||||
@@ -135,6 +143,13 @@ StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> validateAndExtr
|
||||
"Failed to validate EEK chain: " + cosePubKey.moveMessage());
|
||||
}
|
||||
lastPubKey = *std::move(cosePubKey);
|
||||
|
||||
// In prod mode the first pubkey should match a well-known Google public key.
|
||||
if (!testMode && i == 0 &&
|
||||
std::find(kAuthorizedEekRoots.begin(), kAuthorizedEekRoots.end(), lastPubKey) ==
|
||||
kAuthorizedEekRoots.end()) {
|
||||
return Status(STATUS_INVALID_EEK, "Unrecognized root of EEK chain");
|
||||
}
|
||||
}
|
||||
|
||||
auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */);
|
||||
@@ -417,8 +432,8 @@ RemotelyProvisionedComponent::generateBcc() {
|
||||
.add(1 /* Issuer */, "Issuer")
|
||||
.add(2 /* Subject */, "Subject")
|
||||
.add(-4670552 /* Subject Pub Key */, coseKey)
|
||||
.add(-4670553 /* Key Usage */,
|
||||
std::vector<uint8_t>(0x05) /* Big endian order */)
|
||||
.add(-4670553 /* Key Usage (little-endian order) */,
|
||||
std::vector<uint8_t>{0x20} /* keyCertSign = 1<<5 */)
|
||||
.canonicalize()
|
||||
.encode();
|
||||
auto coseSign1 = constructCoseSign1(privKey, /* signing key */
|
||||
|
||||
@@ -94,11 +94,14 @@ cc_test {
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.security.keymint-V1-ndk_platform",
|
||||
"android.hardware.security.secureclock-V1-ndk_platform",
|
||||
"libcppcose",
|
||||
"libgmock_ndk",
|
||||
"libremote_provisioner",
|
||||
"libkeymint",
|
||||
"libkeymint_support",
|
||||
"libkeymint_remote_prov_support",
|
||||
"libkeymint_vts_test_utils",
|
||||
"libremote_provisioner",
|
||||
],
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
|
||||
@@ -26,29 +26,6 @@ namespace aidl::android::hardware::security::keymint::test {
|
||||
|
||||
namespace {
|
||||
|
||||
vector<uint8_t> make_name_from_str(const string& name) {
|
||||
X509_NAME_Ptr x509_name(X509_NAME_new());
|
||||
EXPECT_TRUE(x509_name.get() != nullptr);
|
||||
if (!x509_name) return {};
|
||||
|
||||
EXPECT_EQ(1, X509_NAME_add_entry_by_txt(x509_name.get(), //
|
||||
"CN", //
|
||||
MBSTRING_ASC,
|
||||
reinterpret_cast<const uint8_t*>(name.c_str()),
|
||||
-1, // len
|
||||
-1, // loc
|
||||
0 /* set */));
|
||||
|
||||
int len = i2d_X509_NAME(x509_name.get(), nullptr /* only return length */);
|
||||
EXPECT_GT(len, 0);
|
||||
|
||||
vector<uint8_t> retval(len);
|
||||
uint8_t* p = retval.data();
|
||||
i2d_X509_NAME(x509_name.get(), &p);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool IsSelfSigned(const vector<Certificate>& chain) {
|
||||
if (chain.size() != 1) return false;
|
||||
return ChainSignaturesAreValid(chain);
|
||||
|
||||
@@ -811,30 +811,6 @@ const vector<KeyParameter>& KeyMintAidlTestBase::SecLevelAuthorizations(
|
||||
return (found == key_characteristics.end()) ? kEmptyAuthList : found->authorizations;
|
||||
}
|
||||
|
||||
AuthorizationSet KeyMintAidlTestBase::HwEnforcedAuthorizations(
|
||||
const vector<KeyCharacteristics>& key_characteristics) {
|
||||
AuthorizationSet authList;
|
||||
for (auto& entry : key_characteristics) {
|
||||
if (entry.securityLevel == SecurityLevel::STRONGBOX ||
|
||||
entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT) {
|
||||
authList.push_back(AuthorizationSet(entry.authorizations));
|
||||
}
|
||||
}
|
||||
return authList;
|
||||
}
|
||||
|
||||
AuthorizationSet KeyMintAidlTestBase::SwEnforcedAuthorizations(
|
||||
const vector<KeyCharacteristics>& key_characteristics) {
|
||||
AuthorizationSet authList;
|
||||
for (auto& entry : key_characteristics) {
|
||||
if (entry.securityLevel == SecurityLevel::SOFTWARE ||
|
||||
entry.securityLevel == SecurityLevel::KEYSTORE) {
|
||||
authList.push_back(AuthorizationSet(entry.authorizations));
|
||||
}
|
||||
}
|
||||
return authList;
|
||||
}
|
||||
|
||||
ErrorCode KeyMintAidlTestBase::UseAesKey(const vector<uint8_t>& aesKeyBlob) {
|
||||
auto [result, ciphertext] = ProcessMessage(
|
||||
aesKeyBlob, KeyPurpose::ENCRYPT, "1234567890123456",
|
||||
@@ -1046,6 +1022,28 @@ string bin2hex(const vector<uint8_t>& data) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
AuthorizationSet HwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics) {
|
||||
AuthorizationSet authList;
|
||||
for (auto& entry : key_characteristics) {
|
||||
if (entry.securityLevel == SecurityLevel::STRONGBOX ||
|
||||
entry.securityLevel == SecurityLevel::TRUSTED_ENVIRONMENT) {
|
||||
authList.push_back(AuthorizationSet(entry.authorizations));
|
||||
}
|
||||
}
|
||||
return authList;
|
||||
}
|
||||
|
||||
AuthorizationSet SwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics) {
|
||||
AuthorizationSet authList;
|
||||
for (auto& entry : key_characteristics) {
|
||||
if (entry.securityLevel == SecurityLevel::SOFTWARE ||
|
||||
entry.securityLevel == SecurityLevel::KEYSTORE) {
|
||||
authList.push_back(AuthorizationSet(entry.authorizations));
|
||||
}
|
||||
}
|
||||
return authList;
|
||||
}
|
||||
|
||||
AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain) {
|
||||
std::stringstream cert_data;
|
||||
|
||||
@@ -1097,6 +1095,29 @@ X509_Ptr parse_cert_blob(const vector<uint8_t>& blob) {
|
||||
return X509_Ptr(d2i_X509(nullptr /* allocate new */, &p, blob.size()));
|
||||
}
|
||||
|
||||
vector<uint8_t> make_name_from_str(const string& name) {
|
||||
X509_NAME_Ptr x509_name(X509_NAME_new());
|
||||
EXPECT_TRUE(x509_name.get() != nullptr);
|
||||
if (!x509_name) return {};
|
||||
|
||||
EXPECT_EQ(1, X509_NAME_add_entry_by_txt(x509_name.get(), //
|
||||
"CN", //
|
||||
MBSTRING_ASC,
|
||||
reinterpret_cast<const uint8_t*>(name.c_str()),
|
||||
-1, // len
|
||||
-1, // loc
|
||||
0 /* set */));
|
||||
|
||||
int len = i2d_X509_NAME(x509_name.get(), nullptr /* only return length */);
|
||||
EXPECT_GT(len, 0);
|
||||
|
||||
vector<uint8_t> retval(len);
|
||||
uint8_t* p = retval.data();
|
||||
i2d_X509_NAME(x509_name.get(), &p);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint
|
||||
|
||||
@@ -252,10 +252,6 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam<string> {
|
||||
const vector<KeyParameter>& SecLevelAuthorizations(
|
||||
const vector<KeyCharacteristics>& key_characteristics, SecurityLevel securityLevel);
|
||||
|
||||
AuthorizationSet HwEnforcedAuthorizations(
|
||||
const vector<KeyCharacteristics>& key_characteristics);
|
||||
AuthorizationSet SwEnforcedAuthorizations(
|
||||
const vector<KeyCharacteristics>& key_characteristics);
|
||||
ErrorCode UseAesKey(const vector<uint8_t>& aesKeyBlob);
|
||||
ErrorCode UseHmacKey(const vector<uint8_t>& hmacKeyBlob);
|
||||
ErrorCode UseRsaKey(const vector<uint8_t>& rsaKeyBlob);
|
||||
@@ -280,6 +276,9 @@ bool verify_attestation_record(const string& challenge, //
|
||||
const vector<uint8_t>& attestation_cert);
|
||||
string bin2hex(const vector<uint8_t>& data);
|
||||
X509_Ptr parse_cert_blob(const vector<uint8_t>& blob);
|
||||
vector<uint8_t> make_name_from_str(const string& name);
|
||||
AuthorizationSet HwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics);
|
||||
AuthorizationSet SwEnforcedAuthorizations(const vector<KeyCharacteristics>& key_characteristics);
|
||||
::testing::AssertionResult ChainSignaturesAreValid(const vector<Certificate>& chain);
|
||||
|
||||
#define INSTANTIATE_KEYMINT_AIDL_TEST(name) \
|
||||
|
||||
@@ -17,18 +17,21 @@
|
||||
#define LOG_TAG "VtsRemotelyProvisionableComponentTests"
|
||||
|
||||
#include <RemotelyProvisionedComponent.h>
|
||||
#include <aidl/Gtest.h>
|
||||
#include <aidl/Vintf.h>
|
||||
#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
|
||||
#include <aidl/android/hardware/security/keymint/SecurityLevel.h>
|
||||
#include <android/binder_manager.h>
|
||||
#include <cppbor_parse.h>
|
||||
#include <cppcose/cppcose.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <keymaster/keymaster_configuration.h>
|
||||
#include <keymint_support/authorization_set.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ec_key.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <remote_prov/remote_prov_utils.h>
|
||||
|
||||
#include "KeyMintAidlTestBase.h"
|
||||
|
||||
namespace aidl::android::hardware::security::keymint::test {
|
||||
|
||||
using ::std::string;
|
||||
@@ -52,6 +55,190 @@ bytevec string_to_bytevec(const char* s) {
|
||||
return bytevec(p, p + strlen(s));
|
||||
}
|
||||
|
||||
void p256_pub_key(const vector<uint8_t>& coseKeyData, EVP_PKEY_Ptr* signingKey) {
|
||||
// Extract x and y affine coordinates from the encoded Cose_Key.
|
||||
auto [parsedPayload, __, payloadParseErr] = cppbor::parse(coseKeyData);
|
||||
ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
|
||||
auto coseKey = parsedPayload->asMap();
|
||||
const std::unique_ptr<cppbor::Item>& xItem = coseKey->get(cppcose::CoseKey::PUBKEY_X);
|
||||
ASSERT_NE(xItem->asBstr(), nullptr);
|
||||
vector<uint8_t> x = xItem->asBstr()->value();
|
||||
const std::unique_ptr<cppbor::Item>& yItem = coseKey->get(cppcose::CoseKey::PUBKEY_Y);
|
||||
ASSERT_NE(yItem->asBstr(), nullptr);
|
||||
vector<uint8_t> y = yItem->asBstr()->value();
|
||||
|
||||
// Concatenate: 0x04 (uncompressed form marker) | x | y
|
||||
vector<uint8_t> pubKeyData{0x04};
|
||||
pubKeyData.insert(pubKeyData.end(), x.begin(), x.end());
|
||||
pubKeyData.insert(pubKeyData.end(), y.begin(), y.end());
|
||||
|
||||
EC_KEY_Ptr ecKey = EC_KEY_Ptr(EC_KEY_new());
|
||||
ASSERT_NE(ecKey, nullptr);
|
||||
EC_GROUP_Ptr group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
|
||||
ASSERT_NE(group, nullptr);
|
||||
ASSERT_EQ(EC_KEY_set_group(ecKey.get(), group.get()), 1);
|
||||
EC_POINT_Ptr point = EC_POINT_Ptr(EC_POINT_new(group.get()));
|
||||
ASSERT_NE(point, nullptr);
|
||||
ASSERT_EQ(EC_POINT_oct2point(group.get(), point.get(), pubKeyData.data(), pubKeyData.size(),
|
||||
nullptr),
|
||||
1);
|
||||
ASSERT_EQ(EC_KEY_set_public_key(ecKey.get(), point.get()), 1);
|
||||
|
||||
EVP_PKEY_Ptr pubKey = EVP_PKEY_Ptr(EVP_PKEY_new());
|
||||
ASSERT_NE(pubKey, nullptr);
|
||||
EVP_PKEY_assign_EC_KEY(pubKey.get(), ecKey.release());
|
||||
*signingKey = std::move(pubKey);
|
||||
}
|
||||
|
||||
void check_cose_key(const vector<uint8_t>& data, bool testMode) {
|
||||
auto [parsedPayload, __, payloadParseErr] = cppbor::parse(data);
|
||||
ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
|
||||
|
||||
// The following check assumes that canonical CBOR encoding is used for the COSE_Key.
|
||||
if (testMode) {
|
||||
EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
|
||||
MatchesRegex("{\n"
|
||||
" 1 : 2,\n" // kty: EC2
|
||||
" 3 : -7,\n" // alg: ES256
|
||||
" -1 : 1,\n" // EC id: P256
|
||||
// The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a
|
||||
// sequence of 32 hexadecimal bytes, enclosed in braces and
|
||||
// separated by commas. In this case, some Ed25519 public key.
|
||||
" -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" // pub_x: data
|
||||
" -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" // pub_y: data
|
||||
" -70000 : null,\n" // test marker
|
||||
"}"));
|
||||
} else {
|
||||
EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
|
||||
MatchesRegex("{\n"
|
||||
" 1 : 2,\n" // kty: EC2
|
||||
" 3 : -7,\n" // alg: ES256
|
||||
" -1 : 1,\n" // EC id: P256
|
||||
// The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a
|
||||
// sequence of 32 hexadecimal bytes, enclosed in braces and
|
||||
// separated by commas. In this case, some Ed25519 public key.
|
||||
" -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" // pub_x: data
|
||||
" -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n" // pub_y: data
|
||||
"}"));
|
||||
}
|
||||
}
|
||||
|
||||
void check_maced_pubkey(const MacedPublicKey& macedPubKey, bool testMode,
|
||||
vector<uint8_t>* payload_value) {
|
||||
auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
|
||||
ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
|
||||
|
||||
ASSERT_NE(coseMac0->asArray(), nullptr);
|
||||
ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
|
||||
|
||||
auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
ASSERT_NE(protParms, nullptr);
|
||||
|
||||
// Header label:value of 'alg': HMAC-256
|
||||
ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}");
|
||||
|
||||
auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
ASSERT_NE(unprotParms, nullptr);
|
||||
ASSERT_EQ(unprotParms->size(), 0);
|
||||
|
||||
// The payload is a bstr holding an encoded COSE_Key
|
||||
auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
|
||||
ASSERT_NE(payload, nullptr);
|
||||
check_cose_key(payload->value(), testMode);
|
||||
|
||||
auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
|
||||
ASSERT_TRUE(coseMac0Tag);
|
||||
auto extractedTag = coseMac0Tag->value();
|
||||
EXPECT_EQ(extractedTag.size(), 32U);
|
||||
|
||||
// Compare with tag generated with kTestMacKey. Should only match in test mode
|
||||
auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
|
||||
payload->value());
|
||||
ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message();
|
||||
|
||||
if (testMode) {
|
||||
EXPECT_EQ(*testTag, extractedTag);
|
||||
} else {
|
||||
EXPECT_NE(*testTag, extractedTag);
|
||||
}
|
||||
if (payload_value != nullptr) {
|
||||
*payload_value = payload->value();
|
||||
}
|
||||
}
|
||||
|
||||
ErrMsgOr<MacedPublicKey> corrupt_maced_key(const MacedPublicKey& macedPubKey) {
|
||||
auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
|
||||
if (!coseMac0 || coseMac0->asArray()->size() != kCoseMac0EntryCount) {
|
||||
return "COSE Mac0 parse failed";
|
||||
}
|
||||
auto protParams = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
auto unprotParams = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
|
||||
auto tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
|
||||
if (!protParams || !unprotParams || !payload || !tag) {
|
||||
return "Invalid COSE_Sign1: missing content";
|
||||
}
|
||||
auto corruptMac0 = cppbor::Array();
|
||||
corruptMac0.add(protParams->clone());
|
||||
corruptMac0.add(unprotParams->clone());
|
||||
corruptMac0.add(payload->clone());
|
||||
vector<uint8_t> tagData = tag->value();
|
||||
tagData[0] ^= 0x08;
|
||||
tagData[tagData.size() - 1] ^= 0x80;
|
||||
corruptMac0.add(cppbor::Bstr(tagData));
|
||||
|
||||
return MacedPublicKey{corruptMac0.encode()};
|
||||
}
|
||||
|
||||
ErrMsgOr<cppbor::Array> corrupt_sig(const cppbor::Array* coseSign1) {
|
||||
if (coseSign1->size() != kCoseSign1EntryCount) {
|
||||
return "Invalid COSE_Sign1, wrong entry count";
|
||||
}
|
||||
const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
|
||||
const cppbor::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap();
|
||||
const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
|
||||
const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
|
||||
if (!protectedParams || !unprotectedParams || !payload || !signature) {
|
||||
return "Invalid COSE_Sign1: missing content";
|
||||
}
|
||||
|
||||
auto corruptSig = cppbor::Array();
|
||||
corruptSig.add(protectedParams->clone());
|
||||
corruptSig.add(unprotectedParams->clone());
|
||||
corruptSig.add(payload->clone());
|
||||
vector<uint8_t> sigData = signature->value();
|
||||
sigData[0] ^= 0x08;
|
||||
corruptSig.add(cppbor::Bstr(sigData));
|
||||
|
||||
return std::move(corruptSig);
|
||||
}
|
||||
|
||||
ErrMsgOr<EekChain> corrupt_sig_chain(const EekChain& eek, int which) {
|
||||
auto [chain, _, parseErr] = cppbor::parse(eek.chain);
|
||||
if (!chain || !chain->asArray()) {
|
||||
return "EekChain parse failed";
|
||||
}
|
||||
|
||||
cppbor::Array* eekChain = chain->asArray();
|
||||
if (which >= eekChain->size()) {
|
||||
return "selected sig out of range";
|
||||
}
|
||||
auto corruptChain = cppbor::Array();
|
||||
|
||||
for (int ii = 0; ii < eekChain->size(); ++ii) {
|
||||
if (ii == which) {
|
||||
auto sig = corrupt_sig(eekChain->get(which)->asArray());
|
||||
if (!sig) {
|
||||
return "Failed to build corrupted signature" + sig.moveMessage();
|
||||
}
|
||||
corruptChain.add(sig.moveValue());
|
||||
} else {
|
||||
corruptChain.add(eekChain->get(ii)->clone());
|
||||
}
|
||||
}
|
||||
return EekChain{corruptChain.encode(), eek.last_pubkey, eek.last_privkey};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::string> {
|
||||
@@ -78,7 +265,8 @@ using GenerateKeyTests = VtsRemotelyProvisionedComponentTests;
|
||||
INSTANTIATE_REM_PROV_AIDL_TEST(GenerateKeyTests);
|
||||
|
||||
/**
|
||||
* Generate and validate a production-mode key. MAC tag can't be verified.
|
||||
* Generate and validate a production-mode key. MAC tag can't be verified, but
|
||||
* the private key blob should be usable in KeyMint operations.
|
||||
*/
|
||||
TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) {
|
||||
MacedPublicKey macedPubKey;
|
||||
@@ -86,48 +274,59 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) {
|
||||
bool testMode = false;
|
||||
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
|
||||
ASSERT_TRUE(status.isOk());
|
||||
vector<uint8_t> coseKeyData;
|
||||
check_maced_pubkey(macedPubKey, testMode, &coseKeyData);
|
||||
AttestationKey attestKey;
|
||||
attestKey.keyBlob = std::move(privateKeyBlob);
|
||||
attestKey.issuerSubjectName = make_name_from_str("Android Keystore Key");
|
||||
|
||||
auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
|
||||
ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
|
||||
// Also talk to an IKeyMintDevice.
|
||||
// TODO: if there were multiple instances of IRemotelyProvisionedComponent and IKeyMintDevice,
|
||||
// what should the correlation between them be?
|
||||
vector<string> params = ::android::getAidlHalInstanceNames(IKeyMintDevice::descriptor);
|
||||
ASSERT_GT(params.size(), 0U);
|
||||
ASSERT_TRUE(AServiceManager_isDeclared(params[0].c_str()));
|
||||
::ndk::SpAIBinder binder(AServiceManager_waitForService(params[0].c_str()));
|
||||
std::shared_ptr<IKeyMintDevice> keyMint = IKeyMintDevice::fromBinder(binder);
|
||||
KeyMintHardwareInfo info;
|
||||
ASSERT_TRUE(keyMint->getHardwareInfo(&info).isOk());
|
||||
|
||||
ASSERT_NE(coseMac0->asArray(), nullptr);
|
||||
ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
|
||||
// Generate an ECDSA key that is attested by the generated P256 keypair.
|
||||
AuthorizationSet keyDesc = AuthorizationSetBuilder()
|
||||
.Authorization(TAG_NO_AUTH_REQUIRED)
|
||||
.EcdsaSigningKey(256)
|
||||
.AttestationChallenge("foo")
|
||||
.AttestationApplicationId("bar")
|
||||
.Digest(Digest::NONE)
|
||||
.SetDefaultValidity();
|
||||
KeyCreationResult creationResult;
|
||||
auto result = keyMint->generateKey(keyDesc.vector_data(), attestKey, &creationResult);
|
||||
ASSERT_TRUE(result.isOk());
|
||||
vector<uint8_t> attested_key_blob = std::move(creationResult.keyBlob);
|
||||
vector<KeyCharacteristics> attested_key_characteristics =
|
||||
std::move(creationResult.keyCharacteristics);
|
||||
vector<Certificate> attested_key_cert_chain = std::move(creationResult.certificateChain);
|
||||
EXPECT_EQ(attested_key_cert_chain.size(), 1);
|
||||
|
||||
auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
ASSERT_NE(protParms, nullptr);
|
||||
ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}");
|
||||
AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics);
|
||||
AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics);
|
||||
EXPECT_TRUE(verify_attestation_record("foo", "bar", sw_enforced, hw_enforced,
|
||||
info.securityLevel,
|
||||
attested_key_cert_chain[0].encodedCertificate));
|
||||
|
||||
auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
ASSERT_NE(unprotParms, nullptr);
|
||||
ASSERT_EQ(unprotParms->size(), 0);
|
||||
// Attestation by itself is not valid (last entry is not self-signed).
|
||||
EXPECT_FALSE(ChainSignaturesAreValid(attested_key_cert_chain));
|
||||
|
||||
auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
|
||||
ASSERT_NE(payload, nullptr);
|
||||
auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
|
||||
ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
|
||||
EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
|
||||
MatchesRegex("{\n"
|
||||
" 1 : 2,\n"
|
||||
" 3 : -7,\n"
|
||||
" -1 : 1,\n"
|
||||
// The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
|
||||
// 32 hexadecimal bytes, enclosed in braces and separated by commas.
|
||||
// In this case, some Ed25519 public key.
|
||||
" -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
|
||||
" -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
|
||||
"}"));
|
||||
// The signature over the attested key should correspond to the P256 public key.
|
||||
X509_Ptr key_cert(parse_cert_blob(attested_key_cert_chain[0].encodedCertificate));
|
||||
ASSERT_TRUE(key_cert.get());
|
||||
EVP_PKEY_Ptr signing_pubkey;
|
||||
p256_pub_key(coseKeyData, &signing_pubkey);
|
||||
ASSERT_TRUE(signing_pubkey.get());
|
||||
|
||||
auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
|
||||
ASSERT_TRUE(coseMac0Tag);
|
||||
auto extractedTag = coseMac0Tag->value();
|
||||
EXPECT_EQ(extractedTag.size(), 32U);
|
||||
|
||||
// Compare with tag generated with kTestMacKey. Shouldn't match.
|
||||
auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
|
||||
payload->value());
|
||||
ASSERT_TRUE(testTag) << "Tag calculation failed: " << testTag.message();
|
||||
|
||||
EXPECT_NE(*testTag, extractedTag);
|
||||
ASSERT_TRUE(X509_verify(key_cert.get(), signing_pubkey.get()))
|
||||
<< "Verification of attested certificate failed "
|
||||
<< "OpenSSL error string: " << ERR_error_string(ERR_get_error(), NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,56 +339,20 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) {
|
||||
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob);
|
||||
ASSERT_TRUE(status.isOk());
|
||||
|
||||
auto [coseMac0, _, mac0ParseErr] = cppbor::parse(macedPubKey.macedKey);
|
||||
ASSERT_TRUE(coseMac0) << "COSE Mac0 parse failed " << mac0ParseErr;
|
||||
|
||||
ASSERT_NE(coseMac0->asArray(), nullptr);
|
||||
ASSERT_EQ(coseMac0->asArray()->size(), kCoseMac0EntryCount);
|
||||
|
||||
auto protParms = coseMac0->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
ASSERT_NE(protParms, nullptr);
|
||||
ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n 1 : 5,\n}");
|
||||
|
||||
auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
ASSERT_NE(unprotParms, nullptr);
|
||||
ASSERT_EQ(unprotParms->size(), 0);
|
||||
|
||||
auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
|
||||
ASSERT_NE(payload, nullptr);
|
||||
auto [parsedPayload, __, payloadParseErr] = cppbor::parse(payload->value());
|
||||
ASSERT_TRUE(parsedPayload) << "Key parse failed: " << payloadParseErr;
|
||||
EXPECT_THAT(cppbor::prettyPrint(parsedPayload.get()),
|
||||
MatchesRegex("{\n"
|
||||
" 1 : 2,\n"
|
||||
" 3 : -7,\n"
|
||||
" -1 : 1,\n"
|
||||
// The regex {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}} matches a sequence of
|
||||
// 32 hexadecimal bytes, enclosed in braces and separated by commas.
|
||||
// In this case, some Ed25519 public key.
|
||||
" -2 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
|
||||
" -3 : {(0x[0-9a-f]{2}, ){31}0x[0-9a-f]{2}},\n"
|
||||
" -70000 : null,\n"
|
||||
"}"));
|
||||
|
||||
auto coseMac0Tag = coseMac0->asArray()->get(kCoseMac0Tag)->asBstr();
|
||||
ASSERT_TRUE(coseMac0);
|
||||
auto extractedTag = coseMac0Tag->value();
|
||||
EXPECT_EQ(extractedTag.size(), 32U);
|
||||
|
||||
// Compare with tag generated with kTestMacKey. Should match.
|
||||
auto testTag = cppcose::generateCoseMac0Mac(remote_prov::kTestMacKey, {} /* external_aad */,
|
||||
payload->value());
|
||||
ASSERT_TRUE(testTag) << testTag.message();
|
||||
|
||||
EXPECT_EQ(*testTag, extractedTag);
|
||||
check_maced_pubkey(macedPubKey, testMode, nullptr);
|
||||
}
|
||||
|
||||
class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
|
||||
protected:
|
||||
CertificateRequestTest() : eekId_(string_to_bytevec("eekid")) {
|
||||
auto chain = generateEekChain(3, eekId_);
|
||||
CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(32)) {
|
||||
generateEek(3);
|
||||
}
|
||||
|
||||
void generateEek(size_t eekLength) {
|
||||
auto chain = generateEekChain(eekLength, eekId_);
|
||||
EXPECT_TRUE(chain) << chain.message();
|
||||
if (chain) eekChain_ = chain.moveValue();
|
||||
eekLength_ = eekLength;
|
||||
}
|
||||
|
||||
void generateKeys(bool testMode, size_t numKeys) {
|
||||
@@ -201,21 +364,71 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
|
||||
auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &key, &privateKeyBlob);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
auto [parsedMacedKey, _, parseErr] = cppbor::parse(key.macedKey);
|
||||
ASSERT_TRUE(parsedMacedKey) << "Failed parsing MACed key: " << parseErr;
|
||||
ASSERT_TRUE(parsedMacedKey->asArray()) << "COSE_Mac0 not an array?";
|
||||
ASSERT_EQ(parsedMacedKey->asArray()->size(), kCoseMac0EntryCount);
|
||||
|
||||
auto& payload = parsedMacedKey->asArray()->get(kCoseMac0Payload);
|
||||
ASSERT_TRUE(payload);
|
||||
ASSERT_TRUE(payload->asBstr());
|
||||
|
||||
cborKeysToSign_.add(cppbor::EncodedItem(payload->asBstr()->value()));
|
||||
vector<uint8_t> payload_value;
|
||||
check_maced_pubkey(key, testMode, &payload_value);
|
||||
cborKeysToSign_.add(cppbor::EncodedItem(payload_value));
|
||||
}
|
||||
}
|
||||
|
||||
void checkProtectedData(bool testMode, const cppbor::Array& keysToSign,
|
||||
const bytevec& keysToSignMac, const ProtectedData& protectedData) {
|
||||
auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
|
||||
ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
|
||||
ASSERT_TRUE(parsedProtectedData->asArray());
|
||||
ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
|
||||
|
||||
auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
|
||||
ASSERT_TRUE(senderPubkey) << senderPubkey.message();
|
||||
EXPECT_EQ(senderPubkey->second, eekId_);
|
||||
|
||||
auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
|
||||
senderPubkey->first, false /* senderIsA */);
|
||||
ASSERT_TRUE(sessionKey) << sessionKey.message();
|
||||
|
||||
auto protectedDataPayload =
|
||||
decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
|
||||
ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
|
||||
|
||||
auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
|
||||
ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
|
||||
ASSERT_TRUE(parsedPayload->asArray());
|
||||
EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
|
||||
|
||||
auto& signedMac = parsedPayload->asArray()->get(0);
|
||||
auto& bcc = parsedPayload->asArray()->get(1);
|
||||
ASSERT_TRUE(signedMac && signedMac->asArray());
|
||||
ASSERT_TRUE(bcc && bcc->asArray());
|
||||
|
||||
// BCC is [ pubkey, + BccEntry]
|
||||
auto bccContents = validateBcc(bcc->asArray());
|
||||
ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
|
||||
ASSERT_GT(bccContents->size(), 0U);
|
||||
|
||||
auto& signingKey = bccContents->back().pubKey;
|
||||
auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
|
||||
cppbor::Array() // DeviceInfo
|
||||
.add(challenge_)
|
||||
.add(cppbor::Map())
|
||||
.encode());
|
||||
ASSERT_TRUE(macKey) << macKey.message();
|
||||
|
||||
auto coseMac0 = cppbor::Array()
|
||||
.add(cppbor::Map() // protected
|
||||
.add(ALGORITHM, HMAC_256)
|
||||
.canonicalize()
|
||||
.encode())
|
||||
.add(cppbor::Map()) // unprotected
|
||||
.add(keysToSign.encode()) // payload (keysToSign)
|
||||
.add(keysToSignMac); // tag
|
||||
|
||||
auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
|
||||
ASSERT_TRUE(macPayload) << macPayload.message();
|
||||
}
|
||||
|
||||
bytevec eekId_;
|
||||
size_t eekLength_;
|
||||
EekChain eekChain_;
|
||||
bytevec challenge_;
|
||||
std::vector<MacedPublicKey> keysToSign_;
|
||||
cppbor::Array cborKeysToSign_;
|
||||
};
|
||||
@@ -226,66 +439,20 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests {
|
||||
*/
|
||||
TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
|
||||
bool testMode = true;
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto challenge = randomBytes(32);
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, {} /* keysToSign */, eekChain_.chain, challenge, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
for (size_t eekLength : {2, 3, 7}) {
|
||||
SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength);
|
||||
generateEek(eekLength);
|
||||
|
||||
auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
|
||||
ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
|
||||
ASSERT_TRUE(parsedProtectedData->asArray());
|
||||
ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, {} /* keysToSign */, eekChain_.chain, challenge_, &deviceInfo,
|
||||
&protectedData, &keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
|
||||
ASSERT_TRUE(senderPubkey) << senderPubkey.message();
|
||||
EXPECT_EQ(senderPubkey->second, eekId_);
|
||||
|
||||
auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
|
||||
senderPubkey->first, false /* senderIsA */);
|
||||
ASSERT_TRUE(sessionKey) << sessionKey.message();
|
||||
|
||||
auto protectedDataPayload =
|
||||
decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
|
||||
ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
|
||||
|
||||
auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
|
||||
ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
|
||||
ASSERT_TRUE(parsedPayload->asArray());
|
||||
EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
|
||||
|
||||
auto& signedMac = parsedPayload->asArray()->get(0);
|
||||
auto& bcc = parsedPayload->asArray()->get(1);
|
||||
ASSERT_TRUE(signedMac && signedMac->asArray());
|
||||
ASSERT_TRUE(bcc && bcc->asArray());
|
||||
|
||||
// BCC is [ pubkey, + BccEntry]
|
||||
auto bccContents = validateBcc(bcc->asArray());
|
||||
ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
|
||||
ASSERT_GT(bccContents->size(), 0U);
|
||||
|
||||
auto& signingKey = bccContents->back().pubKey;
|
||||
auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
|
||||
cppbor::Array() // DeviceInfo
|
||||
.add(challenge) //
|
||||
.add(cppbor::Map())
|
||||
.encode());
|
||||
ASSERT_TRUE(macKey) << macKey.message();
|
||||
|
||||
auto coseMac0 = cppbor::Array()
|
||||
.add(cppbor::Map() // protected
|
||||
.add(ALGORITHM, HMAC_256)
|
||||
.canonicalize()
|
||||
.encode())
|
||||
.add(cppbor::Map()) // unprotected
|
||||
.add(cppbor::Array().encode()) // payload (keysToSign)
|
||||
.add(std::move(keysToSignMac)); // tag
|
||||
|
||||
auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
|
||||
ASSERT_TRUE(macPayload) << macPayload.message();
|
||||
checkProtectedData(testMode, cppbor::Array(), keysToSignMac, protectedData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,15 +464,20 @@ TEST_P(CertificateRequestTest, EmptyRequest_testMode) {
|
||||
*/
|
||||
TEST_P(CertificateRequestTest, EmptyRequest_prodMode) {
|
||||
bool testMode = false;
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto challenge = randomBytes(32);
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, {} /* keysToSign */, eekChain_.chain, challenge, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk());
|
||||
ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
|
||||
for (size_t eekLength : {2, 3, 7}) {
|
||||
SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength);
|
||||
generateEek(eekLength);
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, {} /* keysToSign */, eekChain_.chain, challenge_, &deviceInfo,
|
||||
&protectedData, &keysToSignMac);
|
||||
EXPECT_FALSE(status.isOk());
|
||||
EXPECT_EQ(status.getServiceSpecificError(),
|
||||
BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,65 +487,20 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testMode) {
|
||||
bool testMode = true;
|
||||
generateKeys(testMode, 4 /* numKeys */);
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto challenge = randomBytes(32);
|
||||
auto status = provisionable_->generateCertificateRequest(testMode, keysToSign_, eekChain_.chain,
|
||||
challenge, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
for (size_t eekLength : {2, 3, 7}) {
|
||||
SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength);
|
||||
generateEek(eekLength);
|
||||
|
||||
auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
|
||||
ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
|
||||
ASSERT_TRUE(parsedProtectedData->asArray());
|
||||
ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, keysToSign_, eekChain_.chain, challenge_, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_TRUE(status.isOk()) << status.getMessage();
|
||||
|
||||
auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
|
||||
ASSERT_TRUE(senderPubkey) << senderPubkey.message();
|
||||
EXPECT_EQ(senderPubkey->second, eekId_);
|
||||
|
||||
auto sessionKey = x25519_HKDF_DeriveKey(eekChain_.last_pubkey, eekChain_.last_privkey,
|
||||
senderPubkey->first, false /* senderIsA */);
|
||||
ASSERT_TRUE(sessionKey) << sessionKey.message();
|
||||
|
||||
auto protectedDataPayload =
|
||||
decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
|
||||
ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
|
||||
|
||||
auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
|
||||
ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
|
||||
ASSERT_TRUE(parsedPayload->asArray());
|
||||
EXPECT_EQ(parsedPayload->asArray()->size(), 2U);
|
||||
|
||||
auto& signedMac = parsedPayload->asArray()->get(0);
|
||||
auto& bcc = parsedPayload->asArray()->get(1);
|
||||
ASSERT_TRUE(signedMac && signedMac->asArray());
|
||||
ASSERT_TRUE(bcc);
|
||||
|
||||
auto bccContents = validateBcc(bcc->asArray());
|
||||
ASSERT_TRUE(bccContents) << "\n" << prettyPrint(bcc.get());
|
||||
ASSERT_GT(bccContents->size(), 0U);
|
||||
|
||||
auto& signingKey = bccContents->back().pubKey;
|
||||
auto macKey = verifyAndParseCoseSign1(testMode, signedMac->asArray(), signingKey,
|
||||
cppbor::Array() // DeviceInfo
|
||||
.add(challenge) //
|
||||
.add(cppbor::Array())
|
||||
.encode());
|
||||
ASSERT_TRUE(macKey) << macKey.message();
|
||||
|
||||
auto coseMac0 = cppbor::Array()
|
||||
.add(cppbor::Map() // protected
|
||||
.add(ALGORITHM, HMAC_256)
|
||||
.canonicalize()
|
||||
.encode())
|
||||
.add(cppbor::Map()) // unprotected
|
||||
.add(cborKeysToSign_.encode()) // payload
|
||||
.add(std::move(keysToSignMac)); // tag
|
||||
|
||||
auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
|
||||
ASSERT_TRUE(macPayload) << macPayload.message();
|
||||
checkProtectedData(testMode, cborKeysToSign_, keysToSignMac, protectedData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,13 +514,117 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodMode) {
|
||||
bool testMode = false;
|
||||
generateKeys(testMode, 4 /* numKeys */);
|
||||
|
||||
for (size_t eekLength : {2, 3, 7}) {
|
||||
SCOPED_TRACE(testing::Message() << "EEK of length " << eekLength);
|
||||
generateEek(eekLength);
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, keysToSign_, eekChain_.chain, challenge_, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
EXPECT_FALSE(status.isOk());
|
||||
EXPECT_EQ(status.getServiceSpecificError(),
|
||||
BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a non-empty certificate request in test mode, but with the MAC corrupted on the keypair.
|
||||
*/
|
||||
TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_testMode) {
|
||||
bool testMode = true;
|
||||
generateKeys(testMode, 1 /* numKeys */);
|
||||
MacedPublicKey keyWithCorruptMac = corrupt_maced_key(keysToSign_[0]).moveValue();
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto challenge = randomBytes(32);
|
||||
auto status = provisionable_->generateCertificateRequest(testMode, keysToSign_, eekChain_.chain,
|
||||
challenge, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, {keyWithCorruptMac}, eekChain_.chain, challenge_, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk()) << status.getMessage();
|
||||
EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_MAC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a non-empty certificate request in prod mode, but with the MAC corrupted on the keypair.
|
||||
*/
|
||||
TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_prodMode) {
|
||||
bool testMode = true;
|
||||
generateKeys(testMode, 1 /* numKeys */);
|
||||
MacedPublicKey keyWithCorruptMac = corrupt_maced_key(keysToSign_[0]).moveValue();
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, {keyWithCorruptMac}, eekChain_.chain, challenge_, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk()) << status.getMessage();
|
||||
auto rc = status.getServiceSpecificError();
|
||||
|
||||
// TODO(drysdale): drop the INVALID_EEK potential error code when a real GEEK is available.
|
||||
EXPECT_TRUE(rc == BnRemotelyProvisionedComponent::STATUS_INVALID_EEK ||
|
||||
rc == BnRemotelyProvisionedComponent::STATUS_INVALID_MAC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a non-empty certificate request in prod mode that has a corrupt EEK chain.
|
||||
* Confirm that the request is rejected.
|
||||
*
|
||||
* TODO(drysdale): Update to use a valid GEEK, so that the test actually confirms that the
|
||||
* implementation is checking signatures.
|
||||
*/
|
||||
TEST_P(CertificateRequestTest, NonEmptyCorruptEekRequest_prodMode) {
|
||||
bool testMode = false;
|
||||
generateKeys(testMode, 4 /* numKeys */);
|
||||
|
||||
for (size_t ii = 0; ii < eekLength_; ii++) {
|
||||
auto chain = corrupt_sig_chain(eekChain_, ii);
|
||||
ASSERT_TRUE(chain) << chain.message();
|
||||
EekChain corruptEek = chain.moveValue();
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, keysToSign_, corruptEek.chain, challenge_, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk());
|
||||
ASSERT_EQ(status.getServiceSpecificError(),
|
||||
BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a non-empty certificate request in prod mode that has an incomplete EEK chain.
|
||||
* Confirm that the request is rejected.
|
||||
*
|
||||
* TODO(drysdale): Update to use a valid GEEK, so that the test actually confirms that the
|
||||
* implementation is checking signatures.
|
||||
*/
|
||||
TEST_P(CertificateRequestTest, NonEmptyIncompleteEekRequest_prodMode) {
|
||||
bool testMode = false;
|
||||
generateKeys(testMode, 4 /* numKeys */);
|
||||
|
||||
// Build an EEK chain that omits the first self-signed cert.
|
||||
auto truncatedChain = cppbor::Array();
|
||||
auto [chain, _, parseErr] = cppbor::parse(eekChain_.chain);
|
||||
ASSERT_TRUE(chain);
|
||||
auto eekChain = chain->asArray();
|
||||
ASSERT_NE(eekChain, nullptr);
|
||||
for (size_t ii = 1; ii < eekChain->size(); ii++) {
|
||||
truncatedChain.add(eekChain->get(ii)->clone());
|
||||
}
|
||||
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
testMode, keysToSign_, truncatedChain.encode(), challenge_, &deviceInfo, &protectedData,
|
||||
&keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk());
|
||||
ASSERT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_EEK);
|
||||
}
|
||||
@@ -408,9 +639,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodKeyInTestCert) {
|
||||
bytevec keysToSignMac;
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto challenge = randomBytes(32);
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
true /* testMode */, keysToSign_, eekChain_.chain, challenge, &deviceInfo,
|
||||
true /* testMode */, keysToSign_, eekChain_.chain, challenge_, &deviceInfo,
|
||||
&protectedData, &keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk());
|
||||
ASSERT_EQ(status.getServiceSpecificError(),
|
||||
@@ -428,8 +658,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) {
|
||||
DeviceInfo deviceInfo;
|
||||
ProtectedData protectedData;
|
||||
auto status = provisionable_->generateCertificateRequest(
|
||||
false /* testMode */, keysToSign_, eekChain_.chain, randomBytes(32) /* challenge */,
|
||||
&deviceInfo, &protectedData, &keysToSignMac);
|
||||
false /* testMode */, keysToSign_, eekChain_.chain, challenge_, &deviceInfo,
|
||||
&protectedData, &keysToSignMac);
|
||||
ASSERT_FALSE(status.isOk());
|
||||
ASSERT_EQ(status.getServiceSpecificError(),
|
||||
BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST);
|
||||
|
||||
@@ -37,6 +37,7 @@ MAKE_OPENSSL_PTR_TYPE(ASN1_OBJECT)
|
||||
MAKE_OPENSSL_PTR_TYPE(BN_CTX)
|
||||
MAKE_OPENSSL_PTR_TYPE(EC_GROUP)
|
||||
MAKE_OPENSSL_PTR_TYPE(EC_KEY)
|
||||
MAKE_OPENSSL_PTR_TYPE(EC_POINT)
|
||||
MAKE_OPENSSL_PTR_TYPE(EVP_PKEY)
|
||||
MAKE_OPENSSL_PTR_TYPE(EVP_PKEY_CTX)
|
||||
MAKE_OPENSSL_PTR_TYPE(RSA)
|
||||
|
||||
@@ -54,6 +54,8 @@ ErrMsgOr<EekChain> generateEekChain(size_t length, const bytevec& eekId) {
|
||||
{} /* AAD */);
|
||||
if (!coseSign1) return coseSign1.moveMessage();
|
||||
eekChain.add(coseSign1.moveValue());
|
||||
|
||||
prev_priv_key = priv_key;
|
||||
}
|
||||
|
||||
bytevec pub_key(X25519_PUBLIC_VALUE_LEN);
|
||||
|
||||
Reference in New Issue
Block a user