#[test]
fn suite_inventory_matches_docs() {
assert_eq!(ferritls_rustls::cipher::TLS13_SUITE_NAMES.len(), 5);
assert!(ferritls_rustls::cipher::TLS13_SUITE_NAMES.contains(&"TLS_AES_128_GCM_SHA256"));
assert!(ferritls_rustls::cipher::TLS13_SUITE_NAMES.contains(&"TLS_AES_256_GCM_SHA384"));
assert!(ferritls_rustls::cipher::TLS13_SUITE_NAMES.contains(&"TLS_CHACHA20_POLY1305_SHA256"));
assert!(ferritls_rustls::cipher::TLS13_SUITE_NAMES.contains(&"TLS_AES_128_CCM_SHA256"));
assert!(ferritls_rustls::cipher::TLS13_SUITE_NAMES.contains(&"TLS_AES_128_CCM_8_SHA256"));
}
#[test]
fn quic_algorithms_wired_for_three_suites() {
let cases = [
(ferritls_rustls::cipher::tls13_aes_128_gcm_sha256(), 16),
(ferritls_rustls::cipher::tls13_aes_256_gcm_sha384(), 32),
(
ferritls_rustls::cipher::tls13_chacha20_poly1305_sha256(),
32,
),
];
for (suite, key_len) in cases {
let quic_alg = suite
.tls13()
.expect("tls13")
.quic
.expect("GCM/ChaCha 套件必须接线 QUIC");
assert_eq!(quic_alg.aead_key_len(), key_len);
assert!(!quic_alg.fips());
}
assert!(
ferritls_rustls::cipher::tls13_aes_128_ccm_sha256()
.tls13()
.expect("tls13")
.quic
.is_none()
&& ferritls_rustls::cipher::tls13_aes_128_ccm_8_sha256()
.tls13()
.expect("tls13")
.quic
.is_none(),
"CCM(含 CCM_8)不参与 QUIC(RFC 9001 §5.1 以 AES-GCM 为强制基准)"
);
}
#[test]
fn ccm8_suite_excluded_from_fips_inventory() {
let fips: Vec<_> = ferritls_rustls::cipher::fips_tls13_suites();
assert_eq!(fips.len(), 3, "批准模式 = GCM×2 + CCM");
assert!(
fips.iter()
.all(|s| s.suite() != rustls::CipherSuite::TLS13_AES_128_CCM_8_SHA256)
);
let all: Vec<_> = ferritls_rustls::cipher::all_tls13_suites();
assert_eq!(all.len(), 5);
assert!(
all.iter()
.any(|s| s.suite() == rustls::CipherSuite::TLS13_AES_128_CCM_8_SHA256)
);
}
#[test]
fn kx_group_names_are_stable() {
use rustls::NamedGroup;
assert_eq!(ferritls_rustls::kx::X25519_GROUP.name(), NamedGroup::X25519);
assert_eq!(
ferritls_rustls::kx::SECP256R1_GROUP.name(),
NamedGroup::secp256r1
);
assert_eq!(
ferritls_rustls::kx::SECP384R1_GROUP.name(),
NamedGroup::secp384r1
);
assert_eq!(
ferritls_rustls::kx::X25519MLKEM768_GROUP.name(),
NamedGroup::X25519MLKEM768
);
assert_eq!(
ferritls_rustls::kx::MLKEM512_GROUP.name(),
NamedGroup::MLKEM512
);
assert_eq!(
ferritls_rustls::kx::MLKEM768_GROUP.name(),
NamedGroup::MLKEM768
);
assert_eq!(
ferritls_rustls::kx::MLKEM1024_GROUP.name(),
NamedGroup::MLKEM1024
);
assert_eq!(ferritls_rustls::kx::ALL_KX_GROUPS.len(), 7);
assert_eq!(ferritls_rustls::kx::FIPS_KX_GROUPS.len(), 6);
assert_eq!(
ferritls_rustls::kx::ALL_KX_GROUPS[0].name(),
NamedGroup::X25519MLKEM768
);
assert_eq!(
ferritls_rustls::kx::FIPS_KX_GROUPS[0].name(),
NamedGroup::X25519MLKEM768
);
}
#[test]
fn default_provider_smoke() {
let p = ferritls_rustls::default_provider();
assert!(!p.fips(), "认证前 fips() 必须为 false");
assert_eq!(p.cipher_suites.len(), 5);
assert_eq!(p.kx_groups.len(), 7);
}
#[test]
fn fips_mode_provider_smoke() {
let p = ferritls_rustls::fips_mode_provider();
assert!(!p.fips());
assert_eq!(p.cipher_suites.len(), 3, "批准模式无 ChaCha20-Poly1305");
assert_eq!(
p.kx_groups.len(),
6,
"批准模式:X25519MLKEM768 混合 + 纯 ML-KEM 三参数集 + P-256/384,无独立 X25519"
);
}
#[test]
fn verification_tables_shape_and_fips_subset() {
use rustls::SignatureScheme;
use std::ptr;
let def = ferritls_rustls::verify::SUPPORTED_ALGORITHMS;
let fips = ferritls_rustls::verify::FIPS_SUPPORTED_ALGORITHMS;
assert_eq!(def.all.len(), 9, "默认表:ECDSA×2 + Ed25519 + RSA×6");
assert_eq!(fips.all.len(), 8, "批准表无 Ed25519");
for a in fips.all {
assert!(
def.all.iter().any(|b| ptr::eq(*a, *b)),
"批准表出现默认表之外的 verifier"
);
}
for (mapping, all, what) in [
(def.mapping, def.all, "默认表"),
(fips.mapping, fips.all, "批准表"),
] {
let mut schemes: Vec<u16> = mapping.iter().map(|(s, _)| u16::from(*s)).collect();
schemes.sort_unstable();
schemes.dedup();
assert_eq!(schemes.len(), mapping.len(), "{what} scheme 键不得重复");
for (scheme, algs) in mapping {
assert_eq!(
algs.len(),
1,
"{what} {scheme:?} 映射必须单算法(TLS 1.3 首项语义的最保守形态)"
);
assert!(
all.iter().any(|b| ptr::eq(algs[0], *b)),
"{what} {scheme:?} 映射的 verifier 不在本表 all 中"
);
}
}
let def_schemes: Vec<u16> = def.mapping.iter().map(|(s, _)| u16::from(*s)).collect();
let fips_schemes: Vec<u16> = fips.mapping.iter().map(|(s, _)| u16::from(*s)).collect();
let diff: Vec<u16> = def_schemes
.iter()
.copied()
.filter(|s| !fips_schemes.contains(s))
.collect();
assert_eq!(
diff.as_slice(),
&[u16::from(SignatureScheme::ED25519)],
"批准表与默认表的 scheme 差集恰为 Ed25519"
);
}
#[test]
fn providers_consume_their_verification_tables() {
use std::ptr;
let def = ferritls_rustls::default_provider();
let fips = ferritls_rustls::fips_mode_provider();
assert!(ptr::eq(
def.signature_verification_algorithms.all,
ferritls_rustls::verify::SUPPORTED_ALGORITHMS.all
));
assert!(ptr::eq(
def.signature_verification_algorithms.mapping,
ferritls_rustls::verify::SUPPORTED_ALGORITHMS.mapping
));
assert!(ptr::eq(
fips.signature_verification_algorithms.all,
ferritls_rustls::verify::FIPS_SUPPORTED_ALGORITHMS.all
));
assert!(ptr::eq(
fips.signature_verification_algorithms.mapping,
ferritls_rustls::verify::FIPS_SUPPORTED_ALGORITHMS.mapping
));
}
#[test]
fn x25519_mlkem768_roundtrip_and_rejects() {
use rustls::crypto::CompletedKeyExchange;
let group = ferritls_rustls::kx::X25519MLKEM768_GROUP;
let client = group.start().expect("client start");
assert_eq!(client.pub_key().len(), 1184 + 32, "客户端 share 形状");
let server: CompletedKeyExchange = group
.start_and_complete(client.pub_key())
.expect("server start_and_complete");
assert_eq!(server.pub_key.len(), 1088 + 32, "服务端 share 形状");
let mut bad_share = client.pub_key().to_vec();
bad_share.pop();
assert!(group.start_and_complete(&bad_share).is_err());
let client2 = group.start().expect("client start 2");
let server2 = group
.start_and_complete(client2.pub_key())
.expect("server 2");
let mut short = server2.pub_key.clone();
short.pop();
assert!(client2.complete(&short).is_err());
let ss_client = client.complete(&server.pub_key).expect("client complete");
assert_eq!(ss_client.secret_bytes().len(), 64, "ss = ML-KEM ‖ X25519");
assert_eq!(
ss_client.secret_bytes(),
server.secret.secret_bytes(),
"两侧共享秘密一致"
);
}
#[test]
fn pure_mlkem_groups_roundtrip_and_rejects() {
use rustls::crypto::CompletedKeyExchange;
let cases = [
(
ferritls_rustls::kx::MLKEM512_GROUP,
800, 768, ),
(ferritls_rustls::kx::MLKEM768_GROUP, 1184, 1088),
(ferritls_rustls::kx::MLKEM1024_GROUP, 1568, 1568),
];
for (group, ek_bytes, ct_bytes) in cases {
let client = group.start().expect("client start");
assert_eq!(client.pub_key().len(), ek_bytes, "客户端 share = ek");
let mut bad_share = client.pub_key().to_vec();
bad_share.pop();
assert!(group.start_and_complete(&bad_share).is_err());
let garbage = vec![0xffu8; ek_bytes];
assert!(
group.start_and_complete(&garbage).is_err(),
"ek 模校验必须拒绝 0xFF 填充"
);
let server: CompletedKeyExchange = group
.start_and_complete(client.pub_key())
.expect("server start_and_complete");
assert_eq!(server.pub_key.len(), ct_bytes, "服务端 share = ct");
assert_eq!(server.secret.secret_bytes().len(), 32, "ss = 32 B");
let client2 = group.start().expect("client start 2");
let server2 = group
.start_and_complete(client2.pub_key())
.expect("server 2");
let mut short = server2.pub_key.clone();
short.pop();
assert!(client2.complete(&short).is_err());
let ss_client = client.complete(&server.pub_key).expect("client complete");
assert_eq!(
ss_client.secret_bytes(),
server.secret.secret_bytes(),
"两侧共享秘密一致"
);
}
}