mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
Porting IRPC functionality.
This is the change that removes the functionality that has been shifted over to appropriate classes and contexts in system/keymaster. Test: atest VtsHalRemotelyProvisionedComponentTargetTest Change-Id: I491f4ef823868322ea6a804d88ca09662c099a44
This commit is contained in:
@@ -32,6 +32,7 @@ cc_library_static {
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libcppbor_external",
|
||||
"libcppcose_rkp",
|
||||
"libutils",
|
||||
"libsoft_attestation_cert",
|
||||
"libkeymaster_portable",
|
||||
@@ -92,6 +93,7 @@ cc_binary {
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libcppbor_external",
|
||||
"libcppcose_rkp",
|
||||
"libutils",
|
||||
"libsoft_attestation_cert",
|
||||
"libkeymaster_portable",
|
||||
|
||||
@@ -35,6 +35,7 @@ cc_test {
|
||||
],
|
||||
static_libs: [
|
||||
"libcppbor_external",
|
||||
"libcppcose_rkp",
|
||||
"libkeymaster_portable",
|
||||
"libpuresoftkeymasterdevice",
|
||||
"android.hardware.keymaster@4.0",
|
||||
|
||||
@@ -35,6 +35,7 @@ cc_library {
|
||||
"android.hardware.keymaster@4.0",
|
||||
"libcrypto",
|
||||
"libbase",
|
||||
"libcppcose_rkp",
|
||||
"libhidlbase",
|
||||
"libhardware",
|
||||
"libkeymaster_portable",
|
||||
|
||||
@@ -62,7 +62,7 @@ cc_library {
|
||||
"android.hardware.security.keymint-V1-ndk_platform",
|
||||
"libbinder_ndk",
|
||||
"libcppbor_external",
|
||||
"libcppcose",
|
||||
"libcppcose_rkp",
|
||||
"libcrypto",
|
||||
"libkeymaster_portable",
|
||||
"libkeymint",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#include <KeyMintUtils.h>
|
||||
#include <cppcose/cppcose.h>
|
||||
#include <keymaster/cppcose/cppcose.h>
|
||||
#include <keymaster/keymaster_configuration.h>
|
||||
#include <remote_prov/remote_prov_utils.h>
|
||||
|
||||
@@ -46,18 +46,8 @@ 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;
|
||||
constexpr uint32_t kAffinePointLength = 32;
|
||||
|
||||
struct AStatusDeleter {
|
||||
void operator()(AStatus* p) { AStatus_delete(p); }
|
||||
};
|
||||
@@ -125,167 +115,10 @@ class StatusOr {
|
||||
std::optional<T> value_;
|
||||
};
|
||||
|
||||
StatusOr<std::pair<bytevec /* EEK pub */, bytevec /* EEK ID */>> validateAndExtractEekPubAndId(
|
||||
bool testMode, const bytevec& endpointEncryptionCertChain) {
|
||||
auto [item, newPos, errMsg] = cppbor::parse(endpointEncryptionCertChain);
|
||||
|
||||
if (!item || !item->asArray()) {
|
||||
return Status("Error parsing EEK chain" + errMsg);
|
||||
}
|
||||
|
||||
const cppbor::Array* certArr = item->asArray();
|
||||
bytevec lastPubKey;
|
||||
for (int i = 0; i < certArr->size(); ++i) {
|
||||
auto cosePubKey = verifyAndParseCoseSign1(testMode, certArr->get(i)->asArray(),
|
||||
std::move(lastPubKey), bytevec{} /* AAD */);
|
||||
if (!cosePubKey) {
|
||||
return Status(STATUS_INVALID_EEK,
|
||||
"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 */);
|
||||
if (!eek) return Status(STATUS_INVALID_EEK, "Failed to get EEK: " + eek.moveMessage());
|
||||
|
||||
return std::make_pair(eek->getBstrValue(CoseKey::PUBKEY_X).value(),
|
||||
eek->getBstrValue(CoseKey::KEY_ID).value());
|
||||
}
|
||||
|
||||
StatusOr<bytevec /* pubkeys */> validateAndExtractPubkeys(bool testMode,
|
||||
const vector<MacedPublicKey>& keysToSign,
|
||||
const bytevec& macKey) {
|
||||
auto pubKeysToMac = cppbor::Array();
|
||||
for (auto& keyToSign : keysToSign) {
|
||||
auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(keyToSign.macedKey);
|
||||
if (!macedKeyItem || !macedKeyItem->asArray() ||
|
||||
macedKeyItem->asArray()->size() != kCoseMac0EntryCount) {
|
||||
return Status("Invalid COSE_Mac0 structure");
|
||||
}
|
||||
|
||||
auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr();
|
||||
auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr();
|
||||
if (!protectedParms || !unprotectedParms || !payload || !tag) {
|
||||
return Status("Invalid COSE_Mac0 contents");
|
||||
}
|
||||
|
||||
auto [protectedMap, __, errMsg] = cppbor::parse(protectedParms);
|
||||
if (!protectedMap || !protectedMap->asMap()) {
|
||||
return Status("Invalid Mac0 protected: " + errMsg);
|
||||
}
|
||||
auto& algo = protectedMap->asMap()->get(ALGORITHM);
|
||||
if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
|
||||
return Status("Unsupported Mac0 algorithm");
|
||||
}
|
||||
|
||||
auto pubKey = CoseKey::parse(payload->value(), EC2, ES256, P256);
|
||||
if (!pubKey) return Status(pubKey.moveMessage());
|
||||
|
||||
bool testKey = static_cast<bool>(pubKey->getMap().get(CoseKey::TEST_KEY));
|
||||
if (testMode && !testKey) {
|
||||
return Status(BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST,
|
||||
"Production key in test request");
|
||||
} else if (!testMode && testKey) {
|
||||
return Status(BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST,
|
||||
"Test key in production request");
|
||||
}
|
||||
|
||||
auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
|
||||
if (!macTag) return Status(STATUS_INVALID_MAC, macTag.moveMessage());
|
||||
if (macTag->size() != tag->value().size() ||
|
||||
CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
|
||||
return Status(STATUS_INVALID_MAC, "MAC tag mismatch");
|
||||
}
|
||||
|
||||
pubKeysToMac.add(pubKey->moveMap());
|
||||
}
|
||||
|
||||
return pubKeysToMac.encode();
|
||||
}
|
||||
|
||||
StatusOr<std::pair<bytevec, bytevec>> buildCosePublicKeyFromKmCert(
|
||||
const keymaster_blob_t* km_cert) {
|
||||
if (km_cert == nullptr) {
|
||||
return Status(STATUS_FAILED, "km_cert is a nullptr");
|
||||
}
|
||||
const uint8_t* temp = km_cert->data;
|
||||
X509* cert = d2i_X509(NULL, &temp, km_cert->data_length);
|
||||
if (cert == nullptr) {
|
||||
return Status(STATUS_FAILED, "d2i_X509 returned null when attempting to get the cert.");
|
||||
}
|
||||
EVP_PKEY* pubKey = X509_get_pubkey(cert);
|
||||
if (pubKey == nullptr) {
|
||||
return Status(STATUS_FAILED, "Boringssl failed to get the public key from the cert");
|
||||
}
|
||||
EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey);
|
||||
if (ecKey == nullptr) {
|
||||
return Status(STATUS_FAILED,
|
||||
"The key in the certificate returned from GenerateKey is not "
|
||||
"an EC key.");
|
||||
}
|
||||
const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey);
|
||||
BIGNUM x;
|
||||
BIGNUM y;
|
||||
BN_CTX* ctx = BN_CTX_new();
|
||||
if (ctx == nullptr) {
|
||||
return Status(STATUS_FAILED, "Memory allocation failure for BN_CTX");
|
||||
}
|
||||
if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey), jacobian_coords, &x, &y,
|
||||
ctx)) {
|
||||
return Status(STATUS_FAILED, "Failed to get affine coordinates");
|
||||
}
|
||||
bytevec x_bytestring(kAffinePointLength);
|
||||
bytevec y_bytestring(kAffinePointLength);
|
||||
if (BN_bn2binpad(&x, x_bytestring.data(), kAffinePointLength) != kAffinePointLength) {
|
||||
return Status(STATUS_FAILED, "Wrote incorrect number of bytes for x coordinate");
|
||||
}
|
||||
if (BN_bn2binpad(&y, y_bytestring.data(), kAffinePointLength) != kAffinePointLength) {
|
||||
return Status(STATUS_FAILED, "Wrote incorrect number of bytes for y coordinate");
|
||||
}
|
||||
BN_CTX_free(ctx);
|
||||
return std::make_pair(x_bytestring, y_bytestring);
|
||||
}
|
||||
|
||||
cppbor::Array buildCertReqRecipients(const bytevec& pubkey, const bytevec& kid) {
|
||||
return cppbor::Array() // Array of recipients
|
||||
.add(cppbor::Array() // Recipient
|
||||
.add(cppbor::Map() // Protected
|
||||
.add(ALGORITHM, ECDH_ES_HKDF_256)
|
||||
.canonicalize()
|
||||
.encode())
|
||||
.add(cppbor::Map() // Unprotected
|
||||
.add(COSE_KEY, cppbor::Map()
|
||||
.add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
|
||||
.add(CoseKey::CURVE, cppcose::X25519)
|
||||
.add(CoseKey::PUBKEY_X, pubkey)
|
||||
.canonicalize())
|
||||
.add(KEY_ID, kid)
|
||||
.canonicalize())
|
||||
.add(cppbor::Null())); // No ciphertext
|
||||
}
|
||||
|
||||
static keymaster_key_param_t kKeyMintEcdsaP256Params[] = {
|
||||
Authorization(TAG_PURPOSE, KM_PURPOSE_ATTEST_KEY),
|
||||
Authorization(TAG_ALGORITHM, KM_ALGORITHM_EC), Authorization(TAG_KEY_SIZE, 256),
|
||||
Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
|
||||
Authorization(TAG_EC_CURVE, KM_EC_CURVE_P_256), Authorization(TAG_NO_AUTH_REQUIRED),
|
||||
// The certificate generated by KM will be discarded, these values don't matter.
|
||||
Authorization(TAG_CERTIFICATE_NOT_BEFORE, 0), Authorization(TAG_CERTIFICATE_NOT_AFTER, 0)};
|
||||
|
||||
} // namespace
|
||||
|
||||
RemotelyProvisionedComponent::RemotelyProvisionedComponent(
|
||||
std::shared_ptr<keymint::AndroidKeyMintDevice> keymint) {
|
||||
std::tie(devicePrivKey_, bcc_) = generateBcc();
|
||||
impl_ = keymint->getKeymasterImpl();
|
||||
}
|
||||
|
||||
@@ -294,43 +127,15 @@ RemotelyProvisionedComponent::~RemotelyProvisionedComponent() {}
|
||||
ScopedAStatus RemotelyProvisionedComponent::generateEcdsaP256KeyPair(bool testMode,
|
||||
MacedPublicKey* macedPublicKey,
|
||||
bytevec* privateKeyHandle) {
|
||||
// TODO(jbires): The following should move from ->GenerateKey to ->GenerateRKPKey and everything
|
||||
// after the GenerateKey call should basically be moved into that new function call
|
||||
// as well once the issue with libcppbor in system/keymaster is sorted out
|
||||
GenerateKeyRequest request(impl_->message_version());
|
||||
request.key_description.Reinitialize(kKeyMintEcdsaP256Params,
|
||||
array_length(kKeyMintEcdsaP256Params));
|
||||
GenerateKeyResponse response(impl_->message_version());
|
||||
impl_->GenerateKey(request, &response);
|
||||
GenerateRkpKeyRequest request(impl_->message_version());
|
||||
request.test_mode = testMode;
|
||||
GenerateRkpKeyResponse response(impl_->message_version());
|
||||
impl_->GenerateRkpKey(request, &response);
|
||||
if (response.error != KM_ERROR_OK) {
|
||||
return km_utils::kmError2ScopedAStatus(response.error);
|
||||
return Status(-static_cast<int32_t>(response.error), "Failure in key generation.");
|
||||
}
|
||||
|
||||
if (response.certificate_chain.entry_count != 1) {
|
||||
// Error: Need the single non-signed certificate with the public key in it.
|
||||
return Status(STATUS_FAILED,
|
||||
"Expected to receive a single certificate from GenerateKey. Instead got: " +
|
||||
std::to_string(response.certificate_chain.entry_count));
|
||||
}
|
||||
auto affineCoords = buildCosePublicKeyFromKmCert(response.certificate_chain.begin());
|
||||
if (!affineCoords.isOk()) return affineCoords.moveError();
|
||||
cppbor::Map cosePublicKeyMap = cppbor::Map()
|
||||
.add(CoseKey::KEY_TYPE, EC2)
|
||||
.add(CoseKey::ALGORITHM, ES256)
|
||||
.add(CoseKey::CURVE, cppcose::P256)
|
||||
.add(CoseKey::PUBKEY_X, affineCoords->first)
|
||||
.add(CoseKey::PUBKEY_Y, affineCoords->second);
|
||||
if (testMode) {
|
||||
cosePublicKeyMap.add(CoseKey::TEST_KEY, cppbor::Null());
|
||||
}
|
||||
|
||||
bytevec cosePublicKey = cosePublicKeyMap.canonicalize().encode();
|
||||
|
||||
auto macedKey = constructCoseMac0(testMode ? remote_prov::kTestMacKey : macKey_,
|
||||
{} /* externalAad */, cosePublicKey);
|
||||
if (!macedKey) return Status(macedKey.moveMessage());
|
||||
|
||||
macedPublicKey->macedKey = macedKey->encode();
|
||||
macedPublicKey->macedKey = km_utils::kmBlob2vector(response.maced_public_key);
|
||||
*privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
@@ -339,126 +144,25 @@ ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest(
|
||||
bool testMode, const vector<MacedPublicKey>& keysToSign,
|
||||
const bytevec& endpointEncCertChain, const bytevec& challenge, DeviceInfo* deviceInfo,
|
||||
ProtectedData* protectedData, bytevec* keysToSignMac) {
|
||||
auto pubKeysToSign = validateAndExtractPubkeys(testMode, keysToSign,
|
||||
testMode ? remote_prov::kTestMacKey : macKey_);
|
||||
if (!pubKeysToSign.isOk()) return pubKeysToSign.moveError();
|
||||
|
||||
bytevec ephemeralMacKey = remote_prov::randomBytes(SHA256_DIGEST_LENGTH);
|
||||
|
||||
auto pubKeysToSignMac = generateCoseMac0Mac(ephemeralMacKey, bytevec{}, *pubKeysToSign);
|
||||
if (!pubKeysToSignMac) return Status(pubKeysToSignMac.moveMessage());
|
||||
*keysToSignMac = *std::move(pubKeysToSignMac);
|
||||
|
||||
bytevec devicePrivKey;
|
||||
cppbor::Array bcc;
|
||||
if (testMode) {
|
||||
std::tie(devicePrivKey, bcc) = generateBcc();
|
||||
} else {
|
||||
devicePrivKey = devicePrivKey_;
|
||||
bcc = bcc_.clone();
|
||||
GenerateCsrRequest request(impl_->message_version());
|
||||
request.test_mode = testMode;
|
||||
request.num_keys = keysToSign.size();
|
||||
request.keys_to_sign_array = new KeymasterBlob[keysToSign.size()];
|
||||
for (int i = 0; i < keysToSign.size(); i++) {
|
||||
request.SetKeyToSign(i, keysToSign[i].macedKey.data(), keysToSign[i].macedKey.size());
|
||||
}
|
||||
request.SetEndpointEncCertChain(endpointEncCertChain.data(), endpointEncCertChain.size());
|
||||
request.SetChallenge(challenge.data(), challenge.size());
|
||||
GenerateCsrResponse response(impl_->message_version());
|
||||
impl_->GenerateCsr(request, &response);
|
||||
|
||||
std::unique_ptr<cppbor::Map> deviceInfoMap = createDeviceInfo();
|
||||
deviceInfo->deviceInfo = deviceInfoMap->encode();
|
||||
auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */, //
|
||||
ephemeralMacKey /* Payload */,
|
||||
cppbor::Array() /* AAD */
|
||||
.add(challenge)
|
||||
.add(std::move(deviceInfoMap))
|
||||
.encode());
|
||||
if (!signedMac) return Status(signedMac.moveMessage());
|
||||
|
||||
bytevec ephemeralPrivKey(X25519_PRIVATE_KEY_LEN);
|
||||
bytevec ephemeralPubKey(X25519_PUBLIC_VALUE_LEN);
|
||||
X25519_keypair(ephemeralPubKey.data(), ephemeralPrivKey.data());
|
||||
|
||||
auto eek = validateAndExtractEekPubAndId(testMode, endpointEncCertChain);
|
||||
if (!eek.isOk()) return eek.moveError();
|
||||
|
||||
auto sessionKey = x25519_HKDF_DeriveKey(ephemeralPubKey, ephemeralPrivKey, eek->first,
|
||||
true /* senderIsA */);
|
||||
if (!sessionKey) return Status(sessionKey.moveMessage());
|
||||
|
||||
auto coseEncrypted =
|
||||
constructCoseEncrypt(*sessionKey, remote_prov::randomBytes(kAesGcmNonceLength),
|
||||
cppbor::Array() // payload
|
||||
.add(signedMac.moveValue())
|
||||
.add(std::move(bcc))
|
||||
.encode(),
|
||||
{}, // aad
|
||||
buildCertReqRecipients(ephemeralPubKey, eek->second));
|
||||
|
||||
if (!coseEncrypted) return Status(coseEncrypted.moveMessage());
|
||||
protectedData->protectedData = coseEncrypted->encode();
|
||||
|
||||
if (response.error != KM_ERROR_OK) {
|
||||
return Status(-static_cast<int32_t>(response.error), "Failure in CSR Generation.");
|
||||
}
|
||||
deviceInfo->deviceInfo = km_utils::kmBlob2vector(response.device_info_blob);
|
||||
protectedData->protectedData = km_utils::kmBlob2vector(response.protected_data_blob);
|
||||
*keysToSignMac = km_utils::kmBlob2vector(response.keys_to_sign_mac);
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
bytevec RemotelyProvisionedComponent::deriveBytesFromHbk(const string& context,
|
||||
size_t numBytes) const {
|
||||
bytevec fakeHbk(32, 0);
|
||||
bytevec result(numBytes);
|
||||
|
||||
// TODO(swillden): Figure out if HKDF can fail. It doesn't seem like it should be able to,
|
||||
// but the function does return an error code.
|
||||
HKDF(result.data(), numBytes, //
|
||||
EVP_sha256(), //
|
||||
fakeHbk.data(), fakeHbk.size(), //
|
||||
nullptr /* salt */, 0 /* salt len */, //
|
||||
reinterpret_cast<const uint8_t*>(context.data()), context.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<cppbor::Map> RemotelyProvisionedComponent::createDeviceInfo() const {
|
||||
auto result = std::make_unique<cppbor::Map>(cppbor::Map());
|
||||
|
||||
// The following placeholders show how the DeviceInfo map would be populated.
|
||||
// result->add(cppbor::Tstr("brand"), cppbor::Tstr("Google"));
|
||||
// result->add(cppbor::Tstr("manufacturer"), cppbor::Tstr("Google"));
|
||||
// result->add(cppbor::Tstr("product"), cppbor::Tstr("Fake"));
|
||||
// result->add(cppbor::Tstr("model"), cppbor::Tstr("Imaginary"));
|
||||
// result->add(cppbor::Tstr("board"), cppbor::Tstr("Chess"));
|
||||
// result->add(cppbor::Tstr("vb_state"), cppbor::Tstr("orange"));
|
||||
// result->add(cppbor::Tstr("bootloader_state"), cppbor::Tstr("unlocked"));
|
||||
// result->add(cppbor::Tstr("os_version"), cppbor::Tstr("SC"));
|
||||
// result->add(cppbor::Tstr("system_patch_level"), cppbor::Uint(20210331));
|
||||
// result->add(cppbor::Tstr("boot_patch_level"), cppbor::Uint(20210331));
|
||||
// result->add(cppbor::Tstr("vendor_patch_level"), cppbor::Uint(20210331));
|
||||
|
||||
result->canonicalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<bytevec /* privKey */, cppbor::Array /* BCC */>
|
||||
RemotelyProvisionedComponent::generateBcc() {
|
||||
bytevec privKey(ED25519_PRIVATE_KEY_LEN);
|
||||
bytevec pubKey(ED25519_PUBLIC_KEY_LEN);
|
||||
|
||||
ED25519_keypair(pubKey.data(), privKey.data());
|
||||
|
||||
auto coseKey = cppbor::Map()
|
||||
.add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
|
||||
.add(CoseKey::ALGORITHM, EDDSA)
|
||||
.add(CoseKey::CURVE, ED25519)
|
||||
.add(CoseKey::KEY_OPS, VERIFY)
|
||||
.add(CoseKey::PUBKEY_X, pubKey)
|
||||
.canonicalize()
|
||||
.encode();
|
||||
auto sign1Payload = cppbor::Map()
|
||||
.add(1 /* Issuer */, "Issuer")
|
||||
.add(2 /* Subject */, "Subject")
|
||||
.add(-4670552 /* Subject Pub Key */, coseKey)
|
||||
.add(-4670553 /* Key Usage (little-endian order) */,
|
||||
std::vector<uint8_t>{0x20} /* keyCertSign = 1<<5 */)
|
||||
.canonicalize()
|
||||
.encode();
|
||||
auto coseSign1 = constructCoseSign1(privKey, /* signing key */
|
||||
cppbor::Map(), /* extra protected */
|
||||
sign1Payload, {} /* AAD */);
|
||||
assert(coseSign1);
|
||||
|
||||
return {privKey, cppbor::Array().add(coseKey).add(coseSign1.moveValue())};
|
||||
}
|
||||
|
||||
} // namespace aidl::android::hardware::security::keymint
|
||||
|
||||
@@ -43,14 +43,6 @@ class RemotelyProvisionedComponent : public BnRemotelyProvisionedComponent {
|
||||
std::vector<uint8_t>* keysToSignMac) override;
|
||||
|
||||
private:
|
||||
// TODO(swillden): Move these into an appropriate Context class.
|
||||
std::vector<uint8_t> deriveBytesFromHbk(const std::string& context, size_t numBytes) const;
|
||||
std::unique_ptr<cppbor::Map> createDeviceInfo() const;
|
||||
std::pair<std::vector<uint8_t>, cppbor::Array> generateBcc();
|
||||
|
||||
std::vector<uint8_t> macKey_ = deriveBytesFromHbk("Key to MAC public keys", 32);
|
||||
std::vector<uint8_t> devicePrivKey_;
|
||||
cppbor::Array bcc_;
|
||||
std::shared_ptr<::keymaster::AndroidKeymaster> impl_;
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ cc_test {
|
||||
"android.hardware.security.keymint-V1-ndk_platform",
|
||||
"android.hardware.security.secureclock-V1-ndk_platform",
|
||||
"libcppbor_external",
|
||||
"libcppcose",
|
||||
"libcppcose_rkp",
|
||||
"libkeymint_remote_prov_support",
|
||||
"libkeymint_vts_test_utils",
|
||||
],
|
||||
@@ -75,7 +75,7 @@ cc_test_library {
|
||||
"android.hardware.security.keymint-V1-ndk_platform",
|
||||
"android.hardware.security.secureclock-V1-ndk_platform",
|
||||
"libcppbor_external",
|
||||
"libcppcose",
|
||||
"libcppcose_rkp",
|
||||
"libgmock_ndk",
|
||||
"libkeymint_remote_prov_support",
|
||||
],
|
||||
@@ -92,20 +92,20 @@ cc_test {
|
||||
],
|
||||
shared_libs: [
|
||||
"libbinder_ndk",
|
||||
"libcppbor_external",
|
||||
"libcrypto",
|
||||
"libkeymaster_portable",
|
||||
"libpuresoftkeymasterdevice",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.security.keymint-V1-ndk_platform",
|
||||
"android.hardware.security.secureclock-V1-ndk_platform",
|
||||
"libcppcose",
|
||||
"libcppbor_external",
|
||||
"libcppcose_rkp",
|
||||
"libgmock_ndk",
|
||||
"libkeymaster_portable",
|
||||
"libkeymint",
|
||||
"libkeymint_support",
|
||||
"libkeymint_remote_prov_support",
|
||||
"libkeymint_vts_test_utils",
|
||||
"libpuresoftkeymasterdevice",
|
||||
"libremote_provisioner",
|
||||
],
|
||||
test_suites: [
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
#include <android-base/logging.h>
|
||||
#include <android/binder_manager.h>
|
||||
#include <cppbor_parse.h>
|
||||
#include <cppcose/cppcose.h>
|
||||
#include <cutils/properties.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <openssl/mem.h>
|
||||
#include <remote_prov/remote_prov_utils.h>
|
||||
|
||||
#include <keymaster/cppcose/cppcose.h>
|
||||
#include <keymint_support/attestation_record.h>
|
||||
#include <keymint_support/key_param_output.h>
|
||||
#include <keymint_support/keymint_utils.h>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#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 <keymaster/cppcose/cppcose.h>
|
||||
#include <keymaster/keymaster_configuration.h>
|
||||
#include <keymint_support/authorization_set.h>
|
||||
#include <openssl/ec.h>
|
||||
|
||||
@@ -57,25 +57,8 @@ cc_library {
|
||||
"include",
|
||||
],
|
||||
shared_libs: [
|
||||
"libcppcose",
|
||||
"libcppbor_external",
|
||||
"libcppcose_rkp",
|
||||
"libcrypto",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library {
|
||||
name: "libcppcose",
|
||||
vendor_available: true,
|
||||
host_supported: true,
|
||||
srcs: [
|
||||
"cppcose.cpp",
|
||||
],
|
||||
export_include_dirs: [
|
||||
"include",
|
||||
],
|
||||
shared_libs: [
|
||||
"libcppbor_external",
|
||||
"libcrypto",
|
||||
"liblog",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cppcose/cppcose.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
namespace cppcose {
|
||||
|
||||
namespace {
|
||||
|
||||
ErrMsgOr<bssl::UniquePtr<EVP_CIPHER_CTX>> aesGcmInitAndProcessAad(const bytevec& key,
|
||||
const bytevec& nonce,
|
||||
const bytevec& aad,
|
||||
bool encrypt) {
|
||||
if (key.size() != kAesGcmKeySize) return "Invalid key size";
|
||||
|
||||
bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
|
||||
if (!ctx) return "Failed to allocate cipher context";
|
||||
|
||||
if (!EVP_CipherInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, key.data(),
|
||||
nonce.data(), encrypt ? 1 : 0)) {
|
||||
return "Failed to initialize cipher";
|
||||
}
|
||||
|
||||
int outlen;
|
||||
if (!aad.empty() && !EVP_CipherUpdate(ctx.get(), nullptr /* out; null means AAD */, &outlen,
|
||||
aad.data(), aad.size())) {
|
||||
return "Failed to process AAD";
|
||||
}
|
||||
|
||||
return std::move(ctx);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
|
||||
const bytevec& payload) {
|
||||
auto macStructure = cppbor::Array()
|
||||
.add("MAC0")
|
||||
.add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
|
||||
.add(externalAad)
|
||||
.add(payload)
|
||||
.encode();
|
||||
|
||||
bytevec macTag(SHA256_DIGEST_LENGTH);
|
||||
uint8_t* out = macTag.data();
|
||||
unsigned int outLen;
|
||||
out = HMAC(EVP_sha256(), //
|
||||
macKey.data(), macKey.size(), //
|
||||
macStructure.data(), macStructure.size(), //
|
||||
out, &outLen);
|
||||
|
||||
assert(out != nullptr && outLen == macTag.size());
|
||||
if (out == nullptr || outLen != macTag.size()) {
|
||||
return "Error computing public key MAC";
|
||||
}
|
||||
|
||||
return macTag;
|
||||
}
|
||||
|
||||
ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
|
||||
const bytevec& payload) {
|
||||
auto tag = generateCoseMac0Mac(macKey, externalAad, payload);
|
||||
if (!tag) return tag.moveMessage();
|
||||
|
||||
return cppbor::Array()
|
||||
.add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
|
||||
.add(cppbor::Map() /* unprotected */)
|
||||
.add(payload)
|
||||
.add(tag.moveValue());
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem) {
|
||||
auto mac = macItem ? macItem->asArray() : nullptr;
|
||||
if (!mac || mac->size() != kCoseMac0EntryCount) {
|
||||
return "Invalid COSE_Mac0";
|
||||
}
|
||||
|
||||
auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
auto payload = mac->get(kCoseMac0Payload)->asBstr();
|
||||
auto tag = mac->get(kCoseMac0Tag)->asBstr();
|
||||
if (!protectedParms || !unprotectedParms || !payload || !tag) {
|
||||
return "Invalid COSE_Mac0 contents";
|
||||
}
|
||||
|
||||
return payload->value();
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
|
||||
const bytevec& macKey) {
|
||||
auto mac = macItem ? macItem->asArray() : nullptr;
|
||||
if (!mac || mac->size() != kCoseMac0EntryCount) {
|
||||
return "Invalid COSE_Mac0";
|
||||
}
|
||||
|
||||
auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
|
||||
auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asMap();
|
||||
auto payload = mac->get(kCoseMac0Payload)->asBstr();
|
||||
auto tag = mac->get(kCoseMac0Tag)->asBstr();
|
||||
if (!protectedParms || !unprotectedParms || !payload || !tag) {
|
||||
return "Invalid COSE_Mac0 contents";
|
||||
}
|
||||
|
||||
auto [protectedMap, _, errMsg] = cppbor::parse(protectedParms);
|
||||
if (!protectedMap || !protectedMap->asMap()) {
|
||||
return "Invalid Mac0 protected: " + errMsg;
|
||||
}
|
||||
auto& algo = protectedMap->asMap()->get(ALGORITHM);
|
||||
if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
|
||||
return "Unsupported Mac0 algorithm";
|
||||
}
|
||||
|
||||
auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
|
||||
if (!macTag) return macTag.moveMessage();
|
||||
|
||||
if (macTag->size() != tag->value().size() ||
|
||||
CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
|
||||
return "MAC tag mismatch";
|
||||
}
|
||||
|
||||
return payload->value();
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
|
||||
const bytevec& payload, const bytevec& aad) {
|
||||
bytevec signatureInput = cppbor::Array()
|
||||
.add("Signature1") //
|
||||
.add(protectedParams)
|
||||
.add(aad)
|
||||
.add(payload)
|
||||
.encode();
|
||||
|
||||
if (key.size() != ED25519_PRIVATE_KEY_LEN) return "Invalid signing key";
|
||||
bytevec signature(ED25519_SIGNATURE_LEN);
|
||||
if (!ED25519_sign(signature.data(), signatureInput.data(), signatureInput.size(), key.data())) {
|
||||
return "Signing failed";
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map protectedParams,
|
||||
const bytevec& payload, const bytevec& aad) {
|
||||
bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode();
|
||||
auto signature = createCoseSign1Signature(key, protParms, payload, aad);
|
||||
if (!signature) return signature.moveMessage();
|
||||
|
||||
return cppbor::Array()
|
||||
.add(protParms)
|
||||
.add(cppbor::Map() /* unprotected parameters */)
|
||||
.add(payload)
|
||||
.add(*signature);
|
||||
}
|
||||
|
||||
ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
|
||||
const bytevec& aad) {
|
||||
return constructCoseSign1(key, {} /* protectedParams */, payload, aad);
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1,
|
||||
const bytevec& signingCoseKey, const bytevec& aad) {
|
||||
if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) {
|
||||
return "Invalid COSE_Sign1";
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams);
|
||||
if (!parsedProtParams) {
|
||||
return errMsg + " when parsing protected params.";
|
||||
}
|
||||
if (!parsedProtParams->asMap()) {
|
||||
return "Protected params must be a map";
|
||||
}
|
||||
|
||||
auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
|
||||
if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) {
|
||||
return "Unsupported signature algorithm";
|
||||
}
|
||||
|
||||
if (!ignoreSignature) {
|
||||
bool selfSigned = signingCoseKey.empty();
|
||||
auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey);
|
||||
if (!key) return "Bad signing key: " + key.moveMessage();
|
||||
|
||||
bytevec signatureInput = 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";
|
||||
}
|
||||
}
|
||||
|
||||
return payload->value();
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& protectedParams,
|
||||
const bytevec& plaintextPayload, const bytevec& aad) {
|
||||
auto ciphertext = aesGcmEncrypt(key, nonce,
|
||||
cppbor::Array() // Enc strucure as AAD
|
||||
.add("Encrypt") // Context
|
||||
.add(protectedParams) // Protected
|
||||
.add(aad) // External AAD
|
||||
.encode(),
|
||||
plaintextPayload);
|
||||
|
||||
if (!ciphertext) return ciphertext.moveMessage();
|
||||
return ciphertext.moveValue();
|
||||
}
|
||||
|
||||
ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& plaintextPayload, const bytevec& aad,
|
||||
cppbor::Array recipients) {
|
||||
auto encryptProtectedHeader = cppbor::Map() //
|
||||
.add(ALGORITHM, AES_GCM_256)
|
||||
.canonicalize()
|
||||
.encode();
|
||||
|
||||
auto ciphertext =
|
||||
createCoseEncryptCiphertext(key, nonce, encryptProtectedHeader, plaintextPayload, aad);
|
||||
if (!ciphertext) return ciphertext.moveMessage();
|
||||
|
||||
return cppbor::Array()
|
||||
.add(encryptProtectedHeader) // Protected
|
||||
.add(cppbor::Map().add(IV, nonce).canonicalize()) // Unprotected
|
||||
.add(*ciphertext) // Payload
|
||||
.add(std::move(recipients));
|
||||
}
|
||||
|
||||
ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
|
||||
const cppbor::Item* coseEncrypt) {
|
||||
if (!coseEncrypt || !coseEncrypt->asArray() ||
|
||||
coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
|
||||
return "Invalid COSE_Encrypt";
|
||||
}
|
||||
|
||||
auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
|
||||
if (!recipients || !recipients->asArray() || recipients->asArray()->size() != 1) {
|
||||
return "Invalid recipients list";
|
||||
}
|
||||
|
||||
auto& recipient = recipients->asArray()->get(0);
|
||||
if (!recipient || !recipient->asArray() || recipient->asArray()->size() != 3) {
|
||||
return "Invalid COSE_recipient";
|
||||
}
|
||||
|
||||
auto& ciphertext = recipient->asArray()->get(2);
|
||||
if (!ciphertext->asSimple() || !ciphertext->asSimple()->asNull()) {
|
||||
return "Unexpected value in recipients ciphertext field " +
|
||||
cppbor::prettyPrint(ciphertext.get());
|
||||
}
|
||||
|
||||
auto& protParms = recipient->asArray()->get(0);
|
||||
if (!protParms || !protParms->asBstr()) return "Invalid protected params";
|
||||
auto [parsedProtParms, _, errMsg] = cppbor::parse(protParms->asBstr());
|
||||
if (!parsedProtParms) return "Failed to parse protected params: " + errMsg;
|
||||
if (!parsedProtParms->asMap()) return "Invalid protected params";
|
||||
|
||||
auto& algorithm = parsedProtParms->asMap()->get(ALGORITHM);
|
||||
if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != ECDH_ES_HKDF_256) {
|
||||
return "Invalid algorithm";
|
||||
}
|
||||
|
||||
auto& unprotParms = recipient->asArray()->get(1);
|
||||
if (!unprotParms || !unprotParms->asMap()) return "Invalid unprotected params";
|
||||
|
||||
auto& senderCoseKey = unprotParms->asMap()->get(COSE_KEY);
|
||||
if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key";
|
||||
|
||||
auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE);
|
||||
if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) {
|
||||
return "Invalid key type";
|
||||
}
|
||||
|
||||
auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE);
|
||||
if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) {
|
||||
return "Unsupported curve";
|
||||
}
|
||||
|
||||
auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X);
|
||||
if (!pubkey || !pubkey->asBstr() ||
|
||||
pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
|
||||
return "Invalid X25519 public key";
|
||||
}
|
||||
|
||||
auto& key_id = unprotParms->asMap()->get(KEY_ID);
|
||||
if (key_id && key_id->asBstr()) {
|
||||
return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value());
|
||||
}
|
||||
|
||||
// If no key ID, just return an empty vector.
|
||||
return std::make_pair(pubkey->asBstr()->value(), bytevec{});
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt,
|
||||
const bytevec& external_aad) {
|
||||
if (!coseEncrypt || !coseEncrypt->asArray() ||
|
||||
coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
|
||||
return "Invalid COSE_Encrypt";
|
||||
}
|
||||
|
||||
auto& protParms = coseEncrypt->asArray()->get(kCoseEncryptProtectedParams);
|
||||
auto& unprotParms = coseEncrypt->asArray()->get(kCoseEncryptUnprotectedParams);
|
||||
auto& ciphertext = coseEncrypt->asArray()->get(kCoseEncryptPayload);
|
||||
auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
|
||||
|
||||
if (!protParms || !protParms->asBstr() || !unprotParms || !ciphertext || !recipients) {
|
||||
return "Invalid COSE_Encrypt";
|
||||
}
|
||||
|
||||
auto [parsedProtParams, _, errMsg] = cppbor::parse(protParms->asBstr()->value());
|
||||
if (!parsedProtParams) {
|
||||
return errMsg + " when parsing protected params.";
|
||||
}
|
||||
if (!parsedProtParams->asMap()) {
|
||||
return "Protected params must be a map";
|
||||
}
|
||||
|
||||
auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
|
||||
if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != AES_GCM_256) {
|
||||
return "Unsupported encryption algorithm";
|
||||
}
|
||||
|
||||
if (!unprotParms->asMap() || unprotParms->asMap()->size() != 1) {
|
||||
return "Invalid unprotected params";
|
||||
}
|
||||
|
||||
auto& nonce = unprotParms->asMap()->get(IV);
|
||||
if (!nonce || !nonce->asBstr() || nonce->asBstr()->value().size() != kAesGcmNonceLength) {
|
||||
return "Invalid nonce";
|
||||
}
|
||||
|
||||
if (!ciphertext->asBstr()) return "Invalid ciphertext";
|
||||
|
||||
auto aad = cppbor::Array() // Enc strucure as AAD
|
||||
.add("Encrypt") // Context
|
||||
.add(protParms->asBstr()->value()) // Protected
|
||||
.add(external_aad) // External AAD
|
||||
.encode();
|
||||
|
||||
return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value());
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA,
|
||||
const bytevec& pubKeyB, bool senderIsA) {
|
||||
bytevec rawSharedKey(X25519_SHARED_KEY_LEN);
|
||||
if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) {
|
||||
return "ECDH operation failed";
|
||||
}
|
||||
|
||||
bytevec kdfContext = cppbor::Array()
|
||||
.add(AES_GCM_256)
|
||||
.add(cppbor::Array() // Sender Info
|
||||
.add(cppbor::Bstr("client"))
|
||||
.add(bytevec{} /* nonce */)
|
||||
.add(senderIsA ? pubKeyA : pubKeyB))
|
||||
.add(cppbor::Array() // Recipient Info
|
||||
.add(cppbor::Bstr("server"))
|
||||
.add(bytevec{} /* nonce */)
|
||||
.add(senderIsA ? pubKeyB : pubKeyA))
|
||||
.add(cppbor::Array() // SuppPubInfo
|
||||
.add(128) // output key length
|
||||
.add(bytevec{})) // protected
|
||||
.encode();
|
||||
|
||||
bytevec retval(SHA256_DIGEST_LENGTH);
|
||||
bytevec salt{};
|
||||
if (!HKDF(retval.data(), retval.size(), //
|
||||
EVP_sha256(), //
|
||||
rawSharedKey.data(), rawSharedKey.size(), //
|
||||
salt.data(), salt.size(), //
|
||||
kdfContext.data(), kdfContext.size())) {
|
||||
return "ECDH HKDF failed";
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> aesGcmEncrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
|
||||
const bytevec& plaintext) {
|
||||
auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, true /* encrypt */);
|
||||
if (!ctx) return ctx.moveMessage();
|
||||
|
||||
bytevec ciphertext(plaintext.size() + kAesGcmTagSize);
|
||||
int outlen;
|
||||
if (!EVP_CipherUpdate(ctx->get(), ciphertext.data(), &outlen, plaintext.data(),
|
||||
plaintext.size())) {
|
||||
return "Failed to encrypt plaintext";
|
||||
}
|
||||
assert(plaintext.size() == outlen);
|
||||
|
||||
if (!EVP_CipherFinal_ex(ctx->get(), ciphertext.data() + outlen, &outlen)) {
|
||||
return "Failed to finalize encryption";
|
||||
}
|
||||
assert(outlen == 0);
|
||||
|
||||
if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize,
|
||||
ciphertext.data() + plaintext.size())) {
|
||||
return "Failed to retrieve tag";
|
||||
}
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec> aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
|
||||
const bytevec& ciphertextWithTag) {
|
||||
auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, false /* encrypt */);
|
||||
if (!ctx) return ctx.moveMessage();
|
||||
|
||||
if (ciphertextWithTag.size() < kAesGcmTagSize) return "Missing tag";
|
||||
|
||||
bytevec plaintext(ciphertextWithTag.size() - kAesGcmTagSize);
|
||||
int outlen;
|
||||
if (!EVP_CipherUpdate(ctx->get(), plaintext.data(), &outlen, ciphertextWithTag.data(),
|
||||
ciphertextWithTag.size() - kAesGcmTagSize)) {
|
||||
return "Failed to decrypt plaintext";
|
||||
}
|
||||
assert(plaintext.size() == outlen);
|
||||
|
||||
bytevec tag(ciphertextWithTag.end() - kAesGcmTagSize, ciphertextWithTag.end());
|
||||
if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag.data())) {
|
||||
return "Failed to set tag: " + std::to_string(ERR_peek_last_error());
|
||||
}
|
||||
|
||||
if (!EVP_CipherFinal_ex(ctx->get(), nullptr, &outlen)) {
|
||||
return "Failed to finalize encryption";
|
||||
}
|
||||
assert(outlen == 0);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
} // namespace cppcose
|
||||
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
|
||||
#include <openssl/cipher.h>
|
||||
#include <openssl/curve25519.h>
|
||||
#include <openssl/digest.h>
|
||||
#include <openssl/hkdf.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/mem.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace cppcose {
|
||||
|
||||
using bytevec = std::vector<uint8_t>;
|
||||
|
||||
constexpr int kCoseSign1EntryCount = 4;
|
||||
constexpr int kCoseSign1ProtectedParams = 0;
|
||||
constexpr int kCoseSign1UnprotectedParams = 1;
|
||||
constexpr int kCoseSign1Payload = 2;
|
||||
constexpr int kCoseSign1Signature = 3;
|
||||
|
||||
constexpr int kCoseMac0EntryCount = 4;
|
||||
constexpr int kCoseMac0ProtectedParams = 0;
|
||||
constexpr int kCoseMac0UnprotectedParams = 1;
|
||||
constexpr int kCoseMac0Payload = 2;
|
||||
constexpr int kCoseMac0Tag = 3;
|
||||
|
||||
constexpr int kCoseEncryptEntryCount = 4;
|
||||
constexpr int kCoseEncryptProtectedParams = 0;
|
||||
constexpr int kCoseEncryptUnprotectedParams = 1;
|
||||
constexpr int kCoseEncryptPayload = 2;
|
||||
constexpr int kCoseEncryptRecipients = 3;
|
||||
|
||||
enum Label : int {
|
||||
ALGORITHM = 1,
|
||||
KEY_ID = 4,
|
||||
IV = 5,
|
||||
COSE_KEY = -1,
|
||||
};
|
||||
|
||||
enum CoseKeyAlgorithm : int {
|
||||
AES_GCM_256 = 3,
|
||||
HMAC_256 = 5,
|
||||
ES256 = -7, // ECDSA with SHA-256
|
||||
EDDSA = -8,
|
||||
ECDH_ES_HKDF_256 = -25,
|
||||
};
|
||||
|
||||
enum CoseKeyCurve : int { P256 = 1, X25519 = 4, ED25519 = 6 };
|
||||
enum CoseKeyType : int { OCTET_KEY_PAIR = 1, EC2 = 2, SYMMETRIC_KEY = 4 };
|
||||
enum CoseKeyOps : int { SIGN = 1, VERIFY = 2, ENCRYPT = 3, DECRYPT = 4 };
|
||||
|
||||
constexpr int kAesGcmNonceLength = 12;
|
||||
constexpr int kAesGcmTagSize = 16;
|
||||
constexpr int kAesGcmKeySize = 32;
|
||||
|
||||
template <typename T>
|
||||
class ErrMsgOr {
|
||||
public:
|
||||
ErrMsgOr(std::string errMsg) : errMsg_(std::move(errMsg)) {}
|
||||
ErrMsgOr(const char* errMsg) : errMsg_(errMsg) {}
|
||||
ErrMsgOr(T val) : value_(std::move(val)) {}
|
||||
|
||||
operator bool() const { return value_.has_value(); }
|
||||
|
||||
T* operator->() & {
|
||||
assert(value_);
|
||||
return &value_.value();
|
||||
}
|
||||
T& operator*() & {
|
||||
assert(value_);
|
||||
return value_.value();
|
||||
};
|
||||
T&& operator*() && {
|
||||
assert(value_);
|
||||
return std::move(value_).value();
|
||||
};
|
||||
|
||||
const std::string& message() { return errMsg_; }
|
||||
std::string moveMessage() { return std::move(errMsg_); }
|
||||
|
||||
T moveValue() {
|
||||
assert(value_);
|
||||
return std::move(value_).value();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string errMsg_;
|
||||
std::optional<T> value_;
|
||||
};
|
||||
|
||||
class CoseKey {
|
||||
public:
|
||||
CoseKey() {}
|
||||
CoseKey(const CoseKey&) = delete;
|
||||
CoseKey(CoseKey&&) = default;
|
||||
|
||||
enum Label : int {
|
||||
KEY_TYPE = 1,
|
||||
KEY_ID = 2,
|
||||
ALGORITHM = 3,
|
||||
KEY_OPS = 4,
|
||||
CURVE = -1,
|
||||
PUBKEY_X = -2,
|
||||
PUBKEY_Y = -3,
|
||||
PRIVATE_KEY = -4,
|
||||
TEST_KEY = -70000 // Application-defined
|
||||
};
|
||||
|
||||
static ErrMsgOr<CoseKey> parse(const bytevec& coseKey) {
|
||||
auto [parsedKey, _, errMsg] = cppbor::parse(coseKey);
|
||||
if (!parsedKey) return errMsg + " when parsing key";
|
||||
if (!parsedKey->asMap()) return "CoseKey must be a map";
|
||||
return CoseKey(static_cast<cppbor::Map*>(parsedKey.release()));
|
||||
}
|
||||
|
||||
static ErrMsgOr<CoseKey> parse(const bytevec& coseKey, CoseKeyType expectedKeyType,
|
||||
CoseKeyAlgorithm expectedAlgorithm, CoseKeyCurve expectedCurve) {
|
||||
auto key = parse(coseKey);
|
||||
if (!key) return key;
|
||||
|
||||
if (!key->checkIntValue(CoseKey::KEY_TYPE, expectedKeyType) ||
|
||||
!key->checkIntValue(CoseKey::ALGORITHM, expectedAlgorithm) ||
|
||||
!key->checkIntValue(CoseKey::CURVE, expectedCurve)) {
|
||||
return "Unexpected key type:";
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static ErrMsgOr<CoseKey> parseEd25519(const bytevec& coseKey) {
|
||||
auto key = parse(coseKey, OCTET_KEY_PAIR, EDDSA, ED25519);
|
||||
if (!key) return key;
|
||||
|
||||
auto& pubkey = key->getMap().get(PUBKEY_X);
|
||||
if (!pubkey || !pubkey->asBstr() ||
|
||||
pubkey->asBstr()->value().size() != ED25519_PUBLIC_KEY_LEN) {
|
||||
return "Invalid Ed25519 public key";
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static ErrMsgOr<CoseKey> parseX25519(const bytevec& coseKey, bool requireKid) {
|
||||
auto key = parse(coseKey, OCTET_KEY_PAIR, ECDH_ES_HKDF_256, X25519);
|
||||
if (!key) return key;
|
||||
|
||||
auto& pubkey = key->getMap().get(PUBKEY_X);
|
||||
if (!pubkey || !pubkey->asBstr() ||
|
||||
pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
|
||||
return "Invalid X25519 public key";
|
||||
}
|
||||
|
||||
auto& kid = key->getMap().get(KEY_ID);
|
||||
if (requireKid && (!kid || !kid->asBstr())) {
|
||||
return "Missing KID";
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static ErrMsgOr<CoseKey> parseP256(const bytevec& coseKey) {
|
||||
auto key = parse(coseKey, EC2, ES256, P256);
|
||||
if (!key) return key;
|
||||
|
||||
auto& pubkey_x = key->getMap().get(PUBKEY_X);
|
||||
auto& pubkey_y = key->getMap().get(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;
|
||||
}
|
||||
|
||||
std::optional<int> getIntValue(Label label) {
|
||||
const auto& value = key_->get(label);
|
||||
if (!value || !value->asInt()) return {};
|
||||
return value->asInt()->value();
|
||||
}
|
||||
|
||||
std::optional<bytevec> getBstrValue(Label label) {
|
||||
const auto& value = key_->get(label);
|
||||
if (!value || !value->asBstr()) return {};
|
||||
return value->asBstr()->value();
|
||||
}
|
||||
|
||||
const cppbor::Map& getMap() const { return *key_; }
|
||||
cppbor::Map&& moveMap() { return std::move(*key_); }
|
||||
|
||||
bool checkIntValue(Label label, int expectedValue) {
|
||||
const auto& value = key_->get(label);
|
||||
return value && value->asInt() && value->asInt()->value() == expectedValue;
|
||||
}
|
||||
|
||||
void add(Label label, int value) { key_->add(label, value); }
|
||||
void add(Label label, bytevec value) { key_->add(label, std::move(value)); }
|
||||
|
||||
bytevec encode() { return key_->canonicalize().encode(); }
|
||||
|
||||
private:
|
||||
CoseKey(cppbor::Map* parsedKey) : key_(parsedKey) {}
|
||||
|
||||
// This is the full parsed key structure.
|
||||
std::unique_ptr<cppbor::Map> key_;
|
||||
};
|
||||
|
||||
ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
|
||||
const bytevec& payload);
|
||||
ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
|
||||
const bytevec& payload);
|
||||
ErrMsgOr<bytevec /* payload */> parseCoseMac0(const cppbor::Item* macItem);
|
||||
ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
|
||||
const bytevec& macKey);
|
||||
|
||||
ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
|
||||
const bytevec& payload, const bytevec& aad);
|
||||
ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
|
||||
const bytevec& aad);
|
||||
ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields,
|
||||
const bytevec& payload, const bytevec& aad);
|
||||
/**
|
||||
* Verify and parse a COSE_Sign1 message, returning the payload.
|
||||
*
|
||||
* @param ignoreSignature indicates whether signature verification should be skipped. If true, no
|
||||
* verification of the signature will be done.
|
||||
*
|
||||
* @param coseSign1 is the COSE_Sign1 to verify and parse.
|
||||
*
|
||||
* @param signingCoseKey is a CBOR-encoded COSE_Key to use to verify the signature. The bytevec may
|
||||
* be empty, in which case the function assumes that coseSign1's payload is the COSE_Key to
|
||||
* use, i.e. that coseSign1 is a self-signed "certificate".
|
||||
*/
|
||||
ErrMsgOr<bytevec /* payload */> verifyAndParseCoseSign1(bool ignoreSignature,
|
||||
const cppbor::Array* coseSign1,
|
||||
const bytevec& signingCoseKey,
|
||||
const bytevec& aad);
|
||||
|
||||
ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& protectedParams, const bytevec& aad);
|
||||
ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& plaintextPayload, const bytevec& aad,
|
||||
cppbor::Array recipients);
|
||||
ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>> getSenderPubKeyFromCoseEncrypt(
|
||||
const cppbor::Item* encryptItem);
|
||||
inline ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>>
|
||||
getSenderPubKeyFromCoseEncrypt(const std::unique_ptr<cppbor::Item>& encryptItem) {
|
||||
return getSenderPubKeyFromCoseEncrypt(encryptItem.get());
|
||||
}
|
||||
|
||||
ErrMsgOr<bytevec /* plaintextPayload */> decryptCoseEncrypt(const bytevec& key,
|
||||
const cppbor::Item* encryptItem,
|
||||
const bytevec& aad);
|
||||
|
||||
ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey,
|
||||
const bytevec& recipientPubKey, bool senderIsA);
|
||||
|
||||
ErrMsgOr<bytevec /* ciphertextWithTag */> aesGcmEncrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& aad,
|
||||
const bytevec& plaintext);
|
||||
ErrMsgOr<bytevec /* plaintext */> aesGcmDecrypt(const bytevec& key, const bytevec& nonce,
|
||||
const bytevec& aad,
|
||||
const bytevec& ciphertextWithTag);
|
||||
|
||||
} // namespace cppcose
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <cppcose/cppcose.h>
|
||||
#include <keymaster/cppcose/cppcose.h>
|
||||
|
||||
namespace aidl::android::hardware::security::keymint::remote_prov {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user