diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h index 038a483de3..9454d346f0 100644 --- a/include/mbedtls/mbedtls_config.h +++ b/include/mbedtls/mbedtls_config.h @@ -796,6 +796,28 @@ */ #define MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +/** + * \def MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 + * + * Enable support for X25519MLKEM768 hybrid post-quantum key exchange + * in TLS 1.3. Requires liboqs to be linked. + */ +#define MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 + +/** + * \def MBEDTLS_SSL_TLS1_3_SIG_MLDSA65 + * + * Enable pure ML-DSA-65 signatures (TLS \c SignatureScheme \c 0x0905, + * X.509 OID from RFC 9881 id-ml-dsa-65) for TLS 1.3 and X.509. Verification + * uses liboqs (OQS_SIG_alg_ml_dsa_65). TLS CertificateVerify uses pure ML-DSA + * (message is the verify structure, not a pre-hash). + * + * Requires: \c MBEDTLS_SSL_PROTO_TLS1_3, X.509, and liboqs. + * + * Comment out to omit ML-DSA-65 signature support from the build. + */ +#define MBEDTLS_SSL_TLS1_3_SIG_MLDSA65 + /** * \def MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED * diff --git a/include/mbedtls/oid.h b/include/mbedtls/oid.h index d769ff2180..bc8938ac37 100644 --- a/include/mbedtls/oid.h +++ b/include/mbedtls/oid.h @@ -265,6 +265,12 @@ * ecdsa-with-SHA2(3) 4 } */ #define MBEDTLS_OID_ECDSA_SHA512 MBEDTLS_OID_ANSI_X9_62_SIG_SHA2 "\x04" +/* + * id-ml-dsa-65 (RFC 9881): { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistAlgorithm(4) mldsa(3) 18 } + * Pure ML-DSA-65 for X.509. Enabled with \c MBEDTLS_SSL_TLS1_3_SIG_MLDSA65. + */ +#define MBEDTLS_OID_MLDSA65 MBEDTLS_OID_NIST_ALG "\x03\x12" + #if defined(MBEDTLS_X509_USE_C) /** * \brief Translate an ASN.1 OID into its numeric representation diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index 00e28442d6..b66cd9caf2 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -237,6 +237,8 @@ #define MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE4096 0x0102 #define MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE6144 0x0103 #define MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE8192 0x0104 +/** X25519 + ML-KEM-768 hybrid (draft-ietf-tls-ecdhe-mlkem) */ +#define MBEDTLS_SSL_IANA_TLS_GROUP_X25519MLKEM768 0x11EC /* * TLS 1.3 Key Exchange Modes @@ -500,6 +502,9 @@ #define MBEDTLS_TLS1_3_SIG_ED25519 0x0807 #define MBEDTLS_TLS1_3_SIG_ED448 0x0808 +/* Pure ML-DSA-65 (FIPS 204 Level 3), per draft-ietf-tls-mldsa. */ +#define MBEDTLS_TLS1_3_SIG_MLDSA65 0x0905 + /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ #define MBEDTLS_TLS1_3_SIG_RSA_PSS_PSS_SHA256 0x0809 #define MBEDTLS_TLS1_3_SIG_RSA_PSS_PSS_SHA384 0x080A diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 8a0e7ab1f8..037b504379 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -24,6 +24,7 @@ set(src_tls ssl_cookie.c ssl_debug_helpers_generated.c ssl_msg.c + ssl_hybrid_tls.c ssl_ticket.c ssl_tls.c ssl_tls12_client.c diff --git a/library/ssl_client.c b/library/ssl_client.c index 354fe9b72b..4a07e4a106 100644 --- a/library/ssl_client.c +++ b/library/ssl_client.c @@ -264,6 +264,11 @@ static int ssl_write_supported_groups_ext(mbedtls_ssl_context *ssl, if (mbedtls_ssl_tls13_named_group_is_ffdh(*group_list)) { propose_group = 1; } +#endif +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(*group_list)) { + propose_group = 1; + } #endif } #endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED */ diff --git a/library/ssl_hybrid_tls.c b/library/ssl_hybrid_tls.c new file mode 100644 index 0000000000..2d6fac445e --- /dev/null +++ b/library/ssl_hybrid_tls.c @@ -0,0 +1,263 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +/* + * TLS 1.3 hybrid post-quantum key exchange: X25519 + ML-KEM-768 + * + * Wire format (client key share, 1216 bytes total): + * ML-KEM-768 ek : 1184 bytes + * X25519 public key : 32 bytes + * + * Wire format (server key share, 1120 bytes total): + * ML-KEM-768 ciphertext : 1088 bytes + * X25519 public key : 32 bytes + * + * Combined shared secret (64 bytes) fed into TLS key schedule: + * ML-KEM shared secret (32) || X25519 shared secret (32) + * + * Reference: draft-ietf-tls-ecdhe-mlkem-04 + * IANA codepoint: 0x11EC + */ + +#include "ssl_misc.h" + +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + +#include +#include "mbedtls/error.h" +#include "mbedtls/platform.h" +#include "mbedtls/psa_util.h" +#include "debug_internal.h" +#include "psa/crypto.h" +#include "ssl_tls13_hybrid_kem.h" + +/* Suppress typedef-redefinition warnings from liboqs headers (Clang only). */ +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtypedef-redefinition" +#endif +#include +#include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +/* Local PSA→mbedtls error translation, same pattern as ssl_tls13_generic.c */ +static int local_err_translation(psa_status_t status) +{ + return psa_status_to_mbedtls(status, psa_to_ssl_errors, + ARRAY_LENGTH(psa_to_ssl_errors), + psa_generic_status_to_mbedtls); +} +#define PSA_TO_MBEDTLS_ERR(status) local_err_translation(status) + +/* + * Client side: generate X25519 + ML-KEM-768 keypair, write key share. + * + * Writes MLKEM768_ek (1184) || X25519_pub (32) = 1216 bytes into buf + * (caller wraps this in KeyShareEntry with length prefix). + * Stores X25519 private key handle in handshake->xxdh_psa_privkey (PSA). + * Stores ML-KEM-768 secret key in handshake->mlkem_sk until decapsulation. + */ +int mbedtls_ssl_tls13_hybrid_x25519mlkem768_write_client_share( + mbedtls_ssl_context *ssl, + unsigned char *buf, + unsigned char *end, + size_t *out_len) +{ + psa_status_t status = PSA_ERROR_GENERIC_ERROR; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + size_t x25519_pub_len = 0; + OQS_STATUS oqs_ret; + mbedtls_ssl_handshake_params *handshake = ssl->handshake; + + *out_len = 0; + + MBEDTLS_SSL_CHK_BUF_PTR(buf, end, MBEDTLS_X25519MLKEM768_CLIENT_SHARE_LEN); + + /* --- Part 1: ML-KEM-768 keypair via liboqs --- */ + /* Wire format: MLKEM ek (1184) || X25519 pub (32) per draft-ietf-tls-ecdhe-mlkem-04 */ + /* ek (public key) goes into first 1184 bytes of buf */ + /* dk (secret key) is stored in handshake struct for later decaps */ + oqs_ret = OQS_KEM_ml_kem_768_keypair( + buf, + handshake->mlkem_sk); + + if (oqs_ret != OQS_SUCCESS) { + MBEDTLS_SSL_DEBUG_MSG(1, ("OQS_KEM_ml_kem_768_keypair failed")); + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + + MBEDTLS_SSL_DEBUG_BUF(4, "ML-KEM-768 ek", buf, MBEDTLS_MLKEM768_PUBLIC_KEY_LEN); + + /* --- Part 2: X25519 keypair via PSA --- */ + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDH); + psa_set_key_type(&key_attributes, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY)); + psa_set_key_bits(&key_attributes, MBEDTLS_X25519_KEY_BITS); + + status = psa_generate_key(&key_attributes, &handshake->xxdh_psa_privkey); + if (status != PSA_SUCCESS) { + ret = PSA_TO_MBEDTLS_ERR(status); + MBEDTLS_SSL_DEBUG_RET(1, "psa_generate_key (X25519)", ret); + return ret; + } + + /* Export X25519 public key into last 32 bytes of buf (after ML-KEM ek) */ + status = psa_export_public_key(handshake->xxdh_psa_privkey, + buf + MBEDTLS_MLKEM768_PUBLIC_KEY_LEN, + MBEDTLS_X25519_PUBLIC_KEY_LEN, + &x25519_pub_len); + if (status != PSA_SUCCESS) { + ret = PSA_TO_MBEDTLS_ERR(status); + MBEDTLS_SSL_DEBUG_RET(1, "psa_export_public_key (X25519)", ret); + return ret; + } + + if (x25519_pub_len != MBEDTLS_X25519_PUBLIC_KEY_LEN) { + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + + MBEDTLS_SSL_DEBUG_BUF(4, "X25519 public key", + buf + MBEDTLS_MLKEM768_PUBLIC_KEY_LEN, + MBEDTLS_X25519_PUBLIC_KEY_LEN); + + *out_len = MBEDTLS_X25519MLKEM768_CLIENT_SHARE_LEN; + return 0; +} + +/* + * Client side: process server key share, produce combined shared secret. + * + * Server KeyShareEntry: opaque key_exchange<1..2^16-1> = + * uint16 length (1120) || MLKEM768_ct (1088) || X25519_pub (32) + * (buf points at the start of key_exchange, i.e. length || payload). + * + * Produces combined_secret = MLKEM_ss (32) || X25519_ss (32) = 64 bytes + * stored in handshake->xxdh_psa_peerkey for the TLS 1.3 key schedule. + * Order per draft-ietf-tls-ecdhe-mlkem-04 Section 4.3. + * + * The X25519 private key (xxdh_psa_privkey) is destroyed here after use. + * The ML-KEM secret key (mlkem_sk) is wiped here after decapsulation. + * ssl_tls13_keys.c detects xxdh_psa_privkey == INIT and uses peerkey + * directly as the pre-computed shared secret input to HKDF. + */ +int mbedtls_ssl_tls13_hybrid_x25519mlkem768_read_server_share( + mbedtls_ssl_context *ssl, + const unsigned char *buf, + size_t buf_len) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + psa_status_t status; + OQS_STATUS oqs_ret; + mbedtls_ssl_handshake_params *handshake = ssl->handshake; + + uint8_t x25519_ss[MBEDTLS_X25519_SHARED_SECRET_LEN]; + uint8_t mlkem_ss[MBEDTLS_MLKEM768_SHARED_SECRET_LEN]; + size_t x25519_ss_len = 0; + + uint16_t ke_payload_len; + const unsigned char *payload; + + /* RFC 8446 KeyShareEntry: NamedGroup || opaque key_exchange<1..2^16-1> */ + if (buf_len < MBEDTLS_SSL_KEY_SHARE_ENTRY_LEN_SIZE + MBEDTLS_X25519MLKEM768_SERVER_SHARE_LEN) { + MBEDTLS_SSL_DEBUG_MSG(1, ("server key share too short: %u < %u", + (unsigned) buf_len, + (unsigned) (MBEDTLS_SSL_KEY_SHARE_ENTRY_LEN_SIZE + MBEDTLS_X25519MLKEM768_SERVER_SHARE_LEN))); + MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_HANDSHAKE_FAILURE, + MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE); + return MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE; + } + + ke_payload_len = MBEDTLS_GET_UINT16_BE(buf, 0); + if (ke_payload_len != MBEDTLS_X25519MLKEM768_SERVER_SHARE_LEN) { + MBEDTLS_SSL_DEBUG_MSG(1, ("bad hybrid server key_exchange length: %u != %u", + (unsigned) ke_payload_len, + (unsigned) MBEDTLS_X25519MLKEM768_SERVER_SHARE_LEN)); + MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_ILLEGAL_PARAMETER, + MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER); + return MBEDTLS_ERR_SSL_ILLEGAL_PARAMETER; + } + + payload = buf + MBEDTLS_SSL_KEY_SHARE_ENTRY_LEN_SIZE; + /* + * draft-ietf-tls-ecdhe-mlkem-04 §4.2: server share is + * ML-KEM ciphertext (1088) || X25519 server public key (32). + */ + MBEDTLS_SSL_DEBUG_BUF(4, "Server KeyShareEntry payload (MLKEM768_ct || X25519_pub)", + payload, MBEDTLS_X25519MLKEM768_SERVER_SHARE_LEN); + + const unsigned char *mlkem_ct = payload; + const unsigned char *x25519_peer_pub = payload + MBEDTLS_MLKEM768_CIPHERTEXT_LEN; + + MBEDTLS_SSL_DEBUG_BUF(4, "Server X25519 public key (from payload)", + x25519_peer_pub, MBEDTLS_X25519_PUBLIC_KEY_LEN); + + /* --- Part 1: ML-KEM-768 decapsulation --- */ + oqs_ret = OQS_KEM_ml_kem_768_decaps( + mlkem_ss, + mlkem_ct, + handshake->mlkem_sk); + + /* Wipe the ML-KEM secret key from the handshake struct immediately */ + mbedtls_platform_zeroize(handshake->mlkem_sk, sizeof(handshake->mlkem_sk)); + + if (oqs_ret != OQS_SUCCESS) { + MBEDTLS_SSL_DEBUG_MSG(1, ("OQS_KEM_ml_kem_768_decaps failed")); + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + + MBEDTLS_SSL_DEBUG_BUF(4, "ML-KEM-768 shared secret", + mlkem_ss, MBEDTLS_MLKEM768_SHARED_SECRET_LEN); + + /* --- Part 2: X25519 key agreement --- */ + status = psa_raw_key_agreement( + PSA_ALG_ECDH, + handshake->xxdh_psa_privkey, /* our X25519 private key (PSA handle) */ + x25519_peer_pub, /* server's X25519 public key (32 bytes) */ + MBEDTLS_X25519_PUBLIC_KEY_LEN, + x25519_ss, + sizeof(x25519_ss), + &x25519_ss_len); + + /* Destroy the ephemeral X25519 private key immediately after use */ + psa_destroy_key(handshake->xxdh_psa_privkey); + handshake->xxdh_psa_privkey = MBEDTLS_SVC_KEY_ID_INIT; + + if (status != PSA_SUCCESS) { + ret = PSA_TO_MBEDTLS_ERR(status); + MBEDTLS_SSL_DEBUG_RET(1, "psa_raw_key_agreement (X25519)", ret); + return ret; + } + + MBEDTLS_SSL_DEBUG_BUF(4, "X25519 shared secret", x25519_ss, x25519_ss_len); + + /* --- Part 3: concatenate for TLS 1.3 key schedule (IKM to HKDF-Extract) --- */ + if (sizeof(handshake->xxdh_psa_peerkey) < MBEDTLS_X25519MLKEM768_SHARED_SECRET_LEN) { + return MBEDTLS_ERR_SSL_INTERNAL_ERROR; + } + + /* IKM for HKDF: ML-KEM SS || X25519 SS (draft-ietf-tls-ecdhe-mlkem-04 §4.3) */ + memcpy(handshake->xxdh_psa_peerkey, + mlkem_ss, MBEDTLS_MLKEM768_SHARED_SECRET_LEN); + memcpy(handshake->xxdh_psa_peerkey + MBEDTLS_MLKEM768_SHARED_SECRET_LEN, + x25519_ss, MBEDTLS_X25519_SHARED_SECRET_LEN); + handshake->xxdh_psa_peerkey_len = MBEDTLS_X25519MLKEM768_SHARED_SECRET_LEN; + + /* Wipe local copies of both secrets */ + mbedtls_platform_zeroize(x25519_ss, sizeof(x25519_ss)); + mbedtls_platform_zeroize(mlkem_ss, sizeof(mlkem_ss)); + + MBEDTLS_SSL_DEBUG_BUF(4, "Combined shared secret (MLKEM||X25519)", + handshake->xxdh_psa_peerkey, + handshake->xxdh_psa_peerkey_len); + + return 0; +} + +#endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 */ diff --git a/library/ssl_misc.h b/library/ssl_misc.h index f0ca823f33..3a363da517 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -25,6 +25,7 @@ extern const mbedtls_error_pair_t psa_to_ssl_errors[7]; #include "mbedtls/pk.h" #include "ssl_ciphersuites_internal.h" +#include "ssl_tls13_hybrid_kem.h" #include "x509_internal.h" /* Shorthand for restartable ECC */ @@ -762,6 +763,11 @@ struct mbedtls_ssl_handshake_params { size_t xxdh_psa_peerkey_len; #endif /* MBEDTLS_KEY_EXCHANGE_SOME_XXDH_PSA_ANY_ENABLED */ +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + /** ML-KEM-768 secret key (liboqs) between client key share and decapsulation */ + uint8_t mlkem_sk[MBEDTLS_MLKEM768_SECRET_KEY_LEN]; +#endif + #if defined(MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED) psa_pake_operation_t psa_pake_ctx; /*!< EC J-PAKE key exchange */ mbedtls_svc_key_id_t psa_pake_password; @@ -2227,6 +2233,13 @@ static inline int mbedtls_ssl_tls13_named_group_is_ffdh(uint16_t named_group) named_group <= MBEDTLS_SSL_IANA_TLS_GROUP_FFDHE8192; } +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) +static inline int mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(uint16_t named_group) +{ + return named_group == MBEDTLS_SSL_IANA_TLS_GROUP_X25519MLKEM768; +} +#endif + static inline int mbedtls_ssl_named_group_is_offered( const mbedtls_ssl_context *ssl, uint16_t named_group) { @@ -2260,7 +2273,13 @@ static inline int mbedtls_ssl_named_group_is_supported(uint16_t named_group) return 1; } #endif -#if !defined(PSA_WANT_ALG_ECDH) && !defined(PSA_WANT_ALG_FFDH) +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(named_group)) { + return 1; + } +#endif +#if !defined(PSA_WANT_ALG_ECDH) && !defined(PSA_WANT_ALG_FFDH) && \ + !defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) (void) named_group; #endif return 0; @@ -2333,6 +2352,10 @@ static inline int mbedtls_ssl_tls13_sig_alg_for_cert_verify_is_supported( break; #endif /* PSA_WANT_ALG_SHA_512 */ #endif /* PSA_WANT_ALG_RSA_PSS */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + case MBEDTLS_TLS1_3_SIG_MLDSA65: + break; +#endif default: return 0; } @@ -2418,6 +2441,12 @@ static inline int mbedtls_ssl_get_pk_sigalg_and_md_alg_from_sig_alg( break; #endif /* PSA_WANT_ALG_SHA_512 */ #endif /* PSA_WANT_ALG_RSA_PSS */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + case MBEDTLS_TLS1_3_SIG_MLDSA65: + *md_alg = MBEDTLS_MD_NONE; + *pk_type = MBEDTLS_PK_MLDSA65; + return 0; +#endif default: return MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE; } diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 504853a2ee..4fc36d4d98 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -2392,6 +2392,9 @@ void mbedtls_ssl_conf_sig_algs(mbedtls_ssl_config *conf, * about this list. */ static const uint16_t ssl_preset_default_groups[] = { +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + MBEDTLS_SSL_IANA_TLS_GROUP_X25519MLKEM768, +#endif #if defined(PSA_WANT_ECC_MONTGOMERY_255) MBEDTLS_SSL_IANA_TLS_GROUP_X25519, #endif @@ -4552,6 +4555,10 @@ void mbedtls_ssl_handshake_free(mbedtls_ssl_context *ssl) } #endif /* MBEDTLS_KEY_EXCHANGE_SOME_XXDH_PSA_ANY_ENABLED */ +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + mbedtls_platform_zeroize(handshake->mlkem_sk, sizeof(handshake->mlkem_sk)); +#endif + #if defined(MBEDTLS_SSL_PROTO_TLS1_3) mbedtls_ssl_transform_free(handshake->transform_handshake); mbedtls_free(handshake->transform_handshake); @@ -5324,6 +5331,10 @@ static const uint16_t ssl_preset_default_sig_algs[] = { MBEDTLS_TLS1_3_SIG_RSA_PKCS1_SHA256, #endif /* PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC && PSA_WANT_ALG_SHA_256 */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + MBEDTLS_TLS1_3_SIG_MLDSA65, +#endif + MBEDTLS_TLS_SIG_NONE }; diff --git a/library/ssl_tls13_client.c b/library/ssl_tls13_client.c index e8b6063f32..9d45f3fa19 100644 --- a/library/ssl_tls13_client.c +++ b/library/ssl_tls13_client.c @@ -20,6 +20,10 @@ #include "ssl_debug_helpers.h" #include "mbedtls/psa_util.h" +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) +#include "ssl_tls13_hybrid_kem.h" +#endif + #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED) /* Define a local translating function to save code size by not using too many * arguments in each translating place. */ @@ -180,6 +184,24 @@ static int ssl_tls13_reset_key_share(mbedtls_ssl_context *ssl) } #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED) +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(group_id)) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; + + status = psa_destroy_key(ssl->handshake->xxdh_psa_privkey); + if (status != PSA_SUCCESS) { + ret = PSA_TO_MBEDTLS_ERR(status); + MBEDTLS_SSL_DEBUG_RET(1, "psa_destroy_key", ret); + return ret; + } + + ssl->handshake->xxdh_psa_privkey = MBEDTLS_SVC_KEY_ID_INIT; + mbedtls_platform_zeroize(ssl->handshake->mlkem_sk, + sizeof(ssl->handshake->mlkem_sk)); + return 0; + } else +#endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 */ if (mbedtls_ssl_tls13_named_group_is_ecdhe(group_id) || mbedtls_ssl_tls13_named_group_is_ffdh(group_id)) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; @@ -223,6 +245,12 @@ static int ssl_tls13_get_default_group_id(mbedtls_ssl_context *ssl, } for (; *group_list != 0; group_list++) { +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(*group_list)) { + *group_id = *group_list; + return 0; + } +#endif #if defined(PSA_WANT_ALG_ECDH) if ((mbedtls_ssl_get_psa_curve_info_from_tls_id( *group_list, NULL, NULL) == PSA_SUCCESS) && @@ -286,7 +314,11 @@ static int ssl_tls13_write_key_share_ext(mbedtls_ssl_context *ssl, /* HRR could already have requested something else. */ group_id = ssl->handshake->offered_group_id; if (!mbedtls_ssl_tls13_named_group_is_ecdhe(group_id) && - !mbedtls_ssl_tls13_named_group_is_ffdh(group_id)) { + !mbedtls_ssl_tls13_named_group_is_ffdh(group_id) +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + && !mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(group_id) +#endif + ) { MBEDTLS_SSL_PROC_CHK(ssl_tls13_get_default_group_id(ssl, &group_id)); } @@ -300,6 +332,25 @@ static int ssl_tls13_write_key_share_ext(mbedtls_ssl_context *ssl, * only one key share entry is allowed. */ client_shares = p; +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(group_id)) { + unsigned char *group = p; + size_t key_exchange_len = 0; + + MBEDTLS_SSL_CHK_BUF_PTR(p, end, 4); + p += 4; + ret = mbedtls_ssl_tls13_hybrid_x25519mlkem768_write_client_share( + ssl, p, end, &key_exchange_len); + p += key_exchange_len; + if (ret != 0) { + MBEDTLS_SSL_DEBUG_MSG(1, ("client hello: failed hybrid X25519MLKEM768 key share")); + return ret; + } + + MBEDTLS_PUT_UINT16_BE(group_id, group, 0); + MBEDTLS_PUT_UINT16_BE(key_exchange_len, group, 2); + } else +#endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 */ #if defined(PSA_WANT_ALG_ECDH) || defined(PSA_WANT_ALG_FFDH) if (mbedtls_ssl_tls13_named_group_is_ecdhe(group_id) || mbedtls_ssl_tls13_named_group_is_ffdh(group_id)) { @@ -407,6 +458,12 @@ static int ssl_tls13_parse_hrr_key_share_ext(mbedtls_ssl_context *ssl, if (*group_list != selected_group) { continue; } +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(*group_list)) { + found = 1; + break; + } +#endif #if defined(PSA_WANT_ALG_ECDH) if (mbedtls_ssl_tls13_named_group_is_ecdhe(*group_list)) { if (mbedtls_ssl_get_psa_curve_info_from_tls_id( @@ -494,6 +551,17 @@ static int ssl_tls13_parse_key_share_ext(mbedtls_ssl_context *ssl, } #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED) +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768(group)) { + MBEDTLS_SSL_DEBUG_MSG(2, + ("DHE group name: %s", mbedtls_ssl_named_group_to_str(group))); + ret = mbedtls_ssl_tls13_hybrid_x25519mlkem768_read_server_share( + ssl, p, end - p); + if (ret != 0) { + return ret; + } + } else +#endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 */ if (mbedtls_ssl_tls13_named_group_is_ecdhe(group) || mbedtls_ssl_tls13_named_group_is_ffdh(group)) { MBEDTLS_SSL_DEBUG_MSG(2, diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index abb8b4f3e4..8b1e5a4c3e 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -13,6 +13,7 @@ #include "mbedtls/error.h" #include "debug_internal.h" +#include "mbedtls/md.h" #include "mbedtls/oid.h" #include "mbedtls/platform.h" #include "mbedtls/constant_time.h" @@ -26,6 +27,10 @@ #include "psa/crypto.h" +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) +#include "mbedtls/mldsa65_oqs.h" +#endif + #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED) /* Define a local translating function to save code size by not using too many * arguments in each translating place. */ @@ -305,9 +310,14 @@ static int ssl_tls13_parse_certificate_verify(mbedtls_ssl_context *ssl, goto error; } - hash_alg = mbedtls_md_psa_alg_from_type(md_alg); - if (hash_alg == 0) { - goto error; +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + if (sig_alg != MBEDTLS_PK_SIGALG_MLDSA65) +#endif + { + hash_alg = mbedtls_md_psa_alg_from_type(md_alg); + if (hash_alg == 0) { + goto error; + } } MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate Verify: Signature algorithm ( %04x )", @@ -316,6 +326,15 @@ static int ssl_tls13_parse_certificate_verify(mbedtls_ssl_context *ssl, /* * Check the certificate's key type matches the signature alg */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + if (sig_alg == MBEDTLS_PK_SIGALG_MLDSA65) { + if (mbedtls_pk_get_type(&ssl->session_negotiate->peer_cert->pk) != + MBEDTLS_PK_MLDSA65) { + MBEDTLS_SSL_DEBUG_MSG(1, ("signature algorithm doesn't match cert key")); + goto error; + } + } else +#endif if (!mbedtls_pk_can_do_psa(&ssl->session_negotiate->peer_cert->pk, mbedtls_psa_alg_from_pk_sigalg(sig_alg, hash_alg), PSA_KEY_USAGE_VERIFY_HASH)) { @@ -328,6 +347,22 @@ static int ssl_tls13_parse_certificate_verify(mbedtls_ssl_context *ssl, p += 2; MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, signature_len); + +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + if (sig_alg == MBEDTLS_PK_SIGALG_MLDSA65) { + /* Pure ML-DSA: sign the verify structure, not a hash of it. */ + ret = mbedtls_pk_mldsa65_oqs_verify( + &ssl->session_negotiate->peer_cert->pk, + verify_buffer, verify_buffer_len, + p, signature_len); + if (ret == 0) { + return 0; + } + MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_pk_mldsa65_oqs_verify", ret); + goto error; + } +#endif + status = psa_hash_compute(hash_alg, verify_buffer, verify_buffer_len, @@ -873,6 +908,11 @@ cleanup: int mbedtls_ssl_tls13_check_sig_alg_cert_key_match(uint16_t sig_alg, mbedtls_pk_context *key) { +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) + if (sig_alg == MBEDTLS_TLS1_3_SIG_MLDSA65) { + return mbedtls_pk_get_type(key) == MBEDTLS_PK_MLDSA65; + } +#endif mbedtls_pk_type_t pk_type = (mbedtls_pk_type_t) mbedtls_ssl_sig_from_pk(key); size_t key_size = mbedtls_pk_get_bitlen(key); diff --git a/library/ssl_tls13_hybrid_kem.h b/library/ssl_tls13_hybrid_kem.h new file mode 100644 index 0000000000..75bb66206b --- /dev/null +++ b/library/ssl_tls13_hybrid_kem.h @@ -0,0 +1,44 @@ +/* + * TLS 1.3 hybrid post-quantum key exchange: X25519MLKEM768 sizes and helpers. + * + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ +#ifndef MBEDTLS_SSL_TLS13_HYBRID_KEM_H +#define MBEDTLS_SSL_TLS13_HYBRID_KEM_H +#include "mbedtls/ssl.h" +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + +/* Key and secret sizes for the X25519MLKEM768 hybrid group + * (draft-ietf-tls-ecdhe-mlkem-04, IANA codepoint 0x11EC). */ +#define MBEDTLS_X25519_KEY_BITS 255 +#define MBEDTLS_X25519_PUBLIC_KEY_LEN 32 +#define MBEDTLS_X25519_SHARED_SECRET_LEN 32 +#define MBEDTLS_MLKEM768_PUBLIC_KEY_LEN 1184 /* ek */ +#define MBEDTLS_MLKEM768_SECRET_KEY_LEN 2400 /* dk */ +#define MBEDTLS_MLKEM768_CIPHERTEXT_LEN 1088 +#define MBEDTLS_MLKEM768_SHARED_SECRET_LEN 32 + +/* Client key share: ML-KEM-768 ek || X25519 public key (1216 bytes). */ +#define MBEDTLS_X25519MLKEM768_CLIENT_SHARE_LEN \ + (MBEDTLS_MLKEM768_PUBLIC_KEY_LEN + MBEDTLS_X25519_PUBLIC_KEY_LEN) + +/* Server key share: ML-KEM-768 ciphertext || X25519 public key (1120 bytes). */ +#define MBEDTLS_X25519MLKEM768_SERVER_SHARE_LEN \ + (MBEDTLS_MLKEM768_CIPHERTEXT_LEN + MBEDTLS_X25519_PUBLIC_KEY_LEN) + +/* IKM for the TLS 1.3 key schedule: ML-KEM ss || X25519 ss (64 bytes). */ +#define MBEDTLS_X25519MLKEM768_SHARED_SECRET_LEN \ + (MBEDTLS_MLKEM768_SHARED_SECRET_LEN + MBEDTLS_X25519_SHARED_SECRET_LEN) + +/* RFC 8446 KeyShareEntry: uint16 length prefix ahead of opaque key_exchange. */ +#define MBEDTLS_SSL_KEY_SHARE_ENTRY_LEN_SIZE 2 + +int mbedtls_ssl_tls13_hybrid_x25519mlkem768_write_client_share( + mbedtls_ssl_context *ssl, unsigned char *buf, unsigned char *end, + size_t *out_len); +int mbedtls_ssl_tls13_hybrid_x25519mlkem768_read_server_share( + mbedtls_ssl_context *ssl, const unsigned char *buf, size_t buf_len); +#endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 */ +#endif /* MBEDTLS_SSL_TLS13_HYBRID_KEM_H */ diff --git a/library/ssl_tls13_keys.c b/library/ssl_tls13_keys.c index 865e02c2dc..28170e612c 100644 --- a/library/ssl_tls13_keys.c +++ b/library/ssl_tls13_keys.c @@ -1473,6 +1473,19 @@ static int ssl_tls13_key_schedule_stage_handshake(mbedtls_ssl_context *ssl) handshake->xxdh_psa_privkey = MBEDTLS_SVC_KEY_ID_INIT; #endif /* PSA_WANT_ALG_ECDH || PSA_WANT_ALG_FFDH */ +#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768) + } else if (mbedtls_ssl_tls13_named_group_is_hybrid_x25519mlkem768( + handshake->offered_group_id)) { + shared_secret_len = handshake->xxdh_psa_peerkey_len; + shared_secret = mbedtls_calloc(1, shared_secret_len); + if (shared_secret == NULL) { + return MBEDTLS_ERR_SSL_ALLOC_FAILED; + } + memcpy(shared_secret, handshake->xxdh_psa_peerkey, shared_secret_len); + mbedtls_platform_zeroize(handshake->xxdh_psa_peerkey, + sizeof(handshake->xxdh_psa_peerkey)); + handshake->xxdh_psa_peerkey_len = 0; +#endif /* MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_GROUP_X25519MLKEM768 */ } else { MBEDTLS_SSL_DEBUG_MSG(1, ("Group not supported.")); return MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE; diff --git a/library/x509_crt.c b/library/x509_crt.c index a6fcd1b4e6..6ef56d4f0c 100644 --- a/library/x509_crt.c +++ b/library/x509_crt.c @@ -232,6 +232,16 @@ static int x509_profile_check_key(const mbedtls_x509_crt_profile *profile, } #endif /* PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) && \ + defined(MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS) + if (pk_alg == MBEDTLS_PK_MLDSA65) { + if (mbedtls_pk_get_bitlen(pk) > 0) { + return 0; + } + return -1; + } +#endif + return -1; } @@ -2018,6 +2028,8 @@ static int x509_crt_verifycrl(mbedtls_x509_crt *crt, mbedtls_x509_crt *ca, unsigned char hash[MBEDTLS_MD_MAX_SIZE]; psa_algorithm_t psa_algorithm; size_t hash_length; + const unsigned char *verify_input; + size_t verify_input_len; if (ca == NULL) { return flags; @@ -2050,16 +2062,23 @@ static int x509_crt_verifycrl(mbedtls_x509_crt *crt, mbedtls_x509_crt *ca, flags |= MBEDTLS_X509_BADCRL_BAD_PK; } - psa_algorithm = mbedtls_md_psa_alg_from_type(crl_list->sig_md); - if (psa_hash_compute(psa_algorithm, - crl_list->tbs.p, - crl_list->tbs.len, - hash, - sizeof(hash), - &hash_length) != PSA_SUCCESS) { - /* Note: this can't happen except after an internal error */ - flags |= MBEDTLS_X509_BADCRL_NOT_TRUSTED; - break; + if (crl_list->sig_pk == MBEDTLS_PK_SIGALG_MLDSA65) { + verify_input = crl_list->tbs.p; + verify_input_len = crl_list->tbs.len; + } else { + psa_algorithm = mbedtls_md_psa_alg_from_type(crl_list->sig_md); + if (psa_hash_compute(psa_algorithm, + crl_list->tbs.p, + crl_list->tbs.len, + hash, + sizeof(hash), + &hash_length) != PSA_SUCCESS) { + /* Note: this can't happen except after an internal error */ + flags |= MBEDTLS_X509_BADCRL_NOT_TRUSTED; + break; + } + verify_input = hash; + verify_input_len = hash_length; } if (x509_profile_check_key(profile, &ca->pk) != 0) { @@ -2067,7 +2086,7 @@ static int x509_crt_verifycrl(mbedtls_x509_crt *crt, mbedtls_x509_crt *ca, } if (mbedtls_pk_verify_ext(crl_list->sig_pk, &ca->pk, - crl_list->sig_md, hash, hash_length, + crl_list->sig_md, verify_input, verify_input_len, crl_list->sig.p, crl_list->sig.len) != 0) { flags |= MBEDTLS_X509_BADCRL_NOT_TRUSTED; break; @@ -2112,30 +2131,42 @@ static int x509_crt_check_signature(const mbedtls_x509_crt *child, { size_t hash_len; unsigned char hash[MBEDTLS_MD_MAX_SIZE]; - psa_algorithm_t hash_alg = mbedtls_md_psa_alg_from_type(child->sig_md); + psa_algorithm_t hash_alg; psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; + const unsigned char *verify_input; + size_t verify_input_len; - /* Skip expensive computation on obvious mismatch */ - if (!mbedtls_pk_can_do_psa(&parent->pk, - mbedtls_psa_alg_from_pk_sigalg(child->sig_pk, hash_alg), - PSA_KEY_USAGE_VERIFY_HASH)) { - return -1; - } + if (child->sig_pk == MBEDTLS_PK_SIGALG_MLDSA65) { + /* Pure ML-DSA: verify over the raw TBS, no pre-hash. */ + verify_input = child->tbs.p; + verify_input_len = child->tbs.len; + } else { + hash_alg = mbedtls_md_psa_alg_from_type(child->sig_md); - status = psa_hash_compute(hash_alg, - child->tbs.p, - child->tbs.len, - hash, - sizeof(hash), - &hash_len); - if (status != PSA_SUCCESS) { - return MBEDTLS_ERR_PLATFORM_HW_ACCEL_FAILED; + /* Skip expensive computation on obvious mismatch */ + if (!mbedtls_pk_can_do_psa(&parent->pk, + mbedtls_psa_alg_from_pk_sigalg(child->sig_pk, hash_alg), + PSA_KEY_USAGE_VERIFY_HASH)) { + return -1; + } + + status = psa_hash_compute(hash_alg, + child->tbs.p, + child->tbs.len, + hash, + sizeof(hash), + &hash_len); + if (status != PSA_SUCCESS) { + return MBEDTLS_ERR_PLATFORM_HW_ACCEL_FAILED; + } + verify_input = hash; + verify_input_len = hash_len; } #if defined(MBEDTLS_ECP_RESTARTABLE) if (rs_ctx != NULL && child->sig_pk == MBEDTLS_PK_SIGALG_ECDSA) { return mbedtls_pk_verify_restartable(&parent->pk, - child->sig_md, hash, hash_len, + child->sig_md, verify_input, verify_input_len, child->sig.p, child->sig.len, &rs_ctx->pk); } #else @@ -2143,7 +2174,7 @@ static int x509_crt_check_signature(const mbedtls_x509_crt *child, #endif return mbedtls_pk_verify_ext(child->sig_pk, &parent->pk, - child->sig_md, hash, hash_len, + child->sig_md, verify_input, verify_input_len, child->sig.p, child->sig.len); } diff --git a/library/x509_oid.c b/library/x509_oid.c index 8c67cdfa1c..6053f531c6 100644 --- a/library/x509_oid.c +++ b/library/x509_oid.c @@ -472,6 +472,13 @@ static const oid_sig_alg_t oid_sig_alg[] = MBEDTLS_MD_NONE, MBEDTLS_PK_SIGALG_RSA_PSS, }, #endif /* PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) && defined(PSA_WANT_ALG_SHA_512) + { + OID_DESCRIPTOR(MBEDTLS_OID_MLDSA65, "id-ml-dsa-65", "ML-DSA-65"), + /* TBSCertificate is digested with SHA-512 before ML-DSA verify (X.509). */ + MBEDTLS_MD_SHA512, MBEDTLS_PK_SIGALG_MLDSA65, + }, +#endif { NULL_OID_DESCRIPTOR, MBEDTLS_MD_NONE, MBEDTLS_PK_SIGALG_NONE, diff --git a/tf-psa-crypto/extras/mldsa65_oqs.c b/tf-psa-crypto/extras/mldsa65_oqs.c new file mode 100644 index 0000000000..13e1ab5f71 --- /dev/null +++ b/tf-psa-crypto/extras/mldsa65_oqs.c @@ -0,0 +1,92 @@ +/* + * ML-DSA-65 (pure) signature verification using liboqs. + * + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#include "tf_psa_crypto_common.h" + +/* Requires the ML-DSA-65 integration to be enabled — this TU depends on + * liboqs headers, which are absent when CONFIG_LIBOQS_ENABLED=n (e.g. the + * classical-TLS baseline build). */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) && \ + (defined(MBEDTLS_X509_USE_C) || defined(MBEDTLS_SSL_PROTO_TLS1_3)) + +#define MBEDTLS_ALLOW_PRIVATE_ACCESS +#include "mbedtls/pk.h" +#include "pk_internal.h" + +#include "mbedtls/mldsa65_oqs.h" + +#include "mbedtls/private/error_common.h" +#include "mbedtls/platform.h" + +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtypedef-redefinition" +#endif +#include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include "psa/crypto.h" + +int mbedtls_pk_mldsa65_oqs_verify(mbedtls_pk_context *pk, + const unsigned char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len) +{ + OQS_SIG *mldsa; + OQS_STATUS status; + + if (pk == NULL || (msg == NULL && msg_len != 0) || sig == NULL) { + return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + } + + if (pk->pub_raw_len != MBEDTLS_MLDSA65_PUBLIC_KEY_LEN) { + return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + } + + if (sig_len != MBEDTLS_MLDSA65_SIGNATURE_LEN) { + return MBEDTLS_ERR_PK_BAD_INPUT_DATA; + } + + + mldsa = OQS_SIG_new(OQS_SIG_alg_ml_dsa_65); + if (mldsa == NULL) { + return MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE; + } + + status = OQS_SIG_verify(mldsa, msg, msg_len, sig, sig_len, pk->pub_raw); + OQS_SIG_free(mldsa); + + if (status != OQS_SUCCESS) { + mbedtls_printf("mldsa65_oqs: OQS_SIG_verify failed\n"); + } + + return (status == OQS_SUCCESS) ? 0 : MBEDTLS_ERR_PK_INVALID_ALG; +} + +#else /* MBEDTLS_SSL_TLS1_3_SIG_MLDSA65 && (MBEDTLS_X509_USE_C || MBEDTLS_SSL_PROTO_TLS1_3) */ + +#include "mbedtls/pk.h" +#include "mbedtls/private/error_common.h" +#include "mbedtls/mldsa65_oqs.h" + +int mbedtls_pk_mldsa65_oqs_verify(mbedtls_pk_context *pk, + const unsigned char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len) +{ + (void) pk; + (void) msg; + (void) msg_len; + (void) sig; + (void) sig_len; + return MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE; +} + +#endif /* MBEDTLS_SSL_TLS1_3_SIG_MLDSA65 && (MBEDTLS_X509_USE_C || MBEDTLS_SSL_PROTO_TLS1_3) */ diff --git a/tf-psa-crypto/extras/pk.c b/tf-psa-crypto/extras/pk.c index 99d1fa4aeb..4d4909cb43 100644 --- a/tf-psa-crypto/extras/pk.c +++ b/tf-psa-crypto/extras/pk.c @@ -116,6 +116,8 @@ const mbedtls_pk_info_t *mbedtls_pk_info_from_type(mbedtls_pk_type_t pk_type) case MBEDTLS_PK_ECDSA: return &mbedtls_ecdsa_info; #endif /* PSA_HAVE_ALG_SOME_ECDSA */ + case MBEDTLS_PK_MLDSA65: + return &mbedtls_mldsa65_info; default: return NULL; } @@ -1219,6 +1221,13 @@ int mbedtls_pk_verify_ext(mbedtls_pk_sigalg_t type, return MBEDTLS_ERR_PK_BAD_INPUT_DATA; } + if (type == MBEDTLS_PK_SIGALG_MLDSA65) { + if (mbedtls_pk_get_type(ctx) != MBEDTLS_PK_MLDSA65) { + return MBEDTLS_ERR_PK_TYPE_MISMATCH; + } + return mbedtls_pk_verify(ctx, md_alg, hash, hash_len, sig, sig_len); + } + if (!mbedtls_pk_can_do(ctx, (mbedtls_pk_type_t) type)) { return MBEDTLS_ERR_PK_TYPE_MISMATCH; } diff --git a/tf-psa-crypto/extras/pk_wrap.c b/tf-psa-crypto/extras/pk_wrap.c index 8e4c773877..4391b47e58 100644 --- a/tf-psa-crypto/extras/pk_wrap.c +++ b/tf-psa-crypto/extras/pk_wrap.c @@ -664,4 +664,36 @@ const mbedtls_pk_info_t mbedtls_rsa_opaque_info = { #endif /* MBEDTLS_ECP_RESTARTABLE */ }; +#include "mbedtls/mldsa65_oqs.h" + +static int mldsa65_can_do(mbedtls_pk_type_t type) +{ + return type == MBEDTLS_PK_MLDSA65; +} + +static int mldsa65_verify_wrap(mbedtls_pk_context *pk, mbedtls_md_type_t md_alg, + const unsigned char *hash, size_t hash_len, + const unsigned char *sig, size_t sig_len) +{ + (void) md_alg; + /* Verify exactly the byte string provided by the caller. + * TLS 1.3 passes the full verify structure; X.509 passes the selected + * TBSCertificate/CRL verification input from the X.509 verify path. */ + return mbedtls_pk_mldsa65_oqs_verify(pk, hash, hash_len, sig, sig_len); +} + +const mbedtls_pk_info_t mbedtls_mldsa65_info = { + .type = MBEDTLS_PK_MLDSA65, + .name = "MLDSA65", + .can_do = mldsa65_can_do, + .verify_func = mldsa65_verify_wrap, + .sign_func = NULL, +#if defined(MBEDTLS_ECP_RESTARTABLE) + .verify_rs_func = NULL, + .sign_rs_func = NULL, + .rs_alloc_func = NULL, + .rs_free_func = NULL, +#endif /* MBEDTLS_ECP_RESTARTABLE */ +}; + #endif /* MBEDTLS_PK_C */ diff --git a/tf-psa-crypto/extras/pk_wrap.h b/tf-psa-crypto/extras/pk_wrap.h index ce92ada9ae..288d7c373e 100644 --- a/tf-psa-crypto/extras/pk_wrap.h +++ b/tf-psa-crypto/extras/pk_wrap.h @@ -87,6 +87,8 @@ extern const mbedtls_pk_info_t mbedtls_eckeydh_info; extern const mbedtls_pk_info_t mbedtls_ecdsa_info; #endif +extern const mbedtls_pk_info_t mbedtls_mldsa65_info; + extern const mbedtls_pk_info_t mbedtls_ecdsa_opaque_info; extern const mbedtls_pk_info_t mbedtls_rsa_opaque_info; diff --git a/tf-psa-crypto/extras/pkparse.c b/tf-psa-crypto/extras/pkparse.c index ae3ef58364..fae748beba 100644 --- a/tf-psa-crypto/extras/pkparse.c +++ b/tf-psa-crypto/extras/pkparse.c @@ -20,6 +20,7 @@ #include "mbedtls/private/error_common.h" #include "mbedtls/private/ecp.h" #include "pk_internal.h" +#include "mbedtls/mldsa65_oqs.h" #include @@ -497,6 +498,12 @@ static int pk_get_pk_alg(unsigned char **p, return MBEDTLS_ERR_PK_INVALID_ALG; } + if (*pk_alg == MBEDTLS_PK_MLDSA65 && + ((params->tag != MBEDTLS_ASN1_NULL && params->tag != 0) || + params->len != 0)) { + return MBEDTLS_ERR_PK_INVALID_ALG; + } + return 0; } @@ -574,7 +581,22 @@ int mbedtls_pk_parse_subpubkey(unsigned char **p, const unsigned char *end, } } else #endif /* PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY */ - ret = MBEDTLS_ERR_PK_UNKNOWN_PK_ALG; + if (pk_alg == MBEDTLS_PK_MLDSA65) { + if (len != MBEDTLS_MLDSA65_PUBLIC_KEY_LEN) { + ret = MBEDTLS_ERR_PK_INVALID_PUBKEY; + } else if (len > MBEDTLS_PK_MAX_PUBKEY_RAW_LEN) { + ret = MBEDTLS_ERR_PK_BUFFER_TOO_SMALL; + } else { + memcpy(pk->pub_raw, *p, len); + pk->pub_raw_len = len; + pk->bits = len * 8; + *p += len; + ret = 0; + } + } else + { + ret = MBEDTLS_ERR_PK_UNKNOWN_PK_ALG; + } if (ret == 0 && *p != end) { ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PK_INVALID_PUBKEY, diff --git a/tf-psa-crypto/include/mbedtls/mldsa65_oqs.h b/tf-psa-crypto/include/mbedtls/mldsa65_oqs.h new file mode 100644 index 0000000000..47f2598c4d --- /dev/null +++ b/tf-psa-crypto/include/mbedtls/mldsa65_oqs.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ +#ifndef MBEDTLS_MLDSA65_OQS_H +#define MBEDTLS_MLDSA65_OQS_H + +#include "tf-psa-crypto/build_info.h" + +#include "mbedtls/pk.h" + +#include + +/** ML-DSA-65 raw public key size in bytes (FIPS 204, RFC 9881). */ +#define MBEDTLS_MLDSA65_PUBLIC_KEY_LEN 1952 + +/** ML-DSA-65 signature size in bytes (FIPS 204, RFC 9881). */ +#define MBEDTLS_MLDSA65_SIGNATURE_LEN 3309 + +/** + * \brief Verify a pure ML-DSA-65 signature (liboqs). + * + * \param msg Message that was signed (e.g. TLS 1.3 verify structure, or + * digest for X.509 depending on the caller). + * \param msg_len Length of \p msg. + */ +int mbedtls_pk_mldsa65_oqs_verify(mbedtls_pk_context *pk, + const unsigned char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len); + +#endif /* MBEDTLS_MLDSA65_OQS_H */ diff --git a/tf-psa-crypto/include/mbedtls/pk.h b/tf-psa-crypto/include/mbedtls/pk.h index 3eb4dc2d26..1d5c4290b2 100644 --- a/tf-psa-crypto/include/mbedtls/pk.h +++ b/tf-psa-crypto/include/mbedtls/pk.h @@ -51,6 +51,8 @@ typedef enum { MBEDTLS_PK_SIGALG_RSA_PKCS1V15, // PSA_ALG_RSA_PKCS1V15_SIGN MBEDTLS_PK_SIGALG_RSA_PSS, // PSA_ALG_RSA_PSS_ANY_SALT MBEDTLS_PK_SIGALG_ECDSA, // MBEDTLS_PK_ALG_ECDSA + /** ML-DSA-65 (FIPS 204, RFC 9881); must match \c MBEDTLS_PK_MLDSA65 */ + MBEDTLS_PK_SIGALG_MLDSA65 = 7, } mbedtls_pk_sigalg_t; /** @@ -100,6 +102,20 @@ typedef struct mbedtls_pk_info_t mbedtls_pk_info_t; #define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN MBEDTLS_PK_MAX_RSA_PUBKEY_RAW_LEN #endif +/* PQC pubkey headroom, only when ML-DSA-65 is enabled since pub_raw is inline in every mbedtls_pk_context and some live on the stack; 1952 is the FIPS 204 raw key length pkparse.c copies in, spelled out because mldsa65_oqs.h includes this header. */ +#if defined(MBEDTLS_SSL_TLS1_3_SIG_MLDSA65) +#define MBEDTLS_PK_PQC_MAX_PUBKEY_RAW_LEN 1952 +#if MBEDTLS_PK_PQC_MAX_PUBKEY_RAW_LEN > MBEDTLS_PK_MAX_PUBKEY_RAW_LEN +#undef MBEDTLS_PK_MAX_PUBKEY_RAW_LEN +#define MBEDTLS_PK_MAX_PUBKEY_RAW_LEN MBEDTLS_PK_PQC_MAX_PUBKEY_RAW_LEN +#endif +#endif /* MBEDTLS_SSL_TLS1_3_SIG_MLDSA65 */ + +typedef enum { + MBEDTLS_PK_RSA_PKCS_V15 = 0, + MBEDTLS_PK_RSA_PKCS_V21, +} mbedtls_pk_rsa_padding_t; + /** * \brief Public key container */ diff --git a/tf-psa-crypto/include/mbedtls/private/pk_private.h b/tf-psa-crypto/include/mbedtls/private/pk_private.h index f704857b5e..bccf0d79c4 100644 --- a/tf-psa-crypto/include/mbedtls/private/pk_private.h +++ b/tf-psa-crypto/include/mbedtls/private/pk_private.h @@ -26,6 +26,7 @@ typedef enum { MBEDTLS_PK_ECKEY, MBEDTLS_PK_ECKEY_DH, MBEDTLS_PK_OPAQUE, + MBEDTLS_PK_MLDSA65 = 7, } mbedtls_pk_type_t; /** diff --git a/tf-psa-crypto/utilities/crypto_oid.h b/tf-psa-crypto/utilities/crypto_oid.h index ce5beba988..8151abb98e 100644 --- a/tf-psa-crypto/utilities/crypto_oid.h +++ b/tf-psa-crypto/utilities/crypto_oid.h @@ -160,6 +160,9 @@ #define MBEDTLS_OID_PKCS5_PBKDF2 MBEDTLS_OID_PKCS5 "\x0c" /**< id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12} */ #define MBEDTLS_OID_PKCS5_PBES2 MBEDTLS_OID_PKCS5 "\x0d" /**< id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13} */ +/** id-ml-dsa-65 (RFC 9881) */ +#define MBEDTLS_OID_MLDSA65 MBEDTLS_OID_NIST_ALG "\x03\x12" /**< id-ml-dsa-65 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistAlgorithm(4) sigAlgs(3) 18 } */ + /* * EC key algorithms from RFC 5480 */ diff --git a/tf-psa-crypto/utilities/oid.c b/tf-psa-crypto/utilities/oid.c index 934e5cdf59..110ee04e03 100644 --- a/tf-psa-crypto/utilities/oid.c +++ b/tf-psa-crypto/utilities/oid.c @@ -132,6 +132,10 @@ static const oid_pk_alg_t oid_pk_alg[] = OID_DESCRIPTOR(MBEDTLS_OID_EC_ALG_ECDH, "id-ecDH", "EC key for ECDH"), MBEDTLS_PK_ECKEY_DH, }, + { + OID_DESCRIPTOR(MBEDTLS_OID_MLDSA65, "id-ml-dsa-65", "ML-DSA-65"), + MBEDTLS_PK_MLDSA65, + }, { NULL_OID_DESCRIPTOR, MBEDTLS_PK_NONE,