Skip to content

Commit 822b016

Browse files
jakemasskmcgrailjustsmth
authored
Add ASN.1 decoding for ML-KEM private keys as seeds (#2707)
### Issues: Resolves #N/A Addresses #aws/aws-lc-rs#799 ### Description of changes: This PR adds support for the parsing of ML-KEM private keys encoded in the ASN.1 seed format, as described in https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates/. As such, 64 byte ASN.1 encoded private keys, such as this example ml-kem-512 private key from the IETF draft can now be parsed in aws-lc using `EVP_parse_private_key`: ``` -----BEGIN PRIVATE KEY----- MFQCAQAwCwYJYIZIAWUDBAQBBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= -----END PRIVATE KEY----- ``` The `EVP_PKEY` produced will have **both** the full private and public ML-KEM key expanded material within. This very much follows a similar PR for ML-DSA #2157. While there are a few ways to tackle the issue of getting the public key when the private is provided, for example making use of the `OneAsymmetricKey` structure as in https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-11#section-6. However as PKCS8 v2 isn't well supported, I feel encoding `OneAsymmetricKey` won't be easy for those wanting to bring key material into aws-lc/-rs. Python Cryptography doesn't support it today, for example. OpenSSL encodes keys by default using the `BOTH` approach (seed and expanded material), so importing from OpenSSL produced private seeds will now be possible, and populate both pub and priv keys. #### Background The IETF draft standard defines three private key formats for ML-KEM certificates: 1. __seed [0] OCTET STRING__ - 64-byte deterministic seed (added in this PR) 2. __expandedKey OCTET STRING__ - Full private key material (#2624) 3. __both SEQUENCE {seed, expandedKey}__ - Both formats together (future work) Previously, AWS-LC only supported the expandedKey format. This PR adds support for the more compact seed format, which uses deterministic key generation from a 64-byte seed. How does this help aws/aws-lc-rs#799? When the private key is provided as a seed, both the expanded private and secret key are populated within the EVP_PKEY structure, allowing, for the first time, a fully populated key on import. ### Call-outs: __Core Implementation:__ - __`crypto/evp_extra/p_kem_asn1.c`__: Enhanced `kem_priv_decode()` to detect and parse seed format using ASN.1 context-specific tag `[0]` (stubbed out BOTH format) - __`crypto/fipsmodule/kem/kem.c`__: Added `KEM_KEY_set_raw_keypair_from_seed()` function to generate keypairs from seeds using deterministic ML-KEM functions - __`crypto/fipsmodule/kem/internal.h`__: Added function declaration for seed-based key generation FIPS Questions: Are we allowed to expose this functionality as part of the FIPS module? Yes, see https://csrc.nist.gov/projects/post-quantum-cryptography/faqs Question 1: >Are cryptographic modules implementing FIPS 203 or FIPS 204 allowed to use seeds as the default key format instead of expanded keys? For example, in FIPS 203, can a cryptographic module store a 64-byte seed (d, z) instead of the output (dk, ek) of Algorithm 19 (ML-KEM.KeyGen), and compute (dk, ek) as needed via (dk, ek) <-- ML-KEM.KeyGen_internal(d, z)? > > **Response**: For both FIPS 203 and FIPS 204, a KeyGen seed is considered an acceptable alternative format for a key-pair, or for the private (i.e., decapsulation or signing) key. In particular, generating the seed in one cryptographic module and then importing or exporting it into another cryptographic module is allowed. The internal key generation functions ML-KEM.KeyGen_Internal(d, z) and ML-DSA.KeyGen_internal(ξ) can be accessed for this purpose. ### Testing: - __`crypto/evp_extra/p_kem_test.cc`__: Added comprehensive `ParsePrivateKeySeed` test using IETF standard test vectors - Added test vectors for ML-KEM-512, ML-KEM-768, and ML-KEM-1024 seed formats from Appendix C of the draft standard - Tests verify that seed-generated keypairs match expected public keys from the standard 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. --------- Co-authored-by: Sean McGrail <[email protected]> Co-authored-by: Justin W Smith <[email protected]>
1 parent eaaa97b commit 822b016

File tree

4 files changed

+330
-154
lines changed

4 files changed

+330
-154
lines changed

crypto/evp_extra/p_kem_asn1.c

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -178,26 +178,45 @@ static int kem_priv_decode(EVP_PKEY *out, CBS *oid, CBS *params, CBS *key,
178178
return 0;
179179
}
180180

181-
// At the moment, we only support expandedKey format from
182-
// https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates.
183-
// TODO(awslc): add support for "seed" and "both" formats.
184-
if (!CBS_peek_asn1_tag(key, CBS_ASN1_OCTETSTRING)) {
181+
// Support multiple ML-KEM private key formats from
182+
// https://datatracker.ietf.org/doc/draft-ietf-lamps-kyber-certificates/
183+
// Case 1: seed [0] OCTET STRING
184+
// Case 2: expandedKey OCTET STRING
185+
// Case 3: TODO: both SEQUENCE {seed, expandedKey}
186+
187+
if (CBS_peek_asn1_tag(key, CBS_ASN1_CONTEXT_SPECIFIC)) {
188+
// Case 1: seed [0] OCTET STRING
189+
CBS seed;
190+
if (!CBS_get_asn1(key, &seed, CBS_ASN1_CONTEXT_SPECIFIC)) {
191+
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
192+
return 0;
193+
}
194+
195+
if (CBS_len(&seed) != out->pkey.kem_key->kem->keygen_seed_len) {
196+
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
197+
return 0;
198+
}
199+
200+
return KEM_KEY_set_raw_keypair_from_seed(out->pkey.kem_key, &seed);
201+
} else if (CBS_peek_asn1_tag(key, CBS_ASN1_OCTETSTRING)) {
202+
// Case 2: expandedKey OCTET STRING
203+
CBS expanded_key;
204+
if (!CBS_get_asn1(key, &expanded_key, CBS_ASN1_OCTETSTRING)) {
205+
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
206+
return 0;
207+
}
208+
209+
if (CBS_len(&expanded_key) != out->pkey.kem_key->kem->secret_key_len) {
210+
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
211+
return 0;
212+
}
213+
214+
return KEM_KEY_set_raw_secret_key(out->pkey.kem_key, CBS_data(&expanded_key));
215+
} else {
216+
// Case 3: both SEQUENCE {seed, expandedKey} - not implemented yet
185217
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
186218
return 0;
187219
}
188-
189-
CBS expanded_key;
190-
if (!CBS_get_asn1(key, &expanded_key, CBS_ASN1_OCTETSTRING)) {
191-
OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
192-
return 0;
193-
}
194-
195-
if (CBS_len(&expanded_key) != out->pkey.kem_key->kem->secret_key_len) {
196-
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
197-
return 0;
198-
}
199-
200-
return KEM_KEY_set_raw_secret_key(out->pkey.kem_key, CBS_data(&expanded_key));
201220
}
202221

203222
static int kem_priv_encode(CBB *out, const EVP_PKEY *pkey) {

0 commit comments

Comments
 (0)