1276 lines
41 KiB
C
1276 lines
41 KiB
C
/*
|
|
* 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;
|
|
}
|