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

ML-DSA private keys from seeds #2157

Merged
merged 13 commits into from
Feb 5, 2025
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
Loading