Skip to content

Commit

Permalink
Support keypair calculation for PQDSA PKEY (#2145)
Browse files Browse the repository at this point in the history
### Issues:
Resolves #CryptoAlg-2868

### Description of changes: 
Following from the first part of this PR to add low level support for
ML-DSA public key generation from private keys. This PR uses the new
`pqdsa` method `pqdsa->pqdsa_pack_pk_from_sk`. We supply
`ml_dsa_44_pack_pk_from_sk`, `ml_dsa_65_pack_pk_from_sk`, and
`ml_dsa_87_pack_pk_from_sk` from `ml_dsa.h` to provide this
functionality for ML-DSA.

As we use `EVP_parse_private_key` to import `PQDSA` keys into AWS-LC, we
need to modify the `PQDSA` asn.1 private key decoding function to
additionally populate the corresponding public key.

The `pqdsa_priv_decode` function in `p_pqdsa_asn1.c` has been modified
to attempt to generate a public key from the provided secret key, should
the method be defined for that `pqdsa` structure. As ML-DSA is the only
current `PQDSA`, the library currently does implement methods for all
`PQDSA` PKEY types.

### Call-outs:
> what happens if a new `PQDSA` algorithm is introduced to aws-lc, and
doesn't support `pk` calculation from `sk`?

We populate the methods as `NULL` and will modify the G.test that
expects the public key to be generated. We can make this change simply
if/when we add to the `PQDSA` capabilites with an algorithm that doesn't
support `pk` from `sk`.

### Testing:
To test functionality, I have adapted the `PQDSAParameterTest` G.test
`MarshalParse` to now verify that the public key is populated after
calling `EVP_parse_private_key`. We then verify that the public key
calculated is equal to the original public 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 Jan 29, 2025
1 parent ea58b3f commit 695c3a0
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 5 deletions.
30 changes: 28 additions & 2 deletions crypto/evp_extra/p_pqdsa_asn1.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,38 @@ static int pqdsa_priv_decode(EVP_PKEY *out, CBS *params, CBS *key, CBS *pubkey)
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}
// set the pqdsa params on the fresh pkey

// Set the pqdsa params on the fresh pkey
if (!EVP_PKEY_pqdsa_set_params(out, OBJ_cbs2nid(params))) {
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
return 0;
}
return PQDSA_KEY_set_raw_private_key(out->pkey.pqdsa_key,CBS_data(key));

// Set the private key
if (!PQDSA_KEY_set_raw_private_key(out->pkey.pqdsa_key, CBS_data(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);
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;
}

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

return 1;
}

static int pqdsa_priv_encode(CBB *out, const EVP_PKEY *pkey) {
Expand Down
11 changes: 11 additions & 0 deletions crypto/evp_extra/p_pqdsa_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,17 @@ TEST_P(PQDSAParameterTest, MarshalParse) {
ASSERT_TRUE(priv_pkey_from_der);
EXPECT_EQ(Bytes(priv_pkey_from_der->pkey.pqdsa_key->private_key, GetParam().private_key_len),
Bytes(pkey->pkey.pqdsa_key->private_key, GetParam().private_key_len));

// When importing a PQDSA private key, the public key will be calculated and
// used to populate the public key. To test the calculated key is correct,
// we first check that the public key has been populated, then test for equality
// with the expected public key:
ASSERT_NE(priv_pkey_from_der, nullptr);
EXPECT_NE(priv_pkey_from_der->pkey.pqdsa_key->public_key, nullptr);
EXPECT_NE(priv_pkey_from_der->pkey.pqdsa_key->private_key, nullptr);

EXPECT_EQ(Bytes(priv_pkey_from_der->pkey.pqdsa_key->public_key, GetParam().public_key_len),
Bytes(pkey->pkey.pqdsa_key->public_key, GetParam().public_key_len));
}

TEST_P(PQDSAParameterTest, SIGOperations) {
Expand Down
3 changes: 3 additions & 0 deletions crypto/pqdsa/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ typedef struct {
size_t sig_len,
const uint8_t *digest,
size_t digest_len);

int (*pqdsa_pack_pk_from_sk)(uint8_t *public_key,
const uint8_t *private_key);
} PQDSA_METHOD;

// PQDSA structure and helper functions.
Expand Down
9 changes: 6 additions & 3 deletions crypto/pqdsa/pqdsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,23 +89,26 @@ static const PQDSA_METHOD sig_ml_dsa_44_method = {
ml_dsa_44_sign,
ml_dsa_extmu_44_sign,
ml_dsa_44_verify,
ml_dsa_extmu_44_verify
ml_dsa_extmu_44_verify,
ml_dsa_44_pack_pk_from_sk
};

static const PQDSA_METHOD sig_ml_dsa_65_method = {
ml_dsa_65_keypair,
ml_dsa_65_sign,
ml_dsa_extmu_65_sign,
ml_dsa_65_verify,
ml_dsa_extmu_65_verify
ml_dsa_extmu_65_verify,
ml_dsa_65_pack_pk_from_sk
};

static const PQDSA_METHOD sig_ml_dsa_87_method = {
ml_dsa_87_keypair,
ml_dsa_87_sign,
ml_dsa_extmu_87_sign,
ml_dsa_87_verify,
ml_dsa_extmu_87_verify
ml_dsa_extmu_87_verify,
ml_dsa_87_pack_pk_from_sk
};

static const PQDSA sig_ml_dsa_44 = {
Expand Down

0 comments on commit 695c3a0

Please sign in to comment.