/* * 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 #include #include #include #include #include #include namespace cppcose { constexpr int kP256AffinePointSize = 32; constexpr int kP384AffinePointSize = 48; using EVP_PKEY_Ptr = bssl::UniquePtr; using EVP_PKEY_CTX_Ptr = bssl::UniquePtr; using ECDSA_SIG_Ptr = bssl::UniquePtr; using EC_KEY_Ptr = bssl::UniquePtr; namespace { ErrMsgOr> aesGcmInitAndProcessAad(const bytevec& key, const bytevec& nonce, const bytevec& aad, bool encrypt) { if (key.size() != kAesGcmKeySize) return "Invalid key size"; bssl::UniquePtr 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); } ErrMsgOr signP256Digest(const bytevec& key, const bytevec& data) { auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr)); if (bn.get() == nullptr) { return "Error creating BIGNUM"; } auto ec_key = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); if (EC_KEY_set_private_key(ec_key.get(), bn.get()) != 1) { return "Error setting private key from BIGNUM"; } auto sig = ECDSA_SIG_Ptr(ECDSA_do_sign(data.data(), data.size(), ec_key.get())); if (sig == nullptr) { return "Error signing digest"; } size_t len = i2d_ECDSA_SIG(sig.get(), nullptr); bytevec signature(len); unsigned char* p = (unsigned char*)signature.data(); i2d_ECDSA_SIG(sig.get(), &p); return signature; } ErrMsgOr ecdh(const bytevec& publicKey, const bytevec& privateKey) { auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) != 1) { return "Error decoding publicKey"; } auto ecKey = EC_KEY_Ptr(EC_KEY_new()); auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); if (ecKey.get() == nullptr || pkey.get() == nullptr) { return "Memory allocation failed"; } if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) { return "Error setting group"; } if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { return "Error setting point"; } if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) { return "Error setting key"; } auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr)); if (bn.get() == nullptr) { return "Error creating BIGNUM for private key"; } auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) { return "Error setting private key from BIGNUM"; } auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new()); if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) { return "Error setting private key"; } auto ctx = EVP_PKEY_CTX_Ptr(EVP_PKEY_CTX_new(privPkey.get(), NULL)); if (ctx.get() == nullptr) { return "Error creating context"; } if (EVP_PKEY_derive_init(ctx.get()) != 1) { return "Error initializing context"; } if (EVP_PKEY_derive_set_peer(ctx.get(), pkey.get()) != 1) { return "Error setting peer"; } /* Determine buffer length for shared secret */ size_t secretLen = 0; if (EVP_PKEY_derive(ctx.get(), NULL, &secretLen) != 1) { return "Error determing length of shared secret"; } bytevec sharedSecret(secretLen); if (EVP_PKEY_derive(ctx.get(), sharedSecret.data(), &secretLen) != 1) { return "Error deriving shared secret"; } return sharedSecret; } ErrMsgOr ecdsaCoseSignatureToDer(int point_size, const bytevec& ecdsaCoseSignature) { if (ecdsaCoseSignature.size() != (size_t)(point_size * 2)) { return "COSE signature wrong length"; } auto rBn = BIGNUM_Ptr(BN_bin2bn(ecdsaCoseSignature.data(), point_size, nullptr)); if (rBn.get() == nullptr) { return "Error creating BIGNUM for r"; } auto sBn = BIGNUM_Ptr(BN_bin2bn(ecdsaCoseSignature.data() + point_size, point_size, nullptr)); if (sBn.get() == nullptr) { return "Error creating BIGNUM for s"; } ECDSA_SIG sig; sig.r = rBn.get(); sig.s = sBn.get(); size_t len = i2d_ECDSA_SIG(&sig, nullptr); bytevec derSignature(len); unsigned char* p = (unsigned char*)derSignature.data(); i2d_ECDSA_SIG(&sig, &p); return derSignature; } ErrMsgOr ecdsaDerSignatureToCose(int point_size, const bytevec& ecdsaSignature) { const unsigned char* p = ecdsaSignature.data(); auto sig = ECDSA_SIG_Ptr(d2i_ECDSA_SIG(nullptr, &p, ecdsaSignature.size())); if (sig == nullptr) { return "Error decoding DER signature"; } bytevec ecdsaCoseSignature(point_size * 2, 0); if (BN_bn2binpad(ECDSA_SIG_get0_r(sig.get()), ecdsaCoseSignature.data(), point_size) != point_size) { return "Error encoding r"; } if (BN_bn2binpad(ECDSA_SIG_get0_s(sig.get()), ecdsaCoseSignature.data() + point_size, point_size) != point_size) { return "Error encoding s"; } return ecdsaCoseSignature; } bool verifyEcdsaDigest(int curve_nid, const bytevec& key, const bytevec& digest, const bytevec& signature) { const unsigned char* p = (unsigned char*)signature.data(); auto sig = ECDSA_SIG_Ptr(d2i_ECDSA_SIG(nullptr, &p, signature.size())); if (sig.get() == nullptr) { return false; } auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(curve_nid)); auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); if (EC_POINT_oct2point(group.get(), point.get(), key.data(), key.size(), nullptr) != 1) { return false; } auto ecKey = EC_KEY_Ptr(EC_KEY_new()); if (ecKey.get() == nullptr) { return false; } if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) { return false; } if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { return false; } int rc = ECDSA_do_verify(digest.data(), digest.size(), sig.get(), ecKey.get()); if (rc != 1) { return false; } return true; } } // namespace ErrMsgOr generateHmacSha256(const bytevec& key, const bytevec& data) { HmacSha256 digest; unsigned int outLen; uint8_t* out = HMAC(EVP_sha256(), // key.data(), key.size(), // data.data(), data.size(), // digest.data(), &outLen); if (out == nullptr || outLen != digest.size()) { return "Error generating HMAC"; } return digest; } ErrMsgOr generateCoseMac0Mac(HmacSha256Function macFunction, 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(); auto macTag = macFunction(macStructure); if (!macTag) { return "Error computing public key MAC"; } return *macTag; } ErrMsgOr constructCoseMac0(HmacSha256Function macFunction, const bytevec& externalAad, const bytevec& payload) { auto tag = generateCoseMac0Mac(macFunction, externalAad, payload); if (!tag) return tag.moveMessage(); return cppbor::Array() .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode()) .add(cppbor::Map() /* unprotected */) .add(payload) .add(std::pair(tag->begin(), tag->end())); } ErrMsgOr 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)->asMap(); 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 macFunction = [&macKey](const bytevec& input) { return generateHmacSha256(macKey, input); }; auto macTag = generateCoseMac0Mac(macFunction, {} /* 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 createECDSACoseSign1Signature(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(); auto ecdsaSignature = signP256Digest(key, sha256(signatureInput)); if (!ecdsaSignature) return ecdsaSignature.moveMessage(); return ecdsaDerSignatureToCose(kP256AffinePointSize, *ecdsaSignature); } ErrMsgOr 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 constructECDSACoseSign1(const bytevec& key, cppbor::Map protectedParams, const bytevec& payload, const bytevec& aad) { bytevec protParms = protectedParams.add(ALGORITHM, ES256).canonicalize().encode(); auto signature = createECDSACoseSign1Signature(key, protParms, payload, aad); if (!signature) return signature.moveMessage(); return cppbor::Array() .add(std::move(protParms)) .add(cppbor::Map() /* unprotected parameters */) .add(std::move(payload)) .add(std::move(*signature)); } ErrMsgOr 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(std::move(protParms)) .add(cppbor::Map() /* unprotected parameters */) .add(std::move(payload)) .add(std::move(*signature)); } ErrMsgOr constructCoseSign1(const bytevec& key, const bytevec& payload, const bytevec& aad) { return constructCoseSign1(key, {} /* protectedParams */, payload, aad); } ErrMsgOr verifyAndParseCoseSign1(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::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap(); const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr(); if (!protectedParams || !unprotectedParams || !payload) { return "Missing input parameters"; } 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 || algorithm->asInt()->value() == ES256 || algorithm->asInt()->value() == ES384)) { return "Unsupported signature algorithm"; } const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr(); if (!signature || signature->value().empty()) { return "Missing signature input"; } bool selfSigned = signingCoseKey.empty(); bytevec signatureInput = cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); if (algorithm->asInt()->value() == EDDSA) { auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey); if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty()) { return "Bad signing key: " + key.moveMessage(); } if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), key->getBstrValue(CoseKey::PUBKEY_X)->data())) { return "Signature verification failed"; } } else if (algorithm->asInt()->value() == ES256) { auto key = CoseKey::parseP256(selfSigned ? payload->value() : signingCoseKey); if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty() || key->getBstrValue(CoseKey::PUBKEY_Y)->empty()) { return "Bad signing key: " + key.moveMessage(); } auto publicKey = key->getEcPublicKey(); if (!publicKey) return publicKey.moveMessage(); auto ecdsaDerSignature = ecdsaCoseSignatureToDer(kP256AffinePointSize, signature->value()); if (!ecdsaDerSignature) return ecdsaDerSignature.moveMessage(); // convert public key to uncompressed form by prepending 0x04 at begin. publicKey->insert(publicKey->begin(), 0x04); if (!verifyEcdsaDigest(NID_X9_62_prime256v1, publicKey.moveValue(), sha256(signatureInput), *ecdsaDerSignature)) { return "Signature verification failed"; } } else { // ES384 auto key = CoseKey::parseP384(selfSigned ? payload->value() : signingCoseKey); if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty() || key->getBstrValue(CoseKey::PUBKEY_Y)->empty()) { return "Bad signing key: " + key.moveMessage(); } auto publicKey = key->getEcPublicKey(); if (!publicKey) return publicKey.moveMessage(); auto ecdsaDerSignature = ecdsaCoseSignatureToDer(kP384AffinePointSize, signature->value()); if (!ecdsaDerSignature) return ecdsaDerSignature.moveMessage(); // convert public key to uncompressed form by prepending 0x04 at begin. publicKey->insert(publicKey->begin(), 0x04); if (!verifyEcdsaDigest(NID_secp384r1, publicKey.moveValue(), sha384(signatureInput), *ecdsaDerSignature)) { return "Signature verification failed"; } } return payload->value(); } ErrMsgOr 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 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> 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 && keyType->asInt()->value() != EC2)) { return "Invalid key type"; } auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE); if (!curve || !curve->asInt() || (keyType->asInt()->value() == OCTET_KEY_PAIR && curve->asInt()->value() != X25519) || (keyType->asInt()->value() == EC2 && curve->asInt()->value() != P256)) { return "Unsupported curve"; } bytevec publicKey; if (keyType->asInt()->value() == EC2) { auto& pubX = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X); if (!pubX || !pubX->asBstr() || pubX->asBstr()->value().size() != kP256AffinePointSize) { return "Invalid EC public key"; } auto& pubY = senderCoseKey->asMap()->get(CoseKey::PUBKEY_Y); if (!pubY || !pubY->asBstr() || pubY->asBstr()->value().size() != kP256AffinePointSize) { return "Invalid EC public key"; } auto key = CoseKey::getEcPublicKey(pubX->asBstr()->value(), pubY->asBstr()->value()); if (!key) return key.moveMessage(); publicKey = key.moveValue(); } else { 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"; } publicKey = pubkey->asBstr()->value(); } auto& key_id = unprotParms->asMap()->get(KEY_ID); if (key_id && key_id->asBstr()) { return std::make_pair(publicKey, key_id->asBstr()->value()); } // If no key ID, just return an empty vector. return std::make_pair(publicKey, bytevec{}); } ErrMsgOr 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 consructKdfContext(const bytevec& pubKeyA, const bytevec& privKeyA, const bytevec& pubKeyB, bool senderIsA) { if (privKeyA.empty() || pubKeyA.empty() || pubKeyB.empty()) { return "Missing input key parameters"; } 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(kAesGcmKeySizeBits) // output key length .add(bytevec{})) // protected .encode(); return kdfContext; } ErrMsgOr ECDH_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, const bytevec& pubKeyB, bool senderIsA) { if (privKeyA.empty() || pubKeyA.empty() || pubKeyB.empty()) { return "Missing input key parameters"; } // convert public key to uncompressed form by prepending 0x04 at begin bytevec publicKey; publicKey.insert(publicKey.begin(), 0x04); publicKey.insert(publicKey.end(), pubKeyB.begin(), pubKeyB.end()); auto rawSharedKey = ecdh(publicKey, privKeyA); if (!rawSharedKey) return rawSharedKey.moveMessage(); auto kdfContext = consructKdfContext(pubKeyA, privKeyA, pubKeyB, senderIsA); if (!kdfContext) return kdfContext.moveMessage(); 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 x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, const bytevec& pubKeyB, bool senderIsA) { if (privKeyA.empty() || pubKeyA.empty() || pubKeyB.empty()) { return "Missing input key parameters"; } bytevec rawSharedKey(X25519_SHARED_KEY_LEN); if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) { return "ECDH operation failed"; } auto kdfContext = consructKdfContext(pubKeyA, privKeyA, pubKeyB, senderIsA); if (!kdfContext) return kdfContext.moveMessage(); 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 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() == static_cast(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 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() == static_cast(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; } bytevec sha256(const bytevec& data) { bytevec ret(SHA256_DIGEST_LENGTH); SHA256_CTX ctx; SHA256_Init(&ctx); SHA256_Update(&ctx, data.data(), data.size()); SHA256_Final((unsigned char*)ret.data(), &ctx); return ret; } bytevec sha384(const bytevec& data) { bytevec ret(SHA384_DIGEST_LENGTH); SHA512_CTX ctx; SHA384_Init(&ctx); SHA384_Update(&ctx, data.data(), data.size()); SHA384_Final((unsigned char*)ret.data(), &ctx); return ret; } } // namespace cppcose