Support for P256 curve in RKP for Strongbox

Test: Run Rkp Vts tests.

Change-Id: Ic38fd2318dd8749ae125f1e78d25f2722bd367e5
This commit is contained in:
subrahmanyaman 2022-02-02 23:10:55 +00:00
parent 1048b42ea6
commit fb213d6031
5 changed files with 472 additions and 70 deletions

View file

@ -169,6 +169,7 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::
provisionable_ = IRemotelyProvisionedComponent::fromBinder(binder);
}
ASSERT_NE(provisionable_, nullptr);
ASSERT_TRUE(provisionable_->getHardwareInfo(&rpcHardwareInfo).isOk());
}
static vector<string> build_params() {
@ -178,6 +179,7 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam<std::
protected:
std::shared_ptr<IRemotelyProvisionedComponent> 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<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& 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<BccEntryData>* 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);

View file

@ -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",

View file

@ -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<EekChain> generateEekChain(size_t length, const bytevec& eekId);
ErrMsgOr<EekChain> 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;

View file

@ -17,10 +17,16 @@
#include <iterator>
#include <tuple>
#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
#include <android-base/properties.h>
#include <cppbor.h>
#include <json/json.h>
#include <keymaster/km_openssl/ec_key.h>
#include <keymaster/km_openssl/ecdsa_operation.h>
#include <keymaster/km_openssl/openssl_err.h>
#include <keymaster/km_openssl/openssl_utils.h>
#include <openssl/base64.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <remote_prov/remote_prov_utils.h>
@ -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<EC_KEY>;
using EVP_PKEY_Ptr = bssl::UniquePtr<EVP_PKEY>;
using EVP_PKEY_CTX_Ptr = bssl::UniquePtr<EVP_PKEY_CTX>;
ErrMsgOr<bytevec> 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<bytevec> 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<std::tuple<bytevec, bytevec>> 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<std::tuple<bytevec, bytevec>> 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<std::tuple<bytevec, bytevec>> 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<std::tuple<bytevec, bytevec>> 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<std::tuple<bytevec, bytevec>> 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<bytevec> 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<EekChain> generateEekChain(size_t length, const bytevec& eekId) {
ErrMsgOr<cppbor::Array> 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<EekChain> 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<EekChain> 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<bytevec> validatePayloadAndFetchPubKey(const cppbor::Map* payload) {
@ -139,7 +318,8 @@ ErrMsgOr<bytevec> 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<bytevec> 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();

View file

@ -14,8 +14,12 @@
* limitations under the License.
*/
#include "cppbor.h"
#include "keymaster/cppcose/cppcose.h"
#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
#include <android-base/properties.h>
#include <cppbor_parse.h>
#include <cstdint>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <keymaster/android_keymaster_utils.h>
@ -23,25 +27,120 @@
#include <keymaster/remote_provisioning_utils.h>
#include <openssl/curve25519.h>
#include <remote_prov/remote_prov_utils.h>
#include <cstdint>
#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<uint8_t>;
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<CoseKey> 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<std::tuple<std::vector<uint8_t> /* EEK pubX */, std::vector<uint8_t> /* EEK pubY */,
std::vector<uint8_t> /* 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<uint8_t> 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<CoseKeyCurve>(*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<uint8_t> 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