Skip to content

Conversation

@mamckee
Copy link
Collaborator

@mamckee mamckee commented Nov 25, 2025

This PR updates the SymCrypt provider ML-KEM and ML-KEM hybrid to match the behavior of the OpenSSL default provider implementation, added in OpenSSL 3.5. This PR also fixes minor bugs and behavior differences found when testing the SymCrypt provider with OpenSSL 3.5's new ML-KEM tests.

  • Different ML-KEM parameter sets, and hybrid algorithms are now registered as their own algorithm for key management, KEM, encode, and decode.
  • Split ML-KEM hybrid into its own key management and KEM interfaces to better match OpenSSL's behavior and for maintainability.
  • Removed ML-KEM hybrid encoder and decoders since they don't exist in OpenSSL 3.5 and are based on test OIDS.

@qmuntal
Copy link
Member

qmuntal commented Nov 25, 2025

FYI We recently added support for MLKEM to https://github.com/golang-fips/openssl, so you can use it to test that you will be at least compatible with the Microsoft build of Go. #Resolved


#define SCOSSL_SN_P384_MLKEM1024 "id-alg-secp384r1-ml-kem-1024"
#define SCOSSL_LN_P384_MLKEM1024 "P384-ML-KEM-1024"
#define SCOSSL_OID_P384_MLKEM1024 "2.16.840.1.101.3.4.4.6"
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

This section is curious to me - looking at latest OpenSSL and 3.5, the SN and OIDs for hybrid key exchange do not seem to be defined, also the LN is different.

It seems particularly funky to define these OIDs for hybrids with the NIST algorithm identifiers: https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration
NIST owns the 2.16.840.1.101.3 OID space and does not define these hybrids.

i.e.
PROV_NAMES_X25519MLKEM768 is "X25519MLKEM768", not "X25519-ML-KEM-768"
PROV_NAMES_SecP256r1MLKEM768 is "SecP256r1MLKEM768", not "P256-ML-KEM-768"
PROV_NAMES_SecP384r1MLKEM1024 is "SecP384r1MLKEM1024" #Pending

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As far as the OIDs go, since we don't need to encode or decode, we can just remove them. I believe these were the same OIDs that the oqs provider were using as a placeholder, but it looks like no OIDs are on the table for these hybrids anyways.

The SNs are in line with other openssl SNs, and the hybrids are registered with the name that provider name used in the default provider. OpenSSL doesn't define any SNs or NIDs for these algorithms, but having a NID for these is convenient.

The provider name and SN don't always match, but usually both are used as the algorithm name in the dispatch table. I know the order can matter (e.g. for digests, the first name is used for EVP_MD_name, so SHA256 returns "SHA2-256" instead of the SN) but I'm not sure about KEM. I can make the provider name come first in the list to be safe.

#define SCOSSL_ALG_NAME_MLKEM1024 SCOSSL_LN_MLKEM1024":MLKEM1024:"SCOSSL_SN_MLKEM1024":"SCOSSL_OID_MLKEM1024

// TLS ML-KEM hybrids. HPKE uses a different combiner mechanism that will be implemented separately.
#define SCOSSL_ALG_NAME_X25519_MLKEM768 SCOSSL_LN_X25519_MLKEM768":X25519MLKEM768:"SCOSSL_SN_X25519_MLKEM768":"SCOSSL_OID_X25519_MLKEM768
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

X25519MLKEM768

I see, this is where the OpenSSL LN is coming in - I think we should not have the OIDs for these composites, but fine to leave the primary LN as is. #Pending

OSSL_PARAM_END};

static const OSSL_PARAM p_scossl_mlkem_hybrid_keymgmt_gettable_param_types[] = {
OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),

Missing OSSL_PKEY_PARAM_BITS I think?

I would personally be happy to not expose OSSL_PKEY_PARAM_BITS or OSSL_PKEY_PARAM_SECURITY_BITS from SCOSSL ML-KEM / hybrid-ML-KEM if there's no app-compat implication, the numbers are kind of arbitrary. #Pending

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There were a couple places these were fetched in testing, but I would rather just assume there's an app compat concern and just do the same thing as the default provider. We've had callers depend on all kinds of things we didn't expect and they're simple enough parameters to support.

const SCOSSL_MLKEM_GROUP_INFO *groupInfo;
SYMCRYPT_MLKEM_PARAMS mlkemParams;

BYTE pbSeed[SCOSSL_MLKEM_PRIVATE_SEED_LENGTH];
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

pbSeed

nit: prefer abSeed or just seed for a byte array - pbSeed indicates a pointer at the point of use - it threw me a bit when looking at how this is set in p_scossl_mlkem_keygen_set_params #Pending

return SCOSSL_FAILURE;
}
if ((p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_KEM_SEED)) != NULL &&
!OSSL_PARAM_get_octet_string(p, (void **)&pbSeed, SCOSSL_MLKEM_PRIVATE_SEED_LENGTH, &genCtx->cbSeed))
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

&genCtx->cbSeed

Curious if we should fail here when genCtx->cbSeed has been set to something other than SCOSSL_MLKEM_PRIVATE_SEED_LENGTH #Pending

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. I must have misread the OpenSSL behavior since it's written a bit weird and thought that the max size was set but not validated. It should fail still in keygen, but it would be better to fail here for consistency.

{
SCOSSL_PROV_LOG_SYMCRYPT_ERROR("SymCryptMlKemkeySetValue failed", scError);
goto cleanup;
}
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

It's a little odd to me that we stay in a state where the keygen_ctx will always produce the same key after the seed is set once. The OpenSSL documentation is scarce here.

Should we wipe the keygen_ctx's copy of the seed on success here? #Pending

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, looks like that's what OpenSSL is doing too.

{
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
goto cleanup;
}
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

I think we have some opportunities for funkiness (spurious failure, use after free, double free, etc.) when multiple parameters are being exported, unless with secure_clear_free and set pbKey/cbKey to NULL/0 on success in these blocks.

p_scossl_mlkem_keymgmt_get_encoded_key can look at stale values in pbKey and cbKey #Pending

Copy link
Contributor

Choose a reason for hiding this comment

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

p_scossl_mlkem_keymgmt_export has similar pattern to what we have here, just setting pbKey to NULL after each OPENSSL_secure_clear_free call at the start of the blocks - I think that's fine.

It's honestly a little funky that p_scossl_mlkem_keymgmt_get_encoded_key looks at existing values in pbKey/cbKey rather than unconditionally allocating

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I think there was a case that needed this in the combined keymgmt but I forgot to remove the check here. The hyrbrid version doesn't check the existing value.

I'll just free these on success too.

{OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (void (*)(void))p_scossl_mlkem_keymgmt_impexp_types},
{OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))p_scossl_mlkem_keymgmt_import},
{OSSL_FUNC_KEYMGMT_EXPORT, (void (*)(void))p_scossl_mlkem_keymgmt_export},
{0, NULL}};
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 9, 2026

Choose a reason for hiding this comment

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

Is this still needed? #Pending

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For OpenSSL 3.3 yes. I think we'll still have some OpenSSL 3.3 testing scenarios until Azure Linux updates to OpenSSL 3.5.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm reviewing on codeflow btw - this comment is asking whether p_scossl_mlkem_keymgmt_functions is still needed now it looks like only p_scossl_mlkem512_keymgmt_functions, p_scossl_mlkem768_keymgmt_functions, p_scossl_mlkem1024_keymgmt_functions are used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see now, sorry I was on github before so I didn't see the comment was pointing here.

if (paramPrivKey != NULL)
{
OPENSSL_secure_clear_free(pbKey, cbKey);

Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 10, 2026

Choose a reason for hiding this comment

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

Same comment here w.r.t. setting pbKey to NULL #Pending

goto cleanup;
}

// OpenSSL always exports hybrid priate keys as decapsulation keys
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 10, 2026

Choose a reason for hiding this comment

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

priate

typo: private #Pending

OPENSSL_secure_clear_free(pbKey, cbKey);
}

return SCOSSL_SUCCESS;
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 10, 2026

Choose a reason for hiding this comment

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

return SCOSSL_SUCCESS;

Should this be return ret? #Pending

@samuel-lee-msft
Copy link
Contributor

samuel-lee-msft commented Jan 10, 2026

return SCOSSL_SUCCESS;

Also should be return ret? #Pending


Refers to: SymCryptProvider/src/keymgmt/p_scossl_mlkem_keymgmt.c:899 in 66b665e. [](commit_id = 66b665e, deletion_comment = False)


if (ctx->keyCtx->format != SYMCRYPT_MLKEMKEY_FORMAT_PRIVATE_SEED &&
ctx->keyCtx->format != SYMCRYPT_MLKEMKEY_FORMAT_DECAPSULATION_KEY)
if (keyCtx->format != SYMCRYPT_MLKEMKEY_FORMAT_PRIVATE_SEED &&
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 10, 2026

Choose a reason for hiding this comment

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

keyCtx->format

Dereferencing keyCtx before the NULL check in p_scossl_mlkem_init - seems inconsistent if not a real problem #Pending

}

// Performs ML-KEM encapsulation using the previously initialized context. If
// this is a hybrid group, then hybrid encapsulation is performed.
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 10, 2026

Choose a reason for hiding this comment

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

nit: Performs hybrid ML-KEM encapsulation using the previously initialized context. #Pending

SCOSSL_ECDH_CTX *classicKeyexchCtx;
} SCOSSL_MLKEM_HYBRID_CTX;

static const OSSL_PARAM p_scossl_mlkem_param_types[] = {
Copy link
Contributor

@samuel-lee-msft samuel-lee-msft Jan 10, 2026

Choose a reason for hiding this comment

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

p_scossl_mlkem_param_types

nit: p_scossl_mlkem_hybrid_param_types for clarity? #Pending

Copy link
Contributor

@samuel-lee-msft samuel-lee-msft left a comment

Choose a reason for hiding this comment

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

LGTM modulo feedback!

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.

4 participants