]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
ML-KEM hybrids for TLS
authorViktor Dukhovni <openssl-users@dukhovni.org>
Fri, 20 Dec 2024 09:26:50 +0000 (20:26 +1100)
committerTomas Mraz <tomas@openssl.org>
Fri, 14 Feb 2025 09:50:58 +0000 (10:50 +0100)
- When used as KEMs in TLS the ECDHE algorithms are NOT subjected to
  HPKE Extract/Expand key derivation.  Instead the TLS HKDF is used
  as usual.

- Consequently these KEMs are just the usual ECDHE key exchange
  operations, be it with the encap ECDH private key unavoidably
  ephemeral.

- A new "MLX" KEM provider is added that supports four hybrids of EC/ECX
  DH with ML-KEM:

    * ML-KEM-768 + X25519
    * ML-KEM-1024 + X448
    * P-256 + ML-KEM-768
    * P-384 + ML-KEM-1024

- Support listing of implemented TLS groups.

  The SSL_CTX_get0_implemented_groups() function and new
  `openssl list -tls-groups` and `openssl list -all-tls-groups`
  commands make it possible to determine which groups are
  implemented by the SSL library for a particular TLS version
  or range of versions matching an SSL_CTX.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26220)

32 files changed:
AUTHORS.md
apps/list.c
crypto/err/openssl.txt
crypto/ml_kem/ml_kem.c
doc/man1/openssl-list.pod.in
doc/man1/openssl-s_client.pod.in
doc/man1/openssl-s_server.pod.in
doc/man3/SSL_CONF_cmd.pod
doc/man3/SSL_CTX_set1_curves.pod
doc/man7/ossl-guide-migration.pod
include/crypto/ml_kem.h
include/internal/tlsgroups.h
include/openssl/proverr.h
include/openssl/ssl.h.in
providers/common/capabilities.c
providers/common/include/prov/proverr.h
providers/common/provider_err.c
providers/defltprov.c
providers/implementations/include/prov/implementations.h
providers/implementations/include/prov/mlx_kem.h [new file with mode: 0644]
providers/implementations/include/prov/names.h
providers/implementations/kem/build.info
providers/implementations/kem/ml_kem.c
providers/implementations/kem/mlx_kem.c [new file with mode: 0644]
providers/implementations/keymgmt/build.info
providers/implementations/keymgmt/ml_kem_kmgmt.c
providers/implementations/keymgmt/mlx_kmgmt.c [new file with mode: 0644]
ssl/s3_lib.c
ssl/ssl_local.h
ssl/t1_lib.c
test/sslapitest.c
util/other.syms

index dc6b534b82a576a66d03bf5bf7a9c8254e1c337c..f9a290305000554d682948f0c948c1186428d592 100644 (file)
@@ -12,6 +12,7 @@ Groups
 
  * OpenSSL Software Services, Inc.
  * OpenSSL Software Foundation, Inc.
+ * Google LLC
 
 Individuals
 -----------
index 944722e78f2bb50ba8a7bd138180b2f5a60fbb24..36c8f24d3459169391c3795b290b65714dcd4588 100644 (file)
@@ -23,6 +23,8 @@
 #include <openssl/store.h>
 #include <openssl/core_names.h>
 #include <openssl/rand.h>
+#include <openssl/safestack.h>
+#include <openssl/ssl.h>
 #include <openssl/tls1.h>
 #include "apps.h"
 #include "app_params.h"
@@ -776,6 +778,42 @@ static int list_tls_sigalg_caps(OSSL_PROVIDER *provider, void *cbdata)
     return 1;
 }
 
+#if !defined(OPENSSL_NO_TLS1_3) || !defined(OPENSSL_NO_TLS1_2)
+static void list_tls_groups(int version, int all)
+{
+    SSL_CTX *ctx = NULL;
+    STACK_OF(OPENSSL_CSTRING) *groups;
+    size_t i, num;
+
+    if ((groups = sk_OPENSSL_CSTRING_new_null()) == NULL) {
+        BIO_printf(bio_err, "ERROR: Memory allocation\n");
+        return;
+    }
+    if ((ctx = SSL_CTX_new(TLS_method())) == NULL) {
+        BIO_printf(bio_err, "ERROR: Memory allocation\n");
+        goto err;
+    }
+    if (!SSL_CTX_set_min_proto_version(ctx, version)
+        || !SSL_CTX_set_max_proto_version(ctx, version)) {
+        BIO_printf(bio_err, "ERROR: setting TLS protocol version\n");
+        goto err;
+    }
+    if (!SSL_CTX_get0_implemented_groups(ctx, all, groups)) {
+        BIO_printf(bio_err, "ERROR: getting implemented TLS group list\n");
+        goto err;
+    }
+    num = sk_OPENSSL_CSTRING_num(groups);
+    for (i = 0; i < num; ++i) {
+        BIO_printf(bio_out, "%s%c", sk_OPENSSL_CSTRING_value(groups, i),
+                   (i < num - 1) ? ':' : '\n');
+    }
+  err:
+    SSL_CTX_free(ctx);
+    sk_OPENSSL_CSTRING_free(groups);
+    return;
+}
+#endif
+
 static void list_tls_signatures(void)
 {
     int tls_sigalg_listed = 0;
@@ -1515,6 +1553,15 @@ typedef enum HELPLIST_CHOICE {
     OPT_TLS_SIGNATURE_ALGORITHMS, OPT_ASYM_CIPHER_ALGORITHMS,
     OPT_STORE_LOADERS, OPT_PROVIDER_INFO, OPT_OBJECTS,
     OPT_SELECT_NAME,
+#if !defined(OPENSSL_NO_TLS1_3) || !defined(OPENSSL_NO_TLS1_2)
+    OPT_ALL_TLS_GROUPS, OPT_TLS_GROUPS,
+# if !defined(OPENSSL_NO_TLS1_2)
+    OPT_TLS1_2,
+# endif
+# if !defined(OPENSSL_NO_TLS1_3)
+    OPT_TLS1_3,
+# endif
+#endif
 #ifndef OPENSSL_NO_DEPRECATED_3_0
     OPT_ENGINES,
 #endif
@@ -1572,6 +1619,20 @@ const OPTIONS list_options[] = {
      "List of public key methods"},
     {"store-loaders", OPT_STORE_LOADERS, '-',
      "List of store loaders"},
+#if !defined(OPENSSL_NO_TLS1_2) || !defined(OPENSSL_NO_TLS1_3)
+    {"tls-groups", OPT_TLS_GROUPS, '-',
+     "List implemented TLS key exchange 'groups'" },
+    {"all-tls-groups", OPT_ALL_TLS_GROUPS, '-',
+     "List implemented TLS key exchange 'groups' and all aliases" },
+# ifndef OPENSSL_NO_TLS1_2
+    {"tls1_2", OPT_TLS1_2, '-',
+     "When listing 'groups', list those compatible with TLS1.2"},
+# endif
+# ifndef OPENSSL_NO_TLS1_3
+    {"tls1_3", OPT_TLS1_3, '-',
+     "When listing 'groups', list those compatible with TLS1.3"},
+# endif
+#endif
     {"providers", OPT_PROVIDER_INFO, '-',
      "List of provider information"},
 #ifndef OPENSSL_NO_DEPRECATED_3_0
@@ -1594,6 +1655,14 @@ int list_main(int argc, char **argv)
     HELPLIST_CHOICE o;
     int one = 0, done = 0;
     int print_newline = 0;
+#if !defined(OPENSSL_NO_TLS1_3) || !defined(OPENSSL_NO_TLS1_2)
+    int all_tls_groups = 0;
+# if !defined(OPENSSL_NO_TLS1_3)
+    unsigned int tls_version = TLS1_3_VERSION;
+# else
+    unsigned int tls_version = TLS1_2_VERSION;
+# endif
+#endif
     struct {
         unsigned int commands:1;
         unsigned int all_algorithms:1;
@@ -1612,6 +1681,7 @@ int list_main(int argc, char **argv)
         unsigned int tls_signature_algorithms:1;
         unsigned int keyexchange_algorithms:1;
         unsigned int kem_algorithms:1;
+        unsigned int tls_groups:1;
         unsigned int asym_cipher_algorithms:1;
         unsigned int pk_algorithms:1;
         unsigned int pk_method:1;
@@ -1692,6 +1762,25 @@ opthelp:
         case OPT_KEM_ALGORITHMS:
             todo.kem_algorithms = 1;
             break;
+#if !defined(OPENSSL_NO_TLS1_3) || !defined(OPENSSL_NO_TLS1_2)
+        case OPT_TLS_GROUPS:
+            todo.tls_groups = 1;
+            break;
+        case OPT_ALL_TLS_GROUPS:
+            all_tls_groups = 1;
+            todo.tls_groups = 1;
+            break;
+# if !defined(OPENSSL_NO_TLS1_2)
+        case OPT_TLS1_2:
+            tls_version = TLS1_2_VERSION;
+            break;
+# endif
+# if !defined(OPENSSL_NO_TLS1_3)
+        case OPT_TLS1_3:
+            tls_version = TLS1_3_VERSION;
+            break;
+# endif
+#endif
         case OPT_ASYM_CIPHER_ALGORITHMS:
             todo.asym_cipher_algorithms = 1;
             break;
@@ -1811,6 +1900,10 @@ opthelp:
         MAYBE_ADD_NL(list_keyexchanges());
     if (todo.kem_algorithms)
         MAYBE_ADD_NL(list_kems());
+#if !defined(OPENSSL_NO_TLS1_3) || !defined(OPENSSL_NO_TLS1_2)
+    if (todo.tls_groups)
+        MAYBE_ADD_NL(list_tls_groups(tls_version, all_tls_groups));
+#endif
     if (todo.pk_algorithms)
         MAYBE_ADD_NL(list_pkey());
     if (todo.pk_method)
index decf2d6f2ec42dbded5f8d45dde1a08d84b3c143..35f7480cec3ea8bcdf41bcc07072586e8a304bd2 100644 (file)
@@ -1141,6 +1141,8 @@ PROV_R_NOT_XOF_OR_INVALID_LENGTH:113:not xof or invalid length
 PROV_R_NO_INSTANCE_ALLOWED:242:no instance allowed
 PROV_R_NO_KEY_SET:114:no key set
 PROV_R_NO_PARAMETERS_SET:177:no parameters set
+PROV_R_NULL_LENGTH_POINTER:247:null length pointer
+PROV_R_NULL_OUTPUT_BUFFER:245:null output buffer
 PROV_R_ONESHOT_CALL_OUT_OF_ORDER:239:oneshot call out of order
 PROV_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE:178:\
        operation not supported for this keytype
@@ -1176,9 +1178,11 @@ PROV_R_UNSUPPORTED_CEK_ALG:145:unsupported cek alg
 PROV_R_UNSUPPORTED_KEY_SIZE:153:unsupported key size
 PROV_R_UNSUPPORTED_MAC_TYPE:137:unsupported mac type
 PROV_R_UNSUPPORTED_NUMBER_OF_ROUNDS:152:unsupported number of rounds
+PROV_R_UNSUPPORTED_SELECTION:248:unsupported selection
 PROV_R_UPDATE_CALL_OUT_OF_ORDER:240:update call out of order
 PROV_R_URI_AUTHORITY_UNSUPPORTED:223:uri authority unsupported
 PROV_R_VALUE_ERROR:138:value error
+PROV_R_WRONG_CIPHERTEXT_SIZE:246:wrong ciphertext size
 PROV_R_WRONG_FINAL_BLOCK_LENGTH:107:wrong final block length
 PROV_R_WRONG_OUTPUT_BUFFER_SIZE:139:wrong output buffer size
 PROV_R_XOF_DIGESTS_NOT_ALLOWED:183:xof digests not allowed
index 1e15fb8d615f2e84ceca3a7512506d9f5acf55cd..8960bf2f471a270439500c703551462b922db911 100644 (file)
@@ -7,8 +7,6 @@
  * https://www.openssl.org/source/license.html
  */
 
-/* Copyright (c) 2024, Google Inc. */
-
 #include <internal/common.h>
 #include <internal/constant_time.h>
 #include <internal/sha3.h>
index a1d74f8e51930f115f19133ada4ec12833fe2b09..602f6636769a88e5d6c7ea5fa1cd05b3e4ccdf1e 100644 (file)
@@ -32,6 +32,10 @@ B<openssl list>
 [B<-key-managers>]
 [B<-key-exchange-algorithms>]
 [B<-kem-algorithms>]
+[B<-tls-groups>]
+[B<-all-tls-groups>]
+[B<-tls1_2>]
+[B<-tls1_3>]
 [B<-signature-algorithms>]
 [B<-tls-signature-algorithms>]
 [B<-asymcipher-algorithms>]
@@ -191,6 +195,29 @@ Display a list of key exchange algorithms.
 
 Display a list of key encapsulation algorithms.
 
+=item B<-tls-groups>
+
+Display a list of the IANA names of all available (implemented) TLS groups.
+By default the listed groups are those compatible with TLS 1.3.
+
+=item B<-all-tls-groups>
+
+Display a list of the names of all available (implemented) TLS groups,
+including any aliases.
+Some groups are known under multiple names, for example, B<secp256r1> is also
+known as B<P-256>.
+By default the listed groups are those compatible with TLS 1.3.
+
+=item B<-tls1_2>
+
+When listing TLS groups, list those compatible with TLS 1.2
+
+=item B<-tls1_3>
+
+When listing TLS groups, output those compatible with TLS 1.3.
+TLS 1.3 is the current default protocol version, but the default version is
+subject to change, so best to specify the version explicitly.
+
 =item B<-signature-algorithms>
 
 Display a list of signature algorithms.
index 8e685d35515393c977c46c3ee9df4be4bd3d9680..76b96d4ddaabc5904f13d2c8e5c2ea01ac9d9f88 100644 (file)
@@ -669,11 +669,14 @@ For example strings, see L<SSL_CTX_set1_sigalgs(3)>
 Specifies the list of supported curves to be sent by the client. The curve is
 ultimately selected by the server.
 
-The list of all supported groups includes named EC parameters as well as X25519
-and X448 or FFDHE groups, and may also include groups implemented in 3rd-party
-providers. For a list of named EC parameters, use:
-
-    $ openssl ecparam -list_curves
+The list of available groups includes various built-in named EC curves, as well
+as X25519 and X448, FFDHE groups, and any additional groups implemented in the
+default or 3rd-party providers.
+The commands below list the available groups for TLS 1.2 and TLS 1.3,
+respectively:
+
+    $ openssl list -tls1_2 -tls-groups
+    $ openssl list -tls1_3 -tls-groups
 
 =item B<-cipher> I<cipherlist>
 
index 0d8dd9bd0a736e70bb3e8493fdc9a8555cba6bf9..c4b95a3c4936ada8d91eacc9d41b003da692b7f9 100644 (file)
@@ -675,11 +675,14 @@ Signature algorithms to support for client certificate authentication
 
 Specifies the elliptic curve to use. NOTE: this is single curve, not a list.
 
-The list of all supported groups includes named EC parameters as well as X25519
-and X448 or FFDHE groups, and may also include groups implemented in 3rd-party
-providers. For a list of named EC parameters, use:
-
-    $ openssl ecparam -list_curves
+The list of available groups includes various built-in named EC curves, as well
+as X25519 and X448, FFDHE groups, and any additional groups implemented in the
+default or 3rd-party providers.
+The commands below list the available groups for TLS 1.2 and TLS 1.3,
+respectively.
+
+    $ openssl list -tls1_2 -tls-groups
+    $ openssl list -tls1_3 -tls-groups
 
 =item B<-cipher> I<val>
 
index d232c37a9965066e9b3d06b046b23ab3f77a857d..d6592b33a53c2b63a756a2c22be1a812aa858901 100644 (file)
@@ -155,13 +155,36 @@ group to use. This setting affects groups used for signatures (in TLSv1.2
 and earlier) and key exchange.
 
 In its simplest form the I<groups> argument is a colon separated list of
-groups. Each group can be either the B<NIST> name (e.g. B<P-256>), some other
-commonly used name where applicable (e.g. B<X25519>, B<ffdhe2048>) or an
-OpenSSL OID name (e.g. B<prime256v1>). Group names are case sensitive. The list
-should be in order of preference with the most preferred group first.
+groups.  The preferred names are those listed in the IANA
+L<TLS Supported Groups|https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8>
+registry.
+
+For some groups, OpenSSL supports additional aliases.
+Such an alias could be a B<NIST> name (e.g. B<P-256>), an OpenSSL OID name
+(e.g. B<prime256v1>), or some other commonly used name.
+Group names are case sensitive.
+The list should be in order of preference with the most preferred group first.
+
 The first group listed will also be used for the B<key_share> sent by a client
 in a TLSv1.3 B<ClientHello>.
 
+The commands below list the IANA names for TLS 1.2 and TLS 1.3,
+respectively:
+
+    $ openssl list -tls1_2 -tls-groups
+    $ openssl list -tls1_3 -tls-groups
+
+The recommended groups (in order of decreasing performance) for TLS 1.3 are presently:
+
+B<x25519>,
+B<secp256r1>,
+B<x448>,
+and
+B<secp384r1>.
+
+The stronger security margins of the last two, come at a significant
+performance penalty.
+
 An enriched alternative syntax, that enables clients to send multiple keyshares
 and allows servers to prioritise some groups over others, is described in
 L<SSL_CTX_set1_groups_list(3)>.
@@ -169,28 +192,6 @@ Since TLS 1.2 has neither keyshares nor a hello retry mechanism, with TLS 1.2
 the enriched syntax is ultimately equivalent to just a simple ordered list of
 groups, as with the simple form above.
 
-The supported groups for B<TLSv1.3> include:
-B<secp256r1>,
-B<secp384r1>,
-B<secp521r1>,
-B<x25519>,
-B<x448>,
-B<brainpoolP256r1tls13>,
-B<brainpoolP384r1tls13>,
-B<brainpoolP512r1tls13>,
-B<ffdhe2048>,
-B<ffdhe3072>,
-B<ffdhe4096>,
-B<ffdhe6144>,
-B<ffdhe8192>
-B<MLKEM512>,
-B<MLKEM768>,
-and
-B<MLKEM1024>.
-
-Additional providers may make available further algorithms via the
-TLS-GROUP capability. See L<provider-base(7)>.
-
 =item B<-curves> I<groups>
 
 This is a synonym for the B<-groups> command.
@@ -450,30 +451,28 @@ signatures (in TLSv1.2 and earlier) and key exchange. The first group listed
 will also be used for the B<key_share> sent by a client in a TLSv1.3
 B<ClientHello>.
 
-The B<value> argument is a colon separated list of groups. The group can be
-either the B<NIST> name (e.g. B<P-256>), some other commonly used name where
-applicable (e.g. B<X25519>, B<ffdhe2048>) or an OpenSSL OID name
-(e.g. B<prime256v1>). Group names are case sensitive. The list should be in
-order of preference with the most preferred group first.
+The B<groups> argument is a colon separated list of groups.  The preferred
+names are those listed in the IANA
+L<TLS Supported Groups|https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8>
+registry.
+For some groups, OpenSSL supports additional aliases.
+Such an alias could be a B<NIST> name (e.g. B<P-256>), an OpenSSL OID name
+(e.g. B<prime256v1>), or some other commonly used name.
+Group names are case sensitive.
+The list should be in order of preference with the most preferred group first.
 
-The supported groups for B<TLSv1.3> include:
-B<secp256r1>,
-B<secp384r1>,
-B<secp521r1>,
-B<x25519>,
-B<x448>,
-B<brainpoolP256r1tls13>,
-B<brainpoolP384r1tls13>,
-B<brainpoolP512r1tls13>,
-B<ffdhe2048>,
-B<ffdhe3072>,
-B<ffdhe4096>,
-B<ffdhe6144>,
-B<ffdhe8192>
-B<MLKEM512>,
-B<MLKEM768>,
-and
-B<MLKEM1024>.
+The commands below list the available groups for TLS 1.2 and TLS 1.3,
+respectively:
+
+    $ openssl list -tls1_2 -tls-groups
+    $ openssl list -tls1_3 -tls-groups
+
+An enriched alternative syntax, that enables clients to send multiple keyshares
+and allows servers to prioritise some groups over others, is described in
+L<SSL_CTX_set1_groups_list(3)>.
+Since TLS 1.2 has neither keyshares nor a hello retry mechanism, with TLS 1.2
+the enriched syntax is ultimately equivalent to just a simple ordered list of
+groups, as with the simple form above.
 
 =item B<Curves>
 
@@ -853,7 +852,21 @@ added in OpenSSL 3.2.
 
 B<PreferNoDHEKEX> was added in OpenSSL 3.3.
 
-Support for B<ML-KEM> was added in OpenSSL 3.5.
+OpenSSL 3.5 introduces support for post-quantum (PQ) TLS key exchange via the
+B<MLKEM512>, B<MLKEM768> and B<MLKEM1024> TLS groups.
+These are based on the underlying B<ML-KEM-512>, B<ML-KEM-768> and
+B<ML-KEM-1024> algorithms from FIPS 203.
+
+OpenSSL 3.5 also introduces support for three I<hybrid> ECDH PQ key exchange
+TLS I<groups>: B<X25519MLKEM768>, B<SecP256r1MLKEM768> and
+B<SecP384r1MLKEM1024>.
+They offer CPU performance comparable to the associated ECDH group, though at
+the cost of significantly larger key exchange messages.
+The third group, B<SecP384r1MLKEM1024> is substantially more CPU-intensive,
+largely as a result of the high CPU cost of ECDH for the underlying B<P-384>
+group.
+Also its key exchange messages at close to 1700 bytes are larger than the
+roughly 1200 bytes for the first two groups.
 
 =head1 COPYRIGHT
 
index dd0930e8d4330e97b6a148ba24290d5812018f94..b36466b386267711a58dd251807dea3be131c95f 100755 (executable)
@@ -6,7 +6,7 @@ SSL_CTX_set1_groups, SSL_CTX_set1_groups_list, SSL_set1_groups,
 SSL_set1_groups_list, SSL_get1_groups, SSL_get0_iana_groups,
 SSL_get_shared_group, SSL_get_negotiated_group, SSL_CTX_set1_curves,
 SSL_CTX_set1_curves_list, SSL_set1_curves, SSL_set1_curves_list,
-SSL_get1_curves, SSL_get_shared_curve
+SSL_get1_curves, SSL_get_shared_curve, SSL_CTX_get0_implemented_groups
 - EC supported curve functions
 
 =head1 SYNOPSIS
@@ -33,6 +33,9 @@ SSL_get1_curves, SSL_get_shared_curve
  int SSL_get1_curves(SSL *ssl, int *curves);
  int SSL_get_shared_curve(SSL *s, int n);
 
+ int SSL_CTX_get0_implemented_groups(SSL_CTX *ctx, int all,
+                                     STACK_OF(OPENSSL_CSTRING) *names);
+
 =head1 DESCRIPTION
 
 For all of the functions below that set the supported groups there must be at
@@ -85,24 +88,11 @@ SSL_CTX_set1_groups_list() sets the supported groups for B<ctx> to
 string I<list>. In contrast to SSL_CTX_set1_groups(), the names of the
 groups, rather than their NIDs, are used.
 
-The supported groups for B<TLSv1.3> include:
-B<P-256>,
-B<P-384>,
-B<P-521>,
-B<X25519>,
-B<X448>,
-B<ffdhe2048>,
-B<ffdhe3072>,
-B<ffdhe4096>,
-B<ffdhe6144>
-B<ffdhe8192>,
-B<MLKEM512>,
-B<MLKEM768>,
-B<MLKEM1024>,
-B<brainpoolP256r1tls13>,
-B<brainpoolP384r1tls13>, and
-B<brainpoolP512r1tls13>.
-Support for other groups may be added by external providers.
+The commands below list the available groups for TLS 1.2 and TLS 1.3,
+respectively:
+
+    $ openssl list -tls1_2 -tls-groups
+    $ openssl list -tls1_3 -tls-groups
 
 Each group can be either the B<NIST> name (e.g. B<P-256>), some other commonly
 used name where applicable (e.g. B<X25519>, B<ffdhe2048>) or an OpenSSL OID name
@@ -226,6 +216,19 @@ current, non-resumption, connection).  This can be called by either client or
 server. If the NID for the shared group is unknown then the value is set to the
 bitwise OR of TLSEXT_nid_unknown (0x1000000) and the id of the group.
 
+SSL_CTX_get0_implemented_groups() populates a stack with the names of TLS
+groups that are compatible with the TLS version of the B<ctx> argument.
+The returned names are references to internal constants and must not be
+modified or freed.  When B<all> is nonzero, the returned list includes not
+only the preferred IANA names of the groups, but also any associated aliases.
+If the SSL_CTX is version-flexible, the groups will be those compatible
+with any configured minimum and maximum protocol versions.
+The B<names> stack should be allocated by the caller and be empty, the
+matching group names are appended to the provided stack.
+The B<-tls-groups> and B<-all-tls-groups> options of the
+L<openssl list|openssl-list(1)> command output these lists for either
+TLS 1.2 or TLS 1.3 (by default).
+
 All these functions are implemented as macros.
 
 The curve functions are synonyms for the equivalently named group functions and
@@ -242,8 +245,9 @@ consider using the SSL_CONF interface instead of manually parsing options.
 
 =head1 RETURN VALUES
 
-SSL_CTX_set1_groups(), SSL_CTX_set1_groups_list(), SSL_set1_groups() and
-SSL_set1_groups_list(), return 1 for success and 0 for failure.
+SSL_CTX_set1_groups(), SSL_CTX_set1_groups_list(), SSL_set1_groups(),
+SSL_set1_groups_list(), and SSL_CTX_get0_implemented_groups() return 1 for
+success and 0 for failure.
 
 SSL_get1_groups() returns the number of groups, which may be zero.
 
@@ -275,6 +279,8 @@ SSL_set1_groups_list() was added in OpenSSL 3.3.
 
 Support for B<ML-KEM> was added in OpenSSL 3.5.
 
+B<SSL_CTX_get0_implemented_groups> was first implemented in OpenSSL 3.5.
+
 Earlier versions of this document described the list as a preference order.
 However, OpenSSL's behavior as a TLS 1.3 server is to consider I<all>
 supported groups as comparable in security.
index 7e365ed18f6cf23fd44e64b320eca2cd24f46dba..15467c75e01b6a53cfa89d3a04c0189c993bb2c3 100644 (file)
@@ -365,7 +365,7 @@ categories. See L<OSSL_trace_enabled(3)>.
 L<EVP_PKEY_public_check(3)> and L<EVP_PKEY_param_check(3)> now work for
 more key types. This includes RSA, DSA, ED25519, X25519, ED448 and X448.
 Previously (in 1.1.1) they would return -2. For key types that do not have
-parameters then L<EVP_PKEY_param_check(3)> will always return 1.
+parameters L<EVP_PKEY_param_check(3)> will always return 1.
 
 =head3 Other notable deprecations and changes
 
index 0604b967633c070775b23c4c5ad224890ff80cc2..fafaa98b726d3ca27c36e0170fbd75bc3201525b 100644 (file)
@@ -7,8 +7,6 @@
  * https://www.openssl.org/source/license.html
  */
 
-/* Copyright (c) 2024, Google Inc. */
-
 #ifndef OPENSSL_HEADER_ML_KEM_H
 # define OPENSSL_HEADER_ML_KEM_H
 # pragma once
index 4662032bca353ce71b3e9ba82a470959550f10c1..e307152c96f623e98f9b1a88d423a94e66aac351 100644 (file)
@@ -59,5 +59,8 @@
 # define OSSL_TLS_GROUP_ID_mlkem512         0x0200
 # define OSSL_TLS_GROUP_ID_mlkem768         0x0201
 # define OSSL_TLS_GROUP_ID_mlkem1024        0x0202
+# define OSSL_TLS_GROUP_ID_SecP256r1MLKEM768  0x11EB
+# define OSSL_TLS_GROUP_ID_X25519MLKEM768     0x11EC
+# define OSSL_TLS_GROUP_ID_SecP384r1MLKEM1024 0x11ED
 
 #endif
index d10b653152ec70adecb495f5c1f5eb539783e4b6..1ac4f59f0d480f8658544ed3e8ca8dcf26c1d643 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2024 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
 # define PROV_R_NO_INSTANCE_ALLOWED                       242
 # define PROV_R_NO_KEY_SET                                114
 # define PROV_R_NO_PARAMETERS_SET                         177
+# define PROV_R_NULL_LENGTH_POINTER                       247
+# define PROV_R_NULL_OUTPUT_BUFFER                        245
 # define PROV_R_ONESHOT_CALL_OUT_OF_ORDER                 239
 # define PROV_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE  178
 # define PROV_R_OUTPUT_BUFFER_TOO_SMALL                   106
 # define PROV_R_UNSUPPORTED_KEY_SIZE                      153
 # define PROV_R_UNSUPPORTED_MAC_TYPE                      137
 # define PROV_R_UNSUPPORTED_NUMBER_OF_ROUNDS              152
+# define PROV_R_UNSUPPORTED_SELECTION                     248
 # define PROV_R_UPDATE_CALL_OUT_OF_ORDER                  240
 # define PROV_R_URI_AUTHORITY_UNSUPPORTED                 223
 # define PROV_R_VALUE_ERROR                               138
+# define PROV_R_WRONG_CIPHERTEXT_SIZE                     246
 # define PROV_R_WRONG_FINAL_BLOCK_LENGTH                  107
 # define PROV_R_WRONG_OUTPUT_BUFFER_SIZE                  139
 # define PROV_R_XOF_DIGESTS_NOT_ALLOWED                   183
index 7659917b5fce7b39389551b16765b77ffb4c56e7..9036e23f7d000afaf53c19a6fca127e4ef66d249 100644 (file)
@@ -1336,6 +1336,7 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
 # define SSL_CTRL_SET_RETRY_VERIFY               136
 # define SSL_CTRL_GET_VERIFY_CERT_STORE          137
 # define SSL_CTRL_GET_CHAIN_CERT_STORE           138
+# define SSL_CTRL_GET0_IMPLEMENTED_GROUPS        139
 # define SSL_CERT_SET_FIRST                      1
 # define SSL_CERT_SET_NEXT                       2
 # define SSL_CERT_SET_SERVER                     3
@@ -1444,6 +1445,9 @@ DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
         SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(int *)(glist))
 # define SSL_CTX_set1_groups_list(ctx, s) \
         SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s))
+# define SSL_CTX_get0_implemented_groups(ctx, all, out) \
+        SSL_CTX_ctrl(ctx,SSL_CTRL_GET0_IMPLEMENTED_GROUPS, all, \
+        (STACK_OF(OPENSSL_CSTRING) *)(out))
 # define SSL_set1_groups(s, glist, glistlen) \
         SSL_ctrl(s,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist))
 # define SSL_set1_groups_list(s, str) \
index 3c2882601db98f5e094dfc35fa09a9b7454ccb72..82ea4736f728e9c566862cb0d79f2eb1b3ddb330 100644 (file)
@@ -36,76 +36,62 @@ typedef struct tls_group_constants_st {
     int is_kem;              /* Indicates utility as KEM */
 } TLS_GROUP_CONSTANTS;
 
+/*
+ * The indices of entries in this table must be independent of which TLS groups
+ * we do or not support.  It just lists basic facts about the groups, and is
+ * used by (numeric slot number) reference in the "param_group_list" below.
+ * Therefore, there must be no #ifdefs in this table, the index of each entry
+ * must be independent of compile-time options.
+ *
+ * For the FFDHE groups, the security bit values are as given by
+ * BN_security_bits().  For the ML-KEM hybrids these are the ML-KEM security
+ * bits.
+ */
 static const TLS_GROUP_CONSTANTS group_list[] = {
-    { OSSL_TLS_GROUP_ID_sect163k1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect163r1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect163r2, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect193r1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect193r2, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect233k1, 112, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect233r1, 112, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect239k1, 112, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect283k1, 128, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect283r1, 128, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect409k1, 192, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect409r1, 192, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect571k1, 256, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_sect571r1, 256, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp160k1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp160r1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp160r2, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp192k1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp192r1, 80, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp224k1, 112, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp224r1, 112, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp256k1, 128, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_secp256r1, 128, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
-    { OSSL_TLS_GROUP_ID_secp384r1, 192, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
-    { OSSL_TLS_GROUP_ID_secp521r1, 256, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
-    { OSSL_TLS_GROUP_ID_brainpoolP256r1, 128, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_brainpoolP384r1, 192, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_brainpoolP512r1, 256, TLS1_VERSION, TLS1_2_VERSION,
-      DTLS1_VERSION, DTLS1_2_VERSION, 0 },
-    { OSSL_TLS_GROUP_ID_x25519, 128, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
-    { OSSL_TLS_GROUP_ID_x448, 224, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
-    { OSSL_TLS_GROUP_ID_brainpoolP256r1_tls13, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
-    { OSSL_TLS_GROUP_ID_brainpoolP384r1_tls13, 192, TLS1_3_VERSION, 0, -1, -1, 0 },
-    { OSSL_TLS_GROUP_ID_brainpoolP512r1_tls13, 256, TLS1_3_VERSION, 0, -1, -1, 0 },
-    /* Security bit values as given by BN_security_bits() */
-    { OSSL_TLS_GROUP_ID_ffdhe2048, 112, TLS1_3_VERSION, 0, -1, -1, 0 },
-    { OSSL_TLS_GROUP_ID_ffdhe3072, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
-    { OSSL_TLS_GROUP_ID_ffdhe4096, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
-    { OSSL_TLS_GROUP_ID_ffdhe6144, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
-    { OSSL_TLS_GROUP_ID_ffdhe8192, 192, TLS1_3_VERSION, 0, -1, -1, 0 },
-#ifndef OPENSSL_NO_ML_KEM
-    { OSSL_TLS_GROUP_ID_mlkem512, ML_KEM_512_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
-    { OSSL_TLS_GROUP_ID_mlkem768, ML_KEM_768_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
-    { OSSL_TLS_GROUP_ID_mlkem1024, ML_KEM_1024_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
-#endif
+    /*  0 */ { OSSL_TLS_GROUP_ID_sect163k1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  1 */ { OSSL_TLS_GROUP_ID_sect163r1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  2 */ { OSSL_TLS_GROUP_ID_sect163r2, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  3 */ { OSSL_TLS_GROUP_ID_sect193r1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  4 */ { OSSL_TLS_GROUP_ID_sect193r2, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  5 */ { OSSL_TLS_GROUP_ID_sect233k1, 112, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  6 */ { OSSL_TLS_GROUP_ID_sect233r1, 112, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  7 */ { OSSL_TLS_GROUP_ID_sect239k1, 112, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  8 */ { OSSL_TLS_GROUP_ID_sect283k1, 128, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /*  9 */ { OSSL_TLS_GROUP_ID_sect283r1, 128, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 10 */ { OSSL_TLS_GROUP_ID_sect409k1, 192, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 11 */ { OSSL_TLS_GROUP_ID_sect409r1, 192, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 12 */ { OSSL_TLS_GROUP_ID_sect571k1, 256, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 13 */ { OSSL_TLS_GROUP_ID_sect571r1, 256, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 14 */ { OSSL_TLS_GROUP_ID_secp160k1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 15 */ { OSSL_TLS_GROUP_ID_secp160r1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 16 */ { OSSL_TLS_GROUP_ID_secp160r2, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 17 */ { OSSL_TLS_GROUP_ID_secp192k1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 18 */ { OSSL_TLS_GROUP_ID_secp192r1, 80, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 19 */ { OSSL_TLS_GROUP_ID_secp224k1, 112, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 20 */ { OSSL_TLS_GROUP_ID_secp224r1, 112, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 21 */ { OSSL_TLS_GROUP_ID_secp256k1, 128, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 22 */ { OSSL_TLS_GROUP_ID_secp256r1, 128, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
+    /* 23 */ { OSSL_TLS_GROUP_ID_secp384r1, 192, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
+    /* 24 */ { OSSL_TLS_GROUP_ID_secp521r1, 256, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
+    /* 25 */ { OSSL_TLS_GROUP_ID_brainpoolP256r1, 128, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 26 */ { OSSL_TLS_GROUP_ID_brainpoolP384r1, 192, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 27 */ { OSSL_TLS_GROUP_ID_brainpoolP512r1, 256, TLS1_VERSION, TLS1_2_VERSION, DTLS1_VERSION, DTLS1_2_VERSION, 0 },
+    /* 28 */ { OSSL_TLS_GROUP_ID_x25519, 128, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
+    /* 29 */ { OSSL_TLS_GROUP_ID_x448, 224, TLS1_VERSION, 0, DTLS1_VERSION, 0, 0 },
+    /* 30 */ { OSSL_TLS_GROUP_ID_brainpoolP256r1_tls13, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 31 */ { OSSL_TLS_GROUP_ID_brainpoolP384r1_tls13, 192, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 32 */ { OSSL_TLS_GROUP_ID_brainpoolP512r1_tls13, 256, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 33 */ { OSSL_TLS_GROUP_ID_ffdhe2048, 112, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 34 */ { OSSL_TLS_GROUP_ID_ffdhe3072, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 35 */ { OSSL_TLS_GROUP_ID_ffdhe4096, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 36 */ { OSSL_TLS_GROUP_ID_ffdhe6144, 128, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 37 */ { OSSL_TLS_GROUP_ID_ffdhe8192, 192, TLS1_3_VERSION, 0, -1, -1, 0 },
+    /* 38 */ { OSSL_TLS_GROUP_ID_mlkem512, ML_KEM_512_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
+    /* 39 */ { OSSL_TLS_GROUP_ID_mlkem768, ML_KEM_768_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
+    /* 40 */ { OSSL_TLS_GROUP_ID_mlkem1024, ML_KEM_1024_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
+    /* 41 */ { OSSL_TLS_GROUP_ID_X25519MLKEM768, ML_KEM_768_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
+    /* 42 */ { OSSL_TLS_GROUP_ID_SecP256r1MLKEM768, ML_KEM_768_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
+    /* 43 */ { OSSL_TLS_GROUP_ID_SecP384r1MLKEM1024, ML_KEM_1024_SECBITS, TLS1_3_VERSION, 0, -1, -1, 1 },
 };
 
 #define TLS_GROUP_ENTRY(tlsname, realname, algorithm, idx) \
@@ -136,6 +122,18 @@ static const TLS_GROUP_CONSTANTS group_list[] = {
         OSSL_PARAM_END \
     }
 
+/*-
+ * - The 4th field of each entry is an index into "group_list" above.
+ *
+ * - The 3rd field is the key management algorithm name.
+
+ * - The 2nd field is the GROUP_NAME used with the provider, needed for
+ *   providers that implement a family of related algorithms, but required
+ *   non-null even when the provider implements just one.
+ *
+ * - The 1st field is the TLS group name used in SSL_CTX_set_group_list(),
+ *   aliases repeat everything but the first field.
+ */
 static const OSSL_PARAM param_group_list[][11] = {
 # ifndef OPENSSL_NO_EC
 #  ifndef OPENSSL_NO_EC2M
@@ -219,9 +217,16 @@ static const OSSL_PARAM param_group_list[][11] = {
 # endif
 # if !defined(OPENSSL_NO_ML_KEM) && !defined(FIPS_MODULE)
     /* https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 */
-    TLS_GROUP_ENTRY("MLKEM512", "MLKEM512", "ML-KEM-512", 38),
-    TLS_GROUP_ENTRY("MLKEM768", "MLKEM768", "ML-KEM-768", 39),
-    TLS_GROUP_ENTRY("MLKEM1024", "MLKEM1024", "ML-KEM-1024", 40),
+    TLS_GROUP_ENTRY("MLKEM512", "", "ML-KEM-512", 38),
+    TLS_GROUP_ENTRY("MLKEM768", "", "ML-KEM-768", 39),
+    TLS_GROUP_ENTRY("MLKEM1024", "", "ML-KEM-1024", 40),
+# endif
+# if !defined(OPENSSL_NO_ML_KEM) && !defined(OPENSSL_NO_EC)
+#  if !defined(OPENSSL_NO_ECX)
+    TLS_GROUP_ENTRY("X25519MLKEM768", "", "X25519MLKEM768", 41),
+#  endif
+    TLS_GROUP_ENTRY("SecP256r1MLKEM768", "", "SecP256r1MLKEM768", 42),
+    TLS_GROUP_ENTRY("SecP384r1MLKEM1024", "", "SecP384r1MLKEM1024", 43),
 # endif
 };
 #endif /* !defined(OPENSSL_NO_EC) || !defined(OPENSSL_NO_DH) || !defined(OPENSSL_NO_ML_KEM) */
index 34247ed2f7e0119d1589b89dd16aaad2324f9177..87a84e9c21f8b64a9e5f11b2178c882f55810d05 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 2020-2024 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2020-2025 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
index df4bab0966d8c4f07f95ce0aa30dc4edf5453eac..301c5707893a00d244e31c6bc5899851f51eb42c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2024 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2025 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -166,6 +166,10 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
      "no instance allowed"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_NO_KEY_SET), "no key set"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_NO_PARAMETERS_SET), "no parameters set"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_NULL_LENGTH_POINTER),
+     "null length pointer"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_NULL_OUTPUT_BUFFER),
+     "null output buffer"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_ONESHOT_CALL_OUT_OF_ORDER),
      "oneshot call out of order"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE),
@@ -223,11 +227,15 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
      "unsupported mac type"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_UNSUPPORTED_NUMBER_OF_ROUNDS),
      "unsupported number of rounds"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_UNSUPPORTED_SELECTION),
+     "unsupported selection"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_UPDATE_CALL_OUT_OF_ORDER),
      "update call out of order"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_URI_AUTHORITY_UNSUPPORTED),
      "uri authority unsupported"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_VALUE_ERROR), "value error"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_WRONG_CIPHERTEXT_SIZE),
+     "wrong ciphertext size"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_WRONG_FINAL_BLOCK_LENGTH),
      "wrong final block length"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_WRONG_OUTPUT_BUFFER_SIZE),
index 4f42cb61ba842a7f96a548e0fe2115537b7a837a..f8b6cb4d349b9b3e32cf557a508ffc77952e474a 100644 (file)
@@ -486,6 +486,14 @@ static const OSSL_ALGORITHM deflt_asym_kem[] = {
     { PROV_NAMES_ML_KEM_512, "provider=default", ossl_ml_kem_asym_kem_functions },
     { PROV_NAMES_ML_KEM_768, "provider=default", ossl_ml_kem_asym_kem_functions },
     { PROV_NAMES_ML_KEM_1024, "provider=default", ossl_ml_kem_asym_kem_functions },
+# if !defined(OPENSSL_NO_ECX)
+    { "X25519MLKEM768", "provider=default", ossl_mlx_kem_asym_kem_functions },
+    { "X448MLKEM1024", "provider=default", ossl_mlx_kem_asym_kem_functions },
+# endif
+# if !defined(OPENSSL_NO_EC)
+    { "SecP256r1MLKEM768", "provider=default", ossl_mlx_kem_asym_kem_functions },
+    { "SecP384r1MLKEM1024", "provider=default", ossl_mlx_kem_asym_kem_functions },
+# endif
 #endif
     { NULL, NULL, NULL }
 };
@@ -556,6 +564,18 @@ static const OSSL_ALGORITHM deflt_keymgmt[] = {
       PROV_DESCS_ML_KEM_768 },
     { PROV_NAMES_ML_KEM_1024, "provider=default", ossl_ml_kem_1024_keymgmt_functions,
       PROV_DESCS_ML_KEM_1024 },
+# if !defined(OPENSSL_NO_ECX)
+    { PROV_NAMES_X25519MLKEM768, "provider=default", ossl_mlx_x25519_kem_kmgmt_functions,
+      PROV_DESCS_X25519MLKEM768 },
+    { PROV_NAMES_X448MLKEM1024, "provider=default", ossl_mlx_x448_kem_kmgmt_functions,
+      PROV_DESCS_X448MLKEM1024 },
+# endif
+# if !defined(OPENSSL_NO_EC)
+    { PROV_NAMES_SecP256r1MLKEM768, "provider=default", ossl_mlx_p256_kem_kmgmt_functions,
+      PROV_DESCS_SecP256r1MLKEM768 },
+    { PROV_NAMES_SecP384r1MLKEM1024, "provider=default", ossl_mlx_p384_kem_kmgmt_functions,
+      PROV_DESCS_SecP384r1MLKEM1024 },
+# endif
 #endif
     { NULL, NULL, NULL }
 };
index 8c284a9b47aa1b58568bfcd27a169e4d6c372794..79ee0628c9ac51cc3adb5c662aee13a518624211 100644 (file)
@@ -308,18 +308,20 @@ extern const OSSL_DISPATCH ossl_dhx_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_dsa_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_rsa_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_rsapss_keymgmt_functions[];
-#ifndef OPENSSL_NO_ECX
+extern const OSSL_DISPATCH ossl_kdf_keymgmt_functions[];
+extern const OSSL_DISPATCH ossl_mac_legacy_keymgmt_functions[];
+extern const OSSL_DISPATCH ossl_cmac_legacy_keymgmt_functions[];
+#ifndef OPENSSL_NO_EC
+extern const OSSL_DISPATCH ossl_ec_keymgmt_functions[];
+# ifndef OPENSSL_NO_ECX
 extern const OSSL_DISPATCH ossl_x25519_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_x448_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_ed25519_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_ed448_keymgmt_functions[];
-#endif
-extern const OSSL_DISPATCH ossl_ec_keymgmt_functions[];
-extern const OSSL_DISPATCH ossl_kdf_keymgmt_functions[];
-extern const OSSL_DISPATCH ossl_mac_legacy_keymgmt_functions[];
-extern const OSSL_DISPATCH ossl_cmac_legacy_keymgmt_functions[];
-#ifndef OPENSSL_NO_SM2
+# endif
+# ifndef OPENSSL_NO_SM2
 extern const OSSL_DISPATCH ossl_sm2_keymgmt_functions[];
+# endif
 #endif
 extern const OSSL_DISPATCH ossl_ml_dsa_44_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_ml_dsa_65_keymgmt_functions[];
@@ -328,13 +330,25 @@ extern const OSSL_DISPATCH ossl_ml_dsa_87_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_ml_kem_512_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_ml_kem_768_keymgmt_functions[];
 extern const OSSL_DISPATCH ossl_ml_kem_1024_keymgmt_functions[];
+# ifndef OPENSSL_NO_EC
+#  ifndef OPENSSL_NO_ECX
+extern const OSSL_DISPATCH ossl_mlx_x25519_kem_kmgmt_functions[];
+extern const OSSL_DISPATCH ossl_mlx_x448_kem_kmgmt_functions[];
+#  endif
+extern const OSSL_DISPATCH ossl_mlx_p256_kem_kmgmt_functions[];
+extern const OSSL_DISPATCH ossl_mlx_p384_kem_kmgmt_functions[];
+# endif
 #endif
 
 /* Key Exchange */
 extern const OSSL_DISPATCH ossl_dh_keyexch_functions[];
+#ifndef OPENSSL_NO_EC
+extern const OSSL_DISPATCH ossl_ecdh_keyexch_functions[];
+# ifndef OPENSSL_NO_ECX
 extern const OSSL_DISPATCH ossl_x25519_keyexch_functions[];
 extern const OSSL_DISPATCH ossl_x448_keyexch_functions[];
-extern const OSSL_DISPATCH ossl_ecdh_keyexch_functions[];
+# endif
+#endif
 extern const OSSL_DISPATCH ossl_kdf_tls1_prf_keyexch_functions[];
 extern const OSSL_DISPATCH ossl_kdf_hkdf_keyexch_functions[];
 extern const OSSL_DISPATCH ossl_kdf_scrypt_keyexch_functions[];
@@ -403,9 +417,18 @@ extern const OSSL_DISPATCH ossl_sm2_asym_cipher_functions[];
 
 /* Asym Key encapsulation  */
 extern const OSSL_DISPATCH ossl_rsa_asym_kem_functions[];
-extern const OSSL_DISPATCH ossl_ecx_asym_kem_functions[];
+#ifndef OPENSSL_NO_EC
 extern const OSSL_DISPATCH ossl_ec_asym_kem_functions[];
+# ifndef OPENSSL_NO_ECX
+extern const OSSL_DISPATCH ossl_ecx_asym_kem_functions[];
+# endif
+#endif
+#ifndef OPENSSL_NO_ML_KEM
 extern const OSSL_DISPATCH ossl_ml_kem_asym_kem_functions[];
+# ifndef OPENSSL_NO_EC
+extern const OSSL_DISPATCH ossl_mlx_kem_asym_kem_functions[];
+# endif
+#endif
 
 /* Encoders */
 extern const OSSL_DISPATCH ossl_rsa_to_PKCS1_der_encoder_functions[];
diff --git a/providers/implementations/include/prov/mlx_kem.h b/providers/implementations/include/prov/mlx_kem.h
new file mode 100644 (file)
index 0000000..0a6d779
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef OSSL_MLX_KEM_H
+# define OSSL_MLX_KEM_H
+# pragma once
+
+#include <openssl/evp.h>
+#include <openssl/ml_kem.h>
+#include <crypto/ml_kem.h>
+#include <crypto/ecx.h>
+
+typedef struct ecdh_vinfo_st {
+    const char *algorithm_name;
+    const char *group_name;
+    size_t      pubkey_bytes;
+    size_t      prvkey_bytes;
+    size_t      shsec_bytes;
+    int         ml_kem_slot;
+    int         ml_kem_variant;
+} ECDH_VINFO;
+
+typedef struct mlx_key_st {
+    OSSL_LIB_CTX *libctx;
+    char *propq;
+    const ML_KEM_VINFO *minfo;
+    const ECDH_VINFO *xinfo;
+    EVP_PKEY *mkey;
+    EVP_PKEY *xkey;
+    unsigned int state;
+} MLX_KEY;
+
+#define MLX_HAVE_NOKEYS 0
+#define MLX_HAVE_PUBKEY 1
+#define MLX_HAVE_PRVKEY 2
+
+/* Both key parts have whatever the ML-KEM component has */
+#define mlx_kem_have_pubkey(key) ((key)->state > 0)
+#define mlx_kem_have_prvkey(key) ((key)->state > 1)
+
+#endif
index 0c27be5bd4df17b2182e8ca1a456292a1cc2ce1a..1bb38f95b667eb450a44ab2db687a9fc08268b9a 100644 (file)
 #define PROV_DESCS_ML_KEM_768 "OpenSSL ML-KEM-768 implementation"
 #define PROV_NAMES_ML_KEM_1024 "ML-KEM-1024"
 #define PROV_DESCS_ML_KEM_1024 "OpenSSL ML-KEM-1024 implementation"
+#define PROV_NAMES_X25519MLKEM768 "X25519MLKEM768"
+#define PROV_DESCS_X25519MLKEM768 "X25519+ML-KEM-768 TLS hybrid implementation"
+#define PROV_NAMES_X448MLKEM1024 "X448MLKEM1024"
+#define PROV_DESCS_X448MLKEM1024 "X448+ML-KEM-1024 TLS hybrid implementation"
+#define PROV_NAMES_SecP256r1MLKEM768 "SecP256r1MLKEM768"
+#define PROV_DESCS_SecP256r1MLKEM768 "P-256+ML-KEM-768 TLS hybrid implementation"
+#define PROV_NAMES_SecP384r1MLKEM1024 "SecP384r1MLKEM1024"
+#define PROV_DESCS_SecP384r1MLKEM1024 "P-384+ML-KEM-1024 TLS hybrid implementation"
index 7ecaa2d9f219584c52d004946557752859629596..901ccd862ab6b0bd7e5b76459d47c4180fcace92 100644 (file)
@@ -5,6 +5,7 @@ $RSA_KEM_GOAL=../../libdefault.a ../../libfips.a
 $EC_KEM_GOAL=../../libdefault.a
 $TEMPLATE_KEM_GOAL=../../libtemplate.a
 $ML_KEM_GOAL=../../libdefault.a
+$TLS_ML_KEM_HYBRID_GOAL=../../libdefault.a
 
 SOURCE[$RSA_KEM_GOAL]=rsa_kem.c
 
@@ -15,7 +16,11 @@ IF[{- !$disabled{ec} -}]
   ENDIF
 ENDIF
 
-SOURCE[$TEMPLATE_KEM_GOAL]=template_kem.c
 IF[{- !$disabled{'ml-kem'} -}]
-    SOURCE[$ML_KEM_GOAL] = ml_kem.c
+  IF[{- !$disabled{ec} -}]
+    SOURCE[$TLS_ML_KEM_HYBRID_GOAL]=mlx_kem.c
+  ENDIF
+  SOURCE[$ML_KEM_GOAL] = ml_kem.c
 ENDIF
+
+SOURCE[$TEMPLATE_KEM_GOAL]=template_kem.c
index cf3de3190463dc1db5d39f1c6d4c275fae3275dd..e112b9397d909d3e964c9235a5f92ea6fbae4bbb 100644 (file)
@@ -176,9 +176,10 @@ static int ml_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
         goto end;
     }
 
-    /* For now tolerate newly-deprecated NULL length pointers. */
     if (clen == NULL) {
-        clen = &encap_clen;
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+                       "null ciphertext input/output length pointer");
+        goto end;
     } else if (*clen < encap_clen) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
                        "ciphertext buffer too small");
@@ -188,7 +189,9 @@ static int ml_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
     }
 
     if (slen == NULL) {
-        slen = &encap_slen;
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+                       "null shared secret input/output length pointer");
+        goto end;
     } else if (*slen < encap_slen) {
         ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
                        "shared-secret buffer too small");
@@ -252,16 +255,14 @@ static int ml_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen,
     return ossl_ml_kem_decap(shsec, decap_slen, ctext, clen, key);
 }
 
-typedef void (*func_ptr_t)(void);
-
 const OSSL_DISPATCH ossl_ml_kem_asym_kem_functions[] = {
-    { OSSL_FUNC_KEM_NEWCTX, (func_ptr_t) ml_kem_newctx },
-    { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (func_ptr_t) ml_kem_encapsulate_init },
-    { OSSL_FUNC_KEM_ENCAPSULATE, (func_ptr_t) ml_kem_encapsulate },
-    { OSSL_FUNC_KEM_DECAPSULATE_INIT, (func_ptr_t) ml_kem_decapsulate_init },
-    { OSSL_FUNC_KEM_DECAPSULATE, (func_ptr_t) ml_kem_decapsulate },
-    { OSSL_FUNC_KEM_FREECTX, (func_ptr_t) ml_kem_freectx },
-    { OSSL_FUNC_KEM_SET_CTX_PARAMS, (func_ptr_t) ml_kem_set_ctx_params },
-    { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (func_ptr_t) ml_kem_settable_ctx_params },
+    { OSSL_FUNC_KEM_NEWCTX, (OSSL_FUNC) ml_kem_newctx },
+    { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (OSSL_FUNC) ml_kem_encapsulate_init },
+    { OSSL_FUNC_KEM_ENCAPSULATE, (OSSL_FUNC) ml_kem_encapsulate },
+    { OSSL_FUNC_KEM_DECAPSULATE_INIT, (OSSL_FUNC) ml_kem_decapsulate_init },
+    { OSSL_FUNC_KEM_DECAPSULATE, (OSSL_FUNC) ml_kem_decapsulate },
+    { OSSL_FUNC_KEM_FREECTX, (OSSL_FUNC) ml_kem_freectx },
+    { OSSL_FUNC_KEM_SET_CTX_PARAMS, (OSSL_FUNC) ml_kem_set_ctx_params },
+    { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (OSSL_FUNC) ml_kem_settable_ctx_params },
     OSSL_DISPATCH_END
 };
diff --git a/providers/implementations/kem/mlx_kem.c b/providers/implementations/kem/mlx_kem.c
new file mode 100644 (file)
index 0000000..2952adf
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/params.h>
+#include <openssl/proverr.h>
+#include <openssl/rand.h>
+#include "prov/implementations.h"
+#include "prov/mlx_kem.h"
+#include "prov/provider_ctx.h"
+#include "prov/providercommon.h"
+
+static OSSL_FUNC_kem_newctx_fn mlx_kem_newctx;
+static OSSL_FUNC_kem_freectx_fn mlx_kem_freectx;
+static OSSL_FUNC_kem_encapsulate_init_fn mlx_kem_encapsulate_init;
+static OSSL_FUNC_kem_encapsulate_fn mlx_kem_encapsulate;
+static OSSL_FUNC_kem_decapsulate_init_fn mlx_kem_decapsulate_init;
+static OSSL_FUNC_kem_decapsulate_fn mlx_kem_decapsulate;
+static OSSL_FUNC_kem_set_ctx_params_fn mlx_kem_set_ctx_params;
+static OSSL_FUNC_kem_settable_ctx_params_fn mlx_kem_settable_ctx_params;
+
+typedef struct {
+    OSSL_LIB_CTX *libctx;
+    MLX_KEY *key;
+    int op;
+} PROV_MLX_KEM_CTX;
+
+static void *mlx_kem_newctx(void *provctx)
+{
+    PROV_MLX_KEM_CTX *ctx;
+
+    if ((ctx = OPENSSL_malloc(sizeof(*ctx))) == NULL)
+        return NULL;
+
+    ctx->libctx = PROV_LIBCTX_OF(provctx);
+    ctx->key = NULL;
+    ctx->op = 0;
+    return ctx;
+}
+
+static void mlx_kem_freectx(void *vctx)
+{
+    OPENSSL_free(vctx);
+}
+
+static int mlx_kem_init(void *vctx, int op, void *key,
+                        ossl_unused const OSSL_PARAM params[])
+{
+    PROV_MLX_KEM_CTX *ctx = vctx;
+
+    if (!ossl_prov_is_running())
+        return 0;
+    ctx->key = key;
+    ctx->op = op;
+    return 1;
+}
+
+static int
+mlx_kem_encapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[])
+{
+    MLX_KEY *key = vkey;
+
+    if (!mlx_kem_have_pubkey(key)) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        return 0;
+    }
+    return mlx_kem_init(vctx, EVP_PKEY_OP_ENCAPSULATE, key, params);
+}
+
+static int
+mlx_kem_decapsulate_init(void *vctx, void *vkey, const OSSL_PARAM params[])
+{
+    MLX_KEY *key = vkey;
+
+    if (!mlx_kem_have_prvkey(key)) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        return 0;
+    }
+    return mlx_kem_init(vctx, EVP_PKEY_OP_DECAPSULATE, key, params);
+}
+
+static const OSSL_PARAM *mlx_kem_settable_ctx_params(ossl_unused void *vctx,
+                                                     ossl_unused void *provctx)
+{
+    static const OSSL_PARAM params[] = { OSSL_PARAM_END };
+
+    return params;
+}
+
+static int
+mlx_kem_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+    return 1;
+}
+
+static int mlx_kem_encapsulate(void *vctx, unsigned char *ctext, size_t *clen,
+                               unsigned char *shsec, size_t *slen)
+{
+    MLX_KEY *key = ((PROV_MLX_KEM_CTX *) vctx)->key;
+    EVP_PKEY_CTX *ctx = NULL;
+    EVP_PKEY *xkey = NULL;
+    size_t encap_clen;
+    size_t encap_slen;
+    uint8_t *cbuf;
+    uint8_t *sbuf;
+    int ml_kem_slot = key->xinfo->ml_kem_slot;
+    int ret = 0;
+
+    if (!mlx_kem_have_pubkey(key)) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        goto end;
+    }
+    encap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
+    encap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
+
+    if (ctext == NULL) {
+        if (clen == NULL && slen == NULL)
+            return 0;
+        if (clen != NULL)
+            *clen = encap_clen;
+        if (slen != NULL)
+            *slen = encap_slen;
+        return 1;
+    }
+    if (shsec == NULL) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_OUTPUT_BUFFER,
+                       "null shared-secret output buffer");
+        return 0;
+    }
+
+    if (clen == NULL) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+                       "null ciphertext input/output length pointer");
+        return 0;
+    } else if (*clen < encap_clen) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+                       "ciphertext buffer too small");
+        return 0;
+    } else {
+        *clen = encap_clen;
+    }
+
+    if (slen == NULL) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NULL_LENGTH_POINTER,
+                       "null shared secret input/output length pointer");
+        return 0;
+    } else if (*slen < encap_slen) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+                       "shared-secret buffer too small");
+        return 0;
+    } else {
+        *slen = encap_slen;
+    }
+
+    /* ML-KEM encapsulation */
+    encap_clen = key->minfo->ctext_bytes;
+    encap_slen = ML_KEM_SHARED_SECRET_BYTES;
+    cbuf = ctext + ml_kem_slot * key->xinfo->pubkey_bytes;
+    sbuf = shsec + ml_kem_slot * key->xinfo->shsec_bytes;
+    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->mkey, key->propq);
+    if (ctx == NULL
+        || EVP_PKEY_encapsulate_init(ctx, NULL) <= 0
+        || EVP_PKEY_encapsulate(ctx, cbuf, &encap_clen, sbuf, &encap_slen) <= 0)
+        goto end;
+    if (encap_clen != key->minfo->ctext_bytes) {
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "unexpected %s ciphertext output size: %lu",
+                       key->minfo->algorithm_name, (unsigned long) encap_clen);
+        goto end;
+    }
+    if (encap_slen != ML_KEM_SHARED_SECRET_BYTES) {
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "unexpected %s shared secret output size: %lu",
+                       key->minfo->algorithm_name, (unsigned long) encap_slen);
+        goto end;
+    }
+    EVP_PKEY_CTX_free(ctx);
+
+    /*-
+     * ECDHE encapsulation
+     *
+     * Generate own ephemeral private key and add its public key to ctext.
+     *
+     * Note, we could support a settable parameter that sets an extant ECDH
+     * keypair as the keys to use in encap, making it possible to reuse the
+     * same (TLS client) ECDHE keypair for both the classical EC keyshare and a
+     * corresponding ECDHE + ML-KEM keypair.  But the TLS layer would then need
+     * know that this is a hybrid, and that it can partly reuse the same keys
+     * as another group for which a keyshare will be sent.  Deferred until we
+     * support generating multiple keyshares, there's a workable keyshare
+     * prediction specification, and the optimisation is justified.
+     */
+    cbuf = ctext + (1 - ml_kem_slot) * key->minfo->ctext_bytes;
+    encap_clen = key->xinfo->pubkey_bytes;
+    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->xkey, key->propq);
+    if (ctx == NULL
+        || EVP_PKEY_keygen_init(ctx) <= 0
+        || EVP_PKEY_keygen(ctx, &xkey) <= 0
+        || EVP_PKEY_get_octet_string_param(xkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY,
+                                           cbuf, encap_clen, &encap_clen) <= 0)
+        goto end;
+    if (encap_clen != key->xinfo->pubkey_bytes) {
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "unexpected %s public key output size: %lu",
+                       key->xinfo->algorithm_name, (unsigned long) encap_clen);
+        goto end;
+    }
+    EVP_PKEY_CTX_free(ctx);
+
+    /* Derive the ECDH shared secret */
+    encap_slen = key->xinfo->shsec_bytes;
+    sbuf = shsec + (1 - ml_kem_slot) * ML_KEM_SHARED_SECRET_BYTES;
+    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, xkey, key->propq);
+    if (ctx == NULL
+        || EVP_PKEY_derive_init(ctx) <= 0
+        || EVP_PKEY_derive_set_peer(ctx, key->xkey) <= 0
+        || EVP_PKEY_derive(ctx, sbuf, &encap_slen) <= 0)
+        goto end;
+    if (encap_slen != key->xinfo->shsec_bytes) {
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "unexpected %s shared secret output size: %lu",
+                       key->xinfo->algorithm_name, (unsigned long) encap_slen);
+        goto end;
+    }
+
+    ret = 1;
+  end:
+    EVP_PKEY_free(xkey);
+    EVP_PKEY_CTX_free(ctx);
+    return ret;
+}
+
+static int mlx_kem_decapsulate(void *vctx, uint8_t *shsec, size_t *slen,
+                               const uint8_t *ctext, size_t clen)
+{
+    MLX_KEY *key = ((PROV_MLX_KEM_CTX *) vctx)->key;
+    EVP_PKEY_CTX *ctx = NULL;
+    EVP_PKEY *xkey = NULL;
+    const uint8_t *cbuf;
+    uint8_t *sbuf;
+    size_t decap_slen = ML_KEM_SHARED_SECRET_BYTES + key->xinfo->shsec_bytes;
+    size_t decap_clen = key->minfo->ctext_bytes + key->xinfo->pubkey_bytes;
+    int ml_kem_slot = key->xinfo->ml_kem_slot;
+    int ret = 0;
+
+    if (!mlx_kem_have_prvkey(key)) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        return 0;
+    }
+
+    if (shsec == NULL) {
+        if (slen == NULL)
+            return 0;
+        *slen = decap_slen;
+        return 1;
+    }
+
+    /* For now tolerate newly-deprecated NULL length pointers. */
+    if (slen == NULL) {
+        slen = &decap_slen;
+    } else if (*slen < decap_slen) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+                       "shared-secret buffer too small");
+        return 0;
+    } else {
+        *slen = decap_slen;
+    }
+    if (clen != decap_clen) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_WRONG_CIPHERTEXT_SIZE,
+                       "wrong decapsulation input ciphertext size: %lu",
+                       (unsigned long) clen);
+        return 0;
+    }
+
+    /* ML-KEM decapsulation */
+    decap_clen = key->minfo->ctext_bytes;
+    decap_slen = ML_KEM_SHARED_SECRET_BYTES;
+    cbuf = ctext + ml_kem_slot * key->xinfo->pubkey_bytes;
+    sbuf = shsec + ml_kem_slot * key->xinfo->shsec_bytes;
+    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->mkey, key->propq);
+    if (ctx == NULL
+        || EVP_PKEY_decapsulate_init(ctx, NULL) <= 0
+        || EVP_PKEY_decapsulate(ctx, sbuf, &decap_slen, cbuf, decap_clen) <= 0)
+        goto end;
+    if (decap_slen != ML_KEM_SHARED_SECRET_BYTES) {
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "unexpected %s shared secret output size: %lu",
+                       key->minfo->algorithm_name, (unsigned long) decap_slen);
+        goto end;
+    }
+    EVP_PKEY_CTX_free(ctx);
+
+    /* ECDH decapsulation */
+    decap_clen = key->xinfo->pubkey_bytes;
+    decap_slen = key->xinfo->shsec_bytes;
+    cbuf = ctext + (1 - ml_kem_slot) * key->minfo->ctext_bytes;
+    sbuf = shsec + (1 - ml_kem_slot) * ML_KEM_SHARED_SECRET_BYTES;
+    ctx = EVP_PKEY_CTX_new_from_pkey(key->libctx, key->xkey, key->propq);
+    if (ctx == NULL
+        || (xkey = EVP_PKEY_new()) == NULL
+        || EVP_PKEY_copy_parameters(xkey, key->xkey) <= 0
+        || EVP_PKEY_set1_encoded_public_key(xkey, cbuf, decap_clen) <= 0
+        || EVP_PKEY_derive_init(ctx) <= 0
+        || EVP_PKEY_derive_set_peer(ctx, xkey) <= 0
+        || EVP_PKEY_derive(ctx, sbuf, &decap_slen) <= 0)
+        goto end;
+    if (decap_slen != key->xinfo->shsec_bytes) {
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "unexpected %s shared secret output size: %lu",
+                       key->xinfo->algorithm_name, (unsigned long) decap_slen);
+        goto end;
+    }
+
+    ret = 1;
+  end:
+    EVP_PKEY_CTX_free(ctx);
+    EVP_PKEY_free(xkey);
+    return ret;
+}
+
+const OSSL_DISPATCH ossl_mlx_kem_asym_kem_functions[] = {
+    { OSSL_FUNC_KEM_NEWCTX, (OSSL_FUNC) mlx_kem_newctx },
+    { OSSL_FUNC_KEM_ENCAPSULATE_INIT, (OSSL_FUNC) mlx_kem_encapsulate_init },
+    { OSSL_FUNC_KEM_ENCAPSULATE, (OSSL_FUNC) mlx_kem_encapsulate },
+    { OSSL_FUNC_KEM_DECAPSULATE_INIT, (OSSL_FUNC) mlx_kem_decapsulate_init },
+    { OSSL_FUNC_KEM_DECAPSULATE, (OSSL_FUNC) mlx_kem_decapsulate },
+    { OSSL_FUNC_KEM_FREECTX, (OSSL_FUNC) mlx_kem_freectx },
+    { OSSL_FUNC_KEM_SET_CTX_PARAMS, (OSSL_FUNC) mlx_kem_set_ctx_params },
+    { OSSL_FUNC_KEM_SETTABLE_CTX_PARAMS, (OSSL_FUNC) mlx_kem_settable_ctx_params },
+    OSSL_DISPATCH_END
+};
index 4698e15611633cba84152dd1ed515354cbacf508..6abc310e167329b5fbcca4d568b09d3dd3cb988e 100644 (file)
@@ -11,6 +11,7 @@ $RSA_GOAL=../../libdefault.a ../../libfips.a
 $TEMPLATE_GOAL=../../libtemplate.a
 $ML_DSA_GOAL=../../libdefault.a ../../libfips.a
 $ML_KEM_GOAL=../../libdefault.a
+$TLS_ML_KEM_HYBRID_GOAL=../../libdefault.a
 
 IF[{- !$disabled{dh} -}]
   SOURCE[$DH_GOAL]=dh_kmgmt.c
@@ -39,6 +40,13 @@ IF[{- !$disabled{ec} -}]
   ENDIF
 ENDIF
 
+IF[{- !$disabled{'ml-kem'} -}]
+  IF[{- !$disabled{ec} -}]
+    SOURCE[$TLS_ML_KEM_HYBRID_GOAL]=mlx_kmgmt.c
+  ENDIF
+  SOURCE[$ML_KEM_GOAL]=ml_kem_kmgmt.c
+ENDIF
+
 SOURCE[$RSA_GOAL]=rsa_kmgmt.c
 
 SOURCE[$KDF_GOAL]=kdf_legacy_kmgmt.c
@@ -50,7 +58,3 @@ SOURCE[$TEMPLATE_GOAL]=template_kmgmt.c
 IF[{- !$disabled{'ml-dsa'} -}]
   SOURCE[$ML_DSA_GOAL]=ml_dsa_kmgmt.c
 ENDIF
-
-IF[{- !$disabled{'ml-kem'} -}]
-  SOURCE[$ML_KEM_GOAL]=ml_kem_kmgmt.c
-ENDIF
index cda7cec963219d14fc287568388cbb11262e275b..4de7608268deab6ba0e82e8ebe828ab7cc95b18a 100644 (file)
@@ -44,6 +44,9 @@ static OSSL_FUNC_keymgmt_import_types_fn ml_kem_imexport_types;
 static OSSL_FUNC_keymgmt_export_types_fn ml_kem_imexport_types;
 static OSSL_FUNC_keymgmt_dup_fn ml_kem_dup;
 
+static const int minimal_selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS
+    | OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
+
 typedef struct ml_kem_gen_ctx_st {
     OSSL_LIB_CTX *libctx;
     char *propq;
@@ -401,9 +404,6 @@ static int ml_kem_gen_set_params(void *vgctx, const OSSL_PARAM params[])
 static void *ml_kem_gen_init(void *provctx, int selection,
                              const OSSL_PARAM params[], int variant)
 {
-    static const int minimal =
-        OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS
-        | OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
     PROV_ML_KEM_GEN_CTX *gctx = NULL;
 
     /*
@@ -411,7 +411,7 @@ static void *ml_kem_gen_init(void *provctx, int selection,
      * appropriate.
      */
     if (!ossl_prov_is_running()
-        || (selection & minimal) == 0
+        || (selection & minimal_selection) == 0
         || (gctx = OPENSSL_zalloc(sizeof(*gctx))) == NULL)
         return NULL;
 
@@ -489,8 +489,6 @@ static void *ml_kem_dup(const void *vkey, int selection)
     return ossl_ml_kem_key_dup(key, selection);
 }
 
-typedef void (*func_ptr_t)(void);
-
 #define DECLARE_VARIANT(bits) \
     static void *ml_kem_##bits##_new(void *provctx) \
     { \
@@ -503,24 +501,24 @@ typedef void (*func_ptr_t)(void);
         return ml_kem_gen_init(provctx, selection, params, ML_KEM_##bits##_VARIANT); \
     } \
     const OSSL_DISPATCH ossl_ml_kem_##bits##_keymgmt_functions[] = { \
-        { OSSL_FUNC_KEYMGMT_NEW, (func_ptr_t) ml_kem_##bits##_new }, \
-        { OSSL_FUNC_KEYMGMT_FREE, (func_ptr_t) ossl_ml_kem_key_free }, \
-        { OSSL_FUNC_KEYMGMT_GET_PARAMS, (func_ptr_t) ml_kem_get_params }, \
-        { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (func_ptr_t) ml_kem_gettable_params }, \
-        { OSSL_FUNC_KEYMGMT_SET_PARAMS, (func_ptr_t) ml_kem_set_params }, \
-        { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS, (func_ptr_t) ml_kem_settable_params }, \
-        { OSSL_FUNC_KEYMGMT_HAS, (func_ptr_t) ml_kem_has }, \
-        { OSSL_FUNC_KEYMGMT_MATCH, (func_ptr_t) ml_kem_match }, \
-        { OSSL_FUNC_KEYMGMT_GEN_INIT, (func_ptr_t) ml_kem_##bits##_gen_init }, \
-        { OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, (func_ptr_t) ml_kem_gen_set_params }, \
-        { OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS, (func_ptr_t) ml_kem_gen_settable_params }, \
-        { OSSL_FUNC_KEYMGMT_GEN, (func_ptr_t) ml_kem_gen }, \
-        { OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (func_ptr_t) ml_kem_gen_cleanup }, \
-        { OSSL_FUNC_KEYMGMT_DUP, (func_ptr_t) ml_kem_dup }, \
-        { OSSL_FUNC_KEYMGMT_IMPORT, (func_ptr_t) ml_kem_import }, \
-        { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (func_ptr_t) ml_kem_imexport_types }, \
-        { OSSL_FUNC_KEYMGMT_EXPORT, (func_ptr_t) ml_kem_export }, \
-        { OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (func_ptr_t) ml_kem_imexport_types }, \
+        { OSSL_FUNC_KEYMGMT_NEW, (OSSL_FUNC) ml_kem_##bits##_new }, \
+        { OSSL_FUNC_KEYMGMT_FREE, (OSSL_FUNC) ossl_ml_kem_key_free }, \
+        { OSSL_FUNC_KEYMGMT_GET_PARAMS, (OSSL_FUNC) ml_kem_get_params }, \
+        { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (OSSL_FUNC) ml_kem_gettable_params }, \
+        { OSSL_FUNC_KEYMGMT_SET_PARAMS, (OSSL_FUNC) ml_kem_set_params }, \
+        { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS, (OSSL_FUNC) ml_kem_settable_params }, \
+        { OSSL_FUNC_KEYMGMT_HAS, (OSSL_FUNC) ml_kem_has }, \
+        { OSSL_FUNC_KEYMGMT_MATCH, (OSSL_FUNC) ml_kem_match }, \
+        { OSSL_FUNC_KEYMGMT_GEN_INIT, (OSSL_FUNC) ml_kem_##bits##_gen_init }, \
+        { OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, (OSSL_FUNC) ml_kem_gen_set_params }, \
+        { OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS, (OSSL_FUNC) ml_kem_gen_settable_params }, \
+        { OSSL_FUNC_KEYMGMT_GEN, (OSSL_FUNC) ml_kem_gen }, \
+        { OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (OSSL_FUNC) ml_kem_gen_cleanup }, \
+        { OSSL_FUNC_KEYMGMT_DUP, (OSSL_FUNC) ml_kem_dup }, \
+        { OSSL_FUNC_KEYMGMT_IMPORT, (OSSL_FUNC) ml_kem_import }, \
+        { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (OSSL_FUNC) ml_kem_imexport_types }, \
+        { OSSL_FUNC_KEYMGMT_EXPORT, (OSSL_FUNC) ml_kem_export }, \
+        { OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (OSSL_FUNC) ml_kem_imexport_types }, \
         OSSL_DISPATCH_END \
     }
 DECLARE_VARIANT(512);
diff --git a/providers/implementations/keymgmt/mlx_kmgmt.c b/providers/implementations/keymgmt/mlx_kmgmt.c
new file mode 100644 (file)
index 0000000..d0dce62
--- /dev/null
@@ -0,0 +1,804 @@
+/*
+ * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/err.h>
+#include <openssl/param_build.h>
+#include <openssl/params.h>
+#include <openssl/proverr.h>
+#include <openssl/rand.h>
+#include <openssl/self_test.h>
+#include "internal/nelem.h"
+#include "internal/param_build_set.h"
+#include "prov/implementations.h"
+#include "prov/mlx_kem.h"
+#include "prov/provider_ctx.h"
+#include "prov/providercommon.h"
+#include "prov/securitycheck.h"
+
+static OSSL_FUNC_keymgmt_gen_fn mlx_kem_gen;
+static OSSL_FUNC_keymgmt_gen_cleanup_fn mlx_kem_gen_cleanup;
+static OSSL_FUNC_keymgmt_gen_set_params_fn mlx_kem_gen_set_params;
+static OSSL_FUNC_keymgmt_gen_settable_params_fn mlx_kem_gen_settable_params;
+static OSSL_FUNC_keymgmt_get_params_fn mlx_kem_get_params;
+static OSSL_FUNC_keymgmt_gettable_params_fn mlx_kem_gettable_params;
+static OSSL_FUNC_keymgmt_set_params_fn mlx_kem_set_params;
+static OSSL_FUNC_keymgmt_settable_params_fn mlx_kem_settable_params;
+static OSSL_FUNC_keymgmt_has_fn mlx_kem_has;
+static OSSL_FUNC_keymgmt_match_fn mlx_kem_match;
+static OSSL_FUNC_keymgmt_import_fn mlx_kem_import;
+static OSSL_FUNC_keymgmt_export_fn mlx_kem_export;
+static OSSL_FUNC_keymgmt_import_types_fn mlx_kem_imexport_types;
+static OSSL_FUNC_keymgmt_export_types_fn mlx_kem_imexport_types;
+static OSSL_FUNC_keymgmt_dup_fn mlx_kem_dup;
+
+static const int minimal_selection = OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS
+    | OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
+
+/* Must match DECLARE_DISPATCH invocations at the end of the file */
+static const ECDH_VINFO hybrid_vtable[] = {
+    { "EC",  "P-256", 65, 32, 32, 1, ML_KEM_768_VARIANT },
+    { "EC",  "P-384", 97, 48, 48, 1, ML_KEM_1024_VARIANT },
+#if !defined(OPENSSL_NO_ECX)
+    { "X25519", NULL, 32, 32, 32, 0, ML_KEM_768_VARIANT },
+    { "X448",   NULL, 56, 56, 56, 0, ML_KEM_1024_VARIANT },
+#endif
+};
+
+typedef struct mlx_kem_gen_ctx_st {
+    OSSL_LIB_CTX *libctx;
+    char *propq;
+    int selection;
+    unsigned int variant;
+} PROV_ML_KEM_GEN_CTX;
+
+static void mlx_kem_key_free(void *vkey)
+{
+    MLX_KEY *key = vkey;
+
+    if (key == NULL)
+        return;
+    OPENSSL_free(key->propq);
+    EVP_PKEY_free(key->mkey);
+    EVP_PKEY_free(key->xkey);
+    OPENSSL_free(key);
+}
+
+/* Takes ownership of propq */
+static void *
+mlx_kem_key_new(unsigned int v, OSSL_LIB_CTX *libctx, char *propq)
+{
+    MLX_KEY *key = NULL;
+    unsigned int ml_kem_variant;
+
+    if (!ossl_prov_is_running()
+        || v >= OSSL_NELEM(hybrid_vtable)
+        || (key = OPENSSL_malloc(sizeof(*key))) == NULL)
+        goto err;
+
+    ml_kem_variant = hybrid_vtable[v].ml_kem_variant;
+    key->libctx = libctx;
+    key->minfo = ossl_ml_kem_get_vinfo(ml_kem_variant);
+    key->xinfo = &hybrid_vtable[v];
+    key->xkey = key->mkey = NULL;
+    key->state = MLX_HAVE_NOKEYS;
+    key->propq = propq;
+    return key;
+
+  err:
+    OPENSSL_free(propq);
+    return NULL;
+}
+
+
+static int mlx_kem_has(const void *vkey, int selection)
+{
+    const MLX_KEY *key = vkey;
+
+    /* A NULL key MUST fail to have anything */
+    if (!ossl_prov_is_running() || key == NULL)
+        return 0;
+
+    switch (selection & OSSL_KEYMGMT_SELECT_KEYPAIR) {
+    case 0:
+        return 1;
+    case OSSL_KEYMGMT_SELECT_PUBLIC_KEY:
+        return mlx_kem_have_pubkey(key);
+    default:
+        return mlx_kem_have_prvkey(key);
+    }
+}
+
+static int mlx_kem_match(const void *vkey1, const void *vkey2, int selection)
+{
+    const MLX_KEY *key1 = vkey1;
+    const MLX_KEY *key2 = vkey2;
+    int have_pub1 = mlx_kem_have_pubkey(key1);
+    int have_pub2 = mlx_kem_have_pubkey(key2);
+
+    if (!ossl_prov_is_running())
+        return 0;
+
+    /* Compare domain parameters */
+    if (key1->xinfo != key2->xinfo)
+        return 0;
+
+    if (!(selection & OSSL_KEYMGMT_SELECT_KEYPAIR))
+        return 1;
+
+    if (have_pub1 ^ have_pub2)
+        return 0;
+
+    /* As in other providers, equal when both have no key material. */
+    if (!have_pub1)
+        return 1;
+
+    return EVP_PKEY_eq(key1->mkey, key2->mkey)
+        && EVP_PKEY_eq(key1->xkey, key2->xkey);
+}
+
+typedef struct export_cb_arg_st {
+    const char *algorithm_name;
+    uint8_t *pubenc;
+    uint8_t *prvenc;
+    int      pubcount;
+    int      prvcount;
+    size_t   puboff;
+    size_t   prvoff;
+    size_t   publen;
+    size_t   prvlen;
+} EXPORT_CB_ARG;
+
+/* Copy any exported key material into its storage slot */
+static int export_sub_cb(const OSSL_PARAM *params, void *varg)
+{
+    EXPORT_CB_ARG *sub_arg = varg;
+    const OSSL_PARAM *p = NULL;
+    size_t len;
+
+    /*
+     * The caller will decide whether anything essential is missing, but, if
+     * some key material was returned, it should have the right (parameter)
+     * data type and length.
+     */
+    if (ossl_param_is_empty(params))
+        return 1;
+    if (sub_arg->pubenc != NULL
+        && (p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY)) != NULL) {
+        void *pub = sub_arg->pubenc + sub_arg->puboff;
+
+        if (OSSL_PARAM_get_octet_string(p, &pub, sub_arg->publen, &len) != 1)
+            return 0;
+        if (len != sub_arg->publen) {
+            ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                           "Unexpected %s public key length %lu != %lu",
+                           sub_arg->algorithm_name, (unsigned long) len,
+                           sub_arg->publen);
+            return 0;
+        }
+        ++sub_arg->pubcount;
+    }
+    if (sub_arg->prvenc != NULL
+        && (p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY)) != NULL) {
+        void *prv = sub_arg->prvenc + sub_arg->prvoff;
+
+        if (OSSL_PARAM_get_octet_string(p, &prv, sub_arg->prvlen, &len) != 1)
+            return 0;
+        if (len != sub_arg->prvlen) {
+            ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                           "Unexpected %s private key length %lu != %lu",
+                           sub_arg->algorithm_name, (unsigned long) len,
+                           (unsigned long) sub_arg->publen);
+            return 0;
+        }
+        ++sub_arg->prvcount;
+    }
+    return 1;
+}
+
+static int
+export_sub(EXPORT_CB_ARG *sub_arg, int selection, MLX_KEY *key)
+{
+    int slot;
+
+    /*
+     * The caller is responsible for initialising only the pubenc and prvenc
+     * pointer fields, the rest are set here or in the callback.
+     */
+    sub_arg->pubcount = 0;
+    sub_arg->prvcount = 0;
+
+    for (slot = 0; slot < 2; ++slot) {
+        int ml_kem_slot = key->xinfo->ml_kem_slot;
+        EVP_PKEY *pkey;
+
+        /* Export the parts of each component into its storage slot */
+        if (slot == ml_kem_slot) {
+            pkey = key->mkey;
+            sub_arg->algorithm_name = key->minfo->algorithm_name;
+            sub_arg->puboff = slot * key->xinfo->pubkey_bytes;
+            sub_arg->prvoff = slot * key->xinfo->prvkey_bytes;
+            sub_arg->publen = key->minfo->pubkey_bytes;
+            sub_arg->prvlen = key->minfo->prvkey_bytes;
+        } else {
+            pkey = key->xkey;
+            sub_arg->algorithm_name = key->xinfo->algorithm_name;
+            sub_arg->puboff = (1 - ml_kem_slot) * key->minfo->pubkey_bytes;
+            sub_arg->prvoff = (1 - ml_kem_slot) * key->minfo->prvkey_bytes;
+            sub_arg->publen = key->xinfo->pubkey_bytes;
+            sub_arg->prvlen = key->xinfo->prvkey_bytes;
+        }
+        if (!EVP_PKEY_export(pkey, selection, export_sub_cb, (void *)sub_arg))
+            return 0;
+    }
+    return 1;
+}
+
+static int mlx_kem_export(void *vkey, int selection, OSSL_CALLBACK *param_cb,
+                          void *cbarg)
+{
+    MLX_KEY *key = vkey;
+    OSSL_PARAM_BLD *tmpl = NULL;
+    OSSL_PARAM *params = NULL;
+    size_t publen;
+    size_t prvlen;
+    int ret = 0;
+    EXPORT_CB_ARG sub_arg;
+
+    if (!ossl_prov_is_running() || key == NULL)
+        return 0;
+
+    if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0)
+        return 0;
+
+    /* Fail when no key material has yet been provided */
+    if (!mlx_kem_have_pubkey(key)) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        return 0;
+    }
+    publen = key->minfo->pubkey_bytes + key->xinfo->pubkey_bytes;
+    prvlen = key->minfo->prvkey_bytes + key->xinfo->prvkey_bytes;
+    memset(&sub_arg, 0, sizeof(sub_arg));
+
+    if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) {
+        sub_arg.pubenc = OPENSSL_malloc(publen);
+        if (sub_arg.pubenc == NULL)
+            goto err;
+    }
+
+    if (mlx_kem_have_prvkey(key)
+        && (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) {
+        /*
+         * Allocated on the secure heap if configured, this is detected in
+         * ossl_param_build_set_octet_string(), which will then also use the
+         * secure heap.
+         */
+        sub_arg.prvenc = OPENSSL_secure_zalloc(prvlen);
+        if (sub_arg.prvenc == NULL)
+            goto err;
+    }
+
+    tmpl = OSSL_PARAM_BLD_new();
+    if (tmpl == NULL)
+        goto err;
+
+    /* Extract sub-component key material */
+    if (!export_sub(&sub_arg, selection, key))
+        goto err;
+
+    if (sub_arg.pubenc != NULL && sub_arg.pubcount == 2
+        && !ossl_param_build_set_octet_string(
+                tmpl, NULL, OSSL_PKEY_PARAM_PUB_KEY, sub_arg.pubenc, publen))
+        goto err;
+
+    if (sub_arg.prvenc != NULL && sub_arg.prvcount == 2
+        && !ossl_param_build_set_octet_string(
+                tmpl, NULL, OSSL_PKEY_PARAM_PRIV_KEY, sub_arg.prvenc, prvlen))
+        goto err;
+
+    params = OSSL_PARAM_BLD_to_param(tmpl);
+    if (params == NULL)
+        goto err;
+
+    ret = param_cb(params, cbarg);
+    OSSL_PARAM_free(params);
+
+err:
+    OSSL_PARAM_BLD_free(tmpl);
+    OPENSSL_secure_clear_free(sub_arg.prvenc, prvlen);
+    OPENSSL_free(sub_arg.pubenc);
+    return ret;
+}
+
+static const OSSL_PARAM *mlx_kem_imexport_types(int selection)
+{
+    static const OSSL_PARAM key_types[] = {
+        OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0),
+        OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0),
+        OSSL_PARAM_END
+    };
+
+    if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0)
+        return key_types;
+    return NULL;
+}
+
+static int
+load_slot(OSSL_LIB_CTX *libctx, const char *propq, const char *pname,
+          int selection, MLX_KEY *key, int slot, const uint8_t *in,
+          int mbytes, int xbytes)
+{
+    EVP_PKEY_CTX *ctx;
+    EVP_PKEY **ppkey;
+    OSSL_PARAM parr[] = { OSSL_PARAM_END, OSSL_PARAM_END, OSSL_PARAM_END };
+    const char *alg;
+    char *group = NULL;
+    size_t off, len;
+    void *val;
+    int ml_kem_slot = key->xinfo->ml_kem_slot;
+    int ret = 0;
+
+    if (slot == ml_kem_slot) {
+        alg = key->minfo->algorithm_name;
+        ppkey = &key->mkey;
+        off = slot * xbytes;
+        len = mbytes;
+    } else {
+        alg = key->xinfo->algorithm_name;
+        group = (char *) key->xinfo->group_name;
+        ppkey = &key->xkey;
+        off = (1 - ml_kem_slot) * mbytes;
+        len = xbytes;
+    }
+    val = (void *)(in + off);
+
+    if ((ctx = EVP_PKEY_CTX_new_from_name(libctx, alg, propq)) == NULL
+        || EVP_PKEY_fromdata_init(ctx) <= 0)
+        goto err;
+    parr[0] = OSSL_PARAM_construct_octet_string(pname, val, len);
+    if (group != NULL)
+        parr[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME,
+                                                   group, 0);
+    if (EVP_PKEY_fromdata(ctx, ppkey, selection, parr) > 0)
+        ret = 1;
+
+  err:
+    EVP_PKEY_CTX_free(ctx);
+    return ret;
+}
+
+static int
+load_keys(MLX_KEY *key,
+          const uint8_t *pubenc, size_t publen,
+          const uint8_t *prvenc, size_t prvlen)
+{
+    int slot;
+
+    for (slot = 0; slot < 2; ++slot) {
+        if (prvlen) {
+            /* Ignore public keys when private provided */
+            if (!load_slot(key->libctx, key->propq, OSSL_PKEY_PARAM_PRIV_KEY,
+                           minimal_selection, key, slot, prvenc,
+                           key->minfo->prvkey_bytes, key->xinfo->prvkey_bytes))
+                goto err;
+        } else if (publen) {
+            /* Absent private key data, import public keys */
+            if (!load_slot(key->libctx, key->propq, OSSL_PKEY_PARAM_PUB_KEY,
+                           minimal_selection, key, slot, pubenc,
+                           key->minfo->pubkey_bytes, key->xinfo->pubkey_bytes))
+                goto err;
+        }
+    }
+    key->state = prvlen ? MLX_HAVE_PRVKEY : MLX_HAVE_PUBKEY;
+    return 1;
+
+  err:
+    EVP_PKEY_free(key->mkey);
+    EVP_PKEY_free(key->xkey);
+    key->xkey = key->mkey = NULL;
+    key->state = MLX_HAVE_NOKEYS;
+    return 0;
+}
+
+static int mlx_kem_key_fromdata(MLX_KEY *key,
+                               const OSSL_PARAM params[],
+                               int include_private)
+{
+    const OSSL_PARAM *param_prv_key = NULL, *param_pub_key;
+    const void *pubenc = NULL, *prvenc = NULL;
+    size_t pubkey_bytes, prvkey_bytes;
+    size_t publen = 0, prvlen = 0;
+
+    /* Invalid attempt to mutate a key, what is the right error to report? */
+    if (key == NULL || mlx_kem_have_pubkey(key))
+        return 0;
+    pubkey_bytes = key->minfo->pubkey_bytes + key->xinfo->pubkey_bytes;
+    prvkey_bytes = key->minfo->prvkey_bytes + key->xinfo->prvkey_bytes;
+
+    /* What does the caller want to set? */
+    param_pub_key = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY);
+    if (param_pub_key != NULL &&
+        OSSL_PARAM_get_octet_string_ptr(param_pub_key, &pubenc, &publen) != 1)
+        return 0;
+    if (include_private)
+        param_prv_key = OSSL_PARAM_locate_const(params,
+                                                OSSL_PKEY_PARAM_PRIV_KEY);
+    if (param_prv_key != NULL &&
+        OSSL_PARAM_get_octet_string_ptr(param_prv_key, &prvenc, &prvlen) != 1)
+        return 0;
+
+    /* The caller MUST specify at least one of the public or private keys. */
+    if (publen == 0 && prvlen == 0) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        return 0;
+    }
+
+    /*
+     * When a pubkey is provided, its length MUST be correct, if a private key
+     * is also provided, the public key will be otherwise ignored.  We could
+     * look for a matching encoded block, but unclear this is useful.
+     */
+    if (publen != 0 && publen != pubkey_bytes) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH);
+        return 0;
+    }
+    if (prvlen != 0 && prvlen != prvkey_bytes) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH);
+        return 0;
+    }
+
+    return load_keys(key, pubenc, publen, prvenc, prvlen);
+}
+
+static int mlx_kem_import(void *vkey, int selection, const OSSL_PARAM params[])
+{
+    MLX_KEY *key = vkey;
+    int include_private;
+
+    if (!ossl_prov_is_running() || key == NULL)
+        return 0;
+
+    if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0)
+        return 0;
+
+    include_private = selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY ? 1 : 0;
+    return mlx_kem_key_fromdata(key, params, include_private);
+}
+
+static const OSSL_PARAM *mlx_kem_gettable_params(void *provctx)
+{
+    static const OSSL_PARAM arr[] = {
+        OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL),
+        OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),
+        OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL),
+        OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0),
+        OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0),
+        OSSL_PARAM_END
+    };
+
+    return arr;
+}
+
+/*
+ * It is assumed the key is guaranteed non-NULL here, and is from this provider
+ */
+static int mlx_kem_get_params(void *vkey, OSSL_PARAM params[])
+{
+    MLX_KEY *key = vkey;
+    OSSL_PARAM *p, *pub, *prv = NULL;
+    EXPORT_CB_ARG sub_arg;
+    int selection;
+    size_t publen = key->minfo->pubkey_bytes + key->xinfo->pubkey_bytes;
+    size_t prvlen = key->minfo->prvkey_bytes + key->xinfo->prvkey_bytes;
+
+    /* The reported "bit" count is those of the ML-KEM key */
+    p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_BITS);
+    if (p != NULL)
+        if (!OSSL_PARAM_set_int(p, key->minfo->bits))
+            return 0;
+
+    /* The reported security bits are those of the ML-KEM key */
+    p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_BITS);
+    if (p != NULL)
+        if (!OSSL_PARAM_set_int(p, key->minfo->secbits))
+            return 0;
+
+    /* The ciphertext sizes are additive */
+    p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_MAX_SIZE);
+    if (p != NULL)
+        if (!OSSL_PARAM_set_int(p, key->minfo->ctext_bytes + key->xinfo->pubkey_bytes))
+            return 0;
+
+    if (!mlx_kem_have_pubkey(key))
+        return 1;
+
+    memset(&sub_arg, 0, sizeof(sub_arg));
+    pub = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY);
+    if (pub != NULL) {
+        if (pub->data_type != OSSL_PARAM_OCTET_STRING)
+            return 0;
+        pub->return_size = publen;
+        if (pub->data == NULL) {
+            pub = NULL;
+        } else if (pub->data_size < publen) {
+            ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+                           "public key output buffer too short: %lu < %lu",
+                           (unsigned long) pub->data_size,
+                           (unsigned long) publen);
+            return 0;
+        } else {
+            sub_arg.pubenc = pub->data;
+        }
+    }
+    if (mlx_kem_have_prvkey(key)) {
+        prv = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY);
+        if (prv != NULL) {
+            if (prv->data_type != OSSL_PARAM_OCTET_STRING)
+                return 0;
+            prv->return_size = prvlen;
+            if (prv->data == NULL) {
+                prv = NULL;
+            } else if (prv->data_size < prvlen) {
+                ERR_raise_data(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL,
+                               "private key output buffer too short: %lu < %lu",
+                               (unsigned long) prv->data_size,
+                               (unsigned long) prvlen);
+                return 0;
+            } else {
+                sub_arg.prvenc = prv->data;
+            }
+        }
+    }
+    if (pub == NULL && prv == NULL)
+        return 1;
+
+    selection = prv == NULL ? 0 : OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
+    selection |= pub == NULL ? 0 : OSSL_KEYMGMT_SELECT_PUBLIC_KEY;
+    if (key->xinfo->group_name != NULL)
+        selection |= OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS;
+
+    /* Extract sub-component key material */
+    if (!export_sub(&sub_arg, selection, key))
+        return 0;
+
+    if ((pub != NULL && sub_arg.pubcount != 2)
+        || (prv != NULL && sub_arg.prvcount != 2))
+        return 0;
+
+    return 1;
+}
+
+static const OSSL_PARAM *mlx_kem_settable_params(void *provctx)
+{
+    static const OSSL_PARAM arr[] = {
+        OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0),
+        OSSL_PARAM_END
+    };
+
+    return arr;
+}
+
+static int mlx_kem_set_params(void *vkey, const OSSL_PARAM params[])
+{
+    MLX_KEY *key = vkey;
+    const OSSL_PARAM *p;
+    const void *pubenc = NULL;
+    size_t publen = 0;
+
+    if (ossl_param_is_empty(params))
+        return 1;
+
+    /* Only one settable parameter is supported */
+    p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY);
+    if (p == NULL)
+        return 1;
+
+    /* Key mutation is reportedly generally not allowed */
+    if (mlx_kem_have_pubkey(key)) {
+        ERR_raise_data(ERR_LIB_PROV,
+                       PROV_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE,
+                       "keys cannot be mutated");
+        return 0;
+    }
+    /* An unlikely failure mode is the parameter having some unexpected type */
+    if (!OSSL_PARAM_get_octet_string_ptr(p, &pubenc, &publen))
+        return 0;
+
+    p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PROPERTIES);
+    if (p != NULL) {
+        OPENSSL_free(key->propq);
+        key->propq = NULL;
+        if (!OSSL_PARAM_get_utf8_string(p, &key->propq, 0))
+            return 0;
+    }
+
+    if (publen != key->minfo->pubkey_bytes + key->xinfo->pubkey_bytes) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY);
+        return 0;
+    }
+
+    return load_keys(key, pubenc, publen, NULL, 0);
+}
+
+static int mlx_kem_gen_set_params(void *vgctx, const OSSL_PARAM params[])
+{
+    PROV_ML_KEM_GEN_CTX *gctx = vgctx;
+    const OSSL_PARAM *p;
+
+    if (gctx == NULL)
+        return 0;
+    if (ossl_param_is_empty(params))
+        return 1;
+
+    p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PROPERTIES);
+    if (p != NULL) {
+        if (p->data_type != OSSL_PARAM_UTF8_STRING)
+            return 0;
+        OPENSSL_free(gctx->propq);
+        if ((gctx->propq = OPENSSL_strdup(p->data)) == NULL)
+            return 0;
+    }
+    return 1;
+}
+
+static void *mlx_kem_gen_init(int v, OSSL_LIB_CTX *libctx, int selection,
+                              const OSSL_PARAM params[])
+{
+    PROV_ML_KEM_GEN_CTX *gctx = NULL;
+
+    /*
+     * We can only generate private keys, check that the selection is
+     * appropriate.
+     */
+    if (!ossl_prov_is_running()
+        || (selection & minimal_selection) == 0
+        || (gctx = OPENSSL_zalloc(sizeof(*gctx))) == NULL)
+        return NULL;
+
+    gctx->variant = v;
+    gctx->libctx = libctx;
+    gctx->selection = selection;
+    if (mlx_kem_gen_set_params(gctx, params))
+        return gctx;
+
+    mlx_kem_gen_cleanup(gctx);
+    return NULL;
+}
+
+static const OSSL_PARAM *mlx_kem_gen_settable_params(ossl_unused void *vgctx,
+                                                     ossl_unused void *provctx)
+{
+    static OSSL_PARAM settable[] = {
+        OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PROPERTIES, NULL, 0),
+        OSSL_PARAM_END
+    };
+
+    return settable;
+}
+
+static void *mlx_kem_gen(void *vgctx, OSSL_CALLBACK *osslcb, void *cbarg)
+{
+    PROV_ML_KEM_GEN_CTX *gctx = vgctx;
+    MLX_KEY *key;
+    char *propq = gctx->propq;
+
+    if (gctx == NULL
+        || (gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) ==
+            OSSL_KEYMGMT_SELECT_PUBLIC_KEY)
+        return NULL;
+
+    /* Lose ownership of propq */
+    gctx->propq = NULL;
+    if ((key = mlx_kem_key_new(gctx->variant, gctx->libctx, propq)) == NULL)
+        return NULL;
+
+    if ((gctx->selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0)
+        return key;
+
+    /* For now, using the same "propq" for all components */
+    key->mkey = EVP_PKEY_Q_keygen(key->libctx, key->propq,
+                                  key->minfo->algorithm_name);
+    key->xkey = EVP_PKEY_Q_keygen(key->libctx, key->propq,
+                                  key->xinfo->algorithm_name,
+                                  key->xinfo->group_name);
+    if (key->mkey != NULL && key->xkey != NULL) {
+        key->state = MLX_HAVE_PRVKEY;
+        return key;
+    }
+
+    mlx_kem_key_free(key);
+    return NULL;
+}
+
+static void mlx_kem_gen_cleanup(void *vgctx)
+{
+    PROV_ML_KEM_GEN_CTX *gctx = vgctx;
+
+    if (gctx == NULL)
+        return;
+    OPENSSL_free(gctx->propq);
+    OPENSSL_free(gctx);
+}
+
+static void *mlx_kem_dup(const void *vkey, int selection)
+{
+    const MLX_KEY *key = vkey;
+    MLX_KEY *ret;
+
+    if (!ossl_prov_is_running()
+        || (ret = OPENSSL_memdup(key, sizeof(*ret))) == NULL)
+        return NULL;
+
+    switch (selection & OSSL_KEYMGMT_SELECT_KEYPAIR) {
+    case 0:
+        ret->xkey = ret->mkey = NULL;
+        return ret;
+    case OSSL_KEYMGMT_SELECT_KEYPAIR:
+        ret->mkey = EVP_PKEY_dup(key->mkey);
+        ret->xkey = EVP_PKEY_dup(key->xkey);
+        if (ret->xkey != NULL && ret->mkey != NULL)
+            return ret;
+        break;
+    default:
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_UNSUPPORTED_SELECTION,
+                       "duplication of partial key material not supported");
+        break;
+    }
+
+    mlx_kem_key_free(ret);
+    return NULL;
+}
+
+#define DECLARE_DISPATCH(name, variant) \
+    static OSSL_FUNC_keymgmt_new_fn mlx_##name##_kem_new; \
+    static void *mlx_##name##_kem_new(void *provctx) \
+    { \
+        OSSL_LIB_CTX *libctx; \
+                              \
+        libctx = provctx == NULL ? NULL : PROV_LIBCTX_OF(provctx); \
+        return mlx_kem_key_new(variant, libctx, NULL); \
+    } \
+    static OSSL_FUNC_keymgmt_gen_init_fn mlx_##name##_kem_gen_init; \
+    static void *mlx_##name##_kem_gen_init(void *provctx, int selection, \
+                                           const OSSL_PARAM params[]) \
+    { \
+        OSSL_LIB_CTX *libctx; \
+                              \
+        libctx = provctx == NULL ? NULL : PROV_LIBCTX_OF(provctx); \
+        return mlx_kem_gen_init(variant, libctx, selection, params); \
+    } \
+    const OSSL_DISPATCH ossl_mlx_##name##_kem_kmgmt_functions[] = { \
+        { OSSL_FUNC_KEYMGMT_NEW, (OSSL_FUNC) mlx_##name##_kem_new }, \
+        { OSSL_FUNC_KEYMGMT_FREE, (OSSL_FUNC) mlx_kem_key_free }, \
+        { OSSL_FUNC_KEYMGMT_GET_PARAMS, (OSSL_FUNC) mlx_kem_get_params }, \
+        { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (OSSL_FUNC) mlx_kem_gettable_params }, \
+        { OSSL_FUNC_KEYMGMT_SET_PARAMS, (OSSL_FUNC) mlx_kem_set_params }, \
+        { OSSL_FUNC_KEYMGMT_SETTABLE_PARAMS, (OSSL_FUNC) mlx_kem_settable_params }, \
+        { OSSL_FUNC_KEYMGMT_HAS, (OSSL_FUNC) mlx_kem_has }, \
+        { OSSL_FUNC_KEYMGMT_MATCH, (OSSL_FUNC) mlx_kem_match }, \
+        { OSSL_FUNC_KEYMGMT_GEN_INIT, (OSSL_FUNC) mlx_##name##_kem_gen_init }, \
+        { OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, (OSSL_FUNC) mlx_kem_gen_set_params }, \
+        { OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS, (OSSL_FUNC) mlx_kem_gen_settable_params }, \
+        { OSSL_FUNC_KEYMGMT_GEN, (OSSL_FUNC) mlx_kem_gen }, \
+        { OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (OSSL_FUNC) mlx_kem_gen_cleanup }, \
+        { OSSL_FUNC_KEYMGMT_DUP, (OSSL_FUNC) mlx_kem_dup }, \
+        { OSSL_FUNC_KEYMGMT_IMPORT, (OSSL_FUNC) mlx_kem_import }, \
+        { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (OSSL_FUNC) mlx_kem_imexport_types }, \
+        { OSSL_FUNC_KEYMGMT_EXPORT, (OSSL_FUNC) mlx_kem_export }, \
+        { OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (OSSL_FUNC) mlx_kem_imexport_types }, \
+        OSSL_DISPATCH_END \
+    }
+/* See |hybrid_vtable| above */
+DECLARE_DISPATCH(p256, 0);
+DECLARE_DISPATCH(p384, 1);
+#if !defined(OPENSSL_NO_ECX)
+DECLARE_DISPATCH(x25519, 2);
+DECLARE_DISPATCH(x448, 3);
+#endif
index dadc59dbb301cfa310f6c4338edd1b3ec0ea608a..5e7f89847d11cd7f2e83ded6f044ece9fbb8869b 100644 (file)
@@ -4079,6 +4079,12 @@ long ssl3_ctx_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
                                     &ctx->ext.tuples_len,
                                     parg);
 
+    case SSL_CTRL_GET0_IMPLEMENTED_GROUPS:
+        return tls1_get0_implemented_groups(ctx->min_proto_version,
+                                            ctx->max_proto_version,
+                                            ctx->group_list,
+                                            ctx->group_list_len, larg, parg);
+
     case SSL_CTRL_SET_SIGALGS:
         return tls1_set_sigalgs(ctx->cert, parg, larg, 0);
 
index 45d898bc55ab712f340f6f41b1b8c47b9eff6b45..28dcec8b96d5820b5d53912bcc0ec03730d6f812 100644 (file)
@@ -2839,6 +2839,11 @@ __owur int tls1_group_id2nid(uint16_t group_id, int include_unknown);
 __owur uint16_t tls1_nid2group_id(int nid);
 __owur int tls1_check_group_id(SSL_CONNECTION *s, uint16_t group_id,
                                int check_own_curves);
+__owur int tls1_get0_implemented_groups(int min_proto_version,
+                                        int max_proto_version,
+                                        TLS_GROUP_INFO *grps,
+                                        size_t num, long all,
+                                        STACK_OF(OPENSSL_CSTRING) *out);
 __owur uint16_t tls1_shared_group(SSL_CONNECTION *s, int nmatch);
 __owur int tls1_set_groups(uint16_t **grpext, size_t *grpextlen,
                            uint16_t **ksext, size_t *ksextlen,
index 5fc12c5b3b4fa9245dbb4a872924966478139fb9..011ddc51d931b9b5df2efbad7d873d4b8a8f24dc 100644 (file)
@@ -977,6 +977,81 @@ static int tls1_in_list(uint16_t id, const uint16_t *list, size_t listlen)
     return 0;
 }
 
+typedef struct {
+    TLS_GROUP_INFO *grp;
+    size_t ix;
+} TLS_GROUP_IX;
+
+DEFINE_STACK_OF(TLS_GROUP_IX)
+
+static void free_wrapper(TLS_GROUP_IX *a)
+{
+    OPENSSL_free(a);
+}
+
+static int tls_group_ix_cmp(const TLS_GROUP_IX *const *a,
+                            const TLS_GROUP_IX *const *b)
+{
+    int idcmpab = (*a)->grp->group_id < (*b)->grp->group_id;
+    int idcmpba = (*b)->grp->group_id < (*a)->grp->group_id;
+    int ixcmpab = (*a)->ix < (*b)->ix;
+    int ixcmpba = (*b)->ix < (*a)->ix;
+
+    /* Ascending by group id */
+    if (idcmpab != idcmpba)
+        return (idcmpba - idcmpab);
+    /* Ascending by original appearance index */
+    return ixcmpba - ixcmpab;
+}
+
+int tls1_get0_implemented_groups(int min_proto_version, int max_proto_version,
+                                 TLS_GROUP_INFO *grps, size_t num, long all,
+                                 STACK_OF(OPENSSL_CSTRING) *out)
+{
+    STACK_OF(TLS_GROUP_IX) *collect = NULL;
+    TLS_GROUP_IX *gix;
+    uint16_t id = 0;
+    int ret = 0;
+    size_t ix;
+
+    if ((collect = sk_TLS_GROUP_IX_new(tls_group_ix_cmp)) == NULL)
+        return 0;
+
+    if (grps == NULL || out == NULL)
+        return 0;
+    for (ix = 0; ix < num; ++ix, ++grps) {
+        if (grps->mintls > 0 && max_proto_version > 0
+             && grps->mintls > max_proto_version)
+            continue;
+        if (grps->maxtls > 0 && min_proto_version > 0
+            && grps->maxtls < min_proto_version)
+            continue;
+
+        if ((gix = OPENSSL_malloc(sizeof(*gix))) == NULL)
+            goto end;
+        gix->grp = grps;
+        gix->ix = ix;
+        if (sk_TLS_GROUP_IX_push(collect, gix) <= 0)
+            goto end;
+    }
+
+    sk_TLS_GROUP_IX_sort(collect);
+    num = sk_TLS_GROUP_IX_num(collect);
+    for (ix = 0; ix < num; ++ix) {
+        gix = sk_TLS_GROUP_IX_value(collect, ix);
+        if (!all && gix->grp->group_id == id)
+            continue;
+        id = gix->grp->group_id;
+        if (sk_OPENSSL_CSTRING_push(out, gix->grp->tlsname) <= 0)
+            goto end;
+    }
+    return 1;
+
+  end:
+    sk_TLS_GROUP_IX_pop_free(collect, free_wrapper);
+    return ret;
+}
+
 /*-
  * For nmatch >= 0, return the id of the |nmatch|th shared group or 0
  * if there is no match.
index faed3e74d58fea9e64370208db0af5ecb7240509..26b87fcbf3c9f0595503e3933aa7ea2e2da3ea95 100644 (file)
@@ -4938,9 +4938,12 @@ static int test_ciphersuite_change(void)
  * Test 13 = Test MLKEM512
  * Test 14 = Test MLKEM768
  * Test 15 = Test MLKEM1024
- * Test 16 = Test all ML-KEM with TLSv1.2 client and server
- * Test 17 = Test all FFDHE with TLSv1.2 client and server
- * Test 18 = Test all ECDHE with TLSv1.2 client and server
+ * Test 16 = Test X25519MLKEM768
+ * Test 17 = Test SecP256r1MLKEM768
+ * Test 18 = Test SecP384r1MLKEM1024
+ * Test 19 = Test all ML-KEM with TLSv1.2 client and server
+ * Test 20 = Test all FFDHE with TLSv1.2 client and server
+ * Test 21 = Test all ECDHE with TLSv1.2 client and server
  */
 # ifndef OPENSSL_NO_EC
 static int ecdhe_kexch_groups[] = {NID_X9_62_prime256v1, NID_secp384r1,
@@ -4970,7 +4973,7 @@ static int test_key_exchange(int idx)
     switch (idx) {
 # ifndef OPENSSL_NO_EC
 # ifndef OPENSSL_NO_TLS1_2
-        case 18:
+        case 21:
             max_version = TLS1_2_VERSION;
 # endif
             /* Fall through */
@@ -5008,7 +5011,7 @@ static int test_key_exchange(int idx)
 # endif
 # ifndef OPENSSL_NO_DH
 # ifndef OPENSSL_NO_TLS1_2
-        case 17:
+        case 20:
             max_version = TLS1_2_VERSION;
             kexch_name0 = "ffdhe2048";
 # endif
@@ -5041,7 +5044,7 @@ static int test_key_exchange(int idx)
 # endif
 # ifndef OPENSSL_NO_ML_KEM
 #  if !defined(OPENSSL_NO_TLS1_2)
-        case 16:
+        case 19:
             max_version = TLS1_2_VERSION;
 #   if !defined(OPENSSL_NO_EC)
             /* Set at least one EC group so the handshake completes */
@@ -5083,6 +5086,25 @@ static int test_key_exchange(int idx)
             kexch_name0 = "MLKEM1024";
             kexch_names = kexch_name0;
             break;
+#  ifndef OPENSSL_NO_EC
+#   ifndef OPENSSL_NO_ECX
+        case 16:
+            kexch_groups = NULL;
+            kexch_name0 = "X25519MLKEM768";
+            kexch_names = kexch_name0;
+            break;
+#   endif
+        case 17:
+            kexch_groups = NULL;
+            kexch_name0 = "SecP256r1MLKEM768";
+            kexch_names = kexch_name0;
+            break;
+        case 18:
+            kexch_groups = NULL;
+            kexch_name0 = "SecP384r1MLKEM1024";
+            kexch_names = kexch_name0;
+            break;
+#  endif
 # endif
         default:
             /* We're skipping this test */
@@ -5090,7 +5112,7 @@ static int test_key_exchange(int idx)
     }
 
     /* ML-KEM not yet supported in the FIPS module */
-    if (is_fips && idx >= 12 && idx <= 16) {
+    if (is_fips && idx >= 12 && idx <= 19) {
         testresult = 1;
         goto end;
     };
@@ -5144,13 +5166,14 @@ static int test_key_exchange(int idx)
         goto end;
 
     /*
-     * If Handshake succeeds the negotiated kexch alg should be the first one in
-     * configured, except in the case of FFDHE and ML-KEM groups (idx == 17, 18),
-     * which are TLSv1.3 only so we expect no shared group to exist.
+     * If the handshake succeeds the negotiated kexch alg should be the first
+     * one in configured, except in the case of "all" FFDHE and "all" ML-KEM
+     * groups (idx == 19, 20), which are TLSv1.3 only so we expect no shared
+     * group to exist.
      */
     shared_group0 = SSL_get_shared_group(serverssl, 0);
     switch (idx) {
-    case 16:
+    case 19:
 # if !defined(OPENSSL_NO_EC)
         /* MLKEM + TLS 1.2 and no DH => "secp526r1" */
         if (!TEST_int_eq(shared_group0, NID_X9_62_prime256v1))
@@ -5158,7 +5181,7 @@ static int test_key_exchange(int idx)
         break;
 # endif
         /* Fall through */
-    case 17:
+    case 20:
         if (!TEST_int_eq(shared_group0, 0))
             goto end;
         break;
@@ -12961,7 +12984,7 @@ int setup_tests(void)
 #endif /* OSSL_NO_USABLE_TLS1_3 */
 # ifndef OPENSSL_NO_TLS1_2
     /* Test with both TLSv1.3 and 1.2 versions */
-    ADD_ALL_TESTS(test_key_exchange, 18);
+    ADD_ALL_TESTS(test_key_exchange, 21);
 #  if !defined(OPENSSL_NO_EC) && !defined(OPENSSL_NO_DH)
     ADD_ALL_TESTS(test_negotiated_group,
                   4 * (OSSL_NELEM(ecdhe_kexch_groups)
@@ -12969,7 +12992,7 @@ int setup_tests(void)
 #  endif
 # else
     /* Test with only TLSv1.3 versions */
-    ADD_ALL_TESTS(test_key_exchange, 15);
+    ADD_ALL_TESTS(test_key_exchange, 18);
 # endif
     ADD_ALL_TESTS(test_custom_exts, 6);
     ADD_TEST(test_stateless);
index 896592875a78efc478c7973dbe2e0388e6c6f269..5ae03f4e1a81ca93d133b8d5164130e775049c50 100644 (file)
@@ -542,6 +542,7 @@ SSL_CTX_disable_ct                      define
 SSL_CTX_generate_session_ticket_fn      define
 SSL_CTX_get0_chain_certs                define
 SSL_CTX_get0_chain_cert_store           define
+SSL_CTX_get0_implemented_groups         define
 SSL_CTX_get0_verify_cert_store          define
 SSL_CTX_get_default_read_ahead          define
 SSL_CTX_get_extra_chain_certs           define