diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/lib/pk11wrap/pk11hpke.c | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/pk11wrap/pk11hpke.c | 1276 |
1 files changed, 1276 insertions, 0 deletions
diff --git a/security/nss/lib/pk11wrap/pk11hpke.c b/security/nss/lib/pk11wrap/pk11hpke.c new file mode 100644 index 0000000000..7c4bfc3cdc --- /dev/null +++ b/security/nss/lib/pk11wrap/pk11hpke.c @@ -0,0 +1,1276 @@ +/* + * draft-irtf-cfrg-hpke-07 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "keyhi.h" +#include "pkcs11t.h" +#include "pk11func.h" +#include "pk11hpke.h" +#include "pk11pqg.h" +#include "secerr.h" +#include "secitem.h" +#include "secmod.h" +#include "secmodi.h" +#include "secmodti.h" +#include "secutil.h" + +#define SERIALIZATION_VERSION 2 + +static const char *V1_LABEL = "HPKE-v1"; +static const char *EXP_LABEL = "exp"; +static const char *HPKE_LABEL = "HPKE"; +static const char *INFO_LABEL = "info_hash"; +static const char *KEM_LABEL = "KEM"; +static const char *KEY_LABEL = "key"; +static const char *NONCE_LABEL = "base_nonce"; +static const char *PSK_ID_LABEL = "psk_id_hash"; +static const char *SECRET_LABEL = "secret"; +static const char *SEC_LABEL = "sec"; +static const char *EAE_PRK_LABEL = "eae_prk"; +static const char *SH_SEC_LABEL = "shared_secret"; + +struct HpkeContextStr { + const hpkeKemParams *kemParams; + const hpkeKdfParams *kdfParams; + const hpkeAeadParams *aeadParams; + PRUint8 mode; /* Base and PSK modes supported. */ + SECItem *encapPubKey; /* Marshalled public key, sent to receiver. */ + SECItem *baseNonce; /* Deterministic nonce for AEAD. */ + SECItem *pskId; /* PSK identifier (non-secret). */ + PK11Context *aeadContext; /* AEAD context used by Seal/Open. */ + PRUint64 sequenceNumber; /* seqNo for decrypt IV construction. */ + PK11SymKey *sharedSecret; /* ExtractAndExpand output key. */ + PK11SymKey *key; /* Key used with the AEAD. */ + PK11SymKey *exporterSecret; /* Derivation key for ExportSecret. */ + PK11SymKey *psk; /* PSK imported by the application. */ +}; + +static const hpkeKemParams kemParams[] = { + /* KEM, Nsk, Nsecret, Npk, oidTag, Hash mechanism */ + { HpkeDhKemX25519Sha256, 32, 32, 32, SEC_OID_CURVE25519, CKM_SHA256 }, +}; + +#define MAX_WRAPPED_EXP_LEN 72 // Largest kdfParams->Nh + 8 +static const hpkeKdfParams kdfParams[] = { + /* KDF, Nh, mechanism */ + { HpkeKdfHkdfSha256, SHA256_LENGTH, CKM_SHA256 }, + { HpkeKdfHkdfSha384, SHA384_LENGTH, CKM_SHA384 }, + { HpkeKdfHkdfSha512, SHA512_LENGTH, CKM_SHA512 }, +}; +#define MAX_WRAPPED_KEY_LEN 40 // Largest aeadParams->Nk + 8 +static const hpkeAeadParams aeadParams[] = { + /* AEAD, Nk, Nn, tagLen, mechanism */ + { HpkeAeadAes128Gcm, 16, 12, 16, CKM_AES_GCM }, + { HpkeAeadAes256Gcm, 32, 12, 16, CKM_AES_GCM }, + { HpkeAeadChaCha20Poly1305, 32, 12, 16, CKM_CHACHA20_POLY1305 }, +}; + +static inline const hpkeKemParams * +kemId2Params(HpkeKemId kemId) +{ + switch (kemId) { + case HpkeDhKemX25519Sha256: + return &kemParams[0]; + default: + return NULL; + } +} + +static inline const hpkeKdfParams * +kdfId2Params(HpkeKdfId kdfId) +{ + switch (kdfId) { + case HpkeKdfHkdfSha256: + return &kdfParams[0]; + case HpkeKdfHkdfSha384: + return &kdfParams[1]; + case HpkeKdfHkdfSha512: + return &kdfParams[2]; + default: + return NULL; + } +} + +static const inline hpkeAeadParams * +aeadId2Params(HpkeAeadId aeadId) +{ + switch (aeadId) { + case HpkeAeadAes128Gcm: + return &aeadParams[0]; + case HpkeAeadAes256Gcm: + return &aeadParams[1]; + case HpkeAeadChaCha20Poly1305: + return &aeadParams[2]; + default: + return NULL; + } +} + +static PRUint8 * +encodeNumber(PRUint64 value, PRUint8 *b, size_t count) +{ + PRUint64 encoded; + PORT_Assert(b && count > 0 && count <= sizeof(encoded)); + + encoded = PR_htonll(value); + PORT_Memcpy(b, ((unsigned char *)(&encoded)) + (sizeof(encoded) - count), + count); + return b + count; +} + +static PRUint8 * +decodeNumber(PRUint64 *value, PRUint8 *b, size_t count) +{ + unsigned int i; + PRUint64 number = 0; + PORT_Assert(b && value && count <= sizeof(*value)); + + for (i = 0; i < count; i++) { + number = (number << 8) + b[i]; + } + *value = number; + return b + count; +} + +SECStatus +PK11_HPKE_ValidateParameters(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId) +{ + /* If more variants are added, ensure the combination is also + * legal. For now it is, since only the AEAD may vary. */ + const hpkeKemParams *kem = kemId2Params(kemId); + const hpkeKdfParams *kdf = kdfId2Params(kdfId); + const hpkeAeadParams *aead = aeadId2Params(aeadId); + if (!kem || !kdf || !aead) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + return SECSuccess; +} + +HpkeContext * +PK11_HPKE_NewContext(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId, + PK11SymKey *psk, const SECItem *pskId) +{ + SECStatus rv = SECSuccess; + PK11SlotInfo *slot = NULL; + HpkeContext *cx = NULL; + /* Both the PSK and the PSK ID default to empty. */ + SECItem emptyItem = { siBuffer, NULL, 0 }; + + cx = PORT_ZNew(HpkeContext); + if (!cx) { + return NULL; + } + cx->mode = psk ? HpkeModePsk : HpkeModeBase; + cx->kemParams = kemId2Params(kemId); + cx->kdfParams = kdfId2Params(kdfId); + cx->aeadParams = aeadId2Params(aeadId); + CHECK_FAIL_ERR((!!psk != !!pskId), SEC_ERROR_INVALID_ARGS); + CHECK_FAIL_ERR(!cx->kemParams || !cx->kdfParams || !cx->aeadParams, + SEC_ERROR_INVALID_ARGS); + + /* Import the provided PSK or the default. */ + slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL); + CHECK_FAIL(!slot); + if (psk) { + cx->psk = PK11_ReferenceSymKey(psk); + cx->pskId = SECITEM_DupItem(pskId); + } else { + cx->psk = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap, + CKA_DERIVE, &emptyItem, NULL); + cx->pskId = SECITEM_DupItem(&emptyItem); + } + CHECK_FAIL(!cx->psk); + CHECK_FAIL(!cx->pskId); + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(cx->psk); + SECITEM_FreeItem(cx->pskId, PR_TRUE); + cx->pskId = NULL; + cx->psk = NULL; + PORT_Free(cx); + cx = NULL; + } + if (slot) { + PK11_FreeSlot(slot); + } + return cx; +} + +void +PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit) +{ + if (!cx) { + return; + } + + if (cx->aeadContext) { + PK11_DestroyContext((PK11Context *)cx->aeadContext, PR_TRUE); + cx->aeadContext = NULL; + } + PK11_FreeSymKey(cx->exporterSecret); + PK11_FreeSymKey(cx->sharedSecret); + PK11_FreeSymKey(cx->key); + PK11_FreeSymKey(cx->psk); + SECITEM_FreeItem(cx->pskId, PR_TRUE); + SECITEM_FreeItem(cx->baseNonce, PR_TRUE); + SECITEM_FreeItem(cx->encapPubKey, PR_TRUE); + cx->exporterSecret = NULL; + cx->sharedSecret = NULL; + cx->key = NULL; + cx->psk = NULL; + cx->pskId = NULL; + cx->baseNonce = NULL; + cx->encapPubKey = NULL; + if (freeit) { + PORT_ZFree(cx, sizeof(HpkeContext)); + } +} + +/* Export Format: + struct { + uint8 serilizationVersion; + uint16 kemId; + uint16 kdfId; + uint16 aeadId; + uint16 modeId; + uint64 sequenceNumber; + opaque senderPubKey<1..2^16-1>; + opaque baseNonce<1..2^16-1>; + opaque key<1..2^16-1>; + opaque exporterSecret<1..2^16-1>; + } HpkeSerializedContext +*/ +#define EXPORTED_CTX_BASE_LEN 25 /* Fixed size plus 2B for each variable. */ +#define REMAINING_BYTES(walker, buf) \ + buf->len - (walker - buf->data) +SECStatus +PK11_HPKE_ExportContext(const HpkeContext *cx, PK11SymKey *wrapKey, SECItem **serialized) +{ + SECStatus rv; + size_t allocLen; + PRUint8 *walker; + SECItem *keyBytes = NULL; // Maybe wrapped + SECItem *exporterBytes = NULL; // Maybe wrapped + SECItem *serializedCx = NULL; + PRUint8 wrappedKeyBytes[MAX_WRAPPED_KEY_LEN] = { 0 }; + PRUint8 wrappedExpBytes[MAX_WRAPPED_EXP_LEN] = { 0 }; + SECItem wrappedKey = { siBuffer, wrappedKeyBytes, sizeof(wrappedKeyBytes) }; + SECItem wrappedExp = { siBuffer, wrappedExpBytes, sizeof(wrappedExpBytes) }; + + CHECK_FAIL_ERR((!cx || !cx->aeadContext || !serialized), SEC_ERROR_INVALID_ARGS); + CHECK_FAIL_ERR((cx->aeadContext->operation != (CKA_NSS_MESSAGE | CKA_DECRYPT)), + SEC_ERROR_NOT_A_RECIPIENT); + + /* If a wrapping key was provided, do the wrap first + * so that we know what size to allocate. */ + if (wrapKey) { + rv = PK11_WrapSymKey(CKM_AES_KEY_WRAP_KWP, NULL, wrapKey, + cx->key, &wrappedKey); + CHECK_RV(rv); + rv = PK11_WrapSymKey(CKM_AES_KEY_WRAP_KWP, NULL, wrapKey, + cx->exporterSecret, &wrappedExp); + CHECK_RV(rv); + + keyBytes = &wrappedKey; + exporterBytes = &wrappedExp; + } else { + rv = PK11_ExtractKeyValue(cx->key); + CHECK_RV(rv); + keyBytes = PK11_GetKeyData(cx->key); + CHECK_FAIL(!keyBytes); + PORT_Assert(keyBytes->len == cx->aeadParams->Nk); + + rv = PK11_ExtractKeyValue(cx->exporterSecret); + CHECK_RV(rv); + exporterBytes = PK11_GetKeyData(cx->exporterSecret); + CHECK_FAIL(!exporterBytes); + PORT_Assert(exporterBytes->len == cx->kdfParams->Nh); + } + + allocLen = EXPORTED_CTX_BASE_LEN + cx->baseNonce->len + cx->encapPubKey->len; + allocLen += wrapKey ? wrappedKey.len : cx->aeadParams->Nk; + allocLen += wrapKey ? wrappedExp.len : cx->kdfParams->Nh; + + serializedCx = SECITEM_AllocItem(NULL, NULL, allocLen); + CHECK_FAIL(!serializedCx); + + walker = &serializedCx->data[0]; + *(walker)++ = (PRUint8)SERIALIZATION_VERSION; + + walker = encodeNumber(cx->kemParams->id, walker, 2); + walker = encodeNumber(cx->kdfParams->id, walker, 2); + walker = encodeNumber(cx->aeadParams->id, walker, 2); + walker = encodeNumber(cx->mode, walker, 2); + walker = encodeNumber(cx->sequenceNumber, walker, 8); + + /* sender public key, serialized. */ + walker = encodeNumber(cx->encapPubKey->len, walker, 2); + PORT_Memcpy(walker, cx->encapPubKey->data, cx->encapPubKey->len); + walker += cx->encapPubKey->len; + + /* base nonce */ + walker = encodeNumber(cx->baseNonce->len, walker, 2); + PORT_Memcpy(walker, cx->baseNonce->data, cx->baseNonce->len); + walker += cx->baseNonce->len; + + /* key. */ + walker = encodeNumber(keyBytes->len, walker, 2); + PORT_Memcpy(walker, keyBytes->data, keyBytes->len); + walker += keyBytes->len; + + /* exporter_secret. */ + walker = encodeNumber(exporterBytes->len, walker, 2); + PORT_Memcpy(walker, exporterBytes->data, exporterBytes->len); + walker += exporterBytes->len; + + CHECK_FAIL_ERR(REMAINING_BYTES(walker, serializedCx) != 0, + SEC_ERROR_LIBRARY_FAILURE); + *serialized = serializedCx; + +CLEANUP: + if (rv != SECSuccess) { + SECITEM_ZfreeItem(serializedCx, PR_TRUE); + } + return rv; +} + +HpkeContext * +PK11_HPKE_ImportContext(const SECItem *serialized, PK11SymKey *wrapKey) +{ + SECStatus rv = SECSuccess; + HpkeContext *cx = NULL; + PRUint8 *walker; + PRUint64 tmpn; + PRUint8 tmp8; + HpkeKemId kem; + HpkeKdfId kdf; + HpkeAeadId aead; + PK11SlotInfo *slot = NULL; + PK11SymKey *tmpKey = NULL; + SECItem tmpItem = { siBuffer, NULL, 0 }; + SECItem emptyItem = { siBuffer, NULL, 0 }; + + CHECK_FAIL_ERR((!serialized || !serialized->data || serialized->len == 0), + SEC_ERROR_INVALID_ARGS); + CHECK_FAIL_ERR((serialized->len < EXPORTED_CTX_BASE_LEN), SEC_ERROR_BAD_DATA); + + walker = serialized->data; + + tmp8 = *(walker++); + CHECK_FAIL_ERR((tmp8 != SERIALIZATION_VERSION), SEC_ERROR_BAD_DATA); + + walker = decodeNumber(&tmpn, walker, 2); + kem = (HpkeKemId)tmpn; + + walker = decodeNumber(&tmpn, walker, 2); + kdf = (HpkeKdfId)tmpn; + + walker = decodeNumber(&tmpn, walker, 2); + aead = (HpkeAeadId)tmpn; + + /* Create context. We'll manually set the mode, though we + * no longer have the PSK and have no need for it. */ + cx = PK11_HPKE_NewContext(kem, kdf, aead, NULL, NULL); + CHECK_FAIL(!cx); + + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR((tmpn != HpkeModeBase && tmpn != HpkeModePsk), + SEC_ERROR_BAD_DATA); + cx->mode = (HpkeModeId)tmpn; + + walker = decodeNumber(&cx->sequenceNumber, walker, 8); + slot = PK11_GetBestSlot(CKM_HKDF_DERIVE, NULL); + CHECK_FAIL(!slot); + + /* Import sender public key (serialized). */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn >= REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + cx->encapPubKey = SECITEM_DupItem(&tmpItem); + CHECK_FAIL(!cx->encapPubKey); + walker += tmpItem.len; + + /* Import base_nonce. */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn != cx->aeadParams->Nn, SEC_ERROR_BAD_DATA); + CHECK_FAIL_ERR(tmpn >= REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + cx->baseNonce = SECITEM_DupItem(&tmpItem); + CHECK_FAIL(!cx->baseNonce); + walker += tmpItem.len; + + /* Import key */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn >= REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + walker += tmpItem.len; + if (wrapKey) { + cx->key = PK11_UnwrapSymKey(wrapKey, CKM_AES_KEY_WRAP_KWP, + NULL, &tmpItem, cx->aeadParams->mech, + CKA_NSS_MESSAGE | CKA_DECRYPT, 0); + CHECK_FAIL(!cx->key); + } else { + CHECK_FAIL_ERR(tmpn != cx->aeadParams->Nk, SEC_ERROR_BAD_DATA); + tmpKey = PK11_ImportSymKey(slot, cx->aeadParams->mech, + PK11_OriginUnwrap, CKA_NSS_MESSAGE | CKA_DECRYPT, + &tmpItem, NULL); + CHECK_FAIL(!tmpKey); + cx->key = tmpKey; + } + + /* Import exporter_secret. */ + walker = decodeNumber(&tmpn, walker, 2); + CHECK_FAIL_ERR(tmpn != REMAINING_BYTES(walker, serialized), + SEC_ERROR_BAD_DATA); + tmpItem.data = walker; + tmpItem.len = tmpn; + walker += tmpItem.len; + + if (wrapKey) { + cx->exporterSecret = PK11_UnwrapSymKey(wrapKey, CKM_AES_KEY_WRAP_KWP, + NULL, &tmpItem, cx->kdfParams->mech, + CKM_HKDF_DERIVE, 0); + CHECK_FAIL(!cx->exporterSecret); + } else { + CHECK_FAIL_ERR(tmpn != cx->kdfParams->Nh, SEC_ERROR_BAD_DATA); + tmpKey = PK11_ImportSymKey(slot, CKM_HKDF_DERIVE, PK11_OriginUnwrap, + CKA_DERIVE, &tmpItem, NULL); + CHECK_FAIL(!tmpKey); + cx->exporterSecret = tmpKey; + } + + cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech, + CKA_NSS_MESSAGE | CKA_DECRYPT, + cx->key, &emptyItem); + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(tmpKey); + PK11_HPKE_DestroyContext(cx, PR_TRUE); + cx = NULL; + } + if (slot) { + PK11_FreeSlot(slot); + } + + return cx; +} + +SECStatus +PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen) +{ + if (!pk || !len || pk->keyType != ecKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* If no buffer provided, return the length required for + * the serialized public key. */ + if (!buf) { + *len = pk->u.ec.publicValue.len; + return SECSuccess; + } + + if (maxLen < pk->u.ec.publicValue.len) { + PORT_SetError(SEC_ERROR_INPUT_LEN); + return SECFailure; + } + + PORT_Memcpy(buf, pk->u.ec.publicValue.data, pk->u.ec.publicValue.len); + *len = pk->u.ec.publicValue.len; + return SECSuccess; +}; + +SECStatus +PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc, + unsigned int encLen, SECKEYPublicKey **outPubKey) +{ + SECStatus rv; + SECKEYPublicKey *pubKey = NULL; + SECOidData *oidData = NULL; + PLArenaPool *arena; + + if (!cx || !enc || encLen == 0 || !outPubKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + CHECK_FAIL(!arena); + pubKey = PORT_ArenaZNew(arena, SECKEYPublicKey); + CHECK_FAIL(!pubKey); + + pubKey->arena = arena; + pubKey->keyType = ecKey; + pubKey->pkcs11Slot = NULL; + pubKey->pkcs11ID = CK_INVALID_HANDLE; + + rv = SECITEM_MakeItem(pubKey->arena, &pubKey->u.ec.publicValue, + enc, encLen); + CHECK_RV(rv); + pubKey->u.ec.encoding = ECPoint_Undefined; + pubKey->u.ec.size = 0; + + oidData = SECOID_FindOIDByTag(cx->kemParams->oidTag); + CHECK_FAIL_ERR(!oidData, SEC_ERROR_INVALID_ALGORITHM); + + // Create parameters. + CHECK_FAIL(!SECITEM_AllocItem(pubKey->arena, &pubKey->u.ec.DEREncodedParams, + 2 + oidData->oid.len)); + + // Set parameters. + pubKey->u.ec.DEREncodedParams.data[0] = SEC_ASN1_OBJECT_ID; + pubKey->u.ec.DEREncodedParams.data[1] = oidData->oid.len; + PORT_Memcpy(pubKey->u.ec.DEREncodedParams.data + 2, oidData->oid.data, oidData->oid.len); + *outPubKey = pubKey; + +CLEANUP: + if (rv != SECSuccess) { + SECKEY_DestroyPublicKey(pubKey); + } + return rv; +}; + +static SECStatus +pk11_hpke_CheckKeys(const HpkeContext *cx, const SECKEYPublicKey *pk, + const SECKEYPrivateKey *sk) +{ + SECOidTag pkTag; + unsigned int i; + if (pk->keyType != ecKey || (sk && sk->keyType != ecKey)) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + pkTag = SECKEY_GetECCOid(&pk->u.ec.DEREncodedParams); + if (pkTag != cx->kemParams->oidTag) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + for (i = 0; i < PR_ARRAY_SIZE(kemParams); i++) { + if (cx->kemParams->oidTag == kemParams[i].oidTag) { + return SECSuccess; + } + } + + return SECFailure; +} + +static SECStatus +pk11_hpke_GenerateKeyPair(const HpkeContext *cx, SECKEYPublicKey **pkE, + SECKEYPrivateKey **skE) +{ + SECStatus rv = SECSuccess; + SECKEYPrivateKey *privKey = NULL; + SECKEYPublicKey *pubKey = NULL; + SECOidData *oidData = NULL; + SECKEYECParams ecp; + PK11SlotInfo *slot = NULL; + ecp.data = NULL; + PORT_Assert(cx && skE && pkE); + + oidData = SECOID_FindOIDByTag(cx->kemParams->oidTag); + CHECK_FAIL_ERR(!oidData, SEC_ERROR_INVALID_ALGORITHM); + ecp.data = PORT_Alloc(2 + oidData->oid.len); + CHECK_FAIL(!ecp.data); + + ecp.len = 2 + oidData->oid.len; + ecp.type = siDEROID; + ecp.data[0] = SEC_ASN1_OBJECT_ID; + ecp.data[1] = oidData->oid.len; + PORT_Memcpy(&ecp.data[2], oidData->oid.data, oidData->oid.len); + + slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL); + CHECK_FAIL(!slot); + + privKey = PK11_GenerateKeyPair(slot, CKM_EC_KEY_PAIR_GEN, &ecp, &pubKey, + PR_FALSE, PR_TRUE, NULL); + CHECK_FAIL_ERR((!privKey || !pubKey), SEC_ERROR_KEYGEN_FAIL); + PORT_Assert(rv == SECSuccess); + *skE = privKey; + *pkE = pubKey; + +CLEANUP: + if (rv != SECSuccess) { + SECKEY_DestroyPrivateKey(privKey); + SECKEY_DestroyPublicKey(pubKey); + } + if (slot) { + PK11_FreeSlot(slot); + } + PORT_Free(ecp.data); + return rv; +} + +static inline SECItem * +pk11_hpke_MakeExtractLabel(const char *prefix, unsigned int prefixLen, + const char *label, unsigned int labelLen, + const SECItem *suiteId, const SECItem *ikm) +{ + SECItem *out = NULL; + PRUint8 *walker; + out = SECITEM_AllocItem(NULL, NULL, prefixLen + labelLen + suiteId->len + (ikm ? ikm->len : 0)); + if (!out) { + return NULL; + } + + walker = out->data; + PORT_Memcpy(walker, prefix, prefixLen); + walker += prefixLen; + PORT_Memcpy(walker, suiteId->data, suiteId->len); + walker += suiteId->len; + PORT_Memcpy(walker, label, labelLen); + walker += labelLen; + if (ikm && ikm->data) { + PORT_Memcpy(walker, ikm->data, ikm->len); + } + + return out; +} + +static SECStatus +pk11_hpke_LabeledExtractData(const HpkeContext *cx, SECItem *salt, + const SECItem *suiteId, const char *label, + unsigned int labelLen, const SECItem *ikm, SECItem **out) +{ + SECStatus rv; + CK_HKDF_PARAMS params = { 0 }; + PK11SymKey *importedIkm = NULL; + PK11SymKey *prk = NULL; + PK11SlotInfo *slot = NULL; + SECItem *borrowed; + SECItem *outDerived = NULL; + SECItem *labeledIkm; + SECItem paramsItem = { siBuffer, (unsigned char *)¶ms, + sizeof(params) }; + PORT_Assert(cx && ikm && label && labelLen && out && suiteId); + + labeledIkm = pk11_hpke_MakeExtractLabel(V1_LABEL, strlen(V1_LABEL), label, labelLen, suiteId, ikm); + CHECK_FAIL(!labeledIkm); + params.bExtract = CK_TRUE; + params.bExpand = CK_FALSE; + params.prfHashMechanism = cx->kdfParams->mech; + params.ulSaltType = salt ? CKF_HKDF_SALT_DATA : CKF_HKDF_SALT_NULL; + params.pSalt = salt ? (CK_BYTE_PTR)salt->data : NULL; + params.ulSaltLen = salt ? salt->len : 0; + params.pInfo = labeledIkm->data; + params.ulInfoLen = labeledIkm->len; + + slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL); + CHECK_FAIL(!slot); + + importedIkm = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap, + CKA_DERIVE, labeledIkm, NULL); + CHECK_FAIL(!importedIkm); + prk = PK11_Derive(importedIkm, CKM_HKDF_DATA, ¶msItem, + CKM_HKDF_DERIVE, CKA_DERIVE, 0); + CHECK_FAIL(!prk); + rv = PK11_ExtractKeyValue(prk); + CHECK_RV(rv); + borrowed = PK11_GetKeyData(prk); + CHECK_FAIL(!borrowed); + outDerived = SECITEM_DupItem(borrowed); + CHECK_FAIL(!outDerived); + + *out = outDerived; + +CLEANUP: + PK11_FreeSymKey(importedIkm); + PK11_FreeSymKey(prk); + SECITEM_FreeItem(labeledIkm, PR_TRUE); + if (slot) { + PK11_FreeSlot(slot); + } + return rv; +} + +static SECStatus +pk11_hpke_LabeledExtract(const HpkeContext *cx, PK11SymKey *salt, + const SECItem *suiteId, const char *label, CK_MECHANISM_TYPE hashMech, + unsigned int labelLen, PK11SymKey *ikm, PK11SymKey **out) +{ + SECStatus rv = SECSuccess; + SECItem *innerLabel = NULL; + PK11SymKey *labeledIkm = NULL; + PK11SymKey *prk = NULL; + CK_HKDF_PARAMS params = { 0 }; + CK_KEY_DERIVATION_STRING_DATA labelData; + SECItem labelDataItem = { siBuffer, NULL, 0 }; + SECItem paramsItem = { siBuffer, (unsigned char *)¶ms, + sizeof(params) }; + PORT_Assert(cx && ikm && label && labelLen && out && suiteId); + + innerLabel = pk11_hpke_MakeExtractLabel(V1_LABEL, strlen(V1_LABEL), label, labelLen, suiteId, NULL); + CHECK_FAIL(!innerLabel); + labelData.pData = innerLabel->data; + labelData.ulLen = innerLabel->len; + labelDataItem.data = (PRUint8 *)&labelData; + labelDataItem.len = sizeof(labelData); + labeledIkm = PK11_Derive(ikm, CKM_CONCATENATE_DATA_AND_BASE, + &labelDataItem, CKM_GENERIC_SECRET_KEY_GEN, CKA_DERIVE, 0); + CHECK_FAIL(!labeledIkm); + + params.bExtract = CK_TRUE; + params.bExpand = CK_FALSE; + params.prfHashMechanism = hashMech; + params.ulSaltType = salt ? CKF_HKDF_SALT_KEY : CKF_HKDF_SALT_NULL; + params.hSaltKey = salt ? PK11_GetSymKeyHandle(salt) : CK_INVALID_HANDLE; + + prk = PK11_Derive(labeledIkm, CKM_HKDF_DERIVE, ¶msItem, + CKM_HKDF_DERIVE, CKA_DERIVE, 0); + CHECK_FAIL(!prk); + *out = prk; + +CLEANUP: + PK11_FreeSymKey(labeledIkm); + SECITEM_ZfreeItem(innerLabel, PR_TRUE); + return rv; +} + +static SECStatus +pk11_hpke_LabeledExpand(const HpkeContext *cx, PK11SymKey *prk, const SECItem *suiteId, + const char *label, unsigned int labelLen, const SECItem *info, + unsigned int L, CK_MECHANISM_TYPE hashMech, PK11SymKey **outKey, + SECItem **outItem) +{ + SECStatus rv = SECSuccess; + CK_MECHANISM_TYPE keyMech; + CK_MECHANISM_TYPE deriveMech; + CK_HKDF_PARAMS params = { 0 }; + PK11SymKey *derivedKey = NULL; + SECItem *labeledInfoItem = NULL; + SECItem paramsItem = { siBuffer, (unsigned char *)¶ms, + sizeof(params) }; + SECItem *derivedKeyData; + PRUint8 encodedL[2]; + PRUint8 *walker = encodedL; + size_t len; + PORT_Assert(cx && prk && label && (!!outKey != !!outItem)); + + walker = encodeNumber(L, walker, 2); + len = info ? info->len : 0; + len += sizeof(encodedL) + strlen(V1_LABEL) + suiteId->len + labelLen; + labeledInfoItem = SECITEM_AllocItem(NULL, NULL, len); + CHECK_FAIL(!labeledInfoItem); + + walker = labeledInfoItem->data; + PORT_Memcpy(walker, encodedL, sizeof(encodedL)); + walker += sizeof(encodedL); + PORT_Memcpy(walker, V1_LABEL, strlen(V1_LABEL)); + walker += strlen(V1_LABEL); + PORT_Memcpy(walker, suiteId->data, suiteId->len); + walker += suiteId->len; + PORT_Memcpy(walker, label, labelLen); + walker += labelLen; + if (info) { + PORT_Memcpy(walker, info->data, info->len); + } + + params.bExtract = CK_FALSE; + params.bExpand = CK_TRUE; + params.prfHashMechanism = hashMech; + params.ulSaltType = CKF_HKDF_SALT_NULL; + params.pInfo = labeledInfoItem->data; + params.ulInfoLen = labeledInfoItem->len; + deriveMech = outItem ? CKM_HKDF_DATA : CKM_HKDF_DERIVE; + /* If we're expanding to the encryption key use the appropriate mechanism. */ + keyMech = (label && !strcmp(KEY_LABEL, label)) ? cx->aeadParams->mech : CKM_HKDF_DERIVE; + + derivedKey = PK11_Derive(prk, deriveMech, ¶msItem, keyMech, CKA_DERIVE, L); + CHECK_FAIL(!derivedKey); + + if (outItem) { + /* Don't allow export of real keys. */ + CHECK_FAIL_ERR(deriveMech != CKM_HKDF_DATA, SEC_ERROR_LIBRARY_FAILURE); + rv = PK11_ExtractKeyValue(derivedKey); + CHECK_RV(rv); + derivedKeyData = PK11_GetKeyData(derivedKey); + CHECK_FAIL_ERR((!derivedKeyData), SEC_ERROR_NO_KEY); + *outItem = SECITEM_DupItem(derivedKeyData); + CHECK_FAIL(!*outItem); + PK11_FreeSymKey(derivedKey); + } else { + *outKey = derivedKey; + } + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(derivedKey); + } + SECITEM_ZfreeItem(labeledInfoItem, PR_TRUE); + return rv; +} + +static SECStatus +pk11_hpke_ExtractAndExpand(const HpkeContext *cx, PK11SymKey *ikm, + const SECItem *kemContext, PK11SymKey **out) +{ + SECStatus rv; + PK11SymKey *eaePrk = NULL; + PK11SymKey *sharedSecret = NULL; + PRUint8 suiteIdBuf[5]; + PRUint8 *walker; + PORT_Memcpy(suiteIdBuf, KEM_LABEL, strlen(KEM_LABEL)); + SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) }; + PORT_Assert(cx && ikm && kemContext && out); + + walker = &suiteIdBuf[3]; + walker = encodeNumber(cx->kemParams->id, walker, 2); + + rv = pk11_hpke_LabeledExtract(cx, NULL, &suiteIdItem, EAE_PRK_LABEL, + cx->kemParams->hashMech, strlen(EAE_PRK_LABEL), + ikm, &eaePrk); + CHECK_RV(rv); + + rv = pk11_hpke_LabeledExpand(cx, eaePrk, &suiteIdItem, SH_SEC_LABEL, strlen(SH_SEC_LABEL), + kemContext, cx->kemParams->Nsecret, cx->kemParams->hashMech, + &sharedSecret, NULL); + CHECK_RV(rv); + *out = sharedSecret; + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(sharedSecret); + } + PK11_FreeSymKey(eaePrk); + return rv; +} + +static SECStatus +pk11_hpke_Encap(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *skE, + SECKEYPublicKey *pkR) +{ + SECStatus rv; + PK11SymKey *dh = NULL; + SECItem *kemContext = NULL; + SECItem *encPkR = NULL; + unsigned int tmpLen; + + PORT_Assert(cx && skE && pkE && pkR); + + rv = pk11_hpke_CheckKeys(cx, pkE, skE); + CHECK_RV(rv); + rv = pk11_hpke_CheckKeys(cx, pkR, NULL); + CHECK_RV(rv); + + dh = PK11_PubDeriveWithKDF(skE, pkR, PR_FALSE, NULL, NULL, CKM_ECDH1_DERIVE, + CKM_SHA512_HMAC /* unused */, CKA_DERIVE, 0, + CKD_NULL, NULL, NULL); + CHECK_FAIL(!dh); + + /* Encapsulate our sender public key. Many use cases + * (including ECH) require that the application fetch + * this value, so do it once and store into the cx. */ + rv = PK11_HPKE_Serialize(pkE, NULL, &tmpLen, 0); + CHECK_RV(rv); + cx->encapPubKey = SECITEM_AllocItem(NULL, NULL, tmpLen); + CHECK_FAIL(!cx->encapPubKey); + rv = PK11_HPKE_Serialize(pkE, cx->encapPubKey->data, + &cx->encapPubKey->len, cx->encapPubKey->len); + CHECK_RV(rv); + + rv = PK11_HPKE_Serialize(pkR, NULL, &tmpLen, 0); + CHECK_RV(rv); + + kemContext = SECITEM_AllocItem(NULL, NULL, cx->encapPubKey->len + tmpLen); + CHECK_FAIL(!kemContext); + + PORT_Memcpy(kemContext->data, cx->encapPubKey->data, cx->encapPubKey->len); + rv = PK11_HPKE_Serialize(pkR, &kemContext->data[cx->encapPubKey->len], &tmpLen, tmpLen); + CHECK_RV(rv); + + rv = pk11_hpke_ExtractAndExpand(cx, dh, kemContext, &cx->sharedSecret); + CHECK_RV(rv); + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(cx->sharedSecret); + cx->sharedSecret = NULL; + } + SECITEM_FreeItem(encPkR, PR_TRUE); + SECITEM_FreeItem(kemContext, PR_TRUE); + PK11_FreeSymKey(dh); + return rv; +} + +SECStatus +PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int L, + PK11SymKey **out) +{ + SECStatus rv; + PK11SymKey *exported; + PRUint8 suiteIdBuf[10]; + PRUint8 *walker; + PORT_Memcpy(suiteIdBuf, HPKE_LABEL, strlen(HPKE_LABEL)); + SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) }; + + /* Arbitrary info length limit well under the specified max. */ + if (!cx || !info || (!info->data && info->len) || info->len > 0xFFFF || + !L || (L > 255 * cx->kdfParams->Nh)) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + walker = &suiteIdBuf[4]; + walker = encodeNumber(cx->kemParams->id, walker, 2); + walker = encodeNumber(cx->kdfParams->id, walker, 2); + walker = encodeNumber(cx->aeadParams->id, walker, 2); + + rv = pk11_hpke_LabeledExpand(cx, cx->exporterSecret, &suiteIdItem, SEC_LABEL, + strlen(SEC_LABEL), info, L, cx->kdfParams->mech, + &exported, NULL); + CHECK_RV(rv); + *out = exported; + +CLEANUP: + return rv; +} + +static SECStatus +pk11_hpke_Decap(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *skR, + const SECItem *encS) +{ + SECStatus rv; + PK11SymKey *dh = NULL; + SECItem *encR = NULL; + SECItem *kemContext = NULL; + SECKEYPublicKey *pkS = NULL; + unsigned int tmpLen; + + if (!cx || !skR || !pkR || !encS || !encS->data || !encS->len) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = PK11_HPKE_Deserialize(cx, encS->data, encS->len, &pkS); + CHECK_RV(rv); + + rv = pk11_hpke_CheckKeys(cx, pkR, skR); + CHECK_RV(rv); + rv = pk11_hpke_CheckKeys(cx, pkS, NULL); + CHECK_RV(rv); + + dh = PK11_PubDeriveWithKDF(skR, pkS, PR_FALSE, NULL, NULL, CKM_ECDH1_DERIVE, + CKM_SHA512_HMAC /* unused */, CKA_DERIVE, 0, + CKD_NULL, NULL, NULL); + CHECK_FAIL(!dh); + + /* kem_context = concat(enc, pkRm) */ + rv = PK11_HPKE_Serialize(pkR, NULL, &tmpLen, 0); + CHECK_RV(rv); + + kemContext = SECITEM_AllocItem(NULL, NULL, encS->len + tmpLen); + CHECK_FAIL(!kemContext); + + PORT_Memcpy(kemContext->data, encS->data, encS->len); + rv = PK11_HPKE_Serialize(pkR, &kemContext->data[encS->len], &tmpLen, + kemContext->len - encS->len); + CHECK_RV(rv); + rv = pk11_hpke_ExtractAndExpand(cx, dh, kemContext, &cx->sharedSecret); + CHECK_RV(rv); + + /* Store the sender serialized public key, which + * may be required by application use cases. */ + cx->encapPubKey = SECITEM_DupItem(encS); + CHECK_FAIL(!cx->encapPubKey); + +CLEANUP: + if (rv != SECSuccess) { + PK11_FreeSymKey(cx->sharedSecret); + cx->sharedSecret = NULL; + } + PK11_FreeSymKey(dh); + SECKEY_DestroyPublicKey(pkS); + SECITEM_FreeItem(encR, PR_TRUE); + SECITEM_ZfreeItem(kemContext, PR_TRUE); + return rv; +} + +const SECItem * +PK11_HPKE_GetEncapPubKey(const HpkeContext *cx) +{ + if (!cx) { + return NULL; + } + return cx->encapPubKey; +} + +static SECStatus +pk11_hpke_KeySchedule(HpkeContext *cx, const SECItem *info) +{ + SECStatus rv; + SECItem contextItem = { siBuffer, NULL, 0 }; + unsigned int len; + unsigned int off; + PK11SymKey *secret = NULL; + SECItem *pskIdHash = NULL; + SECItem *infoHash = NULL; + PRUint8 suiteIdBuf[10]; + PRUint8 *walker; + PORT_Memcpy(suiteIdBuf, HPKE_LABEL, strlen(HPKE_LABEL)); + SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) }; + PORT_Assert(cx && info && cx->psk && cx->pskId); + + walker = &suiteIdBuf[4]; + walker = encodeNumber(cx->kemParams->id, walker, 2); + walker = encodeNumber(cx->kdfParams->id, walker, 2); + walker = encodeNumber(cx->aeadParams->id, walker, 2); + + rv = pk11_hpke_LabeledExtractData(cx, NULL, &suiteIdItem, PSK_ID_LABEL, + strlen(PSK_ID_LABEL), cx->pskId, &pskIdHash); + CHECK_RV(rv); + rv = pk11_hpke_LabeledExtractData(cx, NULL, &suiteIdItem, INFO_LABEL, + strlen(INFO_LABEL), info, &infoHash); + CHECK_RV(rv); + + // Make the context string + len = sizeof(cx->mode) + pskIdHash->len + infoHash->len; + CHECK_FAIL(!SECITEM_AllocItem(NULL, &contextItem, len)); + off = 0; + PORT_Memcpy(&contextItem.data[off], &cx->mode, sizeof(cx->mode)); + off += sizeof(cx->mode); + PORT_Memcpy(&contextItem.data[off], pskIdHash->data, pskIdHash->len); + off += pskIdHash->len; + PORT_Memcpy(&contextItem.data[off], infoHash->data, infoHash->len); + off += infoHash->len; + + // Compute the keys + rv = pk11_hpke_LabeledExtract(cx, cx->sharedSecret, &suiteIdItem, SECRET_LABEL, + cx->kdfParams->mech, strlen(SECRET_LABEL), + cx->psk, &secret); + CHECK_RV(rv); + rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, KEY_LABEL, strlen(KEY_LABEL), + &contextItem, cx->aeadParams->Nk, cx->kdfParams->mech, + &cx->key, NULL); + CHECK_RV(rv); + rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, NONCE_LABEL, strlen(NONCE_LABEL), + &contextItem, cx->aeadParams->Nn, cx->kdfParams->mech, + NULL, &cx->baseNonce); + CHECK_RV(rv); + rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, EXP_LABEL, strlen(EXP_LABEL), + &contextItem, cx->kdfParams->Nh, cx->kdfParams->mech, + &cx->exporterSecret, NULL); + CHECK_RV(rv); + +CLEANUP: + /* If !SECSuccess, callers will tear down the context. */ + PK11_FreeSymKey(secret); + SECITEM_FreeItem(&contextItem, PR_FALSE); + SECITEM_FreeItem(infoHash, PR_TRUE); + SECITEM_FreeItem(pskIdHash, PR_TRUE); + return rv; +} + +SECStatus +PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *skR, + const SECItem *enc, const SECItem *info) +{ + SECStatus rv; + SECItem empty = { siBuffer, NULL, 0 }; + + CHECK_FAIL_ERR((!cx || !skR || !info || !enc || !enc->data || !enc->len), + SEC_ERROR_INVALID_ARGS); + /* Already setup */ + CHECK_FAIL_ERR((cx->aeadContext), SEC_ERROR_INVALID_STATE); + + rv = pk11_hpke_Decap(cx, pkR, skR, enc); + CHECK_RV(rv); + rv = pk11_hpke_KeySchedule(cx, info); + CHECK_RV(rv); + + /* Store the key context for subsequent calls to Open(). + * PK11_CreateContextBySymKey refs the key internally. */ + PORT_Assert(cx->key); + cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech, + CKA_NSS_MESSAGE | CKA_DECRYPT, + cx->key, &empty); + CHECK_FAIL_ERR((!cx->aeadContext), SEC_ERROR_LIBRARY_FAILURE); + +CLEANUP: + if (rv != SECSuccess) { + /* Clear everything past NewContext. */ + PK11_HPKE_DestroyContext(cx, PR_FALSE); + } + return rv; +} + +SECStatus +PK11_HPKE_SetupS(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *skE, + SECKEYPublicKey *pkR, const SECItem *info) +{ + SECStatus rv; + SECItem empty = { siBuffer, NULL, 0 }; + SECKEYPublicKey *tmpPkE = NULL; + SECKEYPrivateKey *tmpSkE = NULL; + CHECK_FAIL_ERR((!cx || !pkR || !info || (!!skE != !!pkE)), SEC_ERROR_INVALID_ARGS); + /* Already setup */ + CHECK_FAIL_ERR((cx->aeadContext), SEC_ERROR_INVALID_STATE); + + /* If NULL was passed for the local keypair, generate one. */ + if (skE == NULL) { + rv = pk11_hpke_GenerateKeyPair(cx, &tmpPkE, &tmpSkE); + if (rv != SECSuccess) { + /* Code set */ + return SECFailure; + } + rv = pk11_hpke_Encap(cx, tmpPkE, tmpSkE, pkR); + } else { + rv = pk11_hpke_Encap(cx, pkE, skE, pkR); + } + CHECK_RV(rv); + + SECItem defaultInfo = { siBuffer, NULL, 0 }; + if (!info || !info->data) { + info = &defaultInfo; + } + rv = pk11_hpke_KeySchedule(cx, info); + CHECK_RV(rv); + + PORT_Assert(cx->key); + cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech, + CKA_NSS_MESSAGE | CKA_ENCRYPT, + cx->key, &empty); + CHECK_FAIL_ERR((!cx->aeadContext), SEC_ERROR_LIBRARY_FAILURE); + +CLEANUP: + if (rv != SECSuccess) { + /* Clear everything past NewContext. */ + PK11_HPKE_DestroyContext(cx, PR_FALSE); + } + SECKEY_DestroyPrivateKey(tmpSkE); + SECKEY_DestroyPublicKey(tmpPkE); + return rv; +} + +SECStatus +PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, + SECItem **out) +{ + SECStatus rv; + PRUint8 ivOut[12] = { 0 }; + SECItem *ct = NULL; + size_t maxOut; + unsigned char tagBuf[HASH_LENGTH_MAX]; + size_t tagLen; + unsigned int fixedBits; + + /* aad may be NULL, PT may be zero-length but not NULL. */ + if (!cx || !cx->aeadContext || + (aad && aad->len && !aad->data) || + !pt || (pt->len && !pt->data) || + !out) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PORT_Assert(cx->baseNonce->len == sizeof(ivOut)); + PORT_Memcpy(ivOut, cx->baseNonce->data, cx->baseNonce->len); + + tagLen = cx->aeadParams->tagLen; + maxOut = pt->len + tagLen; + fixedBits = (cx->baseNonce->len - 8) * 8; + ct = SECITEM_AllocItem(NULL, NULL, maxOut); + CHECK_FAIL(!ct); + + rv = PK11_AEADOp(cx->aeadContext, + CKG_GENERATE_COUNTER_XOR, fixedBits, + ivOut, sizeof(ivOut), + aad ? aad->data : NULL, + aad ? aad->len : 0, + ct->data, (int *)&ct->len, maxOut, + tagBuf, tagLen, + pt->data, pt->len); + CHECK_RV(rv); + CHECK_FAIL_ERR((ct->len > maxOut - tagLen), SEC_ERROR_LIBRARY_FAILURE); + + /* Append the tag to the ciphertext. */ + PORT_Memcpy(&ct->data[ct->len], tagBuf, tagLen); + ct->len += tagLen; + *out = ct; + +CLEANUP: + if (rv != SECSuccess) { + SECITEM_ZfreeItem(ct, PR_TRUE); + } + return rv; +} + +/* PKCS #11 defines the IV generator function to be ignored on + * decrypt (i.e. it uses the nonce input, as provided, as the IV). + * The sequence number is kept independently on each endpoint and + * the XORed IV is not transmitted, so we have to do our own IV + * construction XOR outside of the token. */ +static SECStatus +pk11_hpke_makeIv(HpkeContext *cx, PRUint8 *iv, size_t ivLen) +{ + unsigned int counterLen = sizeof(cx->sequenceNumber); + PORT_Assert(cx->baseNonce->len == ivLen); + PORT_Assert(counterLen == 8); + if (cx->sequenceNumber == PR_UINT64(0xffffffffffffffff)) { + /* Overflow */ + PORT_SetError(SEC_ERROR_INVALID_KEY); + return SECFailure; + } + + PORT_Memcpy(iv, cx->baseNonce->data, cx->baseNonce->len); + for (size_t i = 0; i < counterLen; i++) { + iv[cx->baseNonce->len - 1 - i] ^= + PORT_GET_BYTE_BE(cx->sequenceNumber, + counterLen - 1 - i, counterLen); + } + return SECSuccess; +} + +SECStatus +PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, + const SECItem *ct, SECItem **out) +{ + SECStatus rv; + PRUint8 constructedNonce[12] = { 0 }; + unsigned int tagLen; + SECItem *pt = NULL; + + /* aad may be NULL, CT may be zero-length but not NULL. */ + if ((!cx || !cx->aeadContext || !ct || !out) || + (aad && aad->len && !aad->data) || + (!ct->data || (ct->data && !ct->len))) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + tagLen = cx->aeadParams->tagLen; + CHECK_FAIL_ERR((ct->len < tagLen), SEC_ERROR_INVALID_ARGS); + + pt = SECITEM_AllocItem(NULL, NULL, ct->len); + CHECK_FAIL(!pt); + + rv = pk11_hpke_makeIv(cx, constructedNonce, sizeof(constructedNonce)); + CHECK_RV(rv); + + rv = PK11_AEADOp(cx->aeadContext, CKG_NO_GENERATE, 0, + constructedNonce, sizeof(constructedNonce), + aad ? aad->data : NULL, + aad ? aad->len : 0, + pt->data, (int *)&pt->len, pt->len, + &ct->data[ct->len - tagLen], tagLen, + ct->data, ct->len - tagLen); + CHECK_RV(rv); + cx->sequenceNumber++; + *out = pt; + +CLEANUP: + if (rv != SECSuccess) { + SECITEM_ZfreeItem(pt, PR_TRUE); + } + return rv; +} |