diff options
Diffstat (limited to 'security/keymint/aidl/default/RemotelyProvisionedComponent.cpp')
-rw-r--r-- | security/keymint/aidl/default/RemotelyProvisionedComponent.cpp | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp new file mode 100644 index 0000000000..f2651fbce7 --- /dev/null +++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp @@ -0,0 +1,430 @@ +/* + * 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 |