Skip to content

Commit

Permalink
ML-DSA ExternalMu support (#2113)
Browse files Browse the repository at this point in the history
This commit adds a new signing and verification mode for ML-DSA known as
"ExternalMu". This signing/verification mode allows for pre-hashing of
large messages that are to be signed with ML-DSA.

This commit focuses mainly on connecting `EVP_PKEY_sign` and
`EVP_PKEY_verify` to PQDSA ASN.1 functionality, to allow users access to
these APIs for pre-hash modes of ML-DSA.

It is important to note that ExternalMu ML-DSA is considered the same
algorithm as FIPS 204 ML-DSA. As such, it shares the same OIDs as 
"Pure" ML-DSA. 

NIST’s AVCP lab has ACVP tests for ExternalMu
https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html
  • Loading branch information
jakemas authored Jan 17, 2025
1 parent 68e861e commit 3263ce2
Show file tree
Hide file tree
Showing 28 changed files with 3,894 additions and 838 deletions.
104 changes: 85 additions & 19 deletions crypto/evp_extra/p_pqdsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ static int pkey_pqdsa_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
return 1;
}

static int pkey_pqdsa_sign_message(EVP_PKEY_CTX *ctx, uint8_t *sig,
size_t *sig_len, const uint8_t *message,
size_t message_len) {
static int pkey_pqdsa_sign_generic(EVP_PKEY_CTX *ctx, uint8_t *sig,
size_t *sig_len, const uint8_t *message,
size_t message_len, int sign_digest) {
GUARD_PTR(sig_len);

PQDSA_PKEY_CTX *dctx = ctx->data;
const PQDSA *pqdsa = dctx->pqdsa;
if (pqdsa == NULL) {
Expand Down Expand Up @@ -95,16 +96,50 @@ static int pkey_pqdsa_sign_message(EVP_PKEY_CTX *ctx, uint8_t *sig,
return 0;
}

if (!pqdsa->method->pqdsa_sign(key->private_key, sig, sig_len, message, message_len, NULL, 0)) {
OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR);
return 0;
// |sign_digest| is a flag we use to indicate that the message to be signed has
// already been pre-processed and hashed into a message digest.
// When the PQDSA algorithm is selected as ML-DSA (i.e., NID_MLDSA{44/65/87}),
// |sign_digest| indicates that the input is |mu| which is the result of a SHAKE256
// hash of the associated public key concatenated with a zero byte to indicate
// pure-mode, the context string length, the contents of the context string,
// and the input message in this order e.g.
// mu = SHAKE256(SHAKE256(pk) || 0 || |ctx| || ctx || M).

// RAW sign mode
if (!sign_digest) {
if (!pqdsa->method->pqdsa_sign_message(key->private_key, sig, sig_len, message, message_len, NULL, 0)) {
OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR);
return 0;
}
}
// DIGEST sign mode
else {
if (!pqdsa->method->pqdsa_sign(key->private_key, sig, sig_len, message, message_len)) {
OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR);
return 0;
}
}

return 1;
}

static int pkey_pqdsa_verify_signature(EVP_PKEY_CTX *ctx, const uint8_t *sig,
size_t sig_len, const uint8_t *message,
size_t message_len) {
// DIGEST signing
static int pkey_pqdsa_sign(EVP_PKEY_CTX *ctx, uint8_t *sig,
size_t *sig_len, const uint8_t *digest,
size_t digest_len) {
return pkey_pqdsa_sign_generic(ctx, sig, sig_len, digest, digest_len, 1);
}

// RAW message signing
static int pkey_pqdsa_sign_message(EVP_PKEY_CTX *ctx, uint8_t *sig,
size_t *sig_len, const uint8_t *message,
size_t message_len) {
return pkey_pqdsa_sign_generic(ctx, sig, sig_len, message, message_len, 0);
}

static int pkey_pqdsa_verify_generic(EVP_PKEY_CTX *ctx, const uint8_t *sig,
size_t sig_len, const uint8_t *message,
size_t message_len, int verify_digest) {
PQDSA_PKEY_CTX *dctx = ctx->data;
const PQDSA *pqdsa = dctx->pqdsa;

Expand All @@ -126,15 +161,49 @@ static int pkey_pqdsa_verify_signature(EVP_PKEY_CTX *ctx, const uint8_t *sig,

PQDSA_KEY *key = ctx->pkey->pkey.pqdsa_key;

if (sig_len != pqdsa->signature_len ||
!pqdsa->method->pqdsa_verify(key->public_key, sig, sig_len, message, message_len, NULL, 0)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SIGNATURE);
return 0;
// |verify_digest| is a flag we use to indicate that the message to be verified has
// already been pre-processed and hashed into a message digest.
// When the PQDSA algorithm is selected as ML-DSA (i.e., NID_MLDSA{44/65/87}),
// |verify_digest| indicates that the input is |mu| which is the result of a SHAKE256
// hash of the associated public key concatenated with a zero byte to indicate
// pure-mode, the context string length, the contents of the context string,
// and the input message in this order e.g.
// mu = SHAKE256(SHAKE256(pk) || 0 || |ctx| || ctx || M).

// RAW verify mode
if(!verify_digest) {
if (sig_len != pqdsa->signature_len ||
!pqdsa->method->pqdsa_verify_message(key->public_key, sig, sig_len, message, message_len, NULL, 0)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SIGNATURE);
return 0;
}
}
// DIGEST verify mode
else {
if (sig_len != pqdsa->signature_len ||
!pqdsa->method->pqdsa_verify(key->public_key, sig, sig_len, message, message_len)) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SIGNATURE);
return 0;
}
}

return 1;
}

// DIGEST verification
static int pkey_pqdsa_verify(EVP_PKEY_CTX *ctx, const uint8_t *sig,
size_t sig_len, const uint8_t *message,
size_t message_len) {
return pkey_pqdsa_verify_generic(ctx, sig, sig_len, message, message_len, 1);
}

// RAW message verification
static int pkey_pqdsa_verify_message(EVP_PKEY_CTX *ctx, const uint8_t *sig,
size_t sig_len, const uint8_t *message,
size_t message_len) {
return pkey_pqdsa_verify_generic(ctx, sig, sig_len, message, message_len, 0);
}

// Additional PQDSA specific EVP functions.

// This function sets pqdsa parameters defined by |nid| in |pkey|.
Expand Down Expand Up @@ -266,11 +335,11 @@ const EVP_PKEY_METHOD pqdsa_pkey_meth = {
pkey_pqdsa_cleanup,
pkey_pqdsa_keygen,
NULL,
NULL,
pkey_pqdsa_sign,
pkey_pqdsa_sign_message,
NULL,
NULL,
pkey_pqdsa_verify_signature,
pkey_pqdsa_verify,
pkey_pqdsa_verify_message,
NULL,
NULL,
NULL,
Expand All @@ -283,6 +352,3 @@ const EVP_PKEY_METHOD pqdsa_pkey_meth = {
NULL,
NULL,
};



Loading

0 comments on commit 3263ce2

Please sign in to comment.