summaryrefslogtreecommitdiff
path: root/security/keymint/support/cppcose.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/keymint/support/cppcose.cpp')
-rw-r--r--security/keymint/support/cppcose.cpp467
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