Skip to content

Commit

Permalink
ML-DSA private keys from seeds (#2157)
Browse files Browse the repository at this point in the history
### Issues:
Resolves #CryptoAlg-2889

### Description of changes: 

The industry has decided upon a few conventions to assist with the
usability of ML-DSA key sizes, one of which is to decrease the amount of
bytes transferred when encoding ML-DSA private keys. As ML-DSA keys can
be deterministically generated from a provided 32-byte seed, many
standards bodies have selected to use private keys in seed format. For
example, the IETF draft that standardizes the use of ML-DSA in X.509.
https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/

This provides:
- Compliance with IETF spec
https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/
- Will allow interoperability with BC to allow customers to import
ML-DSA keys

The functionality that provides key generation from seeds for ML-DSA was
added in my previous PR #1999. This PR
exposes the internal key generation functions within the `PQDSA` struct,
so that a new function named `PQDSA_KEY_set_raw_keypair_from_seed` can
directly call the method.

The function `PQDSA_KEY_set_raw_keypair_from_seed` simply performs:
- a check that the provided `seed` is of the correct length
- allocation of public/private key buffers
- key generation from seed using `pqdsa_keygen_internal`

As such, `pqdsa_priv_decode` has been modified to call:
- `PQDSA_KEY_set_raw_private_key` when the provided length of the key is
`pqdsa->private_key_len`
- `PQDSA_KEY_set_raw_keypair_from_seed` when the provided length of the
key is `pqdsa->keygen_seed_len`

We implement this at the EVP layer by modifying
`EVP_PKEY_pqdsa_new_raw_private_key` to:
- call `PQDSA_KEY_set_raw_private_key` when the `len` provided is
`private_key_len`
- call `PQDSA_KEY_set_raw_keypair_from_seed` when the `len` provided is
`keygen_seed_len`

### Call-outs:

- The API `EVP_PKEY_pqdsa_new_raw_private_key` will now populate both
public and private keys when provided a seed.
- The API `EVP_PKEY_pqdsa_new_raw_public_key` only sets the public key
- The API `PQDSA_KEY_set_raw_private_key` will only set private key
- The API `PQDSA_KEY_set_raw_public_key` will only set public key
- The API `EVP_parse_public_key` sets the public key from DER encoded of
public key
- The API `EVP_parse_private_key` can accept DER encodings of either the
full private key representation, or the 32-byte seed.
- The API `EVP_marshal_public_key` sets the DER encoded public key from
a EVP PKEY
- The API `EVP_marshal_private_key` sets the DER encoded full private
key representation (it cannot marshal the seed, as this cannot be
derived from the full private key representation.

### Testing:

To provide KAT for keys from seeds I have included the `Appendix C.
Examples` from
https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/.
For each provided ML-DSA parameter set, we generate the example private
key provided from seed, and check that the corresponding public key
matches that in the specification. These tests are in
`PQDSAParameterTest` namely, `ParsePrivateKey`.

These examples show how much shorter ML-DSA-87 private keys are in seed
format:
```
 -----BEGIN PRIVATE KEY-----
   MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
   HB0eHw==
   -----END PRIVATE KEY-----
```

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license and the ISC license.
  • Loading branch information
jakemas authored Feb 5, 2025
1 parent 9660ac3 commit 54d5051
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 35 deletions.
22 changes: 16 additions & 6 deletions crypto/evp_extra/p_pqdsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,21 +302,31 @@ EVP_PKEY *EVP_PKEY_pqdsa_new_raw_private_key(int nid, const uint8_t *in, size_t

EVP_PKEY *ret = EVP_PKEY_pqdsa_new(nid);
if (ret == NULL || ret->pkey.pqdsa_key == NULL) {
// EVP_PKEY_kem_new sets the appropriate error.
// EVP_PKEY_pqdsa_new sets the appropriate error.
goto err;
}

const PQDSA *pqdsa = PQDSA_KEY_get0_dsa(ret->pkey.pqdsa_key);
if (pqdsa->private_key_len != len) {
// Get PQDSA instance and validate lengths
const PQDSA *pqdsa = PQDSA_KEY_get0_dsa(ret->pkey.pqdsa_key);
if (len != pqdsa->private_key_len && len != pqdsa->keygen_seed_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
goto err;
}

CBS cbs;
CBS_init(&cbs, in, len);
if (!PQDSA_KEY_set_raw_private_key(ret->pkey.pqdsa_key, &cbs)) {
// PQDSA_KEY_set_raw_private_key sets the appropriate error.
goto err;

// Set key based on input length
if (len == pqdsa->private_key_len) {
if (!PQDSA_KEY_set_raw_private_key(ret->pkey.pqdsa_key, &cbs)) {
// PQDSA_KEY_set_raw_private_key sets the appropriate error.
goto err;
}
} else if (len == pqdsa->keygen_seed_len) {
if (!PQDSA_KEY_set_raw_keypair_from_seed(ret->pkey.pqdsa_key, &cbs)) {
// PQDSA_KEY_set_raw_keypair_from_seed sets the appropriate error.
goto err;
}
}

return ret;
Expand Down
41 changes: 20 additions & 21 deletions crypto/evp_extra/p_pqdsa_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,31 +153,30 @@ static int pqdsa_priv_decode(EVP_PKEY *out, CBS *params, CBS *key, CBS *pubkey)
return 0;
}

// Set the private key
if (!PQDSA_KEY_set_raw_private_key(out->pkey.pqdsa_key, key)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}

// Create buffers to store public key based on size
size_t pk_len = out->pkey.pqdsa_key->pqdsa->public_key_len;
uint8_t *public_key = OPENSSL_malloc(pk_len);

if (public_key == NULL) {
OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
// check the size of the provided input against the private key and seed len
if (CBS_len(key) != out->pkey.pqdsa_key->pqdsa->private_key_len &&
CBS_len(key) != out->pkey.pqdsa_key->pqdsa->keygen_seed_len) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
}

// Construct the public key from the private key
if (!out->pkey.pqdsa_key->pqdsa->method->pqdsa_pack_pk_from_sk(
public_key, CBS_data(key))) {
OPENSSL_free(public_key);
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
// See https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/
// The caller can either provide the full key of size |private_key_len| or
// |keygen_seed_len|.
if (CBS_len(key) == out->pkey.pqdsa_key->pqdsa->private_key_len) {

// Set the private key
if (!PQDSA_KEY_set_raw_private_key(out->pkey.pqdsa_key, key)) {
// PQDSA_KEY_set_raw_private_key sets the appropriate error.
return 0;
}

} else if (CBS_len(key) == out->pkey.pqdsa_key->pqdsa->keygen_seed_len) {
if (!PQDSA_KEY_set_raw_keypair_from_seed(out->pkey.pqdsa_key, key)) {
// PQDSA_KEY_set_raw_keypair_from_seed sets the appropriate error.
return 0;
}
}

out->pkey.pqdsa_key->public_key = public_key;

return 1;
}

Expand Down
239 changes: 233 additions & 6 deletions crypto/evp_extra/p_pqdsa_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

#include <gtest/gtest.h>
#include <openssl/base.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/mem.h>
#include <openssl/obj.h>
#include <openssl/pem.h>
#include "../test/test_util.h"

#include <vector>
Expand Down Expand Up @@ -944,6 +946,165 @@ static const uint8_t mldsa87kPublicKeySPKI[] = {
0x67, 0x46, 0xC8, 0x68, 0xB7, 0x65, 0x05, 0x20, 0x02, 0x70, 0xDA, 0x6B, 0xC7,
0x34};

// https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/06/
// C.2. Example Public Key
const char *mldsa_44_pub_pem_str =
"-----BEGIN PUBLIC KEY-----\n"
"MIIFMjALBglghkgBZQMEAxEDggUhANeytHJUquDbReeTDUqY0sl9jxOX0Xidr6Fw\n"
"JLMW6b7JT8mUbULxm3mnQTu6oz5xSctC7VEVaTrAQfrLmIretf4OHYYxGEmVtZLD\n"
"l9IpTi4U+QqkFLo4JomaxD9MzKy8JumoMrlRGNXLQzy++WYLABOOCBf2HnYsonTD\n"
"atVU6yKqwRYuSrAay6HjjE79j4C2WzM9D3LlXf5xzpweu5iJ58VhBsD9c4A6Kuz+\n"
"r97XqjyyztpU0SvYzTanjPl1lDtHq9JeiArEUuV0LtHo0agq+oblkMdYwVrk0oQN\n"
"kryhpQkPQElll/yn2LlRPxob2m6VCqqY3kZ1B9Sk9aTwWZIWWCw1cvYu2okFqzWB\n"
"ZwxKAnd6M+DKcpX9j0/20aCjp2g9ZfX19/xg2gI+gmxfkhRMAvfRuhB1mHVT6pNn\n"
"/NdtmQt/qZzUWv24g21D5Fn1GH3wWEeXCaAepoNZNfpwRgmQzT3BukAbqUurHd5B\n"
"rGerMxncrKBgSNTE7vJ+4TqcF9BTj0MPLWQtwkFWYN54h32NirxyUjl4wELkKF9D\n"
"GYRsRBJiQpdoRMEOVWuiFbWnGeWdDGsqltOYWQcf3MLN51JKe+2uVOhbMY6FTo/i\n"
"svPt+slxkSgnCq/R5QRMOk/a/Z/zH5B4S46ORZYUSg2vWGUR09mWK56pWvGXtOX8\n"
"YPKx7RXeOlvvX4m9x52RBR2bKBbnT6VFMe/cHL501EiFf0drzVjyHAtlOzt2pOB2\n"
"plWaMCcYVVzGP3SFmqurkl8COGHKjND3utsocfZ9VTJtdFETWtRfShumkRj7ssij\n"
"DuyTku8/l3Bmya3VxxDMZHsVFNIX2VjHAXw+kP0gwE5nS5BIbpNwoxoAHTL0c5ee\n"
"SQZ0nn5Hf6C3RQj4pfI3gxK4PCW9OIygsP/3R4uvQrcWZ+2qyXxGsSlkPlhuWwVa\n"
"DCEZRtTzbmdb7Vhg+gQqMV2YJhZNapI3w1pfv0lUkKW9TfJIuVxKrneEtgVnMWas\n"
"QkW1tLCCoJ6TI+YvIHjFt2eDRG3v1zatOjcC1JsImESQCmGDM5e8RBmzDXqXoLOH\n"
"wZEUdMTUG1PjKpd6y28Op122W7OeWecB52lX3vby1EVZwxp3EitSBOO1whnxaIsU\n"
"7QvAuAGz5ugtzUPpwOn0F0TNmBW9G8iCDYuxI/BPrNGxtoXdWisbjbvz7ZM2cPCV\n"
"oYC08ZLQixC4+rvfzCskUY4y7qCl4MkEyoRHgAg/OwzS0Li2r2e8NVuUlAJdx7Cn\n"
"j6gOOi2/61EyiFHWB4GY6Uk2Ua54fsAlH5Irow6fUd9iptcnhM890gU5MXbfoySl\n"
"Er2Ulwo23TSlFKhnkfDrNvAUWwmrZGUbSgMTsplhGiocSIkWJ1mHaKMRQGC6RENI\n"
"bfUVIqHOiLMJhcIW+ObtF43VZ7MEoNTK+6iCooNC8XqaomrljbYwCD0sNY/fVmw/\n"
"XWKkKFZ7yeqM6VyqDzVHSwv6jzOaJQq0388gg76O77wQVeGP4VNw7ssmBWbYP/Br\n"
"IRquxDyim1TM0A+IFaJGXvC0ZRXMfkHzEk8J7/9zkwmrWLKaFFmgC85QOOk4yWeP\n"
"cusOTuX9quZtn4Vz/Jf8QrSVn0v4th14Qz6GsDNdbpGRxNi/SHs5BcEIz9asJLDO\n"
"t9y3z1H4TQ7Wh7lerrHFM8BvDZcCPZKnCCWDe1m6bLfU5WsKh8IDhiro8xW6WSXo\n"
"7e+meTaaIgJ2YVHxapZfn4Hs52zAcLVYaeTbl4TPBcgwsyQsgxI=\n"
"-----END PUBLIC KEY-----\n";

const char *mldsa_65_pub_pem_str =
"-----BEGIN PUBLIC KEY-----\n"
"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\n"
"HgLy6ERP\n"
"-----END PUBLIC KEY-----\n";

const char *mldsa_87_pub_pem_str =
"-----BEGIN PUBLIC KEY-----\n"
"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\n"
"5VvgYiIvi6kKl0BzMmsxISrs4KNKYA==\n"
"-----END PUBLIC KEY-----\n";

// https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/06/
// C.1. Example Private Key
const char *mldsa_44_priv_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob\n"
"HB0eHw==\n"
"-----END PRIVATE KEY-----\n";

const char *mldsa_65_priv_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MDICAQAwCwYJYIZIAWUDBAMSBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob\n"
"HB0eHw==\n"
"-----END PRIVATE KEY-----\n";

const char *mldsa_87_priv_pem_str =
"-----BEGIN PRIVATE KEY-----\n"
"MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob\n"
"HB0eHw==\n"
"-----END PRIVATE KEY-----\n";

struct PQDSATestVector {
const char name[20];
const int nid;
Expand All @@ -954,6 +1115,8 @@ struct PQDSATestVector {
const uint8_t *kPublicKey;
const uint8_t *kPublicKeySPKI;
const size_t kPublicKeySPKI_len;
const char *public_pem_str;
const char *private_pem_str;

int (*keygen)(uint8_t *public_key, uint8_t *private_key, const uint8_t *seed);

Expand Down Expand Up @@ -1004,6 +1167,8 @@ static const struct PQDSATestVector parameterSet[] = {
mldsa44kPublicKey,
mldsa44kPublicKeySPKI,
1334,
mldsa_44_pub_pem_str,
mldsa_44_priv_pem_str,
ml_dsa_44_keypair_internal,
ml_dsa_44_sign_internal,
ml_dsa_44_verify_internal,
Expand All @@ -1019,6 +1184,8 @@ static const struct PQDSATestVector parameterSet[] = {
mldsa65kPublicKey,
mldsa65kPublicKeySPKI,
1974,
mldsa_65_pub_pem_str,
mldsa_65_priv_pem_str,
ml_dsa_65_keypair_internal,
ml_dsa_65_sign_internal,
ml_dsa_65_verify_internal,
Expand All @@ -1034,6 +1201,8 @@ static const struct PQDSATestVector parameterSet[] = {
mldsa87kPublicKey,
mldsa87kPublicKeySPKI,
2614,
mldsa_87_pub_pem_str,
mldsa_87_priv_pem_str,
ml_dsa_87_keypair_internal,
ml_dsa_87_sign_internal,
ml_dsa_87_verify_internal,
Expand Down Expand Up @@ -1287,22 +1456,20 @@ TEST_P(PQDSAParameterTest, RawFunctions) {
EXPECT_NE(public_pkey->pkey.pqdsa_key->public_key, nullptr);
EXPECT_EQ(public_pkey->pkey.pqdsa_key->private_key, nullptr);

// check that private key is present and public key is not present in private_key
// check that calling EVP_PKEY_pqdsa_new_raw_private_key populates both the
// public and private key
ASSERT_NE(private_pkey, nullptr);
EXPECT_EQ(private_pkey->pkey.pqdsa_key->public_key, nullptr);
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;

// Attempting to get a public/private key that is not present must fail correctly
// 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));
GET_ERR_AND_CHECK_REASON(EVP_R_NOT_A_PRIVATE_KEY);

EXPECT_FALSE(EVP_PKEY_get_raw_public_key(private_pkey.get(), buf, &buf_size));
GET_ERR_AND_CHECK_REASON(EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);

// Null PKEY must fail correctly.
ASSERT_FALSE(EVP_PKEY_get_raw_public_key(nullptr, pk.data(), &pk_len));
GET_ERR_AND_CHECK_REASON(EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
Expand Down Expand Up @@ -1532,6 +1699,66 @@ TEST_P(PQDSAParameterTest, ParsePublicKey) {
ASSERT_TRUE(pkey_from_der);
}

// Helper function that:
// 1. Creates a BIO
// 2. Reads the provided |pem_string| into bio
// 3. Reads the PEM into DER encoding
// 4. Returns the DER data and length
static bool PEM_to_DER(const char* pem_str, uint8_t** out_der, long* out_der_len) {
char *name = nullptr;
char *header = nullptr;

// Create BIO from memory
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem_str, strlen(pem_str)));
if (!bio) {
return false;
}

// Read PEM into DER
if (PEM_read_bio(bio.get(), &name, &header, out_der, out_der_len) <= 0) {
OPENSSL_free(name);
OPENSSL_free(header);
OPENSSL_free(*out_der);
*out_der = nullptr;
return false;
}

OPENSSL_free(name);
OPENSSL_free(header);
return true;
}

TEST_P(PQDSAParameterTest, ParsePrivateKey) {
// ---- 1. Setup phase: parse provided public/private from PEM strings ----
CBS cbs_pub, cbs_priv;
uint8_t *der_pub = nullptr, *der_priv = nullptr;
long der_pub_len = 0, der_priv_len = 0;

ASSERT_TRUE(PEM_to_DER(GetParam().public_pem_str, &der_pub, &der_pub_len));
ASSERT_TRUE(PEM_to_DER(GetParam().private_pem_str, &der_priv, &der_priv_len));

CBS_init(&cbs_pub, der_pub, der_pub_len);
CBS_init(&cbs_priv, der_priv, der_priv_len);

// ---- 2. Attempt to parse private key ----
bssl::UniquePtr<EVP_PKEY> pkey1(EVP_parse_private_key(&cbs_priv));
ASSERT_TRUE(pkey1);

// ---- 3. Attempt to parse public key ----
bssl::UniquePtr<EVP_PKEY> pkey2(EVP_parse_public_key(&cbs_pub));
ASSERT_TRUE(pkey2);

// ---- 4. Compare public keys ----
// EVP_parse_private_key will populate both public and private key, we verify
// that the public key calculated by EVP_parse_private_key is equivalent to
// the public key that was parsed from PEM.
ASSERT_EQ(1, EVP_PKEY_cmp(pkey1.get(), pkey2.get()));

// Clean up
OPENSSL_free(der_pub);
OPENSSL_free(der_priv);
}

TEST_P(PQDSAParameterTest, KeyConsistencyTest) {
// This test: generates a random PQDSA key pair extracts the private key, and
// runs the public key calculator function to populate the coresponding public key.
Expand Down
Loading

0 comments on commit 54d5051

Please sign in to comment.