feat(pqc): hybrid PQC TLS 1.3 — X25519MLKEM768 + ML-DSA-65

Add hybrid post-quantum TLS 1.3 support, baselined on 582ff4820:
- X25519MLKEM768 hybrid key exchange (IANA group 0x11EC)
- pure ML-DSA-65 authentication via liboqs (TLS sig scheme 0x0905,
  X.509 id-ml-dsa-65, RFC 9881) — new library/mldsa65_oqs.{c,h}
- hybrid KEM glue: library/ssl_hybrid_tls.c, ssl_tls13_hybrid_kem.h
- OID / PK / TLS 1.3 wiring across ssl_tls13_*, x509_crt, pk, oid,
  tf-psa-crypto builtin drivers

ML-DSA-65 verify is guarded on MBEDTLS_SSL_TLS1_3_SIG_MLDSA65 so the tree
also builds with liboqs disabled (classical X25519+ECDSA baseline).
This commit is contained in:
Aditya Patwardhan
2026-08-11 12:01:09 +05:30
committed by Mahavir Jain
parent ce3f3485a1
commit 3deef6a6ef
24 changed files with 792 additions and 34 deletions
+22
View File
@@ -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
*
+6
View File
@@ -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
+5
View File
@@ -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
+1
View File
@@ -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
+5
View File
@@ -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 */
+263
View File
@@ -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 <string.h>
#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 <oqs/oqsconfig.h>
#include <oqs/kem_ml_kem.h>
#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 */
+30 -1
View File
@@ -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;
}
+11
View File
@@ -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
};
+69 -1
View File
@@ -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,
+43 -3
View File
@@ -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);
+44
View File
@@ -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 */
+13
View File
@@ -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;
+59 -28
View File
@@ -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);
}
+7
View File
@@ -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,
+92
View File
@@ -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 <string.h>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtypedef-redefinition"
#endif
#include <oqs/sig.h>
#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) */
+9
View File
@@ -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;
}
+32
View File
@@ -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 */
+2
View File
@@ -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;
+23 -1
View File
@@ -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 <string.h>
@@ -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,
@@ -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 <stddef.h>
/** 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 */
+16
View File
@@ -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
*/
@@ -26,6 +26,7 @@ typedef enum {
MBEDTLS_PK_ECKEY,
MBEDTLS_PK_ECKEY_DH,
MBEDTLS_PK_OPAQUE,
MBEDTLS_PK_MLDSA65 = 7,
} mbedtls_pk_type_t;
/**
+3
View File
@@ -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
*/
+4
View File
@@ -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,