/* * 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 #include #include #include #include #include #include #include #include #include #include #include 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; 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 status_; }; template 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 value_; }; StatusOr> 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 validateAndExtractPubkeys(bool testMode, const vector& 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(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> 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) { 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& 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(context.data()), context.size()); return result; } bytevec RemotelyProvisionedComponent::createDeviceInfo() const { return cppbor::Map().encode(); } std::pair 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(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