Commit 10afb436 authored by Kevin Jacobs's avatar Kevin Jacobs

Bug 1678398 - Add Export/Import functions for HPKE context. r=mt

This patch adds and exports two new HPKE functions: `PK11_HPKE_ExportContext` and
`PK11_HPKE_ImportContext`, which are used to export a serialized HPKE context,
then later reimport that context and resume Open and Export operations. Only receiver
contexts are currently supported for export (see the rationale in pk11pub.h).

One other change introduced here is that `PK11_HPKE_GetEncapPubKey` now works as
expected on the receiver side.

If the `wrapKey` argument is provided to the Export/Import functions, then the
symmetric keys are wrapped with AES Key Wrap with Padding (SP800-38F, 6.3)
prior to serialization.

Differential Revision: https://phabricator.services.mozilla.com/D99277

--HG--
extra : moz-landing-system : lando
parent e2528512
2 Added functions:
[A] 'function SECStatus PK11_HPKE_ExportContext(const HpkeContext*, PK11SymKey*, SECItem**)' {PK11_HPKE_ExportContext@@NSS_3.62}
[A] 'function HpkeContext* PK11_HPKE_ImportContext(const SECItem*, PK11SymKey*)' {PK11_HPKE_ImportContext@@NSS_3.62}
......@@ -116,6 +116,32 @@ class HpkeTest {
EXPECT_EQ(msg, opened);
}
void ExportSecret(const ScopedHpkeContext &receiver,
ScopedPK11SymKey &exported) {
std::vector<uint8_t> context = {'c', 't', 'x', 't'};
SECItem context_item = {siBuffer, context.data(),
static_cast<unsigned int>(context.size())};
PK11SymKey *tmp_exported = nullptr;
ASSERT_EQ(SECSuccess, PK11_HPKE_ExportSecret(receiver.get(), &context_item,
64, &tmp_exported));
exported.reset(tmp_exported);
}
void ExportImportRecvContext(ScopedHpkeContext &scoped_cx,
PK11SymKey *wrapping_key) {
SECItem *tmp_exported = nullptr;
EXPECT_EQ(SECSuccess, PK11_HPKE_ExportContext(scoped_cx.get(), wrapping_key,
&tmp_exported));
EXPECT_NE(nullptr, tmp_exported);
ScopedSECItem context(tmp_exported);
scoped_cx.reset();
HpkeContext *tmp_imported =
PK11_HPKE_ImportContext(context.get(), wrapping_key);
EXPECT_NE(nullptr, tmp_imported);
scoped_cx.reset(tmp_imported);
}
bool GenerateKeyPair(ScopedSECKEYPublicKey &pub_key,
ScopedSECKEYPrivateKey &priv_key) {
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
......@@ -437,6 +463,105 @@ TEST_F(ModeParameterizedTest, BadEncapsulatedPubKey) {
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
}
TEST_P(ModeParameterizedTest, ContextExportImportEncrypt) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SealOpen(sender, receiver, msg, aad, nullptr);
ExportImportRecvContext(receiver, nullptr);
SealOpen(sender, receiver, msg, aad, nullptr);
}
TEST_P(ModeParameterizedTest, ContextExportImportExport) {
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
ScopedPK11SymKey sender_export;
ScopedPK11SymKey receiver_export;
ScopedPK11SymKey receiver_reexport;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
ExportSecret(sender, sender_export);
ExportSecret(receiver, receiver_export);
CheckEquality(sender_export.get(), receiver_export.get());
ExportImportRecvContext(receiver, nullptr);
ExportSecret(receiver, receiver_reexport);
CheckEquality(receiver_export.get(), receiver_reexport.get());
}
TEST_P(ModeParameterizedTest, ContextExportImportWithWrap) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
// Generate a wrapping key, then use it for export.
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ASSERT_TRUE(slot);
ScopedPK11SymKey kek(
PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr));
ASSERT_NE(nullptr, kek);
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SealOpen(sender, receiver, msg, aad, nullptr);
ExportImportRecvContext(receiver, kek.get());
SealOpen(sender, receiver, msg, aad, nullptr);
}
TEST_P(ModeParameterizedTest, ExportSenderContext) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SECItem *tmp_exported = nullptr;
EXPECT_EQ(SECFailure,
PK11_HPKE_ExportContext(sender.get(), nullptr, &tmp_exported));
EXPECT_EQ(nullptr, tmp_exported);
EXPECT_EQ(SEC_ERROR_NOT_A_RECIPIENT, PORT_GetError());
}
TEST_P(ModeParameterizedTest, ContextUnwrapBadKey) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
// Generate a wrapping key, then use it for export.
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
ASSERT_TRUE(slot);
ScopedPK11SymKey kek(
PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr));
ASSERT_NE(nullptr, kek);
ScopedPK11SymKey not_kek(
PK11_KeyGen(slot.get(), CKM_AES_CBC, nullptr, 16, nullptr));
ASSERT_NE(nullptr, not_kek);
ScopedHpkeContext sender;
ScopedHpkeContext receiver;
SetUpEphemeralContexts(sender, receiver, std::get<0>(GetParam()),
std::get<1>(GetParam()), std::get<2>(GetParam()),
std::get<3>(GetParam()));
SECItem *tmp_exported = nullptr;
EXPECT_EQ(SECSuccess,
PK11_HPKE_ExportContext(receiver.get(), kek.get(), &tmp_exported));
EXPECT_NE(nullptr, tmp_exported);
ScopedSECItem context(tmp_exported);
EXPECT_EQ(nullptr, PK11_HPKE_ImportContext(context.get(), not_kek.get()));
EXPECT_EQ(SEC_ERROR_BAD_DATA, PORT_GetError());
}
TEST_P(ModeParameterizedTest, EphemeralKeys) {
std::vector<uint8_t> msg = {'s', 'e', 'c', 'r', 'e', 't'};
std::vector<uint8_t> aad = {'a', 'a', 'd'};
......
......@@ -1213,3 +1213,10 @@ PK11_PubUnwrapSymKeyWithMechanism;
;+ local:
;+ *;
;+};
;+NSS_3.62 { # NSS 3.62 release
;+ global:
PK11_HPKE_ExportContext;
PK11_HPKE_ImportContext;
;+ local:
;+ *;
;+};
\ No newline at end of file
......@@ -53,12 +53,24 @@ PK11_HPKE_GetEncapPubKey(const HpkeContext *cx)
return NULL;
}
SECStatus
PK11_HPKE_ExportContext(const HpkeContext *cx, PK11SymKey *wrapKey, SECItem **serialized)
{
PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
return SECFailure;
}
SECStatus
PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info,
unsigned int L, PK11SymKey **outKey)
{
PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
return SECFailure;
}
HpkeContext *
PK11_HPKE_ImportContext(const SECItem *serialized, PK11SymKey *wrapKey)
{
PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
return NULL;
}
SECStatus
PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, const SECItem *ct,
SECItem **outPt)
......@@ -94,6 +106,7 @@ PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *
}
#else
#define SERIALIZATION_VERSION 1
static const char *DRAFT_LABEL = "HPKE-07";
static const char *EXP_LABEL = "exp";
......@@ -129,12 +142,14 @@ static const hpkeKemParams kemParams[] = {
{ 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 },
......@@ -192,6 +207,20 @@ encodeNumber(PRUint64 value, PRUint8 *b, size_t 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)
{
......@@ -288,6 +317,246 @@ PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit)
}
}
/* Export Format:
struct {
uint8 serilizationVersion;
uint8 hpkeVersion;
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 26 /* 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)++ = (PRUint8)HPKE_DRAFT_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);
tmp8 = *(walker++);
CHECK_FAIL_ERR((tmp8 != HPKE_DRAFT_VERSION), SEC_ERROR_INVALID_ALGORITHM);
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)
{
......@@ -799,6 +1068,12 @@ pk11_hpke_Decap(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *s
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);
......@@ -817,7 +1092,6 @@ PK11_HPKE_GetEncapPubKey(const HpkeContext *cx)
if (!cx) {
return NULL;
}
/* Will be NULL on receiver. */
return cx->encapPubKey;
}
......@@ -1088,4 +1362,5 @@ CLEANUP:
}
return rv;
}
#endif // NSS_ENABLE_DRAFT_HPKE
......@@ -746,9 +746,28 @@ HpkeContext *PK11_HPKE_NewContext(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId a
SECStatus PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc,
unsigned int encLen, SECKEYPublicKey **outPubKey);
void PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit);
/* Serialize an initialized receiver context. This only retains the keys and
* associated information necessary to resume Export and Open operations after
* import. Serialization is currently supported for receiver contexts only.
* This is done for two reasons: 1) it avoids having to move the encryption
* sequence number outside of the token (or adding encryption context
* serialization support to softoken), and 2) we don't have to worry about IV
* reuse due to sequence number cloning.
*
* |wrapKey| is required when exporting in FIPS mode. If exported with a
* wrapping key, that same key must be provided to the import function,
* otherwise behavior is undefined.
*
* Even when exported with key wrap, HPKE expects the nonce to also be kept
* secret and that value is not protected by wrapKey. Applications are
* responsible for maintaining the confidentiality of the exported information.
*/
SECStatus PK11_HPKE_ExportContext(const HpkeContext *cx, PK11SymKey *wrapKey, SECItem **serialized);
SECStatus PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int L,
PK11SymKey **outKey);
const SECItem *PK11_HPKE_GetEncapPubKey(const HpkeContext *cx);
HpkeContext *PK11_HPKE_ImportContext(const SECItem *serialized, PK11SymKey *wrapKey);
SECStatus PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, const SECItem *ct, SECItem **outPt);
SECStatus PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, SECItem **outCt);
SECStatus PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment