mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 16:50:18 +00:00
Test: VtsHalRemotelyProvisionedComponentTargetTest Change-Id: I51fb01f4c52949c81f3ad2d694a4afdf0fa67788
431 lines
18 KiB
C++
431 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2021 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 "RemotelyProvisionedComponent.h"
|
|
|
|
#include <assert.h>
|
|
#include <variant>
|
|
|
|
#include <cppbor.h>
|
|
#include <cppbor_parse.h>
|
|
|
|
#include <KeyMintUtils.h>
|
|
#include <cppcose/cppcose.h>
|
|
#include <keymaster/keymaster_configuration.h>
|
|
#include <remote_prov/remote_prov_utils.h>
|
|
|
|
#include <openssl/bn.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/x509.h>
|
|
|
|
namespace aidl::android::hardware::security::keymint {
|
|
|
|
using ::std::string;
|
|
using ::std::tuple;
|
|
using ::std::unique_ptr;
|
|
using ::std::variant;
|
|
using ::std::vector;
|
|
using bytevec = ::std::vector<uint8_t>;
|
|
|
|
using namespace cppcose;
|
|
using namespace keymaster;
|
|
|
|
namespace {
|
|
|
|
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); }
|
|
};
|
|
|
|
// TODO(swillden): Remove the dependency on AStatus stuff. The COSE lib should use something like
|
|
// StatusOr, but it shouldn't depend on AStatus.
|
|
class Status {
|
|
public:
|
|
Status() {}
|
|
Status(int32_t errCode, const std::string& errMsg)
|
|
: status_(AStatus_fromServiceSpecificErrorWithMessage(errCode, errMsg.c_str())) {}
|
|
explicit Status(const std::string& errMsg)
|
|
: status_(AStatus_fromServiceSpecificErrorWithMessage(STATUS_FAILED, errMsg.c_str())) {}
|
|
Status(AStatus* status) : status_(status) {}
|
|
Status(Status&&) = default;
|
|
Status(const Status&) = delete;
|
|
|
|
operator ::ndk::ScopedAStatus() && { return ndk::ScopedAStatus(status_.release()); }
|
|
|
|
bool isOk() { return !status_; }
|
|
|
|
// Don't call getMessage() unless isOk() returns false;
|
|
const char* getMessage() const { return AStatus_getMessage(status_.get()); }
|
|
|
|
private:
|
|
std::unique_ptr<AStatus, AStatusDeleter> status_;
|
|
};
|
|
|
|
template <typename T>
|
|
class StatusOr {
|
|
public:
|
|
StatusOr(AStatus* status) : status_(status) {}
|
|
StatusOr(Status status) : status_(std::move(status)) {}
|
|
StatusOr(T val) : value_(std::move(val)) {}
|
|
|
|
bool isOk() { return status_.isOk(); }
|
|
|
|
T* operator->() & {
|
|
assert(isOk());
|
|
return &value_.value();
|
|
}
|
|
T& operator*() & {
|
|
assert(isOk());
|
|
return value_.value();
|
|
}
|
|
T&& operator*() && {
|
|
assert(isOk());
|
|
return std::move(value_).value();
|
|
}
|
|
|
|
const char* getMessage() const {
|
|
assert(!isOk());
|
|
return status_.getMessage();
|
|
}
|
|
|
|
Status moveError() {
|
|
assert(!isOk());
|
|
return std::move(status_);
|
|
}
|
|
|
|
T moveValue() { return std::move(value_).value(); }
|
|
|
|
private:
|
|
Status status_;
|
|
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);
|
|
}
|
|
|
|
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)->asBstr();
|
|
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_SIGN), 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();
|
|
}
|
|
|
|
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);
|
|
if (response.error != KM_ERROR_OK) {
|
|
return km_utils::kmError2ScopedAStatus(response.error);
|
|
}
|
|
|
|
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();
|
|
*privateKeyHandle = km_utils::kmBlob2vector(response.key_blob);
|
|
return ScopedAStatus::ok();
|
|
}
|
|
|
|
ScopedAStatus RemotelyProvisionedComponent::generateCertificateRequest(
|
|
bool testMode, const vector<MacedPublicKey>& keysToSign,
|
|
const bytevec& endpointEncCertChain, const bytevec& challenge, bytevec* keysToSignMac,
|
|
ProtectedData* protectedData) {
|
|
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();
|
|
}
|
|
|
|
auto signedMac = constructCoseSign1(devicePrivKey /* Signing key */, //
|
|
ephemeralMacKey /* Payload */,
|
|
cppbor::Array() /* AAD */
|
|
.add(challenge)
|
|
.add(createDeviceInfo())
|
|
.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();
|
|
|
|
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;
|
|
}
|
|
|
|
bytevec RemotelyProvisionedComponent::createDeviceInfo() const {
|
|
return cppbor::Map().encode();
|
|
}
|
|
|
|
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 */,
|
|
std::vector<uint8_t>(0x05) /* Big endian order */)
|
|
.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
|