Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to export ML-DSA key-pairs in seed format #2194

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 72 additions & 9 deletions crypto/evp_extra/p_pqdsa_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,40 @@ static void pqdsa_free(EVP_PKEY *pkey) {
pkey->pkey.pqdsa_key = NULL;
}

static int pqdsa_get_priv_raw_seed(PQDSA_KEY *key, const PQDSA *pqdsa,
uint8_t *out,size_t *out_len) {
if (key->seed == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET);
return 0;
}

if (*out_len != key->pqdsa->keygen_seed_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
return 0;
}

OPENSSL_memcpy(out, key->seed, pqdsa->keygen_seed_len);
*out_len = pqdsa->keygen_seed_len;
return 1;
}

static int pqdsa_get_priv_raw_key(PQDSA_KEY *key, const PQDSA *pqdsa,
uint8_t *out,size_t *out_len) {
if (key->private_key == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
return 0;
}

if (*out_len < key->pqdsa->private_key_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
return 0;
}

OPENSSL_memcpy(out, key->private_key, pqdsa->private_key_len);
*out_len = pqdsa->private_key_len;
return 1;
}

static int pqdsa_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out,
size_t *out_len) {
if (pkey->pkey.pqdsa_key == NULL) {
Expand All @@ -28,28 +62,33 @@ static int pqdsa_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out,
PQDSA_KEY *key = pkey->pkey.pqdsa_key;
const PQDSA *pqdsa = key->pqdsa;

if (key->private_key == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
return 0;
}

if (pqdsa == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_NO_PARAMETERS_SET);
return 0;
}

// caller is doing size check, we have to return either the length of the
// full private key, or the seed. Since the private_key_len varies and
// keygen_seed_len is often constant (such as 32 bytes in ML-DSA) we choose
// to return the full private key len size.
if (out == NULL) {
*out_len = key->pqdsa->private_key_len;
return 1;
}

if (*out_len < key->pqdsa->private_key_len) {
if (*out_len >= key->pqdsa->private_key_len) {
if(!pqdsa_get_priv_raw_key(key, pqdsa, out, out_len)) {
return 0;
}
} else if (*out_len == key->pqdsa->keygen_seed_len) {
if(!pqdsa_get_priv_raw_seed(key, pqdsa, out, out_len)) {
return 0;
}
} else {
OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
return 0;
}

OPENSSL_memcpy(out, key->private_key, pqdsa->private_key_len);
*out_len = pqdsa->private_key_len;
return 1;
}

Expand Down Expand Up @@ -129,6 +168,30 @@ static int pqdsa_pub_encode(CBB *out, const EVP_PKEY *pkey) {
return 1;
}

static int pqdsa_priv_encode_seed(CBB *out, const EVP_PKEY *pkey) {
PQDSA_KEY *key = pkey->pkey.pqdsa_key;
const PQDSA *pqdsa = key->pqdsa;
if (key->seed == NULL) {
OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR);
return 0;
}
// See https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/ section 6.
CBB pkcs8, algorithm, oid, seed;
if (!CBB_add_asn1(out, &pkcs8, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1_uint64(&pkcs8, 0 /* version */) ||
!CBB_add_asn1(&pkcs8, &algorithm, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) ||
!CBB_add_bytes(&oid, pqdsa->oid, pqdsa->oid_len) ||
!CBB_add_asn1(&pkcs8, &seed, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&seed, key->seed, pqdsa->keygen_seed_len) ||
!CBB_flush(out)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR);
return 0;
}

return 1;
}

static int pqdsa_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) {
PQDSA_KEY *a_key = a->pkey.pqdsa_key;
PQDSA_KEY *b_key = b->pkey.pqdsa_key;
Expand Down Expand Up @@ -235,7 +298,7 @@ const EVP_PKEY_ASN1_METHOD pqdsa_asn1_meth = {
pqdsa_pub_cmp,
pqdsa_priv_decode,
pqdsa_priv_encode,
NULL /*priv_encode_v2*/,
pqdsa_priv_encode_seed,
NULL /* pqdsa_set_priv_raw */,
NULL /*pqdsa_set_pub_raw */ ,
pqdsa_get_priv_raw,
Expand Down
132 changes: 126 additions & 6 deletions crypto/evp_extra/p_pqdsa_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ struct PQDSATestVector {
const size_t public_key_len;
const size_t private_key_len;
const size_t signature_len;
const size_t seed_len;
const char *kat_filename;
const uint8_t *kPublicKey;
const uint8_t *kPublicKeySPKI;
Expand Down Expand Up @@ -1163,6 +1164,7 @@ static const struct PQDSATestVector parameterSet[] = {
1312,
2560,
2420,
32,
"ml_dsa/kat/MLDSA_44_hedged_pure.txt",
mldsa44kPublicKey,
mldsa44kPublicKeySPKI,
Expand All @@ -1180,6 +1182,7 @@ static const struct PQDSATestVector parameterSet[] = {
1952,
4032,
3309,
32,
"ml_dsa/kat/MLDSA_65_hedged_pure.txt",
mldsa65kPublicKey,
mldsa65kPublicKeySPKI,
Expand All @@ -1197,6 +1200,7 @@ static const struct PQDSATestVector parameterSet[] = {
2592,
4896,
4627,
32,
"ml_dsa/kat/MLDSA_87_hedged_pure.txt",
mldsa87kPublicKey,
mldsa87kPublicKeySPKI,
Expand Down Expand Up @@ -1462,12 +1466,27 @@ TEST_P(PQDSAParameterTest, RawFunctions) {
EXPECT_NE(private_pkey->pkey.pqdsa_key->public_key, nullptr);
EXPECT_NE(private_pkey->pkey.pqdsa_key->private_key, nullptr);

// ---- 5. Test get_raw public/private failure modes ----
uint8_t *buf = nullptr;
size_t buf_size;
// ---- 5. Test get_raw private key seeds ----
size_t seed_len = GetParam().seed_len;
std::vector<uint8_t> seed(seed_len);

ASSERT_TRUE(EVP_PKEY_get_raw_private_key(pkey.get(), seed.data(), &seed_len));
EXPECT_EQ(seed_len, GetParam().seed_len);

// expand the seed back into a PKEY and check correctness with original pkey
bssl::UniquePtr<EVP_PKEY> seeded_pkey(
EVP_PKEY_pqdsa_new_raw_private_key(GetParam().nid, seed.data(), seed.size()));

ASSERT_NE(seeded_pkey, nullptr);
EXPECT_NE(seeded_pkey->pkey.pqdsa_key->public_key, nullptr);
EXPECT_NE(seeded_pkey->pkey.pqdsa_key->private_key, nullptr);
EXPECT_EQ(1, EVP_PKEY_cmp(pkey.get(), seeded_pkey.get()));

// ---- 6. Test get_raw public/private failure modes ----
std::vector<uint8_t> get_sk(sk_len);

// Attempting to get a private key that is not present must fail correctly
EXPECT_FALSE(EVP_PKEY_get_raw_private_key(public_pkey.get(), buf, &buf_size));
EXPECT_FALSE(EVP_PKEY_get_raw_private_key(public_pkey.get(), get_sk.data(), &sk_len));
GET_ERR_AND_CHECK_REASON(EVP_R_NOT_A_PRIVATE_KEY);

// Null PKEY must fail correctly.
Expand Down Expand Up @@ -1496,7 +1515,7 @@ TEST_P(PQDSAParameterTest, RawFunctions) {
ASSERT_FALSE(EVP_PKEY_get_raw_private_key(pkey.get(), sk.data(), &sk_len));
GET_ERR_AND_CHECK_REASON(EVP_R_BUFFER_TOO_SMALL);

// ---- 6. Test new_raw public/private failure modes ----
// ---- 7. Test new_raw public/private failure modes ----
// Invalid lengths
pk_len = GetParam().public_key_len - 1;
ASSERT_FALSE(EVP_PKEY_pqdsa_new_raw_public_key(nid, pk.data(), pk_len));
Expand Down Expand Up @@ -1577,6 +1596,107 @@ TEST_P(PQDSAParameterTest, MarshalParse) {
Bytes(pkey->pkey.pqdsa_key->public_key, GetParam().public_key_len));
}

// Helper function that:
// 1. Creates a memory BIO
// 2. Writes the provided DER data to BIO in PEM format
// 3. Determines resulting PEM size and allocates buffer
// 4. Reads PEM data into output buffer
// 5. Returns the PEM string and length via out parameters
static bool DER_to_PEM(const uint8_t* der, size_t der_len,
jakemas marked this conversation as resolved.
Show resolved Hide resolved
char** out_pem, size_t* out_pem_len) {
// Create memory BIO for writing
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
if (!bio) {
return false;
}

// Write PEM
if (!PEM_write_bio(bio.get(), "PRIVATE KEY", "", der, der_len)) {
return false;
}

// Get PEM size
*out_pem_len = BIO_pending(bio.get());

// Allocate buffer for PEM
*out_pem = (char*)OPENSSL_malloc(*out_pem_len + 1);
if (!*out_pem) {
return false;
}

// Read PEM into buffer
if (BIO_read(bio.get(), *out_pem, *out_pem_len) <= 0) {
OPENSSL_free(*out_pem);
*out_pem = nullptr;
return false;
}
(*out_pem)[*out_pem_len] = '\0';

return true;
}

TEST_P(PQDSAParameterTest, Marshalv2ParseSeed) {
// ---- 1. Setup phase: generate a key ----
int nid = GetParam().nid;
bssl::UniquePtr<EVP_PKEY> pkey(generate_key_pair(nid));

bssl::ScopedCBB cbb;
uint8_t *der;
size_t der_len;

// ---- 2. Test encode (marshal) of private key ----
ASSERT_TRUE(CBB_init(cbb.get(), 0));
ASSERT_TRUE(EVP_marshal_private_key_v2(cbb.get(), pkey.get()));
ASSERT_TRUE(CBB_finish(cbb.get(), &der, &der_len));

// ---- 3. Test parse (unmarshal) of private key ----
CBS cbs;
CBS_init(&cbs, der, der_len);
bssl::UniquePtr<EVP_PKEY> parsed_key(EVP_parse_private_key(&cbs));
ASSERT_TRUE(parsed_key);
ASSERT_TRUE(CBS_len(&cbs) == 0);

// ---- 4. Verify the parsed key matches the original ----
// Compare key parameters
ASSERT_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_id(parsed_key.get()));
ASSERT_EQ(1, EVP_PKEY_cmp(pkey.get(), parsed_key.get()));

// ---- 5. generate from known seed and compare wth known value ----

static const uint8_t KnownSeed[32] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};

// Create PKEY from the generated raw key
bssl::UniquePtr<EVP_PKEY> seeded_pkey(
EVP_PKEY_pqdsa_new_raw_private_key(GetParam().nid, KnownSeed, sizeof(KnownSeed)));
ASSERT_TRUE(seeded_pkey);

//buffer for DER
bssl::ScopedCBB cbb1;
uint8_t *der1;
size_t der1_len;

// encode private key
ASSERT_TRUE(CBB_init(cbb1.get(), 0));
ASSERT_TRUE(EVP_marshal_private_key_v2(cbb1.get(), seeded_pkey.get()));
ASSERT_TRUE(CBB_finish(cbb1.get(), &der1, &der1_len));

char* pem = nullptr;
size_t pem_len = 0;
DER_to_PEM(der1, der1_len, &pem, &pem_len);

// compare exact bytes between PEM encoding from standard, with PEM produced
// from KnownSeed
EXPECT_EQ(Bytes(pem, pem_len), Bytes(GetParam().private_pem_str, pem_len));
OPENSSL_free(der);
OPENSSL_free(der1);
OPENSSL_free(pem);
}

TEST_P(PQDSAParameterTest, SIGOperations) {
// ---- 1. Setup phase: generate PQDSA EVP KEY and sign/verify contexts ----
bssl::UniquePtr<EVP_PKEY> pkey(generate_key_pair(GetParam().nid));
Expand Down Expand Up @@ -1780,7 +1900,7 @@ TEST_P(PQDSAParameterTest, KeyConsistencyTest) {
// ---- 3. Generate a raw public key from the raw private key ----
ASSERT_TRUE(GetParam().pack_key(pk.data(), sk.data()));

// ---- 4. Generate a raw public key from the raw private key ----
// ---- 4. Test that the calculated pk is equal to original pkey ----
CMP_VEC_AND_PKEY_PUBLIC(pk, pkey, pk_len);
}

Expand Down
2 changes: 1 addition & 1 deletion crypto/fipsmodule/evp/p_pqdsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static int pkey_pqdsa_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
PQDSA_KEY *key = PQDSA_KEY_new();
if (key == NULL ||
!PQDSA_KEY_init(key, pqdsa) ||
!pqdsa->method->pqdsa_keygen(key->public_key, key->private_key) ||
!pqdsa->method->pqdsa_keygen(key->public_key, key->private_key, key->seed) ||
!EVP_PKEY_assign(pkey, EVP_PKEY_PQDSA, key)) {
PQDSA_KEY_free(key);
return 0;
Expand Down
15 changes: 9 additions & 6 deletions crypto/fipsmodule/ml_dsa/ml_dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ int ml_dsa_44_keypair_internal_no_self_test(uint8_t *public_key /* OUT */,
}

int ml_dsa_44_keypair(uint8_t *public_key /* OUT */,
uint8_t *private_key /* OUT */) {
uint8_t *private_key /* OUT */,
uint8_t *seed /* OUT */) {
boringssl_ensure_ml_dsa_self_test();
ml_dsa_params params;
ml_dsa_44_params_init(&params);
return (ml_dsa_keypair(&params, public_key, private_key) == 0);
return (ml_dsa_keypair(&params, public_key, private_key, seed) == 0);
}

int ml_dsa_44_pack_pk_from_sk(uint8_t *public_key /* OUT */,
Expand Down Expand Up @@ -186,11 +187,12 @@ int ml_dsa_extmu_44_verify_internal(const uint8_t *public_key /* IN */,
}

int ml_dsa_65_keypair(uint8_t *public_key /* OUT */,
uint8_t *private_key /* OUT */) {
uint8_t *private_key /* OUT */,
uint8_t *seed /* OUT */) {
boringssl_ensure_ml_dsa_self_test();
ml_dsa_params params;
ml_dsa_65_params_init(&params);
return (ml_dsa_keypair(&params, public_key, private_key) == 0);
return (ml_dsa_keypair(&params, public_key, private_key, seed) == 0);
}

int ml_dsa_65_pack_pk_from_sk(uint8_t *public_key /* OUT */,
Expand Down Expand Up @@ -318,11 +320,12 @@ int ml_dsa_extmu_65_verify_internal(const uint8_t *public_key /* IN */,
}

int ml_dsa_87_keypair(uint8_t *public_key /* OUT */,
uint8_t *private_key /* OUT */) {
uint8_t *private_key /* OUT */,
uint8_t *seed /* OUT */) {
boringssl_ensure_ml_dsa_self_test();
ml_dsa_params params;
ml_dsa_87_params_init(&params);
return (ml_dsa_keypair(&params, public_key, private_key) == 0);
return (ml_dsa_keypair(&params, public_key, private_key, seed) == 0);
}

int ml_dsa_87_pack_pk_from_sk(uint8_t *public_key /* OUT */,
Expand Down
9 changes: 6 additions & 3 deletions crypto/fipsmodule/ml_dsa/ml_dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
extern "C" {
#endif
OPENSSL_EXPORT int ml_dsa_44_keypair(uint8_t *public_key,
uint8_t *secret_key);
uint8_t *secret_key,
uint8_t *seed);

OPENSSL_EXPORT int ml_dsa_44_pack_pk_from_sk(uint8_t *public_key,
const uint8_t *private_key);
Expand Down Expand Up @@ -96,7 +97,8 @@ OPENSSL_EXPORT int ml_dsa_extmu_44_verify_internal(const uint8_t *public_key,
const uint8_t *pre, size_t pre_len);

OPENSSL_EXPORT int ml_dsa_65_keypair(uint8_t *public_key,
uint8_t *secret_key);
uint8_t *secret_key,
uint8_t *seed);

OPENSSL_EXPORT int ml_dsa_65_pack_pk_from_sk(uint8_t *public_key,
const uint8_t *private_key);
Expand Down Expand Up @@ -146,7 +148,8 @@ OPENSSL_EXPORT int ml_dsa_extmu_65_verify_internal(const uint8_t *public_key,
const uint8_t *pre, size_t pre_len);

OPENSSL_EXPORT int ml_dsa_87_keypair(uint8_t *public_key,
uint8_t *secret_key);
uint8_t *secret_key,
uint8_t *seed);

OPENSSL_EXPORT int ml_dsa_87_pack_pk_from_sk(uint8_t *public_key,
const uint8_t *private_key);
Expand Down
Loading
Loading