-
Notifications
You must be signed in to change notification settings - Fork 118
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
Addition of generic NIST-DSA PKEY and ASN1 to support ML-DSA #1963
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1963 +/- ##
==========================================
+ Coverage 78.73% 78.90% +0.17%
==========================================
Files 590 595 +5
Lines 101428 102095 +667
Branches 14384 14476 +92
==========================================
+ Hits 79856 80559 +703
+ Misses 20935 20887 -48
- Partials 637 649 +12 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed up to p_dilithium3_asn1.c
, will continue later.
I was also checking p_dilithium3.c
against kem.c
and p_kem.c
, so far everything looks consistent to me.
crypto/dilithium/p_dilithium3.c
Outdated
return 0; | ||
} | ||
|
||
// if the public key has already been set either by EVP_parse_public_key or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't sound safe. For example, what happens to the security of ML-DSA if you use one variant parameters with another variant keys?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seconding that, we can check whether the params of the existing key match the params being set. But that seems not needed and it should be an error as is done in EVP_PKEY_CTX_nistdsa_set_params
. What is this function used for? Does it need to be exported? EVP_PKEY_kem_set_params
in https://github.com/aws/aws-lc/blob/main/crypto/fipsmodule/evp/p_kem.c#L357 is static and is used in importing keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I moved EVP_PKEY_pqdsa_set_params
from evp.h so it is no longer exported (I was having problems getting the benchmarking suite speed.cc to find the new API and I thought this was because it needed to be exported, but I just needed to increment the API level) in e47ef83. The function now lives within internal.h
As to what this function is used for, and how that differs to the KEM param set function:
Both EVP_PKEY_pqdsa_set_params
and EVP_PKEY_kem_set_params
set the NID specific parameters for a given PKEY. Why is this required? We use a higher level NID, namely EVP_PKEY_KEM for KEMS and EVP_PKEY_NISTDSA for PQ DSA. The PKEY has this NID, but doesn't know which specific algorithm is being referred to, so the set_params
function sets algorithm specific parameters and methods.
For signature algorithms, we use a flurry of APIs that generate PKEYs based on OIDs, that will then require the specific algorithm OID to fully complete the PKEY structure. For example, consider the ASN.1 utility function:
EVP_PKEY *EVP_parse_public_key(CBS *cbs);
This function returns a newly-allocated EVP PKEY
on success, however, this PKEY OID will be set to EVP_PKEY_NISTDSA
and not a specific OID like NID_MLDSA65
. Thus, to populate the PKEY with the correct parameters and methods, we call EVP_PKEY_pqdsa_set_params
on the PKEY. You can see examples of this in the test code, for example, within TEST_P(MLDSAParameterTest, MarshalParse)
:
// decode the DER structure, then parse as a PKEY.
CBS cbs;
CBS_init(&cbs, der, der_len);
bssl::UniquePtr<EVP_PKEY> pkey_from_der(EVP_parse_public_key(&cbs));
ASSERT_TRUE(pkey_from_der);
// set the correct params for the PKEY
EVP_PKEY_pqdsa_set_params(pkey_from_der.get(), nid);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been working on an implementation more similar to how this is done for KEMs based upon Nevine's comments above.
Due to the single EVP PKEY type EVP_PKEY_PQDSA
that covers different signature algorithms, we have the same issue as EC
and KEM
EVP PKEY types, in that a more specific algorithm identifier is required to set the appropriate sized/algorithm specific key buffers.
Rather than to use EVP_PKEY_pqdsa_set_params
to retroactively set the correct params once a PKEY
has been created, I have implemented EVP_PKEY_pqdsa_new_raw_public_key
and EVP_PKEY_pqdsa_new_raw_secret_key
that take a nid
so they can assign the correct algorithm specific methods/params:
// EVP_PKEY_pqdsa_new_raw_public_key generates a new EVP_PKEY object of type
// EVP_PKEY_PQDSA, initializes the PQDSA key based on |nid| and populates the
// public key part of the PQDSA key with the contents of |in|. It returns the
// pointer to the allocated PKEY on sucess and NULL on error.
OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_pqdsa_new_raw_public_key(int nid, const uint8_t *in, size_t len);
// EVP_PKEY_pqdsa_new_raw_secret_key generates a new EVP_PKEY object of type
// EVP_PKEY_PQDSA, initializes the PQDSA key based on |nid| and populates the
// secret key part of the PQDSA key with the contents of |in|. It returns the
// pointer to the allocated PKEY on sucess and NULL on error.
OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_pqdsa_new_raw_secret_key(int nid, const uint8_t *in, size_t len);
see the new implementation in c0d4e65.
EVP_PKEY_pqdsa_set_params
now behaves as EVP_PKEY_kem_set_params
does, and is not exported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still reviewing
crypto/dilithium/internal.h
Outdated
int (*sign)(uint8_t *sig, size_t *sig_len, | ||
const uint8_t *message, | ||
size_t message_len, | ||
const uint8_t *ctx, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can ctx
be used via the EVP API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument ctx
perhaps is a confusing name, it comes directly from the FIPS 204 standard page 17 and 18.
"The signing algorithm ML-DSA.Sign (Algorithm 2) takes a private key, a message, and a context string as
input. Algorithm 2 ML-DSA.Sign(sk, M, ctx)".
This is not a PKEY, EVP, (or any other type of) ctx.
Upstream reference Dilithium call it "pre" (https://github.com/pq-crystals/dilithium/blob/master/ref/sign.c#L89) short for "prefix string". But I wanted it to be obvious from the standard what this argument was, so went with the standard name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I was asking whether the EVP_PKEY API will be able to accommodate that extra parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh I understand now! Currently no, there is no way to set ctx
from the EVP API. Should we require that functionality, it can be added.
For additional background, the standard states: "By default, the context is the empty string, though applications may specify the use of a non-empty context string." I believe this was added as an optional meta-data field when adding in the concept of domain separation for pre-hash vs pure signing modes.
Should we want to make the ctx
settable at the EVP API we'd need to make changes to the EVP API that I currently do not see an application for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please document that in the code. btw, ctx
is very unfortunate name, can we change it to ctx_string
or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! Changed to pre
for prefix string
as has now also been changed upstream (https://github.com/pq-crystals/dilithium/blob/master/ref/sign.c#L78). See bcbb832
crypto/dilithium/internal.h
Outdated
int (*keygen)(uint8_t *public_key, | ||
uint8_t *secret_key); | ||
|
||
int (*sign)(uint8_t *sig, size_t *sig_len, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be good to have the order of parameters close the EVP API, pkey_nistdsa_sign_message
and pkey_nistdsa_verify_message
; that is
key (replacing ctx in those APIs)
signature
tbs
ctx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in 221c533
the higher level EVP API:
int EVP_DigestSign(EVP_MD_CTX *ctx,
uint8_t *out_sig,
size_t *out_sig_len,
const uint8_t *data,
size_t data_len);
int EVP_DigestVerify(EVP_MD_CTX *ctx,
const uint8_t *sig,
size_t sig_len,
const uint8_t *data,
size_t len);
now more closely matches the format of:
int ml_dsa_65_sign(const uint8_t *secret_key,
uint8_t *sig,
size_t *sig_len,
const uint8_t *message,
size_t message_len,
const uint8_t *ctx,
size_t ctx_len);
int ml_dsa_65_verify(const uint8_t *public_key,
const uint8_t *sig,
size_t sig_len,
const uint8_t *message,
size_t message_len,
const uint8_t *ctx,
size_t ctx_len);
crypto/dilithium/p_dilithium3.c
Outdated
//2.16.840.1.101.3.4.3.18 | ||
static const uint8_t kOIDMLDSA65[] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12}; | ||
|
||
// PQDSA functions: these are init/new/clear/free/get_sig functions for PQDSA_KEY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to move them in this PR for better clarity. I'm thinking that because if this PR is introducing a new architecture, then renaming and moving files is part of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally made the file move in this PR, but it made review difficult because all of the diffs no longer were comparing like for like changes. Andrew suggested making the file moves after the PR is merged to help review, but I can add back in the file changes if you'd prefer.
Essentially, those PQDSA
functions will live in their own p_pqdsa.c
file (within fipsmodule/evp/p_pqdsa.c
) much like p_kem.c
p_ec.c
etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you move the file with git mv
it should keep the diff as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, now that we are deeper into review, I feel less apprehensive about file structure changes, as you are now more familiar with the review. I've made the file changes in 91056cb, note how github treats the re-names as new files, which is what I originally wanted to avoid. Nevertheless, the new file structure is as follows:
KEM | PQDSA | PQDSA NEW Location |
---|---|---|
crypto/fipsmodule/evp/p_kem.c | crypto/dilithium/p_dilithium3.c | crypto/dilithium/p_pqdsa.c |
crypto/evp_extra/p_kem_asn1.c | crypto/dilithium/p_dilithium3_asn1.c | crypto/dilithium/p_pqdsa_asn1.c |
crypto/fipsmodule/kem/kem.c | crypto/dilithium/p_dilithium3.c | crypto/dilithium/pqdsa.c |
crypto/fipsmodule/ml_kem/ml_kem.c | crypto/dilithium/sig_dilithium.c | crypto/dilithium/mldsa.c |
crypto/fipsmodule/ml_kem/ml_kem.h | crypto/dilithium/sig_dilithium.h | crypto/dilithium/mldsa.h |
Then, when we eventually move to the fipsmodule, we'll have two directories:
crypto/fipsmodule/pqdsa/
which will contain genericpqdsa
content, such aspqdsa.c
crypto/fipsmodule/ml_dsa/
which will containml_dsa
specific content, such asmldsa.c
mldsa.h
crypto/fipsmodule/evp
can containp_pqdsa.c
crypto/evp_extra
can containp_pqdsa_asn1.c
crypto/dilithium/p_dilithium3.c
Outdated
|
||
const PQDSA *PQDSA_KEY_get0_sig(PQDSA_KEY* key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const PQDSA *PQDSA_KEY_get0_sig(PQDSA_KEY* key) { | |
const PQDSA *PQDSA_KEY_get0_dsa(PQDSA_KEY* key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e2234e3
crypto/dilithium/p_dilithium3.c
Outdated
out->method = sig_ml_dsa_65_method(); | ||
} | ||
|
||
const PQDSA *PQDSA_find_dsa_by_nid(int nid) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function will need to move with the PQDSA_KEY_xx functions above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed! I put it down here for now, otherwise we get the warning/error sig_ml_dsa_65()
defined but not used.
crypto/dilithium/internal.h
Outdated
int (*sign)(uint8_t *sig, size_t *sig_len, | ||
const uint8_t *message, | ||
size_t message_len, | ||
const uint8_t *ctx, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I was asking whether the EVP_PKEY API will be able to accommodate that extra parameter.
crypto/dilithium/p_dilithium3.c
Outdated
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e2234e3
crypto/dilithium/p_dilithium3.c
Outdated
ctx->pkey->type != EVP_PKEY_NISTDSA) { | ||
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATON_NOT_INITIALIZED); | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e2234e3
crypto/dilithium/p_dilithium3.c
Outdated
ctx->pkey->type != EVP_PKEY_NISTDSA) { | ||
OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATON_NOT_INITIALIZED); | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e2234e3
crypto/dilithium/p_dilithium3.c
Outdated
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SIGNATURE); | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e2234e3
continuing to review |
crypto/dilithium/p_dilithium3_asn1.c
Outdated
dilithium3_free(pkey); | ||
pkey->pkey.ptr = key; | ||
// NOTE: No checks are done in this function, the caller has to ensure | ||
// that the pointers are valid and |in| has the correct size. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's |in|
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed comment in c72e3e6
crypto/dilithium/p_dilithium3_asn1.c
Outdated
// that the pointers are valid and |in| has the correct size. | ||
key->public_key = NULL; | ||
key->secret_key = OPENSSL_memdup(privkey, privkey_len); | ||
pqdsa_free(pkey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this free the memory? How is there an assignment afterwards? Maybe you want to use OPENSSL_cleanse
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that the PKEY
coming into the function is not allocated, it's a fresh PKEY
for which we are setting the private key. I'm following the implementation of ed25519_set_priv_raw
in https://github.com/jakemas/aws-lc/blob/3662fc13a4c4bccadb40a55218d4e71a491f1e5b/crypto/evp_extra/p_ed25519_asn1.c#L63 which also free's the PKEY
before assignment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. It's because it's the member pointer that's being freed. Thanks for the pointer.
crypto/dilithium/p_dilithium3_asn1.c
Outdated
|
||
dilithium3_free(pkey); | ||
pkey->pkey.ptr = key; | ||
pqdsa_free(pkey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment about free
and assignment afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved above.
crypto/dilithium/internal.h
Outdated
int (*sign)(uint8_t *sig, size_t *sig_len, | ||
const uint8_t *message, | ||
size_t message_len, | ||
const uint8_t *ctx, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please document that in the code. btw, ctx
is very unfortunate name, can we change it to ctx_string
or something like that?
crypto/dilithium/ml_dsa.h
Outdated
#define MLDSA65_SIGNATURE_SEED_BYTES 32 | ||
|
||
// ml_dsa_65_keypair generates an ML-DSA-65 keypair and assigns a public key to | ||
// |public_key| and a private key to |secret_key|. It returns 0 upon success. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns 0 upon success.
Can it fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can return -1
when prefix
string too long, added comment to indicate this bcbb832
crypto/dilithium/p_dilithium3.c
Outdated
} | ||
|
||
static int pkey_nistdsa_sign_message(EVP_PKEY_CTX *ctx, uint8_t *sig, | ||
size_t *siglen, const uint8_t *tbs, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since the function is called sign_message
maybe it's better to rename tbs
to message
#include "../rand_extra/pq_custom_randombytes.h" | ||
#include "ml_dsa.h" | ||
|
||
// mldsa65kPublicKey is an example ML-DSA-65 public key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how did you generate it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// given an ML-DSA PKEY with a public key present:
// extract public key
std::vector<uint8_t> pub_buf(pk_len);
size_t pub_len;
// get size
EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &pub_len);
// fill buffer with pk
EVP_PKEY_get_raw_public_key(pkey.get(), pub_buf.data(), &pub_len);
// encode public key as DER
bssl::ScopedCBB cbb;
uint8_t *der;
size_t der_len;
CBB_init(cbb.get(), 0);
EVP_marshal_public_key(cbb.get(), pkey_pk_new.get());
CBB_finish(cbb.get(), &der, &der_len);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we usually get test examples from "independent" implementations instead of our own. Are you aware of another source you can take the test value from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be pulling examples from an independent implementation at https://github.com/lamps-wg/dilithium-certificates/pull/51/files#diff-d2a836a5afde5fbfd8925500dc1e25dfc63f78bc4749ff36c62697e3a0c0355e
crypto/CMakeLists.txt
Outdated
dilithium/p_dilithium3_asn1.c | ||
dilithium/sig_dilithium3.c | ||
dilithium/pqdsa.c | ||
dilithium/p_pqdsa.c |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
np: indent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 7435f9b
crypto/CMakeLists.txt
Outdated
@@ -774,7 +775,7 @@ if(BUILD_TESTING) | |||
ecdh_extra/ecdh_test.cc | |||
dh_extra/dh_test.cc | |||
digest_extra/digest_test.cc | |||
dilithium/p_dilithium_test.cc | |||
dilithium/p_pqdsa_test.cc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
np: indent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 7435f9b
crypto/dilithium/internal.h
Outdated
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 OR ISC | ||
|
||
#ifndef AWSLC_HEADER_SIG_INTERNAL_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#ifndef AWSLC_HEADER_SIG_INTERNAL_H | |
#ifndef AWSLC_HEADER_PQDSA_INTERNAL_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 7435f9b
crypto/dilithium/ml_dsa.c
Outdated
size_t *sig_len /* OUT */, | ||
const uint8_t *message /* IN */, | ||
size_t message_len /* IN */, | ||
const uint8_t *pre /* IN */, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's pre
?
crypto/dilithium/ml_dsa.c
Outdated
ml_dsa_params params; | ||
ml_dsa_65_params_init(¶ms); | ||
return crypto_sign_keypair(¶ms, public_key, secret_key); | ||
return (crypto_sign_keypair(¶ms, public_key, secret_key) == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how did this work so far? aren't we checking the return value? because this change inverts the return value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crypto_sign_keypair
returns 0
on success -1
for failure. This is an anti-pattern with how EVP expects 1
for success. The return values are checked within the PKEY
functions:
- keygen: https://github.com/jakemas/aws-lc/blob/7435f9b2920753efc71a6461c55be213dc24da65/crypto/dilithium/p_pqdsa.c#L52 prior to this change we check:
ml_dsa_65_keypair(key->pub, key->priv) != 0
. For more consistency, we do this check at the lowest level API, and instead within PKEY keygen we check:!pqdsa->method->keygen(key->public_key, key->secret_key)
crypto/dilithium/p_pqdsa.c
Outdated
} | ||
|
||
static int pkey_pqdsa_sign_signature(EVP_PKEY_CTX *ctx, uint8_t *sig, | ||
size_t *siglen, const uint8_t *message, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size_t *siglen, const uint8_t *message, | |
size_t *sig_len, const uint8_t *message, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 7435f9b
crypto/dilithium/p_pqdsa.c
Outdated
} | ||
|
||
static int pkey_pqdsa_verify_signature(EVP_PKEY_CTX *ctx, const uint8_t *sig, | ||
size_t siglen, const uint8_t *message, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size_t siglen, const uint8_t *message, | |
size_t sig_len, const uint8_t *message, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 7435f9b
crypto/dilithium/p_pqdsa_asn1.c
Outdated
return 0; | ||
} | ||
|
||
// NOTE: No checks are done in this function, the caller has to ensure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functions pqdsa_set_priv_raw
and pqdsa_set_pub_raw
are required by pqdsa_priv_decode
and pqdsa_pub_decode
respectively. These functions are used by EVP_parse_public_key
for example.
These functions are not intended for setting a PKEY from raw bytes, due to the fact that we can't allocate memory for the key as we don't know it's size. We have the same issue in EC KEY and KEM KEY, as the top level NID (EVP_PKEY_KEM for example, doesn't specify algorithm specific parameter sizes). Instead, should we want to populate a PKEY with raw priv/pub, we use:
OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_pqdsa_new_raw_public_key(int nid, const uint8_t *in, size_t len);
OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_pqdsa_new_raw_secret_key(int nid, const uint8_t *in, size_t len);
As the inclusion of the NID allows us to set the correct parameter level, and prevent PKEYs that have differing key sizes than expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have information as to what key sizes are expected when calling pqdsa_set_priv/pub_raw
, so cannot perform the checks.
crypto/dilithium/p_pqdsa_asn1.c
Outdated
return 0; | ||
} | ||
|
||
// NOTE: No checks are done in this function, the caller has to ensure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #1963 (comment)
crypto/dilithium/p_dilithium3_asn1.c
Outdated
// that the pointers are valid and |in| has the correct size. | ||
key->public_key = NULL; | ||
key->secret_key = OPENSSL_memdup(privkey, privkey_len); | ||
pqdsa_free(pkey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. It's because it's the member pointer that's being freed. Thanks for the pointer.
crypto/dilithium/p_dilithium3_asn1.c
Outdated
|
||
dilithium3_free(pkey); | ||
pkey->pkey.ptr = key; | ||
pqdsa_free(pkey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved above.
crypto/dilithium/pqdsa.c
Outdated
out->oid_len = sizeof(kOIDMLDSA65); | ||
out->comment = "MLDSA65 "; | ||
out->public_key_len = MLDSA65_PUBLIC_KEY_BYTES; | ||
out->secret_key_len = MLDSA65_PRIVATE_KEY_BYTES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we're using both "secret" and "private" to designate the same thing? Why not call it "private" everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed all occurrences to private in 739a3be
crypto/x509/x509_test.cc
Outdated
|
||
bssl::UniquePtr<X509> leaf = | ||
MakeTestCert("Intermediate", "Leaf", pkey, /*is_ca=*/false); | ||
MakeTestCert("Intermediate", "Leaf", pkey.get(), false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MakeTestCert("Intermediate", "Leaf", pkey.get(), false); | |
MakeTestCert("Intermediate", "Leaf", pkey.get(), /*is_ca=*/ false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in ad0a24c
crypto/x509/x509_test.cc
Outdated
ASSERT_FALSE(X509_verify(cert.get(), pkey.get())); | ||
uint32_t err = ERR_get_error(); | ||
ASSERT_EQ(ERR_LIB_X509, ERR_GET_LIB(err)); | ||
ASSERT_EQ(X509_R_INVALID_PARAMETER, ERR_GET_REASON(err)); | ||
ASSERT_EQ(ERR_LIB_ASN1, ERR_GET_LIB(err)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was the reason check omitted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added back reason check (and moved these tests back to be implemented how they were before) in 739a3be
crypto/dilithium/p_pqdsa_test.cc
Outdated
ASSERT_NE(private_pkey, nullptr); | ||
EXPECT_EQ(private_pkey->pkey.pqdsa_key->public_key, nullptr); | ||
EXPECT_NE(private_pkey->pkey.pqdsa_key->secret_key, nullptr); | ||
EXPECT_EQ(0, OPENSSL_memcmp(private_pkey->pkey.pqdsa_key->secret_key, pkey->pkey.pqdsa_key->secret_key, sk_len)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, would you want to test I saw later that it has its own test.EVP_PKEY_get_raw_private_key
?
ASSERT_TRUE(CBB_init(cbb.get(), 0)); | ||
ASSERT_TRUE(EVP_marshal_private_key(cbb.get(), pkey_sk_new.get())); | ||
ASSERT_TRUE(CBB_finish(cbb.get(), &der, &der_len)); | ||
free_der.reset(der); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I should know the answer, but what is free_der
used for above and here?
crypto/dilithium/p_pqdsa_test.cc
Outdated
std::vector<uint8_t> msg = { | ||
0x4a, 0x41, 0x4b, 0x45, 0x20, 0x4d, 0x41, 0x53, 0x53, 0x49, | ||
0x4d, 0x4f, 0x20, 0x41, 0x57, 0x53, 0x32, 0x30, 0x32, 0x32, 0x2e}; | ||
std::vector<uint8_t> badmsg = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What makes it a bad msg? I suggest you name them msg1 and msg2 that differ in one byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed in 2e5d891
// Verify the signed message fails upon a bad signature | ||
ASSERT_FALSE(EVP_DigestVerify(md_ctx.get(), signature1.data(), sig_len, | ||
msg.data(), msg.size())); | ||
md_ctx.Reset(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you need to reset before signing a bad msg?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reset and re-init added in 739a3be
crypto/dilithium/p_pqdsa_test.cc
Outdated
|
||
#else | ||
|
||
TEST(Dilithium3Test, EvpDisabled) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TEST(Dilithium3Test, EvpDisabled) { | |
TEST(PQDSATest, EvpDisabled) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 2e5d891
crypto/evp_extra/evp_asn1.c
Outdated
@@ -91,18 +91,27 @@ static const EVP_PKEY_ASN1_METHOD *parse_key_type(CBS *cbs) { | |||
if (OBJ_cbs2nid(&oid) == NID_rsa) { | |||
return &rsa_asn1_meth; | |||
} | |||
|
|||
#ifdef ENABLE_DILITHIUM | |||
// if |cbs| is empty we overwrite the contents with |oid| so that we can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
// if |cbs| is empty we overwrite the contents with |oid| so that we can | |
// if |cbs| is empty (after parsing |oid| from it), we overwrite the contents with |oid| so that we can |
(line length will need adjustment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in c71bbac
crypto/evp_extra/evp_asn1.c
Outdated
// call pub_decode(ret, &algorithm, &key) with the |algorithm| populated as |oid|. | ||
// We could probably use CBS_peek_asn1_tag for this, to conditionally set |algorithm| | ||
// based on if peeking at the next tag to see if there is params. | ||
if (CBS_len(cbs) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this not needed for the private key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed my comment to call out its use for private keys in c71bbac
crypto/evp_extra/evp_asn1.c
Outdated
// as |cbs|. (This allows the specific OID, e.g. NID_MLDSA65 to be parsed to | ||
// type specific decoding functions within the algorithm parameter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// as |cbs|. (This allows the specific OID, e.g. NID_MLDSA65 to be parsed by
// the type-specific decoding functions within the algorithm parameter.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in e419b99
crypto/evp_extra/evp_asn1.c
Outdated
@@ -70,6 +70,14 @@ | |||
#include "internal.h" | |||
#include "../dilithium/internal.h" | |||
|
|||
// The function parse_key_type takes the algorithm cbs sequence |cbs| and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// The function parse_key_type takes the algorithm cbs sequence |cbs| and | |
// parse_key_type takes the algorithm cbs sequence |cbs| and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in e419b99
crypto/evp_extra/evp_asn1.c
Outdated
// call pub_decode(ret, &algorithm, &key) with the |algorithm| populated as |oid|. | ||
// We could probably use CBS_peek_asn1_tag for this, to conditionally set |algorithm| | ||
// based on if peeking at the next tag to see if there is params. | ||
// if |cbs| is empty after parsing |oid| from it), we overwrite the contents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// if |cbs| is empty after parsing |oid| from it), we overwrite the contents | |
// if |cbs| is empty after parsing |oid| from it, we overwrite the contents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in e419b99
crypto/dilithium/p_pqdsa_asn1.c
Outdated
return 0; | ||
} | ||
// set the pqdsa params on the fresh pkey | ||
EVP_PKEY_pqdsa_set_params(out, OBJ_cbs2nid(params)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check the return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woops! checked in fb8631e
crypto/dilithium/p_pqdsa_asn1.c
Outdated
return 0; | ||
} | ||
// set the pqdsa params on the fresh pkey | ||
EVP_PKEY_pqdsa_set_params(out, OBJ_cbs2nid(params)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check the return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woops! checked in 369080b
crypto/dilithium/p_pqdsa_asn1.c
Outdated
// The parameters must be omitted. | ||
|
||
// the only parameter that can be included is the OID which has length 9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these two statement are contradicting :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 369080b
crypto/dilithium/p_pqdsa_asn1.c
Outdated
// The parameters must be omitted. | ||
|
||
// the only parameter that can be included is the OID which has length 9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these two statement are contradicting :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 369080b
crypto/evp_extra/evp_asn1.c
Outdated
@@ -91,6 +100,19 @@ static const EVP_PKEY_ASN1_METHOD *parse_key_type(CBS *cbs) { | |||
return &rsa_asn1_meth; | |||
} | |||
|
|||
#ifdef ENABLE_DILITHIUM | |||
// if |cbs| is empty after parsing |oid| from it, we overwrite the contents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should do this only if the oid corresponds to one of the pqdsa algorithms. In other words,
if (PQDSA_find_asn1_by_nid(OBJ_cbs2nid(&oid)) != NULL) {
if (CBS_len(cbs) == 0) { ...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, you're right 369080b
crypto/x509/algorithm.c
Outdated
if (EVP_PKEY_id(pkey) == EVP_PKEY_PQDSA) { | ||
return X509_ALGOR_set0(algor, OBJ_nid2obj(NID_MLDSA65), V_ASN1_UNDEF, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is this supposed to work when you add other PQDSA algorithms?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, found an elegant solution in 369080b. The pqdsa
struct will hold information on the specific PKEY type. We could have used EVP_PKEY_base_id
for this, if we didn't disable it in AWS-LC. Nevertheless, if the PKEY
type is EVP_PKEY_PQDSA
as in this conditional, we can get the actual NID from pkey->pkey.pqdsa_key->pqdsa->nid
.
Issues:
Resolves #CryptoAlg-2725
Description of changes:
This is a sizable PR, I've worked extremely hard to keep the size down in a single PR, but with ML-DSA touching X.509, PKEY, ASN1, there are a lot of changes that need to happen simultaneously to preserve library functionality, so I will document this PR well.
1. PKEY structure changes
This PR adds the support to include additional signature algorithms (in this case ML-DSA-44 and ML-DSA-87) to AWS-LC. Before this PR AWS-LC only supported ML-DSA-65, as such, we were utilizing the
void *ptr
ofevp_pkey_st
, rather than a distinct structure for ML-DSA.This PR introduces the new structure
PQDSA_KEY * pqdsa_key;
insideevp_pkey_st
to support ML-DSA and any additional FIPS digital signature algorithms provided by NIST, should AWS-LC wish to include them in the library.While the structure
DSA
already exists it does not provide support for FIPS-like signature APIs (see more at NIST api conventions. Rather than create a PKEY struct of the formMLDSA_KEY
that can only support ML-DSA, this is an opportunity to build support for future signature algorithms that have similar. calling structures, de-randomized testing modes, API converntions, etc. by making a more generic structure type -- much like we did forKEM_KEY
. Under the design in this PR, adding SLH-DSA to the PKEY struct would be very simple, as it conforms to aPQDSA_KEY
in design. So too will be true of any additional signature algorithms produced by NISTs call for additional signature algorithms.As, such,
PQDSA_KEY
utilizes a new structure to hold public/secret keys, as well as aPQDSA
struct which defines signature algorithm specific information:This allows us to use a single PKEY structure for all NIST FIPS signature algorithms, much like the existing PKEY struct
KEM_KEY *kem_key
for KEMS. As a consequence, we are now able to define signature methods such as:much like the existing kem.c file.
These structures will be incredibly useful for subsequent PRs, in which we will be adding de-randomized APIs to support FIPS validation and KATs from seeds. In effect, we will be extending the method to include:
The directory
dilithium/p_dilithium3.c
is used to house genericPKEY
operations, as well as PQDSA specific EVP functions such asEVP_PKEY_pqdsa_set_params
,EVP_PKEY_pqdsa_set_params
, which work in a similar way to p_kem.c functionsEVP_PKEY_CTX_kem_set_params
andEVP_PKEY_kem_set_params
.The directory
dilithium/p_dilithium3_asn1.c
is used to house genericasn.1
operations for NIST FIPS DSA algorithms.Rather than rename/relocate these files in this PR (which will convolute the CR), I will move these files in a subsequent PR.
2. Internal Design
We introduce new structures:
PQDSA_METHOD
,PQDSA
,PQDSA_KEY
which are very similar to how both EC Crypto, and the KEM API is implemented. The figure above shows the newly proposed structures and their integration into the existing EVP structures.PQDSA_METHOD
is a table of function pointers with 5 functions defined for aPQDSA
: key generation, key generation internal (for testing/FIPS), sign, sign internal (for testing/FIPS), and verify. EveryPQDSA
implementation has to implement this API, for examplesig_ml_dsa_44_method
implements the three functions for ML-DSA-44 (analogous toEC_METHOD
and for example,EC_GFp_nistz256_method
).PQDSA
is a structure that holds basic information about the DSA: the id, size of parameters, and the pointer to the implementation of the correspondingPQDSA_METHOD
.PQDSA_KEY
structure is a helper structure that holds pointers to public and secret key and the pointer to the correspondingPQDSA
.PQDSA_PKEY_CTX
is a helper structure used to store DSA parameters in anEVP_PKEY_CTX
object (the same asEC_PKEY_CTX
). SincePQDSA
has everything we need, that’s what we store inPQDSA_PKEY_CTX
.3. OID changes
We now have real OIDs available from https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration. These have been added to the
obj
files and automatically populated bygo run objects.go
. We include the following NIDs for ML-DSA:and a "top-level" NID
EVP_PKEY_NISTDSA
(similar to EVP_PKEY_KEM to subset all NIST FIPS DSA algorithms. Much like for the KEM API, we include functionsSIG_find_dsa_by_nid(int nid)
to return actual algorithm specific methods.4. Testing structure changes
Prior to this PR all testing for ML-DSA-65 was done as a series of g-tests. As we now have the ability to get algorithm/security level specific parameters from the
NISTDSA
struct, I have overhauled the testing suite to be parameterized over the currently supported signature algorithms:For each of the above parameter sets we run:
5. X.509 changes
Changes to X.509 code have been minimized to only the required changes to support this PR. This is already a big PR and I want to avoid expansion wherever possible. For X.509 the changes predominantly are regarding the change of NIDs from the old Dilithium NIDs to the new NIST DSA NIDs/OIDs.
As the OIDs changed, so too much the test certificates
kDilithium3Cert
,kDilithium3CertNull
, andkDilithium3CertParam
. These have been regenerated using the following:For step (1) "generate a valid test certificate" you can use the X.509 test
Dilithium3SignVerifyCert
with added code to print the certificate (or store to file):6. Speed Tool
ML-DSA-65
has been added to the speed tool. To facilitate the new APIs used in this PR, the API version has been incremented by 1. The speed tool now produces benchmarks for all three parameter levels, example output:Call-outs:
algorithm.c
: modified OIDs from DILITHIUM to PQDSAbase.h
: added PQDSA_KEY structevp.h
: added EVP_PKEY_pqdsa_set_params and EVP_PKEY_CTX_pqdsa_set_params functions to setting the specific OID (NID_MLDSAXX) for a PKEY of type EVP_PKEY_NISTDSAevp_extra_test.cc
: updated example MLDSA65 private key with new OID and encodingdilithium/internal.h
: new file to hold ML-DSA structureevp_extra/internal.h
: modified ML-DSA-65 specific ASN.1 and PKEY method to a generic one for all NIST PQC signature algorithmsfipsmodule/evp/internal.h
: added PQDSA_KEY to PKEY structures to hold PKEY types for NIST PQC signature algorithmsnid.h
,obj_dat.h
,obj_mac.num
,obj_xref.c
,objects.txt
: all modified to include designated OIDs for ML-DSA.p_dilithium3.c
: temporarily holds PQDSA new/clear/init functions. All existing PKEY functions (keygen/sign/verify) have been modified to use generic PQDSA and extract algorithm specific parameters/function methods.p_dilithium3_asn1.c
: All existing ASN1 functions (get/set/encode/decode/size/bits/free) have been modified to use generic PQDSA and extract algorithm specific parameters/function methods.p_dilithium_test.cc
: has been modified to parameterize all tests into a single test suite that use algorithm specific parameterizationevp_extra/p_methods
: dilithium specific methods renamedprint.c
,speed.cc
,x509_test.cc
: updated to retain functionalityTesting:
See above for description of changes to testing framework.
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.