From fb213d60310fdd7905be85856b25cf7c79084d9f Mon Sep 17 00:00:00 2001 From: subrahmanyaman Date: Wed, 2 Feb 2022 23:10:55 +0000 Subject: [PATCH] Support for P256 curve in RKP for Strongbox Test: Run Rkp Vts tests. Change-Id: Ic38fd2318dd8749ae125f1e78d25f2722bd367e5 --- .../VtsRemotelyProvisionedComponentTests.cpp | 43 ++- security/keymint/support/Android.bp | 7 + .../include/remote_prov/remote_prov_utils.h | 36 ++- .../keymint/support/remote_prov_utils.cpp | 291 +++++++++++++++--- .../support/remote_prov_utils_test.cpp | 165 +++++++++- 5 files changed, 472 insertions(+), 70 deletions(-) diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp index 829780d442..f154d03c7c 100644 --- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -169,6 +169,7 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParamgetHardwareInfo(&rpcHardwareInfo).isOk()); } static vector build_params() { @@ -178,6 +179,7 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam provisionable_; + RpcHardwareInfo rpcHardwareInfo; }; /** @@ -341,11 +343,10 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) { class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { protected: CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(32)) { - generateTestEekChain(3); } void generateTestEekChain(size_t eekLength) { - auto chain = generateEekChain(eekLength, eekId_); + auto chain = generateEekChain(rpcHardwareInfo.supportedEekCurve, eekLength, eekId_); EXPECT_TRUE(chain) << chain.message(); if (chain) testEekChain_ = chain.moveValue(); testEekLength_ = eekLength; @@ -366,6 +367,17 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { } } + ErrMsgOr getSessionKey(ErrMsgOr>& senderPubkey) { + if (rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_25519 || + rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_NONE) { + return x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, + senderPubkey->first, false /* senderIsA */); + } else { + return ECDH_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, + senderPubkey->first, false /* senderIsA */); + } + } + void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, const bytevec& keysToSignMac, const ProtectedData& protectedData, std::vector* bccOutput = nullptr) { @@ -378,9 +390,7 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { ASSERT_TRUE(senderPubkey) << senderPubkey.message(); EXPECT_EQ(senderPubkey->second, eekId_); - auto sessionKey = - x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, - senderPubkey->first, false /* senderIsA */); + auto sessionKey = getSessionKey(senderPubkey); ASSERT_TRUE(sessionKey) << sessionKey.message(); auto protectedDataPayload = @@ -390,7 +400,8 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; ASSERT_TRUE(parsedPayload->asArray()); - EXPECT_EQ(parsedPayload->asArray()->size(), 2U); + // Strongbox may contain additional certificate chain. + EXPECT_LE(parsedPayload->asArray()->size(), 3U); auto& signedMac = parsedPayload->asArray()->get(0); auto& bcc = parsedPayload->asArray()->get(1); @@ -474,6 +485,7 @@ TEST_P(CertificateRequestTest, NewKeyPerCallInTestMode) { bytevec keysToSignMac; DeviceInfo deviceInfo; ProtectedData protectedData; + generateTestEekChain(3); auto status = provisionable_->generateCertificateRequest( testMode, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo, &protectedData, &keysToSignMac); @@ -513,8 +525,8 @@ TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_prodMode) { DeviceInfo deviceInfo; ProtectedData protectedData; auto status = provisionable_->generateCertificateRequest( - testMode, {} /* keysToSign */, getProdEekChain(), challenge_, &deviceInfo, - &protectedData, &keysToSignMac); + testMode, {} /* keysToSign */, getProdEekChain(rpcHardwareInfo.supportedEekCurve), + challenge_, &deviceInfo, &protectedData, &keysToSignMac); EXPECT_TRUE(status.isOk()); } @@ -554,8 +566,8 @@ TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodMode) { DeviceInfo deviceInfo; ProtectedData protectedData; auto status = provisionable_->generateCertificateRequest( - testMode, keysToSign_, getProdEekChain(), challenge_, &deviceInfo, &protectedData, - &keysToSignMac); + testMode, keysToSign_, getProdEekChain(rpcHardwareInfo.supportedEekCurve), challenge_, + &deviceInfo, &protectedData, &keysToSignMac); EXPECT_TRUE(status.isOk()); } @@ -570,6 +582,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_testMode) { bytevec keysToSignMac; DeviceInfo deviceInfo; ProtectedData protectedData; + generateTestEekChain(3); auto status = provisionable_->generateCertificateRequest( testMode, {keyWithCorruptMac}, testEekChain_.chain, challenge_, &deviceInfo, &protectedData, &keysToSignMac); @@ -589,8 +602,8 @@ TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_prodMode) { DeviceInfo deviceInfo; ProtectedData protectedData; auto status = provisionable_->generateCertificateRequest( - testMode, {keyWithCorruptMac}, getProdEekChain(), challenge_, &deviceInfo, - &protectedData, &keysToSignMac); + testMode, {keyWithCorruptMac}, getProdEekChain(rpcHardwareInfo.supportedEekCurve), + challenge_, &deviceInfo, &protectedData, &keysToSignMac); ASSERT_FALSE(status.isOk()) << status.getMessage(); EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); } @@ -603,7 +616,7 @@ TEST_P(CertificateRequestTest, NonEmptyCorruptEekRequest_prodMode) { bool testMode = false; generateKeys(testMode, 4 /* numKeys */); - auto prodEekChain = getProdEekChain(); + auto prodEekChain = getProdEekChain(rpcHardwareInfo.supportedEekCurve); auto [parsedChain, _, parseErr] = cppbor::parse(prodEekChain); ASSERT_NE(parsedChain, nullptr) << parseErr; ASSERT_NE(parsedChain->asArray(), nullptr); @@ -634,7 +647,7 @@ TEST_P(CertificateRequestTest, NonEmptyIncompleteEekRequest_prodMode) { // Build an EEK chain that omits the first self-signed cert. auto truncatedChain = cppbor::Array(); - auto [chain, _, parseErr] = cppbor::parse(getProdEekChain()); + auto [chain, _, parseErr] = cppbor::parse(getProdEekChain(rpcHardwareInfo.supportedEekCurve)); ASSERT_TRUE(chain); auto eekChain = chain->asArray(); ASSERT_NE(eekChain, nullptr); @@ -662,6 +675,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodKeyInTestCert) { bytevec keysToSignMac; DeviceInfo deviceInfo; ProtectedData protectedData; + generateTestEekChain(3); auto status = provisionable_->generateCertificateRequest( true /* testMode */, keysToSign_, testEekChain_.chain, challenge_, &deviceInfo, &protectedData, &keysToSignMac); @@ -680,6 +694,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) { bytevec keysToSignMac; DeviceInfo deviceInfo; ProtectedData protectedData; + generateTestEekChain(3); auto status = provisionable_->generateCertificateRequest( false /* testMode */, keysToSign_, testEekChain_.chain, challenge_, &deviceInfo, &protectedData, &keysToSignMac); diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp index 36969bbdba..bf2ab02cb2 100644 --- a/security/keymint/support/Android.bp +++ b/security/keymint/support/Android.bp @@ -60,11 +60,15 @@ cc_library { export_include_dirs: [ "include", ], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], shared_libs: [ "libbase", "libcppbor_external", "libcppcose_rkp", "libcrypto", + "libkeymaster_portable", "libjsoncpp", ], } @@ -76,6 +80,9 @@ cc_test { "libgmock", "libgtest_main", ], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], shared_libs: [ "libbase", "libcppbor_external", diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h index 406b7a9b79..1d3abe512f 100644 --- a/security/keymint/support/include/remote_prov/remote_prov_utils.h +++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h @@ -52,6 +52,34 @@ inline constexpr uint8_t kCoseEncodedGeekCert[] = { 0x31, 0xbf, 0x6b, 0xe8, 0x1e, 0x35, 0xe2, 0xf0, 0x2d, 0xce, 0x6c, 0x2f, 0x4f, 0xf2, 0xf5, 0x4f, 0xa5, 0xd4, 0x83, 0xad, 0x96, 0xa2, 0xf1, 0x87, 0x58, 0x04}; +// The Google ECDSA P256 root key for the Endpoint Encryption Key chain, encoded as COSE_Sign1 +inline constexpr uint8_t kCoseEncodedEcdsa256RootCert[] = { + 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x58, 0x4d, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, + 0x58, 0x20, 0xf7, 0x14, 0x8a, 0xdb, 0x97, 0xf4, 0xcc, 0x53, 0xef, 0xd2, 0x64, 0x11, 0xc4, 0xe3, + 0x75, 0x1f, 0x66, 0x1f, 0xa4, 0x71, 0x0c, 0x6c, 0xcf, 0xfa, 0x09, 0x46, 0x80, 0x74, 0x87, 0x54, + 0xf2, 0xad, 0x22, 0x58, 0x20, 0x5e, 0x7f, 0x5b, 0xf6, 0xec, 0xe4, 0xf6, 0x19, 0xcc, 0xff, 0x13, + 0x37, 0xfd, 0x0f, 0xa1, 0xc8, 0x93, 0xdb, 0x18, 0x06, 0x76, 0xc4, 0x5d, 0xe6, 0xd7, 0x6a, 0x77, + 0x86, 0xc3, 0x2d, 0xaf, 0x8f, 0x58, 0x40, 0x2f, 0x97, 0x8e, 0x42, 0xfb, 0xbe, 0x07, 0x2d, 0x95, + 0x47, 0x85, 0x47, 0x93, 0x40, 0xb0, 0x1f, 0xd4, 0x9b, 0x47, 0xa4, 0xc4, 0x44, 0xa9, 0xf2, 0xa1, + 0x07, 0x87, 0x10, 0xc7, 0x9f, 0xcb, 0x11, 0xf4, 0xbf, 0x9f, 0xe8, 0x3b, 0xe0, 0xe7, 0x34, 0x4c, + 0x15, 0xfc, 0x7b, 0xc3, 0x7e, 0x33, 0x05, 0xf4, 0xd1, 0x34, 0x3c, 0xed, 0x02, 0x04, 0x60, 0x7a, + 0x15, 0xe0, 0x79, 0xd3, 0x8a, 0xff, 0x24}; + +// The Google ECDSA P256 Endpoint Encryption Key certificate, encoded as COSE_Sign1 +inline constexpr uint8_t kCoseEncodedEcdsa256GeekCert[] = { + 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x58, 0x71, 0xa6, 0x01, 0x02, 0x02, 0x58, 0x20, 0x35, 0x73, + 0xb7, 0x3f, 0xa0, 0x8a, 0x80, 0x89, 0xb1, 0x26, 0x67, 0xe9, 0xcb, 0x7c, 0x75, 0xa1, 0xaf, 0x02, + 0x61, 0xfc, 0x6e, 0x65, 0x03, 0x91, 0x3b, 0xd3, 0x4b, 0x7d, 0x14, 0x94, 0x3e, 0x46, 0x03, 0x38, + 0x18, 0x20, 0x01, 0x21, 0x58, 0x20, 0xe0, 0x41, 0xcf, 0x2f, 0x0f, 0x34, 0x0f, 0x1c, 0x33, 0x2c, + 0x41, 0xb0, 0xcf, 0xd7, 0x0c, 0x30, 0x55, 0x35, 0xd2, 0x1e, 0x6a, 0x47, 0x13, 0x4b, 0x2e, 0xd1, + 0x48, 0x96, 0x7e, 0x24, 0x9c, 0x68, 0x22, 0x58, 0x20, 0x1f, 0xce, 0x45, 0xc5, 0xfb, 0x61, 0xba, + 0x81, 0x21, 0xf9, 0xe5, 0x05, 0x9b, 0x9b, 0x39, 0x0e, 0x76, 0x86, 0x86, 0x47, 0xb8, 0x1e, 0x2f, + 0x45, 0xf1, 0xce, 0xaf, 0xda, 0x3f, 0x80, 0x68, 0xdb, 0x58, 0x40, 0x8c, 0xb3, 0xba, 0x7e, 0x20, + 0x3e, 0x32, 0xb0, 0x68, 0xdf, 0x60, 0xd1, 0x1d, 0x7d, 0xf0, 0xac, 0x38, 0x8e, 0x51, 0xbc, 0xff, + 0x6c, 0xe1, 0x67, 0x3b, 0x4a, 0x79, 0xbc, 0x56, 0x78, 0xb3, 0x99, 0xd8, 0x7c, 0x8a, 0x07, 0xd8, + 0xda, 0xb5, 0xb5, 0x7f, 0x71, 0xf4, 0xd8, 0x6b, 0xdf, 0x33, 0x27, 0x34, 0x7b, 0x65, 0xd1, 0x2a, + 0xeb, 0x86, 0x99, 0x98, 0xab, 0x3a, 0xb4, 0x80, 0xaa, 0xbd, 0x50}; + /** * Generates random bytes. */ @@ -64,15 +92,15 @@ struct EekChain { }; /** - * Generates an X25518 EEK with the specified eekId and an Ed25519 chain of the - * specified length. All keys are generated randomly. + * Based on the supportedEekCurve, Generates an X25519/ECDH with the specified eekId + * and an Ed25519/ECDSA chain of the specified length. All keys are generated randomly. */ -ErrMsgOr generateEekChain(size_t length, const bytevec& eekId); +ErrMsgOr generateEekChain(int32_t supportedEekCurve, size_t length, const bytevec& eekId); /** * Returns the CBOR-encoded, production Google Endpoint Encryption Key chain. */ -bytevec getProdEekChain(); +bytevec getProdEekChain(int32_t supportedEekCurve); struct BccEntryData { bytevec pubKey; diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp index 35cb891dac..0776282b27 100644 --- a/security/keymint/support/remote_prov_utils.cpp +++ b/security/keymint/support/remote_prov_utils.cpp @@ -17,10 +17,16 @@ #include #include +#include #include #include #include +#include +#include +#include +#include #include +#include #include #include @@ -30,6 +36,166 @@ constexpr uint32_t kBccPayloadIssuer = 1; constexpr uint32_t kBccPayloadSubject = 2; constexpr int32_t kBccPayloadSubjPubKey = -4670552; constexpr int32_t kBccPayloadKeyUsage = -4670553; +constexpr int kP256AffinePointSize = 32; + +using EC_KEY_Ptr = bssl::UniquePtr; +using EVP_PKEY_Ptr = bssl::UniquePtr; +using EVP_PKEY_CTX_Ptr = bssl::UniquePtr; + +ErrMsgOr ecKeyGetPrivateKey(const EC_KEY* ecKey) { + // Extract private key. + const BIGNUM* bignum = EC_KEY_get0_private_key(ecKey); + if (bignum == nullptr) { + return "Error getting bignum from private key"; + } + // Pad with zeros in case the length is lesser than 32. + bytevec privKey(32, 0); + BN_bn2binpad(bignum, privKey.data(), privKey.size()); + return privKey; +} + +ErrMsgOr ecKeyGetPublicKey(const EC_KEY* ecKey) { + // Extract public key. + auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + if (group.get() == nullptr) { + return "Error creating EC group by curve name"; + } + const EC_POINT* point = EC_KEY_get0_public_key(ecKey); + if (point == nullptr) return "Error getting ecpoint from public key"; + + int size = + EC_POINT_point2oct(group.get(), point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + if (size == 0) { + return "Error generating public key encoding"; + } + + bytevec publicKey; + publicKey.resize(size); + EC_POINT_point2oct(group.get(), point, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(), + publicKey.size(), nullptr); + return publicKey; +} + +ErrMsgOr> getAffineCoordinates(const bytevec& pubKey) { + auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + if (group.get() == nullptr) { + return "Error creating EC group by curve name"; + } + auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); + if (EC_POINT_oct2point(group.get(), point.get(), pubKey.data(), pubKey.size(), nullptr) != 1) { + return "Error decoding publicKey"; + } + BIGNUM_Ptr x(BN_new()); + BIGNUM_Ptr y(BN_new()); + BN_CTX_Ptr ctx(BN_CTX_new()); + if (!ctx.get()) return "Failed to create BN_CTX instance"; + + if (!EC_POINT_get_affine_coordinates_GFp(group.get(), point.get(), x.get(), y.get(), + ctx.get())) { + return "Failed to get affine coordinates from ECPoint"; + } + bytevec pubX(kP256AffinePointSize); + bytevec pubY(kP256AffinePointSize); + if (BN_bn2binpad(x.get(), pubX.data(), kP256AffinePointSize) != kP256AffinePointSize) { + return "Error in converting absolute value of x coordinate to big-endian"; + } + if (BN_bn2binpad(y.get(), pubY.data(), kP256AffinePointSize) != kP256AffinePointSize) { + return "Error in converting absolute value of y coordinate to big-endian"; + } + return std::make_tuple(std::move(pubX), std::move(pubY)); +} + +ErrMsgOr> generateEc256KeyPair() { + auto ec_key = EC_KEY_Ptr(EC_KEY_new()); + if (ec_key.get() == nullptr) { + return "Failed to allocate ec key"; + } + + auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + if (group.get() == nullptr) { + return "Error creating EC group by curve name"; + } + + if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 || + EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) { + return "Error generating key"; + } + + auto privKey = ecKeyGetPrivateKey(ec_key.get()); + if (!privKey) return privKey.moveMessage(); + + auto pubKey = ecKeyGetPublicKey(ec_key.get()); + if (!pubKey) return pubKey.moveMessage(); + + return std::make_tuple(pubKey.moveValue(), privKey.moveValue()); +} + +ErrMsgOr> generateX25519KeyPair() { + /* Generate X25519 key pair */ + bytevec pubKey(X25519_PUBLIC_VALUE_LEN); + bytevec privKey(X25519_PRIVATE_KEY_LEN); + X25519_keypair(pubKey.data(), privKey.data()); + return std::make_tuple(std::move(pubKey), std::move(privKey)); +} + +ErrMsgOr> generateED25519KeyPair() { + /* Generate ED25519 key pair */ + bytevec pubKey(ED25519_PUBLIC_KEY_LEN); + bytevec privKey(ED25519_PRIVATE_KEY_LEN); + ED25519_keypair(pubKey.data(), privKey.data()); + return std::make_tuple(std::move(pubKey), std::move(privKey)); +} + +ErrMsgOr> generateKeyPair(int32_t supportedEekCurve, bool isEek) { + switch (supportedEekCurve) { + case RpcHardwareInfo::CURVE_25519: + if (isEek) { + return generateX25519KeyPair(); + } + return generateED25519KeyPair(); + case RpcHardwareInfo::CURVE_P256: + return generateEc256KeyPair(); + default: + return "Unknown EEK Curve."; + } +} + +ErrMsgOr constructCoseKey(int32_t supportedEekCurve, const bytevec& eekId, + const bytevec& pubKey) { + CoseKeyType keyType; + CoseKeyAlgorithm algorithm; + CoseKeyCurve curve; + bytevec pubX; + bytevec pubY; + switch (supportedEekCurve) { + case RpcHardwareInfo::CURVE_25519: + keyType = OCTET_KEY_PAIR; + algorithm = (eekId.empty()) ? EDDSA : ECDH_ES_HKDF_256; + curve = (eekId.empty()) ? ED25519 : cppcose::X25519; + pubX = pubKey; + break; + case RpcHardwareInfo::CURVE_P256: { + keyType = EC2; + algorithm = (eekId.empty()) ? ES256 : ECDH_ES_HKDF_256; + curve = P256; + auto affineCoordinates = getAffineCoordinates(pubKey); + if (!affineCoordinates) return affineCoordinates.moveMessage(); + std::tie(pubX, pubY) = affineCoordinates.moveValue(); + } break; + default: + return "Unknown EEK Curve."; + } + cppbor::Map coseKey = cppbor::Map() + .add(CoseKey::KEY_TYPE, keyType) + .add(CoseKey::ALGORITHM, algorithm) + .add(CoseKey::CURVE, curve) + .add(CoseKey::PUBKEY_X, pubX); + + if (!pubY.empty()) coseKey.add(CoseKey::PUBKEY_Y, pubY); + if (!eekId.empty()) coseKey.add(CoseKey::KEY_ID, eekId); + + return coseKey.canonicalize().encode(); +} bytevec kTestMacKey(32 /* count */, 0 /* byte value */); @@ -39,7 +205,17 @@ bytevec randomBytes(size_t numBytes) { return retval; } -ErrMsgOr generateEekChain(size_t length, const bytevec& eekId) { +ErrMsgOr constructCoseSign1(int32_t supportedEekCurve, const bytevec& key, + const bytevec& payload, const bytevec& aad) { + if (supportedEekCurve == RpcHardwareInfo::CURVE_P256) { + return constructECDSACoseSign1(key, {} /* protectedParams */, payload, aad); + } else { + return cppcose::constructCoseSign1(key, payload, aad); + } +} + +ErrMsgOr generateEekChain(int32_t supportedEekCurve, size_t length, + const bytevec& eekId) { if (length < 2) { return "EEK chain must contain at least 2 certs."; } @@ -48,59 +224,62 @@ ErrMsgOr generateEekChain(size_t length, const bytevec& eekId) { bytevec prev_priv_key; for (size_t i = 0; i < length - 1; ++i) { - bytevec pub_key(ED25519_PUBLIC_KEY_LEN); - bytevec priv_key(ED25519_PRIVATE_KEY_LEN); - - ED25519_keypair(pub_key.data(), priv_key.data()); + auto keyPair = generateKeyPair(supportedEekCurve, false); + if (!keyPair) keyPair.moveMessage(); + auto [pub_key, priv_key] = keyPair.moveValue(); // The first signing key is self-signed. if (prev_priv_key.empty()) prev_priv_key = priv_key; - auto coseSign1 = constructCoseSign1(prev_priv_key, - cppbor::Map() /* payload CoseKey */ - .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) - .add(CoseKey::ALGORITHM, EDDSA) - .add(CoseKey::CURVE, ED25519) - .add(CoseKey::PUBKEY_X, pub_key) - .canonicalize() - .encode(), - {} /* AAD */); + auto coseKey = constructCoseKey(supportedEekCurve, {}, pub_key); + if (!coseKey) return coseKey.moveMessage(); + + auto coseSign1 = + constructCoseSign1(supportedEekCurve, prev_priv_key, coseKey.moveValue(), {} /* AAD */); if (!coseSign1) return coseSign1.moveMessage(); eekChain.add(coseSign1.moveValue()); prev_priv_key = priv_key; } + auto keyPair = generateKeyPair(supportedEekCurve, true); + if (!keyPair) keyPair.moveMessage(); + auto [pub_key, priv_key] = keyPair.moveValue(); - bytevec pub_key(X25519_PUBLIC_VALUE_LEN); - bytevec priv_key(X25519_PRIVATE_KEY_LEN); - X25519_keypair(pub_key.data(), priv_key.data()); + auto coseKey = constructCoseKey(supportedEekCurve, eekId, pub_key); + if (!coseKey) return coseKey.moveMessage(); - auto coseSign1 = constructCoseSign1(prev_priv_key, - cppbor::Map() /* payload CoseKey */ - .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) - .add(CoseKey::KEY_ID, eekId) - .add(CoseKey::ALGORITHM, ECDH_ES_HKDF_256) - .add(CoseKey::CURVE, cppcose::X25519) - .add(CoseKey::PUBKEY_X, pub_key) - .canonicalize() - .encode(), - {} /* AAD */); + auto coseSign1 = + constructCoseSign1(supportedEekCurve, prev_priv_key, coseKey.moveValue(), {} /* AAD */); if (!coseSign1) return coseSign1.moveMessage(); eekChain.add(coseSign1.moveValue()); + if (supportedEekCurve == RpcHardwareInfo::CURVE_P256) { + // convert ec public key to x and y co-ordinates. + auto affineCoordinates = getAffineCoordinates(pub_key); + if (!affineCoordinates) return affineCoordinates.moveMessage(); + auto [pubX, pubY] = affineCoordinates.moveValue(); + pub_key.clear(); + pub_key.insert(pub_key.begin(), pubX.begin(), pubX.end()); + pub_key.insert(pub_key.end(), pubY.begin(), pubY.end()); + } + return EekChain{eekChain.encode(), pub_key, priv_key}; } -bytevec getProdEekChain() { - bytevec prodEek; - prodEek.reserve(1 + sizeof(kCoseEncodedRootCert) + sizeof(kCoseEncodedGeekCert)); - - // In CBOR encoding, 0x82 indicates an array of two items - prodEek.push_back(0x82); - prodEek.insert(prodEek.end(), std::begin(kCoseEncodedRootCert), std::end(kCoseEncodedRootCert)); - prodEek.insert(prodEek.end(), std::begin(kCoseEncodedGeekCert), std::end(kCoseEncodedGeekCert)); - - return prodEek; +bytevec getProdEekChain(int32_t supportedEekCurve) { + cppbor::Array chain; + if (supportedEekCurve == RpcHardwareInfo::CURVE_P256) { + chain.add(cppbor::EncodedItem(bytevec(std::begin(kCoseEncodedEcdsa256RootCert), + std::end(kCoseEncodedEcdsa256RootCert)))); + chain.add(cppbor::EncodedItem(bytevec(std::begin(kCoseEncodedEcdsa256GeekCert), + std::end(kCoseEncodedEcdsa256GeekCert)))); + } else { + chain.add(cppbor::EncodedItem( + bytevec(std::begin(kCoseEncodedRootCert), std::end(kCoseEncodedRootCert)))); + chain.add(cppbor::EncodedItem( + bytevec(std::begin(kCoseEncodedGeekCert), std::end(kCoseEncodedGeekCert)))); + } + return chain.encode(); } ErrMsgOr validatePayloadAndFetchPubKey(const cppbor::Map* payload) { @@ -139,7 +318,8 @@ ErrMsgOr verifyAndParseCoseSign1Cwt(const cppbor::Array* coseSign1, } auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); - if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { + if (!algorithm || !algorithm->asInt() || + (algorithm->asInt()->value() != EDDSA && algorithm->asInt()->value() != ES256)) { return "Unsupported signature algorithm"; } @@ -152,15 +332,36 @@ ErrMsgOr verifyAndParseCoseSign1Cwt(const cppbor::Array* coseSign1, } bool selfSigned = signingCoseKey.empty(); - auto key = CoseKey::parseEd25519(selfSigned ? *serializedKey : signingCoseKey); - if (!key) return "Bad signing key: " + key.moveMessage(); - bytevec signatureInput = - cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); + cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); - if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), - key->getBstrValue(CoseKey::PUBKEY_X)->data())) { - return "Signature verification failed"; + if (algorithm->asInt()->value() == EDDSA) { + auto key = CoseKey::parseEd25519(selfSigned ? *serializedKey : signingCoseKey); + + if (!key) return "Bad signing key: " + key.moveMessage(); + + if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), + key->getBstrValue(CoseKey::PUBKEY_X)->data())) { + return "Signature verification failed"; + } + } else { // P256 + auto key = CoseKey::parseP256(selfSigned ? *serializedKey : signingCoseKey); + if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty() || + key->getBstrValue(CoseKey::PUBKEY_Y)->empty()) { + return "Bad signing key: " + key.moveMessage(); + } + auto publicKey = key->getEcPublicKey(); + if (!publicKey) return publicKey.moveMessage(); + + auto ecdsaDerSignature = ecdsaCoseSignatureToDer(signature->value()); + if (!ecdsaDerSignature) return ecdsaDerSignature.moveMessage(); + + // convert public key to uncompressed form. + publicKey->insert(publicKey->begin(), 0x04); + + if (!verifyEcdsaDigest(publicKey.moveValue(), sha256(signatureInput), *ecdsaDerSignature)) { + return "Signature verification failed"; + } } return serializedKey.moveValue(); diff --git a/security/keymint/support/remote_prov_utils_test.cpp b/security/keymint/support/remote_prov_utils_test.cpp index 8697c5190f..e1c4467a64 100644 --- a/security/keymint/support/remote_prov_utils_test.cpp +++ b/security/keymint/support/remote_prov_utils_test.cpp @@ -14,8 +14,12 @@ * limitations under the License. */ +#include "cppbor.h" +#include "keymaster/cppcose/cppcose.h" +#include #include #include +#include #include #include #include @@ -23,25 +27,120 @@ #include #include #include -#include -#include "cppbor.h" -#include "keymaster/cppcose/cppcose.h" namespace aidl::android::hardware::security::keymint::remote_prov { namespace { using ::keymaster::KeymasterBlob; -using ::keymaster::validateAndExtractEekPubAndId; +using ::keymaster::kStatusFailed; +using ::keymaster::kStatusInvalidEek; +using ::keymaster::StatusOr; using ::testing::ElementsAreArray; +using byte_view = std::basic_string_view; + +struct KeyInfoEcdsa { + CoseKeyCurve curve; + byte_view pubKeyX; + byte_view pubKeyY; + + bool operator==(const KeyInfoEcdsa& other) const { + return curve == other.curve && pubKeyX == other.pubKeyX && pubKeyY == other.pubKeyY; + } +}; + +// The production root signing key for Google ECDSA P256 Endpoint Encryption Key cert chains. +inline constexpr uint8_t kEcdsa256GeekRootX[] = { + 0xf7, 0x14, 0x8a, 0xdb, 0x97, 0xf4, 0xcc, 0x53, 0xef, 0xd2, 0x64, 0x11, 0xc4, 0xe3, 0x75, 0x1f, + 0x66, 0x1f, 0xa4, 0x71, 0x0c, 0x6c, 0xcf, 0xfa, 0x09, 0x46, 0x80, 0x74, 0x87, 0x54, 0xf2, 0xad}; + +inline constexpr uint8_t kEcdsa256GeekRootY[] = { + 0x5e, 0x7f, 0x5b, 0xf6, 0xec, 0xe4, 0xf6, 0x19, 0xcc, 0xff, 0x13, 0x37, 0xfd, 0x0f, 0xa1, 0xc8, + 0x93, 0xdb, 0x18, 0x06, 0x76, 0xc4, 0x5d, 0xe6, 0xd7, 0x6a, 0x77, 0x86, 0xc3, 0x2d, 0xaf, 0x8f}; + +// Hard-coded set of acceptable public COSE_Keys that can act as roots of EEK chains. +inline constexpr KeyInfoEcdsa kAuthorizedEcdsa256EekRoots[] = { + {CoseKeyCurve::P256, byte_view(kEcdsa256GeekRootX, sizeof(kEcdsa256GeekRootX)), + byte_view(kEcdsa256GeekRootY, sizeof(kEcdsa256GeekRootY))}, +}; + +static ErrMsgOr parseEcdh256(const bytevec& coseKey) { + auto key = CoseKey::parse(coseKey, EC2, ECDH_ES_HKDF_256, P256); + if (!key) return key; + + auto& pubkey_x = key->getMap().get(cppcose::CoseKey::PUBKEY_X); + auto& pubkey_y = key->getMap().get(cppcose::CoseKey::PUBKEY_Y); + if (!pubkey_x || !pubkey_y || !pubkey_x->asBstr() || !pubkey_y->asBstr() || + pubkey_x->asBstr()->value().size() != 32 || pubkey_y->asBstr()->value().size() != 32) { + return "Invalid P256 public key"; + } + + return key; +} + +StatusOr /* EEK pubX */, std::vector /* EEK pubY */, + std::vector /* EEK ID */>> +validateAndExtractEcdsa256EekPubAndId(bool testMode, + const KeymasterBlob& endpointEncryptionCertChain) { + auto [item, newPos, errMsg] = + cppbor::parse(endpointEncryptionCertChain.begin(), endpointEncryptionCertChain.end()); + if (!item || !item->asArray()) { + return kStatusFailed; + } + const cppbor::Array* certArr = item->asArray(); + std::vector lastPubKey; + for (size_t i = 0; i < certArr->size(); ++i) { + auto cosePubKey = + verifyAndParseCoseSign1(certArr->get(i)->asArray(), lastPubKey, {} /* AAD */); + if (!cosePubKey) { + return kStatusInvalidEek; + } + lastPubKey = *std::move(cosePubKey); + + // In prod mode the first pubkey should match a well-known Google public key. + if (!testMode && i == 0) { + auto parsedPubKey = CoseKey::parse(lastPubKey); + if (!parsedPubKey) { + return kStatusFailed; + } + auto curve = parsedPubKey->getIntValue(CoseKey::CURVE); + if (!curve) { + return kStatusInvalidEek; + } + auto rawPubX = parsedPubKey->getBstrValue(CoseKey::PUBKEY_X); + if (!rawPubX) { + return kStatusInvalidEek; + } + auto rawPubY = parsedPubKey->getBstrValue(CoseKey::PUBKEY_Y); + if (!rawPubY) { + return kStatusInvalidEek; + } + KeyInfoEcdsa matcher = {static_cast(*curve), + byte_view(rawPubX->data(), rawPubX->size()), + byte_view(rawPubY->data(), rawPubY->size())}; + if (std::find(std::begin(kAuthorizedEcdsa256EekRoots), + std::end(kAuthorizedEcdsa256EekRoots), + matcher) == std::end(kAuthorizedEcdsa256EekRoots)) { + return kStatusInvalidEek; + } + } + } + auto eek = parseEcdh256(lastPubKey); + if (!eek) { + return kStatusInvalidEek; + } + return std::make_tuple(eek->getBstrValue(CoseKey::PUBKEY_X).value(), + eek->getBstrValue(CoseKey::PUBKEY_Y).value(), + eek->getBstrValue(CoseKey::KEY_ID).value()); +} TEST(RemoteProvUtilsTest, GenerateEekChainInvalidLength) { - ASSERT_FALSE(generateEekChain(1, /*eekId=*/{})); + ASSERT_FALSE(generateEekChain(RpcHardwareInfo::CURVE_25519, 1, /*eekId=*/{})); } TEST(RemoteProvUtilsTest, GenerateEekChain) { bytevec kTestEekId = {'t', 'e', 's', 't', 'I', 'd', 0}; for (size_t length : {2, 3, 31}) { - auto get_eek_result = generateEekChain(length, kTestEekId); + auto get_eek_result = generateEekChain(RpcHardwareInfo::CURVE_25519, length, kTestEekId); ASSERT_TRUE(get_eek_result) << get_eek_result.message(); auto& [chain, pubkey, privkey] = *get_eek_result; @@ -57,7 +156,7 @@ TEST(RemoteProvUtilsTest, GenerateEekChain) { } TEST(RemoteProvUtilsTest, GetProdEekChain) { - auto chain = getProdEekChain(); + auto chain = getProdEekChain(RpcHardwareInfo::CURVE_25519); auto validation_result = validateAndExtractEekPubAndId( /*testMode=*/false, KeymasterBlob(chain.data(), chain.size())); @@ -97,5 +196,57 @@ TEST(RemoteProvUtilsTest, JsonEncodeCsr) { ASSERT_EQ(json, expected); } +TEST(RemoteProvUtilsTest, GenerateEcdsaEekChainInvalidLength) { + ASSERT_FALSE(generateEekChain(RpcHardwareInfo::CURVE_P256, 1, /*eekId=*/{})); +} + +TEST(RemoteProvUtilsTest, GenerateEcdsaEekChain) { + bytevec kTestEekId = {'t', 'e', 's', 't', 'I', 'd', 0}; + for (size_t length : {2, 3, 31}) { + auto get_eek_result = generateEekChain(RpcHardwareInfo::CURVE_P256, length, kTestEekId); + ASSERT_TRUE(get_eek_result) << get_eek_result.message(); + + auto& [chain, pubkey, privkey] = *get_eek_result; + + auto validation_result = validateAndExtractEcdsa256EekPubAndId( + /*testMode=*/true, KeymasterBlob(chain.data(), chain.size())); + ASSERT_TRUE(validation_result.isOk()); + + auto& [eekPubX, eekPubY, eekId] = *validation_result; + bytevec eekPub; + eekPub.insert(eekPub.begin(), eekPubX.begin(), eekPubX.end()); + eekPub.insert(eekPub.end(), eekPubY.begin(), eekPubY.end()); + EXPECT_THAT(eekId, ElementsAreArray(kTestEekId)); + EXPECT_THAT(eekPub, ElementsAreArray(pubkey)); + } +} + +TEST(RemoteProvUtilsTest, GetProdEcdsaEekChain) { + auto chain = getProdEekChain(RpcHardwareInfo::CURVE_P256); + + auto validation_result = validateAndExtractEcdsa256EekPubAndId( + /*testMode=*/false, KeymasterBlob(chain.data(), chain.size())); + ASSERT_TRUE(validation_result.isOk()) << "Error: " << validation_result.moveError(); + + auto& [eekPubX, eekPubY, eekId] = *validation_result; + + auto [geekCert, ignoredNewPos, error] = + cppbor::parse(kCoseEncodedEcdsa256GeekCert, sizeof(kCoseEncodedEcdsa256GeekCert)); + ASSERT_NE(geekCert, nullptr) << "Error: " << error; + ASSERT_NE(geekCert->asArray(), nullptr); + + auto& encodedGeekCoseKey = geekCert->asArray()->get(kCoseSign1Payload); + ASSERT_NE(encodedGeekCoseKey, nullptr); + ASSERT_NE(encodedGeekCoseKey->asBstr(), nullptr); + + auto geek = CoseKey::parse(encodedGeekCoseKey->asBstr()->value()); + ASSERT_TRUE(geek) << "Error: " << geek.message(); + + const std::vector empty; + EXPECT_THAT(eekId, ElementsAreArray(geek->getBstrValue(CoseKey::KEY_ID).value_or(empty))); + EXPECT_THAT(eekPubX, ElementsAreArray(geek->getBstrValue(CoseKey::PUBKEY_X).value_or(empty))); + EXPECT_THAT(eekPubY, ElementsAreArray(geek->getBstrValue(CoseKey::PUBKEY_Y).value_or(empty))); +} + } // namespace } // namespace aidl::android::hardware::security::keymint::remote_prov