Skip to content

Commit

Permalink
store seed during keygen and export
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemas committed Feb 13, 2025
1 parent 7518c78 commit 7d8957a
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 36 deletions.
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
134 changes: 128 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,109 @@ 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,
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);

//TODO get priv_raw for seed mode (based on input size)
}

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 +1902,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

0 comments on commit 7d8957a

Please sign in to comment.