Skip to content

Commit a00d389

Browse files
authored
ML-DSA support as a TLS 1.3 signature scheme (#3251)
Wire up draft-ietf-tls-mldsa (IANA code points 0x0904/0x0905/0x0906 for MLDSA44/65/87) through AWS-LC's libssl, backed by the existing PQDSA EVP_PKEY support in libcrypto. ML-DSA is TLS 1.3 only and is advertised in the default signing and verification sigalg lists; the per-sigalg gate in pkey_supports_algorithm keeps these entries from being selected on earlier protocol versions. - Add SSL_SIGN_MLDSA44/65/87 to the public header. - Add a new SSL_PKEY_PQDSA certificate slot. - Extend SSL_SIGNATURE_ALGORITHM with param_nid (replacing the EC-only curve field) so sigalg rows can constrain both EC curves and ML-DSA parameter sets. - Gate PQDSA to TLS 1.3 in pkey_supports_algorithm, and require the sigalg's param_nid to match EVP_PKEY_pqdsa_get_type(pkey). - Route PQDSA through the existing EVP_DigestSign/Verify path, which dispatches to sign_message/verify_message in pure mode. - Add MLDSA44/65/87 to kVerifySignatureAlgorithms and kSignSignatureAlgorithms so they're negotiated by default. Tests: - Add MLDSA test fixtures (cert + seed private key) from the IETF LAMPS WG examples accompanying draft-ietf-lamps-dilithium-certificates, and parameterized handshake tests covering success, TLS 1.2 rejection, and cross-variant sigalg mismatch. - Regenerate the golden ClientHello byte vectors in ssl_test.cc and bump kKeyShare1Offset by the corresponding 6 bytes. - Relax SSLTest.Padding to skip version/session combinations whose baseline ClientHello is now pushed past the 0xff boundary by the larger sigalg list.
1 parent 9360b0a commit a00d389

9 files changed

Lines changed: 808 additions & 30 deletions

File tree

include/openssl/ssl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,9 @@ OPENSSL_EXPORT int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response,
11491149
#define SSL_SIGN_RSA_PSS_RSAE_SHA384 0x0805
11501150
#define SSL_SIGN_RSA_PSS_RSAE_SHA512 0x0806
11511151
#define SSL_SIGN_ED25519 0x0807
1152+
#define SSL_SIGN_MLDSA44 0x0904
1153+
#define SSL_SIGN_MLDSA65 0x0905
1154+
#define SSL_SIGN_MLDSA87 0x0906
11521155

11531156
// SSL_SIGN_RSA_PKCS1_MD5_SHA1 is an internal signature algorithm used to
11541157
// specify raw RSASSA-PKCS1-v1_5 with an MD5/SHA-1 concatenation, as used in TLS

ssl/extensions.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,14 @@ static const uint16_t kVerifySignatureAlgorithms[] = {
299299
SSL_SIGN_RSA_PSS_RSAE_SHA512,
300300
SSL_SIGN_RSA_PKCS1_SHA512,
301301

302+
// ML-DSA. The codepoints are advertised in all TLS versions, but the
303+
// sigalgs are only selectable in TLS 1.3 (filtered by
304+
// |pkey_supports_algorithm|, and rejected at peer-sigalg-check time by
305+
// |tls12_check_peer_sigalg| to satisfy draft-ietf-tls-mldsa §3.3).
306+
SSL_SIGN_MLDSA44,
307+
SSL_SIGN_MLDSA65,
308+
SSL_SIGN_MLDSA87,
309+
302310
// For now, SHA-1 is still accepted but least preferable.
303311
SSL_SIGN_RSA_PKCS1_SHA1,
304312
};
@@ -323,6 +331,12 @@ static const uint16_t kSignSignatureAlgorithms[] = {
323331
SSL_SIGN_RSA_PSS_RSAE_SHA512,
324332
SSL_SIGN_RSA_PKCS1_SHA512,
325333

334+
// ML-DSA. Selectable for signing only in TLS 1.3; the version gate lives
335+
// in |pkey_supports_algorithm|.
336+
SSL_SIGN_MLDSA44,
337+
SSL_SIGN_MLDSA65,
338+
SSL_SIGN_MLDSA87,
339+
326340
// If the peer supports nothing else, sign with SHA-1.
327341
SSL_SIGN_ECDSA_SHA1,
328342
SSL_SIGN_RSA_PKCS1_SHA1,
@@ -344,8 +358,32 @@ bool tls12_add_verify_sigalgs(const SSL_HANDSHAKE *hs, CBB *out) {
344358
return true;
345359
}
346360

361+
// sigalg_valid_for_protocol_version returns whether |sigalg| is permitted at
362+
// the negotiated TLS protocol version. ML-DSA (draft-ietf-tls-mldsa §3.3) is
363+
// defined only for TLS 1.3; a peer that receives ML-DSA in a TLS 1.2
364+
// ServerKeyExchange or CertificateVerify MUST abort with illegal_parameter.
365+
static bool sigalg_valid_for_protocol_version(const SSL *ssl, uint16_t sigalg) {
366+
switch (sigalg) {
367+
case SSL_SIGN_MLDSA44:
368+
case SSL_SIGN_MLDSA65:
369+
case SSL_SIGN_MLDSA87:
370+
return ssl_protocol_version(ssl) >= TLS1_3_VERSION;
371+
default:
372+
return true;
373+
}
374+
}
375+
347376
bool tls12_check_peer_sigalg(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
348377
uint16_t sigalg) {
378+
// Reject sigalgs that are not defined for the negotiated protocol version,
379+
// even if they appear in the local verify list (we still advertise them so
380+
// a higher version can negotiate them).
381+
if (!sigalg_valid_for_protocol_version(hs->ssl, sigalg)) {
382+
OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
383+
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
384+
return false;
385+
}
386+
349387
for (uint16_t verify_sigalg : tls12_get_verify_sigalgs(hs)) {
350388
if (verify_sigalg == sigalg) {
351389
return true;

ssl/internal.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,8 +1842,8 @@ struct SSL_HANDSHAKE_HINTS {
18421842
};
18431843

18441844
struct SSL_HANDSHAKE {
1845-
explicit SSL_HANDSHAKE(SSL *ssl);
1846-
~SSL_HANDSHAKE();
1845+
OPENSSL_EXPORT explicit SSL_HANDSHAKE(SSL *ssl);
1846+
OPENSSL_EXPORT ~SSL_HANDSHAKE();
18471847
static constexpr bool kAllowUniquePtr = true;
18481848

18491849
// ssl is a non-owning pointer to the parent |SSL| object.
@@ -2203,7 +2203,7 @@ struct SSL_HANDSHAKE {
22032203
// so many tickets.
22042204
constexpr size_t kMaxTickets = 16;
22052205

2206-
UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl);
2206+
OPENSSL_EXPORT UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl);
22072207

22082208
// ssl_check_message_type checks if |msg| has type |type|. If so it returns
22092209
// one. Otherwise, it sends an alert and returns zero.
@@ -2486,8 +2486,9 @@ bool tls12_add_verify_sigalgs(const SSL_HANDSHAKE *hs, CBB *out);
24862486
// tls12_check_peer_sigalg checks if |sigalg| is acceptable for the peer
24872487
// signature. It returns true on success and false on error, setting
24882488
// |*out_alert| to an alert to send.
2489-
bool tls12_check_peer_sigalg(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
2490-
uint16_t sigalg);
2489+
OPENSSL_EXPORT bool tls12_check_peer_sigalg(const SSL_HANDSHAKE *hs,
2490+
uint8_t *out_alert,
2491+
uint16_t sigalg);
24912492

24922493

24932494
// Underdocumented functions.
@@ -2504,7 +2505,8 @@ bool tls12_check_peer_sigalg(const SSL_HANDSHAKE *hs, uint8_t *out_alert,
25042505
#define SSL_PKEY_RSA 0
25052506
#define SSL_PKEY_ECC 1
25062507
#define SSL_PKEY_ED25519 2
2507-
#define SSL_PKEY_SIZE 3
2508+
#define SSL_PKEY_PQDSA 3
2509+
#define SSL_PKEY_SIZE 4
25082510

25092511
struct CERT_PKEY {
25102512
UniquePtr<EVP_PKEY> privatekey;

ssl/ssl_cipher.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,8 @@ int ssl_get_certificate_slot_index(const EVP_PKEY *pkey) {
13281328
return SSL_PKEY_ECC;
13291329
case EVP_PKEY_ED25519:
13301330
return SSL_PKEY_ED25519;
1331+
case EVP_PKEY_PQDSA:
1332+
return SSL_PKEY_PQDSA;
13311333
default:
13321334
return -1;
13331335
}
@@ -1341,6 +1343,11 @@ uint32_t ssl_cipher_auth_mask_for_key(const EVP_PKEY *key) {
13411343
case EVP_PKEY_ED25519:
13421344
// Ed25519 keys in TLS 1.2 repurpose the ECDSA ciphers.
13431345
return SSL_aECDSA;
1346+
case EVP_PKEY_PQDSA:
1347+
// ML-DSA is TLS 1.3 only and is not used for TLS <= 1.2 cipher-auth
1348+
// selection. TLS 1.3 paths gate on |SSL_aGENERIC| in
1349+
// |tls12_pkey_supports_cipher_auth| instead.
1350+
return 0;
13441351
default:
13451352
return 0;
13461353
}

0 commit comments

Comments
 (0)