diff options
author | Shawn Willden <swillden@google.com> | 2020-09-30 22:39:22 -0600 |
---|---|---|
committer | Max Bires <jbires@google.com> | 2021-02-16 07:40:59 -0800 |
commit | 274bb55f102d1adaac99f41ae4f6dcde9d2c13d2 (patch) | |
tree | 703ca1e28eb1bfde864a316396fa6bc3211254e5 /security/keymint/support/cppcose.cpp | |
parent | 06811c8124c8e724ed4a41ae54a33a6b97981f4d (diff) |
Add RemotelyProvisionedComponent HAL.
Test: VtsHalRemotelyProvisionedComponentTargetTest
Change-Id: I51fb01f4c52949c81f3ad2d694a4afdf0fa67788
Diffstat (limited to 'security/keymint/support/cppcose.cpp')
-rw-r--r-- | security/keymint/support/cppcose.cpp | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/security/keymint/support/cppcose.cpp b/security/keymint/support/cppcose.cpp new file mode 100644 index 0000000000..c626adeccb --- /dev/null +++ b/security/keymint/support/cppcose.cpp @@ -0,0 +1,467 @@ +/* + * 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::Bstr() /* 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)->asBstr(); + 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)->asBstr(); + 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(bytevec{} /* 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::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr(); + 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 |