Add Pure ML-DSA to the SymCrypt provider#161
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds pure ML-DSA (Module-Lattice-Based Digital Signature Algorithm) signature support to the SymCrypt provider, implementing the signature algorithm through OpenSSL's provider interface. The implementation includes key management, signature operations, and encoder/decoder functionality for ML-DSA-44, ML-DSA-65, and ML-DSA-87 variants. The PR also introduces ML-DSA encoders/decoders for compatibility with OpenSSL versions that don't natively support ML-DSA (e.g., OpenSSL 3.3 on AZL3).
Changes:
- Added ML-DSA signature operations including sign/verify interfaces with support for context strings and parameter validation
- Implemented key management for ML-DSA keys supporting private seed and private key formats, including key generation, import/export, and matching operations
- Added PEM/DER encoders and decoders for ML-DSA PrivateKeyInfo, EncryptedPrivateKeyInfo, and SubjectPublicKeyInfo
- Integrated ML-DSA into OpenSSL provider with TLS signature algorithm capabilities
- Modified fallthrough attribute syntax in AES cipher code for better portability
Reviewed changes
Copilot reviewed 11 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| SymCryptProvider/src/signature/p_scossl_mldsa.h | Header defining ML-DSA signature context structures and algorithm info |
| SymCryptProvider/src/signature/p_scossl_mldsa.c | Implementation of ML-DSA signature operations (sign, verify, parameter handling) |
| SymCryptProvider/src/keymgmt/p_scossl_mldsa_keymgmt.h | Header for ML-DSA key management interface |
| SymCryptProvider/src/keymgmt/p_scossl_mldsa_keymgmt.c | Key management implementation including keygen, import/export, and key operations |
| SymCryptProvider/src/encoder/p_scossl_encode_mldsa.c | Encoder for ML-DSA keys to various formats (DER/PEM, text) |
| SymCryptProvider/src/decoder/p_scossl_decode_mldsa.c | Decoder for ML-DSA keys from various formats |
| SymCryptProvider/src/p_scossl_base.c | Provider registration integrating ML-DSA algorithms and TLS capabilities |
| SymCryptProvider/src/p_scossl_names.h | Algorithm name definitions for ML-DSA variants |
| SymCryptProvider/inc/scossl_provider.h | Constants, OIDs, and parameter definitions for ML-DSA |
| SymCryptProvider/CMakeLists.txt | Build configuration updated to include ML-DSA source files |
| SymCryptProvider/src/ciphers/p_scossl_aes.c | Fallthrough attribute changed from GCC-specific to standard comment |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 14 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 15 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 17 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| _In_ OSSL_CALLBACK *param_cb, _In_ void *cbarg); | ||
|
|
||
| SCOSSL_STATUS p_scossl_mldsa_keymgmt_get_encoded_key(_In_ const SCOSSL_MLDSA_KEY_CTX *keyCtx, SYMCRYPT_MLDSAKEY_FORMAT format, | ||
| _Out_writes_bytes_(*pcbKey) PBYTE *ppbKey, _Out_ SIZE_T *pcbKey); |
| extern "C" { | ||
| #endif | ||
|
|
||
| #define SCOSSL_MLDSA_PRIVATE_SEED_LENGTH 64 |
There was a problem hiding this comment.
And yeah, that is the expectation of OpenSSL too: https://docs.openssl.org/master/man7/EVP_PKEY-ML-DSA/#keygen-parameters
| case SYMCRYPT_MLDSA_PARAMS_MLDSA65: | ||
| return 1952; | ||
| case SYMCRYPT_MLDSA_PARAMS_MLDSA87: | ||
| return 2592; |
There was a problem hiding this comment.
A couple of comments:
- these are byte lengths, not bit lengths - you'll want to multiply by 8 I think
- Consider using
SYMCRYPT_MLDSA_PUBLIC_KEY_SIZE_MLDSAxxmacros rather than magic values for the byte-lengths
| if (ret != SCOSSL_SUCCESS && allocatedKey) | ||
| { | ||
| OPENSSL_secure_clear_free(pbKey, cbKey); | ||
| } |
There was a problem hiding this comment.
NP: This logic seems fine, but diverges from ML-KEM for not much reason, prefer both to have the same shape
| OPENSSL_secure_clear_free(genCtx, sizeof(SCOSSL_MLDSA_KEYGEN_CTX)); | ||
| } | ||
|
|
||
| static SCOSSL_MLDSA_KEYGEN_CTX *p_scossl_mldsa_keygen_init(_In_ const OSSL_PARAM params[], _In_ SYMCRYPT_MLDSA_PARAMS mldsaParams) |
|
|
||
| typedef struct | ||
| { | ||
| OSSL_LIB_CTX *libctx; |
There was a problem hiding this comment.
I don't see that this is ever set or used?
| OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, NULL, 0), | ||
| OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), | ||
| OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), | ||
| OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0), |
| return SCOSSL_FAILURE; | ||
| } | ||
|
|
||
| if (!OSSL_PARAM_set_int(p, cbSignature)) |
| memcmp(pbKey1, pbKey2, cbKey1) != 0) | ||
| { | ||
| goto cleanup; | ||
| } |
There was a problem hiding this comment.
Tend to think it's safer to just compare public keys; we don't ever have a scenario where we have an initialized key and the public key is not available for comparison.
memcmp with the private keys could possibly be considered a sidechannel issue in some scenario.
|
|
||
| cbKey = ASN1_STRING_length(p8Data); | ||
|
|
||
| format = cbKey == 64 ? SYMCRYPT_MLDSAKEY_FORMAT_PRIVATE_SEED : SYMCRYPT_MLDSAKEY_FORMAT_PRIVATE_KEY; |
|
|
||
| #define SCOSSL_MAKE_MLDSA_DECODER(decoderType, bits) \ | ||
| static SCOSSL_MLDSA_KEY_CTX \ | ||
| *p_scossl_##decoderType##_to_mldsa##bits(_In_ SCOSSL_DECODE_CTX *ctx, _In_ BIO *bio) \ |
| goto cleanup; | ||
| } | ||
|
|
||
| if (p_scossl_mldsa_keymgmt_get_encoded_key(keyCtx, SYMCRYPT_MLDSAKEY_FORMAT_PUBLIC_KEY, &pbKey, &cbKey) != SCOSSL_SUCCESS) |
There was a problem hiding this comment.
Should align this with ML-KEM per #171 (comment) - either both let get_encoded_key allocate, or both allocate the public key
|
|
||
| if (keyCtx->format == SYMCRYPT_MLDSAKEY_FORMAT_PRIVATE_SEED) | ||
| { | ||
| if (BIO_printf(out, "MLDSA Private-Key (512 bit private seed encoding):\nprivate-seed") <= 0) |
| return p_scossl_encode_does_selection(&p_scossl_mldsa_##encoderType##_der_desc, selection); \ | ||
| } | ||
|
|
||
| #define MLDSA_ENCODER_DISPATCH(encoderType, bits) \ |
samuel-lee-msft
left a comment
There was a problem hiding this comment.
LGTM modulo changes
This PR adds pure ML-DSA to the SymCrypt provider through the provider signature interface. This PR was tested with the EVP crypto API, as well as TLS and CMS APIS.
This PR also adds an ML-DSA encoder/decoder for testing on platforms with OpenSSL versions that don't support ML-DSA by default (e.g. AZL3 with OpenSSL 3.3).
Composite ML-DSA and external mu will be added later.