Skip to main content

ferritls_rustls/
kx.rs

1//! 密钥交换组:`SupportedKxGroup` / `ActiveKeyExchange` 适配。
2//!
3//! X25519 = 非批准;secp256r1/secp384r1 = 批准(但认证前 `fips()` 恒
4//! `false`,见 lib.rs“fips() 语义”)。
5//!
6//! 私钥由 ferritls-core 的 OS 熵直读生成(批准模式的 DRBG 路径由
7//! `CryptoProvider::secure_random` 与密钥生成入口的自检守卫覆盖;
8//! ECDH 私钥生成熵需求为曲线阶长,风险敞口极小)。
9
10use rustls::crypto::{ActiveKeyExchange, CompletedKeyExchange, SharedSecret, SupportedKxGroup};
11use rustls::{Error as RustlsError, NamedGroup, PeerMisbehaved};
12
13use ferritls_core::ecdh::{p256, p384, x25519};
14use ferritls_core::mlkem;
15
16/// 把 core 的共享秘密错误映射为 rustls 的 InvalidKeyShare。
17fn map_dh_err(_: ferritls_core::Error) -> RustlsError {
18    RustlsError::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare)
19}
20
21/// X25519(RFC 7748)密钥交换组。
22#[derive(Debug)]
23pub struct X25519;
24
25/// secp256r1(NIST P-256)密钥交换组。
26#[derive(Debug)]
27pub struct SecP256R1;
28
29/// secp384r1(NIST P-384)密钥交换组。
30#[derive(Debug)]
31pub struct SecP384R1;
32
33impl SupportedKxGroup for X25519 {
34    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, RustlsError> {
35        let sk = x25519::SecretKey::generate().map_err(map_dh_err)?;
36        let pk = sk.public_key();
37        Ok(Box::new(ActiveX25519 { sk, pk }))
38    }
39
40    fn name(&self) -> NamedGroup {
41        NamedGroup::X25519
42    }
43
44    fn fips(&self) -> bool {
45        // X25519 独立使用为非批准算法;即便认证后也不会在批准模式提供。
46        false
47    }
48}
49
50impl SupportedKxGroup for SecP256R1 {
51    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, RustlsError> {
52        let sk = p256::SecretKey::generate().map_err(map_dh_err)?;
53        let pk = sk.public_key();
54        Ok(Box::new(ActiveSecP256R1 { sk, pk }))
55    }
56
57    fn name(&self) -> NamedGroup {
58        NamedGroup::secp256r1
59    }
60
61    fn fips(&self) -> bool {
62        // 认证(阶段 C)落地前恒 false。
63        false
64    }
65}
66
67impl SupportedKxGroup for SecP384R1 {
68    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, RustlsError> {
69        let sk = p384::SecretKey::generate().map_err(map_dh_err)?;
70        let pk = sk.public_key();
71        Ok(Box::new(ActiveSecP384R1 { sk, pk }))
72    }
73
74    fn name(&self) -> NamedGroup {
75        NamedGroup::secp384r1
76    }
77
78    fn fips(&self) -> bool {
79        false
80    }
81}
82
83/// 进行中的 X25519 密钥交换。
84pub(crate) struct ActiveX25519 {
85    sk: x25519::SecretKey,
86    pk: [u8; 32],
87}
88
89/// 进行中的 P-256 密钥交换。
90pub(crate) struct ActiveSecP256R1 {
91    sk: p256::SecretKey,
92    pk: [u8; p256::PUBLIC_KEY_LEN],
93}
94
95/// 进行中的 P-384 密钥交换。
96pub(crate) struct ActiveSecP384R1 {
97    sk: p384::SecretKey,
98    pk: [u8; p384::PUBLIC_KEY_LEN],
99}
100
101impl std::fmt::Debug for ActiveX25519 {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.write_str("ActiveX25519")
104    }
105}
106
107impl std::fmt::Debug for ActiveSecP256R1 {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        f.write_str("ActiveSecP256R1")
110    }
111}
112
113impl std::fmt::Debug for ActiveSecP384R1 {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.write_str("ActiveSecP384R1")
116    }
117}
118
119impl ActiveKeyExchange for ActiveX25519 {
120    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, RustlsError> {
121        let ss = self.sk.diffie_hellman(peer_pub_key).map_err(map_dh_err)?;
122        Ok(SharedSecret::from(ss.as_bytes().to_vec()))
123    }
124
125    fn pub_key(&self) -> &[u8] {
126        &self.pk
127    }
128
129    fn group(&self) -> NamedGroup {
130        NamedGroup::X25519
131    }
132}
133
134impl ActiveKeyExchange for ActiveSecP256R1 {
135    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, RustlsError> {
136        let ss = self.sk.diffie_hellman(peer_pub_key).map_err(map_dh_err)?;
137        Ok(SharedSecret::from(ss.as_bytes().to_vec()))
138    }
139
140    fn pub_key(&self) -> &[u8] {
141        &self.pk
142    }
143
144    fn group(&self) -> NamedGroup {
145        NamedGroup::secp256r1
146    }
147}
148
149impl ActiveKeyExchange for ActiveSecP384R1 {
150    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, RustlsError> {
151        let ss = self.sk.diffie_hellman(peer_pub_key).map_err(map_dh_err)?;
152        Ok(SharedSecret::from(ss.as_bytes().to_vec()))
153    }
154
155    fn pub_key(&self) -> &[u8] {
156        &self.pk
157    }
158
159    fn group(&self) -> NamedGroup {
160        NamedGroup::secp384r1
161    }
162}
163
164/// X25519MLKEM768(draft-ietf-tls-ecdhe-mlkem,codepoint 0x11EC)混合
165/// 密钥交换组(M8.3)。
166///
167/// 角色与线格式(TLS 1.3,密钥交换数据依赖 ⇒ KEM 角色**不对称**):
168/// - 客户端 = ML-KEM **解封装方**:`start()` 生成 (ek, dk) + X25519
169///   ephemeral,share = ek(1184)‖X25519 pk(32) = 1216 B;
170/// - 服务端 = ML-KEM **封装方**:走 [`SupportedKxGroup::start_and_complete`]
171///   覆写——对客户端 ek 先做 FIPS 203 §7.2 封装密钥检查再封装,
172///   share = ct(1088)‖X25519 pk(32) = 1120 B;
173/// - 共享秘密 = ML-KEM ss(32) ‖ X25519 ss(32) = 64 B。
174///
175/// X25519 独立使用为非批准算法;本混合组是其进入批准模式配置的
176/// 通道(SP 800-52r2 口径)。CMVP 认证前 `fips()` 恒 `false`。
177#[derive(Debug)]
178pub struct X25519Mlkem768;
179
180/// 进行中的 X25519MLKEM768 客户端侧密钥交换(持有解封装密钥)。
181pub(crate) struct ActiveX25519Mlkem768 {
182    dk: mlkem::Mlkem768DecapsKey,
183    sk: x25519::SecretKey,
184    share: Vec<u8>,
185}
186
187impl SupportedKxGroup for X25519Mlkem768 {
188    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, RustlsError> {
189        let (ek, dk) = mlkem::generate_keypair().map_err(map_dh_err)?;
190        let sk = x25519::SecretKey::generate().map_err(map_dh_err)?;
191        let mut share = Vec::with_capacity(mlkem::EK_BYTES + 32);
192        share.extend_from_slice(ek.as_bytes());
193        share.extend_from_slice(&sk.public_key());
194        Ok(Box::new(ActiveX25519Mlkem768 { dk, sk, share }))
195    }
196
197    fn start_and_complete(&self, peer_pub_key: &[u8]) -> Result<CompletedKeyExchange, RustlsError> {
198        // 服务端:peer share = ek(1184) ‖ X25519 pk(32)
199        let invalid = || RustlsError::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare);
200        if peer_pub_key.len() != mlkem::EK_BYTES + 32 {
201            return Err(invalid());
202        }
203        // FIPS 203 §7.2 封装密钥检查(含模校验)在此发生
204        let ek = mlkem::Mlkem768EncapsKey::from_bytes(&peer_pub_key[..mlkem::EK_BYTES])
205            .map_err(|_| invalid())?;
206        let (ct, ss_m) = mlkem::encapsulate(&ek).map_err(map_dh_err)?;
207        let sk = x25519::SecretKey::generate().map_err(map_dh_err)?;
208        let ss_x = self_x25519(&sk, &peer_pub_key[mlkem::EK_BYTES..])?;
209
210        let mut secret = Vec::with_capacity(mlkem::SS_BYTES + 32);
211        secret.extend_from_slice(ss_m.expose_bytes());
212        secret.extend_from_slice(ss_x.as_bytes());
213        let mut pub_key = Vec::with_capacity(mlkem::CT_BYTES + 32);
214        pub_key.extend_from_slice(ct.as_bytes());
215        pub_key.extend_from_slice(&sk.public_key());
216        Ok(CompletedKeyExchange {
217            group: NamedGroup::X25519MLKEM768,
218            pub_key,
219            secret: SharedSecret::from(secret),
220        })
221    }
222
223    fn name(&self) -> NamedGroup {
224        NamedGroup::X25519MLKEM768
225    }
226
227    fn fips(&self) -> bool {
228        // 认证(阶段 C)落地前恒 false(AGENTS.md 规则 3)。
229        false
230    }
231}
232
233impl ActiveKeyExchange for ActiveX25519Mlkem768 {
234    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, RustlsError> {
235        // 客户端:服务端 share = ct(1088) ‖ X25519 pk(32);密文长度
236        // 不符必须中止(draft-ietf-tls-ecdhe-mlkem §3)。
237        let invalid = || RustlsError::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare);
238        if peer_pub_key.len() != mlkem::CT_BYTES + 32 {
239            return Err(invalid());
240        }
241        let ct = mlkem::Mlkem768Ciphertext::from_bytes(&peer_pub_key[..mlkem::CT_BYTES])
242            .map_err(|_| invalid())?;
243        let ss_m = mlkem::decapsulate(&self.dk, &ct);
244        let ss_x = self_x25519(&self.sk, &peer_pub_key[mlkem::CT_BYTES..])?;
245
246        let mut secret = Vec::with_capacity(mlkem::SS_BYTES + 32);
247        secret.extend_from_slice(ss_m.expose_bytes());
248        secret.extend_from_slice(ss_x.as_bytes());
249        Ok(SharedSecret::from(secret))
250    }
251
252    fn pub_key(&self) -> &[u8] {
253        &self.share
254    }
255
256    fn group(&self) -> NamedGroup {
257        NamedGroup::X25519MLKEM768
258    }
259}
260
261fn self_x25519(sk: &x25519::SecretKey, peer: &[u8]) -> Result<x25519::SharedSecret, RustlsError> {
262    sk.diffie_hellman(peer).map_err(map_dh_err)
263}
264
265/// X25519 组单例。
266pub static X25519_GROUP: &dyn SupportedKxGroup = &X25519;
267/// P-256 组单例。
268pub static SECP256R1_GROUP: &dyn SupportedKxGroup = &SecP256R1;
269/// P-384 组单例。
270pub static SECP384R1_GROUP: &dyn SupportedKxGroup = &SecP384R1;
271
272/// X25519MLKEM768 混合组单例(默认列表首项:PQ 优先,同主流
273/// provider/浏览器实践;1216 B ClientHello share)。
274pub static X25519MLKEM768_GROUP: &dyn SupportedKxGroup = &X25519Mlkem768;
275
276/// 默认(非批准模式)密钥交换组清单;顺序即偏好——X25519MLKEM768
277/// 混合优先,其后纯 ML-KEM(768/1024/512)与经典组。注意 rustls 会
278/// 为清单内**每个**组在 ClientHello 里发 key share:全清单 share 约
279/// 4.8 KB(1216 + 1184 + 1568 + 800 + 32 B),只需子集的用户可自行
280/// 装配 provider(`CryptoProvider` 字段公开)。
281pub static ALL_KX_GROUPS: &[&'static dyn SupportedKxGroup] = &[
282    &X25519Mlkem768,
283    &Mlkem768,
284    &Mlkem1024,
285    &Mlkem512,
286    &X25519,
287    &SecP256R1,
288    &SecP384R1,
289];
290
291/// 批准模式密钥交换组清单:ML-KEM 全部三个参数集(FIPS 203 批准;
292/// 纯组与 X25519MLKEM768 混合——X25519 进批准模式的通道)+ 经典
293/// P-256/P-384;无独立 X25519(SP 800-52r2 口径)。
294pub static FIPS_KX_GROUPS: &[&'static dyn SupportedKxGroup] = &[
295    &X25519Mlkem768,
296    &Mlkem768,
297    &Mlkem1024,
298    &Mlkem512,
299    &SecP256R1,
300    &SecP384R1,
301];
302
303// ---------------------------------------------------------------------------
304// 纯 ML-KEM 组(draft-ietf-tls-mlkem-key-agreement,0x0200–0x0202)
305// ---------------------------------------------------------------------------
306
307/// 宏展开三个纯 ML-KEM `SupportedKxGroup`。角色与线格式(KEM 角色与
308/// 混合组同构,仅去掉 X25519 分量):
309/// - 客户端 = 解封装方:share = ek(800/1184/1568 B);
310/// - 服务端 = 封装方:`start_and_complete` 覆写 + FIPS 203 §7.2 封装
311///   密钥检查,share = ct(768/1088/1568 B);
312/// - 共享秘密 = ss(32 B)。
313macro_rules! mlkem_pure_group {
314    ($group:ident, $active:ident, $sname:ident, $set:ident, $ng:expr, $doc:expr) => {
315        #[doc = $doc]
316        #[derive(Debug)]
317        pub struct $group;
318
319        /// 进行中的纯 ML-KEM 客户端侧密钥交换(持有解封装密钥)。
320        pub(crate) struct $active {
321            dk: ferritls_core::mlkem::$set::DecapsKey,
322            share: Vec<u8>,
323        }
324
325        impl std::fmt::Debug for $active {
326            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327                f.write_str(stringify!($active))
328            }
329        }
330
331        impl SupportedKxGroup for $group {
332            fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, RustlsError> {
333                let (ek, dk) =
334                    ferritls_core::mlkem::$set::generate_keypair().map_err(map_dh_err)?;
335                let share = ek.as_bytes().to_vec();
336                Ok(Box::new($active { dk, share }))
337            }
338
339            fn start_and_complete(
340                &self,
341                peer_pub_key: &[u8],
342            ) -> Result<CompletedKeyExchange, RustlsError> {
343                // 服务端:peer share = ek
344                let invalid = || RustlsError::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare);
345                if peer_pub_key.len() != ferritls_core::mlkem::$set::EK_BYTES {
346                    return Err(invalid());
347                }
348                // FIPS 203 §7.2 封装密钥检查(含模校验)在此发生
349                let ek = ferritls_core::mlkem::$set::EncapsKey::from_bytes(peer_pub_key)
350                    .map_err(|_| invalid())?;
351                let (ct, ss) = ferritls_core::mlkem::$set::encapsulate(&ek).map_err(map_dh_err)?;
352                Ok(CompletedKeyExchange {
353                    group: $ng,
354                    pub_key: ct.as_bytes().to_vec(),
355                    secret: SharedSecret::from(ss.expose_bytes().to_vec()),
356                })
357            }
358
359            fn name(&self) -> NamedGroup {
360                $ng
361            }
362
363            fn fips(&self) -> bool {
364                // 认证(阶段 C)落地前恒 false(AGENTS.md 规则 3)。
365                false
366            }
367        }
368
369        impl ActiveKeyExchange for $active {
370            fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, RustlsError> {
371                // 客户端:服务端 share = ct;密文长度不符必须中止
372                let invalid = || RustlsError::PeerMisbehaved(PeerMisbehaved::InvalidKeyShare);
373                if peer_pub_key.len() != ferritls_core::mlkem::$set::CT_BYTES {
374                    return Err(invalid());
375                }
376                let ct = ferritls_core::mlkem::$set::Ciphertext::from_bytes(peer_pub_key)
377                    .map_err(|_| invalid())?;
378                let ss = ferritls_core::mlkem::$set::decapsulate(&self.dk, &ct);
379                Ok(SharedSecret::from(ss.expose_bytes().to_vec()))
380            }
381
382            fn pub_key(&self) -> &[u8] {
383                &self.share
384            }
385
386            fn group(&self) -> NamedGroup {
387                $ng
388            }
389        }
390
391        #[doc = concat!(stringify!($group), " 组单例。")]
392        pub static $sname: &dyn SupportedKxGroup = &$group;
393    };
394}
395
396mlkem_pure_group!(
397    Mlkem512,
398    ActiveMlkem512,
399    MLKEM512_GROUP,
400    k512,
401    NamedGroup::MLKEM512,
402    "MLKEM512(draft-ietf-tls-mlkem-key-agreement,codepoint 0x0200)纯\nML-KEM 密钥交换组(M8.4):Cat 1 参数集(k = 2,η₁ = 3)。"
403);
404mlkem_pure_group!(
405    Mlkem768,
406    ActiveMlkem768,
407    MLKEM768_GROUP,
408    k768,
409    NamedGroup::MLKEM768,
410    "MLKEM768(draft-ietf-tls-mlkem-key-agreement,codepoint 0x0201)纯\nML-KEM 密钥交换组(M8.4):Cat 3 参数集(k = 3)。"
411);
412mlkem_pure_group!(
413    Mlkem1024,
414    ActiveMlkem1024,
415    MLKEM1024_GROUP,
416    k1024,
417    NamedGroup::MLKEM1024,
418    "MLKEM1024(draft-ietf-tls-mlkem-key-agreement,codepoint 0x0202)纯\nML-KEM 密钥交换组(M8.4):Cat 5 参数集(k = 4,du = 11)。"
419);