use super::dtls::{
noxtls_encode_dtls12_handshake_fragments, noxtls_open_dtls13_unified_aes128gcm_record,
noxtls_open_dtls13_unified_aes128gcm_record_with_cid,
noxtls_parse_dtls13_record_header_with_cid_len, noxtls_reassemble_dtls12_handshake_fragments,
noxtls_seal_dtls13_unified_aes128gcm_record_with_cid, DtlsEpochReplayTracker,
};
use super::record::noxtls_build_record_nonce;
use super::{
noxtls_parse_tls13_ocsp_staple_info, noxtls_verify_tls13_ocsp_staple, CipherSuite, Connection,
HandshakeState, ProtectedRecord, RecordContentType, Tls13OcspStapleVerification,
Tls13ServerIdentityKey, TlsRole, TlsVersion,
};
use noxtls_core::Error;
use noxtls_crypto::{
noxtls_ffdhe_public_key, noxtls_p256_ecdsa_sign_sha256,
noxtls_rsa_generate_keypair_secure_auto, noxtls_rsassa_pss_sha256_sign_auto, HmacDrbgSha256,
P256PrivateKey, RsaKeySizePolicy, RsaPrivateKey,
};
use noxtls_x509::{
noxtls_p256_public_key_to_spki_der, noxtls_write_self_signed_certificate_p256_sha256,
noxtls_write_self_signed_certificate_rsa_sha256,
};
const TEST_EXT_KEY_SHARE: u16 = 0x0033;
const TEST_TLS13_KEY_SHARE_GROUP_SECP256R1: u16 = 0x0017;
const TEST_TLS13_KEY_SHARE_GROUP_X25519: u16 = 0x001d;
const TEST_TLS13_SIGALG_ECDSA_SECP256R1_SHA256: u16 = 0x0403;
const TEST_TLS13_SIGALG_RSA_PSS_RSAE_SHA256: u16 = 0x0804;
const TLS12_TEST_CLIENT_RANDOM: [u8; 32] = [0x10; 32];
const TLS12_TEST_SERVER_RANDOM: [u8; 32] = [0x20; 32];
#[must_use]
fn dtls12_test_fragment(
handshake_type: u8,
message_len: u32,
message_seq: u16,
fragment_offset: u32,
fragment_body: &[u8],
) -> Vec<u8> {
const HDR: usize = 12;
let fragment_len = fragment_body.len() as u32;
let mut v = Vec::with_capacity(HDR + fragment_body.len());
v.push(handshake_type);
v.push(((message_len >> 16) & 0xFF) as u8);
v.push(((message_len >> 8) & 0xFF) as u8);
v.push((message_len & 0xFF) as u8);
v.extend_from_slice(&message_seq.to_be_bytes());
v.push(((fragment_offset >> 16) & 0xFF) as u8);
v.push(((fragment_offset >> 8) & 0xFF) as u8);
v.push((fragment_offset & 0xFF) as u8);
v.push(((fragment_len >> 16) & 0xFF) as u8);
v.push(((fragment_len >> 8) & 0xFF) as u8);
v.push((fragment_len & 0xFF) as u8);
v.extend_from_slice(fragment_body);
v
}
#[test]
fn tls13_record_nonce_xor_matches_sequence() {
let base = [0_u8; 12];
let seq = 0x0102_0304_0506_0708_u64;
let nonce = noxtls_build_record_nonce(&base, seq);
let expected = [0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8];
assert_eq!(nonce, expected);
}
fn tls12_finished_connection() -> Connection {
tls12_finished_connection_for_suite(CipherSuite::TlsEcdheRsaWithAes128GcmSha256)
}
fn tls12_finished_connection_for_suite(suite: CipherSuite) -> Connection {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_set_tls13_client_cipher_suites(&[suite])
.expect("client cipher suites should configure");
connection
.noxtls_send_client_hello(&[0xA5; 32])
.expect("client hello should build");
let server_hello = Connection::noxtls_build_server_hello(TlsVersion::Tls12, suite, &[0x5A; 32])
.expect("server hello should build");
connection
.noxtls_recv_server_hello(&server_hello)
.expect("server hello should parse");
let pre_master_secret: Vec<u8> = (0_u8..48).collect();
connection
.noxtls_set_tls12_pre_master_secret(&pre_master_secret)
.expect("pre-master secret should install");
connection
.noxtls_derive_handshake_secret()
.expect("handshake secret should derive");
let verify_data = connection
.noxtls_compute_finished_verify_data()
.expect("finished verify data should compute");
connection
.noxtls_finish(&verify_data)
.expect("connection should finish");
connection
}
#[test]
fn tls12_master_secret_matches_rfc5246_prf_vector() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_send_client_hello(&[0x11; 32])
.expect("client hello should build");
let server_hello = Connection::noxtls_build_server_hello(
TlsVersion::Tls12,
CipherSuite::TlsEcdheRsaWithAes128GcmSha256,
&[0x22; 32],
)
.expect("server hello should build");
connection
.noxtls_recv_server_hello(&server_hello)
.expect("server hello should parse");
let pre_master_secret: Vec<u8> = (0_u8..48).collect();
connection
.noxtls_set_tls12_pre_master_secret(&pre_master_secret)
.expect("pre-master secret should install");
connection
.noxtls_derive_handshake_secret()
.expect("handshake secret should derive");
let expected = [
0xbf, 0x25, 0x51, 0xa8, 0x9a, 0x70, 0x0d, 0x54, 0x08, 0xb0, 0x99, 0xb3, 0xd7, 0x33, 0xfd,
0x2a, 0x9f, 0x44, 0xa5, 0x9f, 0x47, 0xbe, 0xc7, 0x6b, 0x21, 0x9d, 0xe5, 0x79, 0x6d, 0x27,
0x3c, 0x6f, 0x8e, 0x1c, 0xf4, 0x56, 0xa7, 0x68, 0x9d, 0x19, 0x5e, 0x98, 0x94, 0xb1, 0x93,
0x82, 0x2f, 0xc3,
];
assert_eq!(connection.noxtls_tls12_master_secret().unwrap(), expected);
}
#[test]
fn tls13_client_hello_allows_duplicate_signature_algorithms() {
fn ext(out: &mut Vec<u8>, ty: u16, data: &[u8]) {
out.extend_from_slice(&ty.to_be_bytes());
out.extend_from_slice(&(data.len() as u16).to_be_bytes());
out.extend_from_slice(data);
}
let mut extensions = Vec::new();
ext(&mut extensions, 0x002b, &[0x02, 0x03, 0x04]);
ext(
&mut extensions,
0x000d,
&[0x00, 0x06, 0x04, 0x03, 0x08, 0x05, 0x08, 0x05],
);
ext(
&mut extensions,
0x0033,
&[
0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
],
);
let mut body = Vec::new();
body.extend_from_slice(&[0x03, 0x03]);
body.extend_from_slice(&[0x42; 32]);
body.push(0);
body.extend_from_slice(&2_u16.to_be_bytes());
body.extend_from_slice(&0x1302_u16.to_be_bytes());
body.push(1);
body.push(0);
body.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
body.extend_from_slice(&extensions);
let mut client_hello = vec![
0x01,
((body.len() >> 16) & 0xff) as u8,
((body.len() >> 8) & 0xff) as u8,
(body.len() & 0xff) as u8,
];
client_hello.extend_from_slice(&body);
let info = Connection::noxtls_parse_client_hello_info(&client_hello)
.expect("duplicate signature schemes should be deduplicated, not rejected");
assert_eq!(info.extensions.signature_algorithms, vec![0x0403, 0x0805]);
}
#[test]
fn tls13_client_hello_deduplicates_supported_groups_with_bounded_memory() {
fn ext(out: &mut Vec<u8>, ty: u16, data: &[u8]) {
out.extend_from_slice(&ty.to_be_bytes());
out.extend_from_slice(&(data.len() as u16).to_be_bytes());
out.extend_from_slice(data);
}
let mut extensions = Vec::new();
ext(&mut extensions, 0x002b, &[0x02, 0x03, 0x04]);
ext(
&mut extensions,
0x000d,
&[0x00, 0x04, 0x04, 0x03, 0x08, 0x05],
);
ext(
&mut extensions,
0x000a,
&[0x00, 0x06, 0xfe, 0xfe, 0xfe, 0xfe, 0x00, 0x1d],
);
ext(
&mut extensions,
0x0033,
&[
0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
],
);
let mut body = Vec::new();
body.extend_from_slice(&[0x03, 0x03]);
body.extend_from_slice(&[0x24; 32]);
body.push(0);
body.extend_from_slice(&2_u16.to_be_bytes());
body.extend_from_slice(&0x1302_u16.to_be_bytes());
body.push(1);
body.push(0);
body.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
body.extend_from_slice(&extensions);
let mut client_hello = vec![
0x01,
((body.len() >> 16) & 0xff) as u8,
((body.len() >> 8) & 0xff) as u8,
(body.len() & 0xff) as u8,
];
client_hello.extend_from_slice(&body);
let info = Connection::noxtls_parse_client_hello_info(&client_hello)
.expect("supported_groups duplicates should be deduplicated, not rejected");
assert_eq!(info.extensions.supported_groups, vec![0xfefe, 0x001d]);
}
#[test]
fn tls13_server_ignores_malformed_unselected_key_share_entry() {
fn corrupt_key_share_for_group(client_hello: &mut [u8], group: u16) {
let body = &mut client_hello[4..];
let mut offset = 34usize;
let session_id_len = body[offset] as usize;
offset += 1 + session_id_len;
let suites_len = u16::from_be_bytes([body[offset], body[offset + 1]]) as usize;
offset += 2 + suites_len;
let compression_len = body[offset] as usize;
offset += 1 + compression_len;
let extensions_len = u16::from_be_bytes([body[offset], body[offset + 1]]) as usize;
offset += 2;
let mut cursor = offset;
let end = offset + extensions_len;
while cursor < end {
let extension_type = u16::from_be_bytes([body[cursor], body[cursor + 1]]);
let extension_len = u16::from_be_bytes([body[cursor + 2], body[cursor + 3]]) as usize;
cursor += 4;
if extension_type == TEST_EXT_KEY_SHARE {
let list_len = u16::from_be_bytes([body[cursor], body[cursor + 1]]) as usize;
let mut share_cursor = cursor + 2;
let shares_end = share_cursor + list_len;
while share_cursor < shares_end {
let entry_group =
u16::from_be_bytes([body[share_cursor], body[share_cursor + 1]]);
let key_exchange_len =
u16::from_be_bytes([body[share_cursor + 2], body[share_cursor + 3]])
as usize;
share_cursor += 4;
if entry_group == group {
body[share_cursor] = 0x00;
return;
}
share_cursor += key_exchange_len;
}
break;
}
cursor += extension_len;
}
panic!("target key_share group not found");
}
let private_key =
P256PrivateKey::from_bytes([0x10_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let cert_der = noxtls_write_self_signed_certificate_p256_sha256(
&[0x0A],
"localhost",
"20200101000000Z",
"20300101000000Z",
&public_key,
&private_key,
)
.expect("self-signed certificate should build");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
client.noxtls_set_tls13_client_offer_mldsa_signature(false);
let mut client_hello = client
.noxtls_send_client_hello(&[0x23_u8; 32])
.expect("client hello should build");
corrupt_key_share_for_group(&mut client_hello, TEST_TLS13_KEY_SHARE_GROUP_SECP256R1);
let parsed = Connection::noxtls_parse_client_hello_info(&client_hello)
.expect("unused malformed key_share should not fail ClientHello parsing");
assert_eq!(
parsed.extensions.key_share_groups,
vec![
TEST_TLS13_KEY_SHARE_GROUP_X25519,
TEST_TLS13_KEY_SHARE_GROUP_SECP256R1,
]
);
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_identity(
&[cert_der],
Tls13ServerIdentityKey::P256(private_key),
)
.expect("server identity should configure");
server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x24_u8; 32])
.expect("server should accept client hello using the valid x25519 share");
assert_eq!(
server.noxtls_negotiated_key_exchange_group(),
Some(TEST_TLS13_KEY_SHARE_GROUP_X25519)
);
}
#[test]
fn tls13_server_key_exchange_group_preference_rejects_unsupported_group() {
let mut server = Connection::noxtls_new_tls13_server();
let error = server
.noxtls_set_tls13_server_key_exchange_groups(&[0x0201])
.expect_err("pure mlkem768 should not be accepted as a server key_share preference");
match error {
Error::UnsupportedFeature(message) => {
assert_eq!(
message,
"server key_exchange group preference contains an unsupported named group"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_server_key_exchange_group_preference_controls_selection_order() {
let private_key =
P256PrivateKey::from_bytes([0x14_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let cert_der = noxtls_write_self_signed_certificate_p256_sha256(
&[0x0B],
"localhost",
"20200101000000Z",
"20300101000000Z",
&public_key,
&private_key,
)
.expect("self-signed certificate should build");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
client.noxtls_set_tls13_client_offer_mldsa_signature(false);
let client_hello = client
.noxtls_send_client_hello(&[0x25_u8; 32])
.expect("client hello should build");
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_identity(
&[cert_der],
Tls13ServerIdentityKey::P256(private_key),
)
.expect("server identity should configure");
server
.noxtls_set_tls13_server_key_exchange_groups(&[
TEST_TLS13_KEY_SHARE_GROUP_SECP256R1,
TEST_TLS13_KEY_SHARE_GROUP_X25519,
])
.expect("server key_exchange groups should configure");
assert_eq!(
server.noxtls_tls13_server_key_exchange_groups(),
&[
TEST_TLS13_KEY_SHARE_GROUP_SECP256R1,
TEST_TLS13_KEY_SHARE_GROUP_X25519,
]
);
let server_hello = server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x26_u8; 32])
.expect("server should accept client hello");
assert_eq!(
server.noxtls_negotiated_key_exchange_group(),
Some(TEST_TLS13_KEY_SHARE_GROUP_SECP256R1)
);
client
.noxtls_recv_server_hello(&server_hello)
.expect("client should process server hello");
assert_eq!(
client.noxtls_negotiated_key_exchange_group(),
Some(TEST_TLS13_KEY_SHARE_GROUP_SECP256R1)
);
}
#[test]
fn tls13_server_rejects_invalid_client_hello_legacy_version() {
let private_key =
P256PrivateKey::from_bytes([0x11_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let cert_der = noxtls_write_self_signed_certificate_p256_sha256(
&[0x02],
"localhost",
"20200101000000Z",
"20300101000000Z",
&public_key,
&private_key,
)
.expect("self-signed certificate should build");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
let mut client_hello = client
.noxtls_send_client_hello(&[0x44_u8; 32])
.expect("client hello should build");
client_hello[4..6].copy_from_slice(&0x0301_u16.to_be_bytes());
let parsed = Connection::noxtls_parse_client_hello_info(&client_hello)
.expect("mutated client hello still parses");
assert_eq!(parsed.legacy_version, 0x0301);
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_identity(
&[cert_der],
Tls13ServerIdentityKey::P256(private_key),
)
.expect("server identity should configure");
let err = server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x33_u8; 32])
.expect_err("invalid TLS 1.3 ClientHello legacy_version must be rejected");
assert!(err
.to_string()
.contains("tls13 client hello has invalid legacy_version"));
}
#[test]
fn tls12_gcm_packet_carries_explicit_nonce() {
let mut connection = tls12_finished_connection();
let plaintext = b"tls12 explicit nonce payload";
let packet = connection
.noxtls_seal_tls12_record_packet(plaintext, RecordContentType::ApplicationData)
.expect("tls12 packet should seal");
assert_eq!(&packet[..3], &[0x17, 0x03, 0x03]);
let payload_len = u16::from_be_bytes([packet[3], packet[4]]) as usize;
assert_eq!(payload_len, 8 + plaintext.len() + 16);
assert_eq!(&packet[5..13], &0_u64.to_be_bytes());
let (content_type, recovered) = connection
.noxtls_open_own_tls12_record_packet(&packet, 0)
.expect("tls12 packet should open");
assert_eq!(content_type, RecordContentType::ApplicationData);
assert_eq!(recovered, plaintext);
}
#[test]
fn tls12_c_reference_aead_suite_records_roundtrip() {
let suites = [
CipherSuite::TlsEcdheEcdsaWithChacha20Poly1305Sha256,
CipherSuite::TlsDheRsaWithChacha20Poly1305Sha256,
CipherSuite::TlsDheRsaWithAes128GcmSha256,
CipherSuite::TlsDheRsaWithAes256GcmSha384,
CipherSuite::TlsRsaWithAes128GcmSha256,
CipherSuite::TlsRsaWithAes256GcmSha384,
CipherSuite::TlsRsaWithAes128CcmSha256,
CipherSuite::TlsRsaWithAes256CcmSha256,
CipherSuite::TlsRsaWithAes128Ccm8Sha256,
CipherSuite::TlsRsaWithAes256Ccm8Sha256,
CipherSuite::TlsDheRsaWithAes128CcmSha256,
CipherSuite::TlsDheRsaWithAes256CcmSha256,
CipherSuite::TlsDheRsaWithAes128Ccm8Sha256,
CipherSuite::TlsDheRsaWithAes256Ccm8Sha256,
CipherSuite::TlsPskWithAes128Ccm8Sha256,
CipherSuite::TlsEcjpakeWithAes128Ccm8Sha256,
];
for suite in suites {
let mut connection = tls12_finished_connection_for_suite(suite);
let plaintext = b"tls12 c reference suite payload";
let packet = connection
.noxtls_seal_tls12_record_packet(plaintext, RecordContentType::ApplicationData)
.expect("tls12 packet should seal");
let (content_type, recovered) = connection
.noxtls_open_own_tls12_record_packet(&packet, 0)
.expect("tls12 packet should open");
assert_eq!(content_type, RecordContentType::ApplicationData);
assert_eq!(recovered, plaintext);
}
}
fn tls12_handshake_message(message_type: u8, body: &[u8]) -> Vec<u8> {
let mut message = Vec::with_capacity(4 + body.len());
message.push(message_type);
message.push(((body.len() >> 16) & 0xff) as u8);
message.push(((body.len() >> 8) & 0xff) as u8);
message.push((body.len() & 0xff) as u8);
message.extend_from_slice(body);
message
}
fn tls12_p256_test_identity() -> (P256PrivateKey, Vec<u8>) {
let private_key =
P256PrivateKey::from_bytes([0x10_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let cert_der = noxtls_write_self_signed_certificate_p256_sha256(
&[0x0B],
"localhost",
"20200101000000Z",
"20300101000000Z",
&public_key,
&private_key,
)
.expect("self-signed p256 certificate should build");
(private_key, cert_der)
}
fn tls12_rsa_test_identity() -> (RsaPrivateKey, Vec<u8>) {
use std::sync::OnceLock;
static IDENTITY: OnceLock<(RsaPrivateKey, Vec<u8>)> = OnceLock::new();
IDENTITY
.get_or_init(|| {
let mut drbg = HmacDrbgSha256::noxtls_new(&[0x71_u8; 32], &[], &[])
.expect("rsa test drbg should initialize");
let (private_key, public_key) = noxtls_rsa_generate_keypair_secure_auto(
2048,
RsaKeySizePolicy::Minimum2048,
&mut drbg,
)
.expect("rsa test keypair should generate");
let cert_der = noxtls_write_self_signed_certificate_rsa_sha256(
&[0x0C],
"localhost",
"20200101000000Z",
"20300101000000Z",
&public_key,
&private_key,
)
.expect("self-signed rsa certificate should build");
(private_key, cert_der)
})
.clone()
}
fn tls12_certificate_message(cert_der: &[u8]) -> Vec<u8> {
let mut body = Vec::with_capacity(6 + cert_der.len());
let entry_len = 3 + cert_der.len();
body.push(((entry_len >> 16) & 0xff) as u8);
body.push(((entry_len >> 8) & 0xff) as u8);
body.push((entry_len & 0xff) as u8);
body.push(((cert_der.len() >> 16) & 0xff) as u8);
body.push(((cert_der.len() >> 8) & 0xff) as u8);
body.push((cert_der.len() & 0xff) as u8);
body.extend_from_slice(cert_der);
tls12_handshake_message(11, &body)
}
fn tls12_build_server_key_exchange_signed_input(params: &[u8]) -> Vec<u8> {
let mut signed_input = Vec::with_capacity(64 + params.len());
signed_input.extend_from_slice(&TLS12_TEST_CLIENT_RANDOM);
signed_input.extend_from_slice(&TLS12_TEST_SERVER_RANDOM);
signed_input.extend_from_slice(params);
signed_input
}
fn tls12_append_ecdsa_server_key_exchange_signature(
params: &[u8],
private_key: &P256PrivateKey,
) -> Vec<u8> {
let signed_input = tls12_build_server_key_exchange_signed_input(params);
let (r, s) = noxtls_p256_ecdsa_sign_sha256(private_key, &signed_input)
.expect("p256 server key exchange signature should sign");
let mut signature = vec![0_u8; 64];
signature[..32].copy_from_slice(&r);
signature[32..].copy_from_slice(&s);
let mut body = params.to_vec();
body.extend_from_slice(&TEST_TLS13_SIGALG_ECDSA_SECP256R1_SHA256.to_be_bytes());
body.extend_from_slice(&(signature.len() as u16).to_be_bytes());
body.extend_from_slice(&signature);
body
}
fn tls12_append_rsa_pss_server_key_exchange_signature(
params: &[u8],
private_key: &RsaPrivateKey,
) -> Vec<u8> {
let signed_input = tls12_build_server_key_exchange_signed_input(params);
let mut drbg = HmacDrbgSha256::noxtls_new(&TLS12_TEST_SERVER_RANDOM, &[], &[])
.expect("rsa server key exchange test drbg should initialize");
let signature = noxtls_rsassa_pss_sha256_sign_auto(private_key, &signed_input, &mut drbg, 32)
.expect("rsa server key exchange signature should sign");
let mut body = params.to_vec();
body.extend_from_slice(&TEST_TLS13_SIGALG_RSA_PSS_RSAE_SHA256.to_be_bytes());
body.extend_from_slice(&(signature.len() as u16).to_be_bytes());
body.extend_from_slice(&signature);
body
}
fn tls12_suite_uses_signed_dhe_server_key_exchange(suite: CipherSuite) -> bool {
matches!(
suite,
CipherSuite::TlsDheRsaWithChacha20Poly1305Sha256
| CipherSuite::TlsDheRsaWithAes128GcmSha256
| CipherSuite::TlsDheRsaWithAes256GcmSha384
| CipherSuite::TlsDheRsaWithAes128CcmSha256
| CipherSuite::TlsDheRsaWithAes256CcmSha256
| CipherSuite::TlsDheRsaWithAes128Ccm8Sha256
| CipherSuite::TlsDheRsaWithAes256Ccm8Sha256
)
}
fn tls12_suite_uses_signed_ecdhe_server_key_exchange(suite: CipherSuite) -> bool {
matches!(
suite,
CipherSuite::TlsEcdheEcdsaWithAes128GcmSha256
| CipherSuite::TlsEcdheEcdsaWithAes256GcmSha384
| CipherSuite::TlsEcdheEcdsaWithChacha20Poly1305Sha256
| CipherSuite::TlsEcdheRsaWithAes128GcmSha256
| CipherSuite::TlsEcdheRsaWithAes256GcmSha384
| CipherSuite::TlsEcdheRsaWithChacha20Poly1305Sha256
| CipherSuite::TlsEcdheEcdsaWithAes128CcmSha256
| CipherSuite::TlsEcdheEcdsaWithAes256CcmSha256
| CipherSuite::TlsEcdheEcdsaWithAes128Ccm8Sha256
| CipherSuite::TlsEcdheEcdsaWithAes256Ccm8Sha256
)
}
fn tls12_minimal_certificate_message() -> Vec<u8> {
let (_, cert_der) = tls12_p256_test_identity();
tls12_certificate_message(&cert_der)
}
fn tls12_signed_server_key_exchange_body(
suite: CipherSuite,
params: &[u8],
p256_private_key: Option<&P256PrivateKey>,
rsa_private_key: Option<&RsaPrivateKey>,
) -> Vec<u8> {
if tls12_suite_uses_signed_ecdhe_server_key_exchange(suite) {
return tls12_append_ecdsa_server_key_exchange_signature(
params,
p256_private_key.expect("ecdhe server key exchange requires p256 test identity"),
);
}
if tls12_suite_uses_signed_dhe_server_key_exchange(suite) {
return tls12_append_rsa_pss_server_key_exchange_signature(
params,
rsa_private_key.expect("dhe server key exchange requires rsa test identity"),
);
}
params.to_vec()
}
fn tls12_server_hello_done_message() -> Vec<u8> {
tls12_handshake_message(14, &[])
}
fn tls12_finished_message() -> Vec<u8> {
tls12_handshake_message(20, &[0xAA; 12])
}
fn tls12_server_key_exchange_message(body: &[u8]) -> Vec<u8> {
tls12_handshake_message(12, body)
}
fn tls12_client_key_exchange_message(body: &[u8]) -> Vec<u8> {
tls12_handshake_message(16, body)
}
fn tls12_server_connection_after_flight(
suite: CipherSuite,
server_key_exchange: Option<&[u8]>,
) -> Connection {
let (p256_private_key, p256_cert_der) = tls12_p256_test_identity();
let (rsa_private_key, rsa_cert_der) = tls12_rsa_test_identity();
let certificate = if tls12_suite_uses_signed_dhe_server_key_exchange(suite) {
tls12_certificate_message(&rsa_cert_der)
} else {
tls12_certificate_message(&p256_cert_der)
};
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_set_tls13_client_cipher_suites(&[suite])
.expect("client cipher suites should configure");
connection
.noxtls_send_client_hello(&TLS12_TEST_CLIENT_RANDOM)
.expect("client hello should build");
let server_hello =
Connection::noxtls_build_server_hello(TlsVersion::Tls12, suite, &TLS12_TEST_SERVER_RANDOM)
.expect("server hello should build");
let done = tls12_server_hello_done_message();
let mut messages = vec![server_hello, certificate];
if let Some(params) = server_key_exchange {
let body = tls12_signed_server_key_exchange_body(
suite,
params,
Some(&p256_private_key),
Some(&rsa_private_key),
);
messages.push(tls12_server_key_exchange_message(&body));
}
messages.push(done);
connection
.noxtls_process_tls12_server_handshake_flight(&messages)
.expect("tls12 server flight should process");
connection
}
#[test]
fn tls12_suite_specific_server_key_exchange_shapes_are_enforced() {
let ecdhe_params = [0x03, 0x00, 0x17, 0x01, 0x04];
tls12_server_connection_after_flight(
CipherSuite::TlsEcdheEcdsaWithChacha20Poly1305Sha256,
Some(&ecdhe_params),
);
let dhe_params = [0x00, 0x01, 0x17, 0x00, 0x01, 0x05, 0x00, 0x01, 0x11];
tls12_server_connection_after_flight(
CipherSuite::TlsDheRsaWithAes128GcmSha256,
Some(&dhe_params),
);
tls12_server_connection_after_flight(CipherSuite::TlsRsaWithAes128GcmSha256, None);
tls12_server_connection_after_flight(
CipherSuite::TlsPskWithAes128Ccm8Sha256,
Some(&[0x00, 0x00]),
);
tls12_server_connection_after_flight(
CipherSuite::TlsEcjpakeWithAes128Ccm8Sha256,
Some(&[0x01]),
);
}
#[test]
fn tls12_rsa_suite_rejects_unexpected_server_key_exchange() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsRsaWithAes128GcmSha256])
.expect("client cipher suites should configure");
connection
.noxtls_send_client_hello(&TLS12_TEST_CLIENT_RANDOM)
.expect("client hello should build");
let server_hello = Connection::noxtls_build_server_hello(
TlsVersion::Tls12,
CipherSuite::TlsRsaWithAes128GcmSha256,
&TLS12_TEST_SERVER_RANDOM,
)
.expect("server hello should build");
let messages = vec![
server_hello,
tls12_minimal_certificate_message(),
tls12_server_key_exchange_message(&[0x00, 0x00]),
tls12_server_hello_done_message(),
];
let error = connection
.noxtls_process_tls12_server_handshake_flight(&messages)
.expect_err("rsa key transport must not accept server key exchange");
assert!(error.to_string().contains("rsa key transport must omit"));
}
#[test]
fn tls12_suite_specific_client_key_exchange_shapes_are_enforced() {
let cases: &[(CipherSuite, Option<&[u8]>, &[u8])] = &[
(
CipherSuite::TlsRsaWithAes128GcmSha256,
None,
&[0x00, 0x02, 0xAA, 0xBB],
),
(
CipherSuite::TlsEcdheEcdsaWithChacha20Poly1305Sha256,
Some(&[0x03, 0x00, 0x17, 0x01, 0x04]),
&[0x01, 0x04],
),
(
CipherSuite::TlsDheRsaWithAes128GcmSha256,
Some(&[0x00, 0x01, 0x17, 0x00, 0x01, 0x05, 0x00, 0x01, 0x11]),
&[0x00, 0x01, 0x22],
),
(
CipherSuite::TlsPskWithAes128Ccm8Sha256,
Some(&[0x00, 0x00]),
&[0x00, 0x03, b'p', b's', b'k'],
),
(
CipherSuite::TlsEcjpakeWithAes128Ccm8Sha256,
Some(&[0x01]),
&[0x02],
),
];
for (suite, server_key_exchange, client_key_exchange) in cases {
let mut connection = tls12_server_connection_after_flight(*suite, *server_key_exchange);
connection
.noxtls_recv_tls12_change_cipher_spec()
.expect("change cipher spec should be accepted");
let messages = vec![
tls12_client_key_exchange_message(client_key_exchange),
tls12_finished_message(),
];
connection
.noxtls_process_tls12_client_handshake_flight(&messages)
.expect("tls12 client flight should process");
assert_eq!(connection.state, HandshakeState::Finished);
}
}
#[test]
fn tls12_dhe_installs_pre_master_from_parsed_key_exchange_values() {
let prime = [0x17_u8];
let generator = [0x05_u8];
let server_private = [0x06_u8];
let client_private = [0x0F_u8];
let server_public = noxtls_ffdhe_public_key(&server_private, &generator, &prime)
.expect("server public key should compute");
let client_public = noxtls_ffdhe_public_key(&client_private, &generator, &prime)
.expect("client public key should compute");
assert_eq!(server_public, vec![0x08]);
assert_eq!(client_public, vec![0x13]);
let dhe_params = [
0x00,
0x01,
prime[0],
0x00,
0x01,
generator[0],
0x00,
0x01,
server_public[0],
];
let mut connection = tls12_server_connection_after_flight(
CipherSuite::TlsDheRsaWithAes128GcmSha256,
Some(&dhe_params),
);
connection
.noxtls_install_tls12_dhe_pre_master_secret_from_server_key_exchange(&client_private)
.expect("client-side dhe pre-master should install");
let client_master = connection
.noxtls_derive_handshake_secret()
.expect("client-side dhe master should derive");
let mut server_connection = tls12_server_connection_after_flight(
CipherSuite::TlsDheRsaWithAes128GcmSha256,
Some(&dhe_params),
);
server_connection
.noxtls_recv_tls12_change_cipher_spec()
.expect("change cipher spec should be accepted");
server_connection
.noxtls_process_tls12_client_handshake_flight(&[
tls12_client_key_exchange_message(&[0x00, 0x01, client_public[0]]),
tls12_finished_message(),
])
.expect("tls12 client flight should process");
server_connection
.noxtls_install_tls12_dhe_pre_master_secret_from_client_key_exchange(&server_private)
.expect("server-side dhe pre-master should install");
let server_master = server_connection
.noxtls_derive_handshake_secret()
.expect("server-side dhe master should derive");
assert_eq!(client_master, server_master);
}
#[test]
fn tls12_rsa_key_transport_installs_fallback_pre_master_on_decrypt_failure() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsRsaWithAes128GcmSha256])
.expect("client cipher suites should configure");
connection
.noxtls_send_client_hello(&[0xA7; 32])
.expect("client hello should build");
let server_hello = Connection::noxtls_build_server_hello(
TlsVersion::Tls12,
CipherSuite::TlsRsaWithAes128GcmSha256,
&[0x5C; 32],
)
.expect("server hello should build");
connection
.noxtls_recv_server_hello(&server_hello)
.expect("server hello should parse");
let private_key = RsaPrivateKey::from_u128(3233, 2753);
let mut fallback = [0x42_u8; 48];
fallback[0] = 0x03;
fallback[1] = 0x03;
let accepted = connection
.noxtls_set_tls12_rsa_pre_master_secret_from_encrypted(&private_key, &[0x00], &fallback)
.expect("rsa fallback pre-master should install");
assert!(!accepted);
connection
.noxtls_derive_handshake_secret()
.expect("handshake secret should derive");
let verify_data = connection
.noxtls_compute_finished_verify_data()
.expect("finished verify data should compute");
connection
.noxtls_finish(&verify_data)
.expect("connection should finish");
let packet = connection
.noxtls_seal_tls12_record_packet(b"rsa payload", RecordContentType::ApplicationData)
.expect("rsa tls12 packet should seal");
let (_content_type, recovered) = connection
.noxtls_open_own_tls12_record_packet(&packet, 0)
.expect("rsa tls12 packet should open");
assert_eq!(recovered, b"rsa payload");
}
#[test]
fn tls12_rsa_key_transport_installs_pre_master_from_parsed_client_key_exchange() {
let mut connection =
tls12_server_connection_after_flight(CipherSuite::TlsRsaWithAes128GcmSha256, None);
connection
.noxtls_recv_tls12_change_cipher_spec()
.expect("change cipher spec should be accepted");
let messages = vec![
tls12_client_key_exchange_message(&[0x00, 0x02, 0xAA, 0xBB]),
tls12_finished_message(),
];
connection
.noxtls_process_tls12_client_handshake_flight(&messages)
.expect("tls12 client flight should process");
let private_key = RsaPrivateKey::from_u128(3233, 2753);
let mut fallback = [0x24_u8; 48];
fallback[0] = 0x03;
fallback[1] = 0x03;
let accepted = connection
.noxtls_install_tls12_rsa_pre_master_secret_from_client_key_exchange(
&private_key,
&fallback,
)
.expect("parsed rsa pre-master should install fallback");
assert!(!accepted);
connection
.noxtls_derive_handshake_secret()
.expect("handshake secret should derive");
}
#[test]
fn tls12_psk_pre_master_secret_matches_rfc4279_shape() {
let pre_master = Connection::noxtls_build_tls12_psk_pre_master_secret(b"psk")
.expect("psk pre-master should build");
assert_eq!(
pre_master,
vec![0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, b'p', b's', b'k']
);
}
#[test]
fn tls12_psk_suite_derives_record_keys_from_rfc4279_pre_master_secret() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsPskWithAes128Ccm8Sha256])
.expect("client cipher suites should configure");
connection
.noxtls_send_client_hello(&[0xA6; 32])
.expect("client hello should build");
let server_hello = Connection::noxtls_build_server_hello(
TlsVersion::Tls12,
CipherSuite::TlsPskWithAes128Ccm8Sha256,
&[0x5B; 32],
)
.expect("server hello should build");
connection
.noxtls_recv_server_hello(&server_hello)
.expect("server hello should parse");
connection
.noxtls_set_tls12_psk_pre_master_secret(b"psk")
.expect("psk pre-master should install");
connection
.noxtls_derive_handshake_secret()
.expect("handshake secret should derive");
let verify_data = connection
.noxtls_compute_finished_verify_data()
.expect("finished verify data should compute");
connection
.noxtls_finish(&verify_data)
.expect("connection should finish");
let packet = connection
.noxtls_seal_tls12_record_packet(b"psk payload", RecordContentType::ApplicationData)
.expect("psk tls12 packet should seal");
let (_content_type, recovered) = connection
.noxtls_open_own_tls12_record_packet(&packet, 0)
.expect("psk tls12 packet should open");
assert_eq!(recovered, b"psk payload");
}
#[test]
fn tls12_gcm_packet_rejects_missing_explicit_nonce() {
let mut connection = tls12_finished_connection();
let packet = connection
.noxtls_seal_tls12_record_packet(b"x", RecordContentType::ApplicationData)
.expect("tls12 packet should seal");
let mut truncated = packet[..5].to_vec();
truncated.extend_from_slice(&packet[13..]);
let payload_len = (truncated.len() - 5) as u16;
truncated[3..5].copy_from_slice(&payload_len.to_be_bytes());
let error = connection
.noxtls_open_own_tls12_record_packet(&truncated, 0)
.expect_err("missing explicit nonce should fail");
assert_eq!(format!("{error}"), "tls12 record payload too short");
}
#[test]
fn dtls12_reassemble_non_overlapping_round_trip() {
let body = b"hello-dtls-reassembly".to_vec();
let frags = noxtls_encode_dtls12_handshake_fragments(0x01, 0_u16, &body, 7).expect("encode");
let (_, _, got) =
noxtls_reassemble_dtls12_handshake_fragments(&frags, 65_536).expect("reassemble");
assert_eq!(got, body);
}
#[test]
fn dtls12_reassemble_overlapping_last_write_wins() {
let message_len = 4_u32;
let seq = 3_u16;
let f1 = dtls12_test_fragment(0x0B, message_len, seq, 0, b"AA");
let f2 = dtls12_test_fragment(0x0B, message_len, seq, 0, b"BB");
let f3 = dtls12_test_fragment(0x0B, message_len, seq, 2, b"CC");
let got =
noxtls_reassemble_dtls12_handshake_fragments(&[f1, f2, f3], 65_536).expect("reassemble");
assert_eq!(got.2, b"BBCC".as_slice());
}
#[test]
fn tls12_secure_renegotiation_initial_handshake_uses_empty_binding() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
let client_hello = connection
.noxtls_send_client_hello(&[0x51_u8; 32])
.expect("client hello should build");
let parsed =
Connection::noxtls_parse_client_hello_info(&client_hello).expect("client hello parses");
assert_eq!(
parsed.extensions.secure_renegotiation_info,
Some(Vec::new())
);
let server_hello = Connection::noxtls_build_server_hello(
TlsVersion::Tls12,
CipherSuite::TlsEcdheRsaWithAes128GcmSha256,
&[0x52_u8; 32],
)
.expect("server hello should build");
connection
.noxtls_recv_server_hello(&server_hello)
.expect("server hello should accept empty renegotiation_info");
assert!(connection.noxtls_tls12_secure_renegotiation_negotiated());
}
#[test]
fn tls12_secure_renegotiation_rejects_missing_server_extension() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls12);
connection
.noxtls_send_client_hello(&[0x53_u8; 32])
.expect("client hello should build");
let legacy_server_hello = [0x02, 0xC0, 0x2F];
let error = connection
.noxtls_recv_server_hello(&legacy_server_hello)
.expect_err("missing renegotiation_info should fail");
match error {
Error::ParseFailure(message) => {
assert_eq!(
message,
"tls12 server hello missing renegotiation_info extension"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls12_secure_renegotiation_binds_prior_verify_data() {
let mut connection = tls12_finished_connection();
let client_verify = [0xA1_u8; 12];
let server_verify = [0xB2_u8; 12];
connection
.noxtls_set_tls12_secure_renegotiation_verify_data_for_test(&client_verify, &server_verify)
.expect("verify data fixture should install");
let client_hello = connection
.noxtls_start_tls12_secure_renegotiation(&[0x54_u8; 32])
.expect("renegotiation client hello should build");
let parsed =
Connection::noxtls_parse_client_hello_info(&client_hello).expect("client hello parses");
assert_eq!(
parsed.extensions.secure_renegotiation_info,
Some(client_verify.to_vec())
);
let server_hello = Connection::noxtls_build_tls12_server_hello_with_secure_renegotiation(
CipherSuite::TlsEcdheRsaWithAes128GcmSha256,
&[0x55_u8; 32],
&client_verify,
&server_verify,
)
.expect("renegotiating server hello should build");
connection
.noxtls_recv_server_hello(&server_hello)
.expect("renegotiation binding should validate");
}
#[test]
fn tls12_secure_renegotiation_rejects_mismatched_binding() {
let mut connection = tls12_finished_connection();
let client_verify = [0xC1_u8; 12];
let server_verify = [0xD2_u8; 12];
connection
.noxtls_set_tls12_secure_renegotiation_verify_data_for_test(&client_verify, &server_verify)
.expect("verify data fixture should install");
connection
.noxtls_start_tls12_secure_renegotiation(&[0x56_u8; 32])
.expect("renegotiation client hello should build");
let server_hello = Connection::noxtls_build_tls12_server_hello_with_secure_renegotiation(
CipherSuite::TlsEcdheRsaWithAes128GcmSha256,
&[0x57_u8; 32],
&client_verify,
&[0xEE_u8; 12],
)
.expect("renegotiating server hello should build");
let error = connection
.noxtls_recv_server_hello(&server_hello)
.expect_err("mismatched renegotiation binding should fail");
match error {
Error::ParseFailure(message) => {
assert_eq!(
message,
"tls12 server hello renegotiation_info verify_data mismatch"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_client_hello_interop_profile_uses_expected_groups_and_schemes() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
connection.noxtls_set_tls13_client_offer_pq_key_shares(false);
connection.noxtls_set_tls13_client_offer_mldsa_signature(false);
connection
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsAes128GcmSha256])
.expect("set tls13 cipher override");
let client_hello = connection
.noxtls_send_client_hello(&[0x11_u8; 32])
.expect("build client hello");
let parsed =
Connection::noxtls_parse_client_hello_info(&client_hello).expect("parse client hello");
assert_eq!(
parsed.offered_cipher_suites,
vec![CipherSuite::TlsAes128GcmSha256]
);
assert_eq!(parsed.extensions.key_share_groups, vec![0x001D, 0x0017]);
assert!(parsed.extensions.supported_versions.contains(&0x0304));
assert!(parsed.extensions.supported_versions.contains(&0x0303));
assert!(!parsed.extensions.signature_algorithms.contains(&0x0905));
}
#[test]
fn tls13_open_record_allowed_in_server_certificate_verified_state() {
let states = [
HandshakeState::KeysDerived,
HandshakeState::ServerEncryptedExtensionsReceived,
HandshakeState::ServerCertificateRequestReceived,
HandshakeState::ServerCertificateReceived,
HandshakeState::ServerCertificateVerified,
];
for state in states {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
connection.state = state;
let record = ProtectedRecord {
sequence: 0,
ciphertext: vec![0_u8; 1],
tag: [0_u8; 16],
};
let result = connection.noxtls_open_record(&record, &[]);
assert!(result.is_err());
let error = result.expect_err("record opening should fail without traffic keys");
if let Error::StateError(message) = error {
assert_ne!(message, "cannot open record before handshake noxtls_finish");
}
}
}
#[test]
fn tls13_application_key_activation_requires_finished_state() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
let error = connection
.noxtls_activate_tls13_application_traffic_keys()
.expect_err("activation should fail before Finished");
match error {
Error::StateError(message) => {
assert_eq!(
message,
"application traffic keys can only be activated in finished state"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_psk_client_hello_can_offer_early_data() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
connection.noxtls_set_tls13_client_offer_pq_key_shares(false);
connection.noxtls_set_tls13_client_offer_mldsa_signature(false);
let client_hello = connection
.noxtls_send_client_hello_with_psk_and_early_data(
&[0x44_u8; 32],
b"ticket-identity",
0,
b"resumption psk fixture",
)
.expect("early-data PSK ClientHello should build");
let parsed = Connection::noxtls_parse_client_hello_info(&client_hello)
.expect("client hello should parse");
assert!(parsed.extensions.early_data_offered);
}
#[test]
fn tls13_end_of_early_data_requires_accepted_early_data() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
let message = Connection::noxtls_build_tls13_end_of_early_data();
connection.state = HandshakeState::ServerEncryptedExtensionsReceived;
let error = connection
.noxtls_recv_tls13_end_of_early_data(&message)
.expect_err("EndOfEarlyData without accepted early_data should fail");
match error {
Error::StateError(message) => {
assert_eq!(message, "tls13 EndOfEarlyData requires accepted early_data");
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_end_of_early_data_closes_early_data_phase() {
let psk = b"resumption psk fixture";
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
connection.noxtls_set_tls13_client_offer_pq_key_shares(false);
connection.noxtls_set_tls13_client_offer_mldsa_signature(false);
connection
.noxtls_send_client_hello_with_psk_and_early_data(
&[0x45_u8; 32],
b"ticket-identity",
0,
psk,
)
.expect("early-data PSK ClientHello should build");
let record = connection
.noxtls_seal_tls13_early_data_record(psk, b"GET /", &[], 0)
.expect("early data record should seal before EoED");
connection.state = HandshakeState::ServerHelloReceived;
let encrypted_extensions =
Connection::noxtls_build_encrypted_extensions_with_alpn_and_early_data(None, true)
.expect("encrypted extensions should build");
connection
.noxtls_recv_encrypted_extensions(&encrypted_extensions)
.expect("early_data acceptance should parse");
let end_of_early_data = Connection::noxtls_build_tls13_end_of_early_data();
connection
.noxtls_recv_tls13_end_of_early_data(&end_of_early_data)
.expect("EndOfEarlyData should parse after acceptance");
assert!(connection.noxtls_tls13_end_of_early_data_seen());
connection.state = HandshakeState::ClientHelloSent;
let error = connection
.noxtls_open_tls13_early_data_record(psk, &record, &[])
.expect_err("early data after EndOfEarlyData should fail");
match error {
Error::StateError(message) => {
assert_eq!(
message,
"tls13 early-data cannot be opened after EndOfEarlyData"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_end_of_early_data_rejects_non_empty_body() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
connection.noxtls_set_tls13_client_offer_pq_key_shares(false);
connection.noxtls_set_tls13_client_offer_mldsa_signature(false);
connection
.noxtls_send_client_hello_with_psk_and_early_data(
&[0x46_u8; 32],
b"ticket-identity",
0,
b"resumption psk fixture",
)
.expect("early-data PSK ClientHello should build");
connection.state = HandshakeState::ServerHelloReceived;
let encrypted_extensions =
Connection::noxtls_build_encrypted_extensions_with_alpn_and_early_data(None, true)
.expect("encrypted extensions should build");
connection
.noxtls_recv_encrypted_extensions(&encrypted_extensions)
.expect("early_data acceptance should parse");
let malformed = [0x05, 0x00, 0x00, 0x01, 0x00];
let error = connection
.noxtls_recv_tls13_end_of_early_data(&malformed)
.expect_err("non-empty EndOfEarlyData should fail");
match error {
Error::ParseFailure(message) => {
assert_eq!(message, "EndOfEarlyData body must be empty");
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_client_hello_advertises_raw_public_key_certificate_types() {
let mut connection = Connection::noxtls_new(TlsVersion::Tls13);
connection.noxtls_set_tls13_client_offer_pq_key_shares(false);
connection.noxtls_set_tls13_client_offer_mldsa_signature(false);
connection.noxtls_set_tls13_raw_public_keys_enabled(true);
let client_hello = connection
.noxtls_send_client_hello(&[0x47_u8; 32])
.expect("client hello should build");
let parsed =
Connection::noxtls_parse_client_hello_info(&client_hello).expect("client hello parses");
assert_eq!(parsed.extensions.client_certificate_types, vec![0x02]);
assert_eq!(parsed.extensions.server_certificate_types, vec![0x02, 0x00]);
}
#[test]
fn tls13_raw_public_key_server_requires_client_offer() {
let private_key =
P256PrivateKey::from_bytes([0x12_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let spki = noxtls_p256_public_key_to_spki_der(&public_key).expect("p256 spki should encode");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
client.noxtls_set_tls13_client_offer_mldsa_signature(false);
let client_hello = client
.noxtls_send_client_hello(&[0x48_u8; 32])
.expect("client hello should build");
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_raw_public_key_identity(
&spki,
Tls13ServerIdentityKey::P256(private_key),
)
.expect("raw public key identity should configure");
let error = server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x49_u8; 32])
.expect_err("server should reject missing raw-public-key offer");
match error {
Error::ParseFailure(message) => {
assert_eq!(
message,
"client hello did not offer raw public key server certificates"
);
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn tls13_server_role_raw_public_key_handshake_roundtrip() {
let private_key =
P256PrivateKey::from_bytes([0x13_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let spki = noxtls_p256_public_key_to_spki_der(&public_key).expect("p256 spki should encode");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
client.noxtls_set_tls13_client_offer_mldsa_signature(false);
client
.noxtls_set_tls13_expected_server_raw_public_key(&spki)
.expect("expected raw public key should configure");
client
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsAes128GcmSha256])
.expect("set client cipher suites");
let client_hello = client
.noxtls_send_client_hello(&[0x4A_u8; 32])
.expect("client hello should build");
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_raw_public_key_identity(
&spki,
Tls13ServerIdentityKey::P256(private_key),
)
.expect("raw public key identity should configure");
let server_hello = server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x4B_u8; 32])
.expect("server should accept client hello");
client
.noxtls_recv_server_hello(&server_hello)
.expect("client should process server hello");
server
.noxtls_derive_handshake_secret()
.expect("server should derive handshake secret");
let flight_packet = server
.noxtls_build_tls13_server_handshake_flight()
.expect("server raw-public-key flight should build");
let flight_aad = Connection::noxtls_tls13_packet_header_aad(&flight_packet)
.expect("server flight packet header should parse");
client
.noxtls_process_tls13_server_encrypted_handshake_flight(
core::slice::from_ref(&flight_packet),
&flight_aad,
)
.expect("client should process raw-public-key server flight");
assert_eq!(client.state, HandshakeState::Finished);
}
#[test]
fn tls13_server_role_handshake_roundtrip_with_noxtls_client() {
let private_key =
P256PrivateKey::from_bytes([0x11_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let cert_der = noxtls_write_self_signed_certificate_p256_sha256(
&[0x01],
"localhost",
"20200101000000Z",
"20300101000000Z",
&public_key,
&private_key,
)
.expect("self-signed certificate should build");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
client
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsAes128GcmSha256])
.expect("set client cipher suites");
let client_hello = client
.noxtls_send_client_hello(&[0x22_u8; 32])
.expect("client hello should build");
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_identity(
&[cert_der],
Tls13ServerIdentityKey::P256(private_key),
)
.expect("server identity should configure");
let server_hello = server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x33_u8; 32])
.expect("server should accept client hello");
assert_eq!(
server.noxtls_negotiated_key_exchange_group(),
Some(TEST_TLS13_KEY_SHARE_GROUP_X25519)
);
assert_eq!(
server.noxtls_negotiated_certificate_verify_signature_scheme(),
Some(TEST_TLS13_SIGALG_ECDSA_SECP256R1_SHA256)
);
client
.noxtls_recv_server_hello(&server_hello)
.expect("client should process server hello");
assert_eq!(
client.noxtls_negotiated_key_exchange_group(),
Some(TEST_TLS13_KEY_SHARE_GROUP_X25519)
);
server
.noxtls_derive_handshake_secret()
.expect("server should derive handshake secret");
let flight_packet = server
.noxtls_build_tls13_server_handshake_flight()
.expect("server handshake flight should build");
let flight_aad = Connection::noxtls_tls13_packet_header_aad(&flight_packet)
.expect("server flight packet header should parse");
client
.noxtls_process_tls13_server_encrypted_handshake_flight(
core::slice::from_ref(&flight_packet),
&flight_aad,
)
.expect("client should process encrypted server flight");
assert_eq!(client.state, HandshakeState::Finished);
assert_eq!(
client.noxtls_negotiated_certificate_verify_signature_scheme(),
Some(TEST_TLS13_SIGALG_ECDSA_SECP256R1_SHA256)
);
let client_finished = client
.noxtls_prepare_tls13_client_finished_message()
.expect("client finished should build");
let inner_len = client_finished
.len()
.checked_add(1)
.expect("client finished inner length");
let payload_len = inner_len
.checked_add(16)
.expect("client finished ciphertext length");
let mut client_finished_aad = [0_u8; 5];
client_finished_aad[0] = RecordContentType::ApplicationData.to_u8();
client_finished_aad[1] = 0x03;
client_finished_aad[2] = 0x03;
client_finished_aad[3..5].copy_from_slice(&(payload_len as u16).to_be_bytes());
let client_finished_packet = client
.noxtls_seal_tls13_record_packet(
&client_finished,
RecordContentType::Handshake.to_u8(),
&client_finished_aad,
0,
)
.expect("client finished packet should seal");
server
.noxtls_recv_client_finished_packet(&client_finished_packet)
.expect("server should accept client finished");
assert_eq!(server.state, HandshakeState::Finished);
}
#[test]
fn tls13_server_compatibility_ccs_record_matches_wire_format() {
assert_eq!(
Connection::noxtls_build_tls13_compatibility_change_cipher_spec(),
[0x14, 0x03, 0x03, 0x00, 0x01, 0x01]
);
}
#[test]
fn dtls13_connection_id_unified_record_round_trip_and_mismatch_reject() {
let key = [0x11_u8; 16];
let iv = [0x22_u8; 12];
let cid = [0xA1_u8, 0xB2];
let packet = noxtls_seal_dtls13_unified_aes128gcm_record_with_cid(
1,
7,
&key,
&iv,
b"cid protected payload",
&cid,
)
.expect("cid-bearing dtls13 packet should seal");
let (header, _) = noxtls_parse_dtls13_record_header_with_cid_len(&packet, cid.len())
.expect("cid-bearing dtls13 header should parse with negotiated cid length");
assert_eq!(header.epoch, 1);
assert_eq!(header.sequence, 7);
assert_eq!(header.connection_id, cid);
let mut replay = DtlsEpochReplayTracker::noxtls_new();
let (opened_header, plaintext) =
noxtls_open_dtls13_unified_aes128gcm_record_with_cid(&packet, &key, &iv, &mut replay, &cid)
.expect("matching cid should open");
assert_eq!(opened_header.connection_id, cid);
assert_eq!(plaintext, b"cid protected payload");
let mut wrong_replay = DtlsEpochReplayTracker::noxtls_new();
assert!(noxtls_open_dtls13_unified_aes128gcm_record_with_cid(
&packet,
&key,
&iv,
&mut wrong_replay,
&[0xA1, 0xB3],
)
.is_err());
let mut no_cid_replay = DtlsEpochReplayTracker::noxtls_new();
assert!(
noxtls_open_dtls13_unified_aes128gcm_record(&packet, &key, &iv, &mut no_cid_replay,)
.is_err()
);
}
#[test]
fn dtls13_connection_ids_apply_to_application_datagrams() {
let client_key = [0x31_u8; 16];
let client_iv = [0x32_u8; 12];
let server_key = [0x41_u8; 16];
let server_iv = [0x42_u8; 12];
let client_to_server_cid = [0xC1_u8, 0xD2, 0xE3];
let mut client = Connection::noxtls_new(TlsVersion::Dtls13);
client
.noxtls_install_dtls13_traffic_keys(client_key, client_iv, server_key, server_iv)
.expect("client dtls13 keys should install");
client
.noxtls_set_dtls13_outbound_connection_id(&client_to_server_cid)
.expect("client outbound cid should configure");
client.state = HandshakeState::Finished;
let mut server = Connection::noxtls_new(TlsVersion::Dtls13);
server.tls_role = TlsRole::Server;
server
.noxtls_install_dtls13_traffic_keys(client_key, client_iv, server_key, server_iv)
.expect("server dtls13 keys should install");
server
.noxtls_set_dtls13_inbound_connection_id(&client_to_server_cid)
.expect("server inbound cid should configure");
server.state = HandshakeState::Finished;
let datagrams = client
.write_dtls13_application_data(b"cid app data", 10)
.expect("client should write cid-bearing datagram");
assert_eq!(datagrams.len(), 1);
let (header, _) =
noxtls_parse_dtls13_record_header_with_cid_len(&datagrams[0], client_to_server_cid.len())
.expect("connection output should include cid-bearing header");
assert_eq!(header.connection_id, client_to_server_cid);
let plaintexts = server
.read_dtls13_application_data(&datagrams[0], 11)
.expect("server should read cid-bearing datagram");
assert_eq!(plaintexts, vec![b"cid app data".to_vec()]);
let mut wrong_server = Connection::noxtls_new(TlsVersion::Dtls13);
wrong_server.tls_role = TlsRole::Server;
wrong_server
.noxtls_install_dtls13_traffic_keys(client_key, client_iv, server_key, server_iv)
.expect("wrong server dtls13 keys should install");
wrong_server
.noxtls_set_dtls13_inbound_connection_id(&[0xC1, 0xD2, 0xE4])
.expect("wrong server inbound cid should configure");
wrong_server.state = HandshakeState::Finished;
assert!(wrong_server
.read_dtls13_application_data(&datagrams[0], 12)
.is_err());
}
#[test]
fn tls13_mtls_client_certificate_roundtrip() {
let server_private =
P256PrivateKey::from_bytes([0x21_u8; 32]).expect("server p256 private key should parse");
let server_public = server_private
.public_key()
.expect("server p256 public key should derive");
let server_cert = noxtls_write_self_signed_certificate_p256_sha256(
&[0x31],
"mtls.server.test",
"20260101000000Z",
"20270101000000Z",
&server_public,
&server_private,
)
.expect("server certificate should encode");
let client_private =
P256PrivateKey::from_bytes([0x22_u8; 32]).expect("client p256 private key should parse");
let client_public = client_private
.public_key()
.expect("client p256 public key should derive");
let client_cert = noxtls_write_self_signed_certificate_p256_sha256(
&[0x32],
"mtls.client.test",
"20260101000000Z",
"20270101000000Z",
&client_public,
&client_private,
)
.expect("client certificate should encode");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.noxtls_set_tls13_client_offer_pq_key_shares(false);
client
.noxtls_set_tls13_client_cipher_suites(&[CipherSuite::TlsAes128GcmSha256])
.expect("set client cipher suites");
client.noxtls_set_tls13_require_certificate_auth(true);
client
.noxtls_configure_tls13_server_auth(
core::slice::from_ref(&server_cert),
&[],
"20260601000000Z",
)
.expect("client server-auth trust should configure");
client
.noxtls_set_tls13_server_expected_hostname(Some("mtls.server.test"))
.expect("server hostname policy should configure");
client
.noxtls_configure_tls13_client_identity(
core::slice::from_ref(&client_cert),
Tls13ServerIdentityKey::P256(client_private),
)
.expect("client identity should configure");
let client_hello = client
.noxtls_send_client_hello(&[0x44_u8; 32])
.expect("client hello should build");
let mut server = Connection::noxtls_new_tls13_server();
server
.noxtls_configure_tls13_server_identity(
core::slice::from_ref(&server_cert),
Tls13ServerIdentityKey::P256(server_private),
)
.expect("server identity should configure");
server.noxtls_set_tls13_require_client_auth(true);
server
.noxtls_configure_tls13_client_auth(
core::slice::from_ref(&client_cert),
&[],
"20260601000000Z",
)
.expect("server client-auth trust should configure");
let server_hello = server
.noxtls_accept_tls13_client_hello(&client_hello, &[0x55_u8; 32])
.expect("server should accept client hello");
client
.noxtls_recv_server_hello(&server_hello)
.expect("client should process server hello");
server
.noxtls_derive_handshake_secret()
.expect("server should derive handshake secret");
let server_flight = server
.noxtls_build_tls13_server_handshake_flight_with_client_certificate_request(true)
.expect("server mTLS flight should build");
let server_flight_aad = Connection::noxtls_tls13_packet_header_aad(&server_flight)
.expect("server flight aad should parse");
client
.noxtls_process_tls13_server_encrypted_handshake_flight(
core::slice::from_ref(&server_flight),
&server_flight_aad,
)
.expect("client should process requested mTLS server flight");
assert_eq!(client.state, HandshakeState::Finished);
let mut client_messages = Vec::new();
for message in client
.noxtls_prepare_tls13_client_authentication_messages()
.expect("client auth messages should build")
{
client_messages.extend_from_slice(&message);
}
let client_finished = client
.noxtls_prepare_tls13_client_finished_message()
.expect("client finished should build");
client_messages.extend_from_slice(&client_finished);
let inner_len = client_messages
.len()
.checked_add(1)
.expect("client auth inner length");
let payload_len = inner_len
.checked_add(16)
.expect("client auth ciphertext length");
let mut client_auth_aad = [0_u8; 5];
client_auth_aad[0] = RecordContentType::ApplicationData.to_u8();
client_auth_aad[1] = 0x03;
client_auth_aad[2] = 0x03;
client_auth_aad[3..5].copy_from_slice(&(payload_len as u16).to_be_bytes());
let client_auth_packet = client
.noxtls_seal_tls13_record_packet(
&client_messages,
RecordContentType::Handshake.to_u8(),
&client_auth_aad,
0,
)
.expect("client auth packet should seal");
server
.noxtls_recv_tls13_client_authentication_packet(&client_auth_packet)
.expect("server should verify client certificate flight");
assert_eq!(server.state, HandshakeState::Finished);
}
fn test_der_len(len: usize) -> Vec<u8> {
if len < 128 {
return vec![len as u8];
}
if len <= 0xff {
return vec![0x81, len as u8];
}
vec![0x82, ((len >> 8) & 0xff) as u8, (len & 0xff) as u8]
}
fn test_der(tag: u8, body: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.push(tag);
out.extend_from_slice(&test_der_len(body.len()));
out.extend_from_slice(body);
out
}
fn test_der_sequence(parts: &[Vec<u8>]) -> Vec<u8> {
let mut body = Vec::new();
for part in parts {
body.extend_from_slice(part);
}
test_der(0x30, &body)
}
fn test_ocsp_response(
status_tag: u8,
produced_at: &str,
this_update: &str,
next_update: &str,
) -> Vec<u8> {
let cert_id = test_der_sequence(&[]);
let cert_status = test_der(status_tag, &[]);
let this_update = test_der(0x18, this_update.as_bytes());
let next_update = test_der(0xA0, &test_der(0x18, next_update.as_bytes()));
let single_response = test_der_sequence(&[cert_id, cert_status, this_update, next_update]);
let responses = test_der_sequence(&[single_response]);
let responder_id = test_der(0xA1, &test_der(0x04, b"responder"));
let produced_at = test_der(0x18, produced_at.as_bytes());
let response_data = test_der_sequence(&[responder_id, produced_at, responses]);
let signature_algorithm = test_der_sequence(&[]);
let signature = test_der(0x03, &[0x00]);
let basic_response = test_der_sequence(&[response_data, signature_algorithm, signature]);
let response_type = test_der(
0x06,
&[0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01],
);
let response_octets = test_der(0x04, &basic_response);
let response_bytes = test_der_sequence(&[response_type, response_octets]);
test_der_sequence(&[test_der(0x0A, &[0x00]), test_der(0xA0, &response_bytes)])
}
#[test]
fn tls13_ocsp_staple_builtin_parser_classifies_status() {
let good = test_ocsp_response(
0x80,
"20260501000000Z",
"20260501000000Z",
"20260601000000Z",
);
let info = noxtls_parse_tls13_ocsp_staple_info(&good, "20260515000000Z")
.expect("fresh good ocsp staple should parse");
assert_eq!(info.status, Tls13OcspStapleVerification::Good);
assert_eq!(info.produced_at, "20260501000000Z");
assert_eq!(info.this_update, "20260501000000Z");
assert_eq!(info.next_update.as_deref(), Some("20260601000000Z"));
assert_eq!(
noxtls_verify_tls13_ocsp_staple(&good, "20260515000000Z")
.expect("fresh good ocsp staple should verify"),
Tls13OcspStapleVerification::Good
);
let expired = test_ocsp_response(
0x80,
"20260501000000Z",
"20260501000000Z",
"20260510000000Z",
);
assert_eq!(
noxtls_verify_tls13_ocsp_staple(&expired, "20260515000000Z")
.expect("expired ocsp staple should classify"),
Tls13OcspStapleVerification::Expired
);
let revoked = test_ocsp_response(
0xA1,
"20260501000000Z",
"20260501000000Z",
"20260601000000Z",
);
assert_eq!(
noxtls_verify_tls13_ocsp_staple(&revoked, "20260515000000Z")
.expect("revoked ocsp staple should classify"),
Tls13OcspStapleVerification::Revoked
);
}
#[test]
fn tls13_certificate_processing_validates_ocsp_staple_without_custom_hook() {
let private_key =
P256PrivateKey::from_bytes([0x51_u8; 32]).expect("p256 private key fixture should parse");
let public_key = private_key.public_key().expect("p256 public key");
let cert_der = noxtls_write_self_signed_certificate_p256_sha256(
&[0x51],
"ocsp.example.test",
"20260101000000Z",
"20270101000000Z",
&public_key,
&private_key,
)
.expect("self-signed certificate should build");
let good_ocsp = test_ocsp_response(
0x80,
"20260501000000Z",
"20260501000000Z",
"20260601000000Z",
);
let certificate =
Connection::noxtls_build_certificate_message_with_ocsp_staple(&cert_der, Some(&good_ocsp))
.expect("certificate with ocsp staple should build");
let mut client = Connection::noxtls_new(TlsVersion::Tls13);
client.state = HandshakeState::ServerEncryptedExtensionsReceived;
client.noxtls_set_tls13_require_certificate_auth(true);
client.noxtls_set_tls13_require_ocsp_staple(true);
client
.noxtls_configure_tls13_server_auth(
core::slice::from_ref(&cert_der),
&[],
"20260515000000Z",
)
.expect("server auth policy should configure");
client
.noxtls_set_tls13_server_expected_hostname(Some("ocsp.example.test"))
.expect("hostname should configure");
client
.noxtls_recv_certificate(&certificate)
.expect("fresh ocsp staple should be accepted");
assert!(client.noxtls_tls13_server_ocsp_staple_verified());
assert_eq!(
client.noxtls_tls13_server_ocsp_staple(),
Some(good_ocsp.as_slice())
);
let expired_ocsp = test_ocsp_response(
0x80,
"20260501000000Z",
"20260501000000Z",
"20260510000000Z",
);
let expired_certificate = Connection::noxtls_build_certificate_message_with_ocsp_staple(
&cert_der,
Some(&expired_ocsp),
)
.expect("certificate with expired ocsp staple should build");
let mut expired_client = Connection::noxtls_new(TlsVersion::Tls13);
expired_client.state = HandshakeState::ServerEncryptedExtensionsReceived;
expired_client.noxtls_set_tls13_require_certificate_auth(true);
expired_client.noxtls_set_tls13_require_ocsp_staple(true);
expired_client
.noxtls_configure_tls13_server_auth(&[cert_der], &[], "20260515000000Z")
.expect("server auth policy should configure");
expired_client
.noxtls_set_tls13_server_expected_hostname(Some("ocsp.example.test"))
.expect("hostname should configure");
assert!(expired_client
.noxtls_recv_certificate(&expired_certificate)
.is_err());
}