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

Support keypair calculation for PQDSA PKEY #2145

Merged
merged 8 commits into from
Jan 29, 2025

Conversation

jakemas
Copy link
Contributor

@jakemas jakemas commented Jan 28, 2025

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.

@jakemas jakemas requested a review from a team as a code owner January 28, 2025 20:04
return 0;
}

// Calculate public key from private key if method exists
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How expensive is this calculation? What customer workflow needs the public key given the private key? Could this be lazily initialized?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other types of EVP_PKEY (which have public/private components) ensure that the public key is populated whenever a private key is deserialized. For example, see EC and ED25519.

Other EVP_PKEY_xxx methods (such as EVP_PKEY_cmp) assume that the public key is populated for any EVP_PKEY that has functions available for public keys. (In fact EVP_PKEY_cmp can currently SEGFAULT for EVP_PKEY_PQDSA due to this.)

There might be other places for this computation to take place, but it needs to be done when deserializing a private key.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhh good point, looking at ED25519 a little more should we add raw support?

  1. priv_decode -> ed25519_priv_decode decodes the private key and calls ed25519_set_priv_raw
  2. set_priv_raw -> ed25519_set_priv_raw calculates the public key from the private key, and compares it to the supplied public key if provided

Here in ML-DSA:

  1. priv_decode -> pqdsa_priv_decode decodes the private key and calculates the public key and sets it
  2. set_prive_raw -> not implemented for pqdsa

Copy link
Contributor Author

@jakemas jakemas Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do implement set priv raw for pqdsa, just not as part of the EVP methods...

Some more background: set_priv_raw is not implemented for ML-DSA or ML-KEM. In ED25519 things are a little different, as there are no "parameter sets" for ED25519, it's just one algorithm. In ML-DSA/ML-KEM we have multiple NIDs to define algorithm versions.

As such, we implement EVP_PKEY_pqdsa_new_raw_private_key at https://github.com/aws/aws-lc/blob/main/include/openssl/evp.h#L960-L970 just like for KEMs. I suppose your suggestion is then to remove EVP_PKEY_pqdsa_new_raw_private_key / EVP_PKEY_pqdsa_new_raw_public_key and support via EVP_PKEY_new_raw_private_key/ EVP_PKEY_new_raw_public_key? The problem is, EVP_PKEY_new_raw_private_key expects type not nid and type = EVP_PKEY_PQDSA is not specific enough to know which NID it is... there's no OID, and we can't use the length to determine uniqueness as another scheme could have the same length.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the aws-lc-rs, I'm just using the length of the "raw" key to differentiate between the NIDs under the EVP_PKEY_PQDSA type: justsmth/aws-lc-rs@088e116

Would this be an acceptable approach for more cohesive integration into EVP_PKEY_new_raw_private_key and EVP_PKEY_new_raw_public_key?

Copy link
Contributor Author

@jakemas jakemas Jan 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I considered that too, it works, but it does present an issue should another algorithm be added that has the same length of raw keys -- which may sound unlikely, but would present issues with HashML-DSA that has a different OID/NID but same lengths. I do have some ideas -- but likely out of scope for this PR.

}
// set the public key
int ret = PQDSA_KEY_set_raw_public_key(out->pkey.pqdsa_key, public_key);
OPENSSL_free(public_key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since public_key was allocated internally can the PKEY just take ownership of it without having to copy it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this as being possible, as the size of the public_key is dynamic, and defined by out->pkey.pqdsa_key->pqdsa->public_key_len. Am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you allocate the key on L162, looking at PQDSA_KEY_set_raw_public_key it just calls OPENSSL_memdup which allocates a new buffer and memcpy the data into that new buffer, then this function frees public_key. Why not store public_key in out->pkey.pqdsa_key->public_key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it! 54224d2

@codecov-commenter
Copy link

codecov-commenter commented Jan 28, 2025

Codecov Report

Attention: Patch coverage is 53.33333% with 7 lines in your changes missing coverage. Please review.

Project coverage is 78.96%. Comparing base (ea58b3f) to head (54224d2).

Files with missing lines Patch % Lines
crypto/evp_extra/p_pqdsa_asn1.c 50.00% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2145      +/-   ##
==========================================
- Coverage   78.97%   78.96%   -0.02%     
==========================================
  Files         611      611              
  Lines      105500   105514      +14     
  Branches    14938    14941       +3     
==========================================
- Hits        83316    83314       -2     
- Misses      21531    21547      +16     
  Partials      653      653              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

justsmth
justsmth previously approved these changes Jan 28, 2025
}

// Calculate public key from private key if method exists
if (out->pkey.pqdsa_key->pqdsa->method->pqdsa_pack_pk_from_sk != NULL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we always return failure (0) if we're unable to initialize the public key? I assume the pqdsa_pack_pk_from_sk function ptr should always be non-NULL here (line 360) so this condition would never fail?

Upon seeing a successful return code, a caller assumes the EVP_PKEY is initialized and might subsequently SEGFAULT on a call to (e.g.) EVP_PKEY_cmp.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. I was trying to prevent against a failure if the ptr is non-null, as you say. Removed this check in cd8da04 and cleaned up the surrounding logic to only return SUCESS when both priv and pub are populated.

return 0;
}

// Calculate public key from private key if method exists
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other types of EVP_PKEY (which have public/private components) ensure that the public key is populated whenever a private key is deserialized. For example, see EC and ED25519.

Other EVP_PKEY_xxx methods (such as EVP_PKEY_cmp) assume that the public key is populated for any EVP_PKEY that has functions available for public keys. (In fact EVP_PKEY_cmp can currently SEGFAULT for EVP_PKEY_PQDSA due to this.)

There might be other places for this computation to take place, but it needs to be done when deserializing a private key.

justsmth
justsmth previously approved these changes Jan 28, 2025
skmcgrail
skmcgrail previously approved these changes Jan 28, 2025
@jakemas jakemas dismissed stale reviews from skmcgrail and justsmth via db900c0 January 28, 2025 22:46
@justsmth justsmth merged commit 695c3a0 into aws:main Jan 29, 2025
122 of 126 checks passed
manastasova pushed a commit to manastasova/aws-lc that referenced this pull request Jan 30, 2025
### 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants