1use crate::{
2 hybrid::derive_hybrid_secret,
3 kem::{Kem, KemAlgorithm, KemInstance, SecretKey},
4 session::SecureSession,
5 OqsError,
6};
7
8use rand_core::OsRng;
9use sha2::{Digest, Sha256};
10use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
11
12const HANDSHAKE_CONTEXT: &[u8] = b"oqs-safe-v0.5.0-hybrid-handshake";
13const HANDSHAKE_TRANSCRIPT_DOMAIN: &[u8] = b"oqs-safe-v0.6.0-handshake-transcript";
14
15#[cfg_attr(
16 feature = "serialization",
17 derive(serde::Serialize, serde::Deserialize)
18)]
19#[derive(Clone, Debug)]
20pub struct ClientHello {
21 pub client_x25519_public: Vec<u8>,
22 pub client_kem_public: Vec<u8>,
23}
24
25#[cfg_attr(
26 feature = "serialization",
27 derive(serde::Serialize, serde::Deserialize)
28)]
29#[derive(Clone, Debug)]
30pub struct ServerHello {
31 pub server_x25519_public: Vec<u8>,
32 pub kem_ciphertext: Vec<u8>,
33}
34
35#[cfg(feature = "serialization")]
36impl ClientHello {
37 pub fn to_bytes(&self) -> Result<Vec<u8>, HandshakeError> {
38 bincode::serialize(self).map_err(|_| HandshakeError::InvalidHandshakeState)
39 }
40
41 pub fn from_bytes(bytes: &[u8]) -> Result<Self, HandshakeError> {
42 bincode::deserialize(bytes).map_err(|_| HandshakeError::InvalidHandshakeState)
43 }
44}
45
46#[cfg(feature = "serialization")]
47impl ServerHello {
48 pub fn to_bytes(&self) -> Result<Vec<u8>, HandshakeError> {
49 bincode::serialize(self).map_err(|_| HandshakeError::InvalidHandshakeState)
50 }
51
52 pub fn from_bytes(bytes: &[u8]) -> Result<Self, HandshakeError> {
53 bincode::deserialize(bytes).map_err(|_| HandshakeError::InvalidHandshakeState)
54 }
55}
56
57#[derive(Debug, Clone)]
58pub struct HandshakeTranscript {
59 hasher: Sha256,
60}
61
62impl HandshakeTranscript {
63 pub fn new() -> Self {
64 let mut hasher = Sha256::new();
65 hasher.update(HANDSHAKE_TRANSCRIPT_DOMAIN);
66 Self { hasher }
67 }
68
69 pub fn update_labelled(&mut self, label: &[u8], data: &[u8]) {
70 update_len_prefixed(&mut self.hasher, label);
71 update_len_prefixed(&mut self.hasher, data);
72 }
73
74 pub fn update_algorithm(&mut self, algorithm: KemAlgorithm) {
75 self.update_labelled(b"kem_algorithm", format!("{algorithm:?}").as_bytes());
76 }
77
78 pub fn update_client_hello(&mut self, client_hello: &ClientHello) {
79 self.update_labelled(b"client_x25519_public", &client_hello.client_x25519_public);
80 self.update_labelled(b"client_kem_public", &client_hello.client_kem_public);
81 }
82
83 pub fn update_server_hello(&mut self, server_hello: &ServerHello) {
84 self.update_labelled(b"server_x25519_public", &server_hello.server_x25519_public);
85 self.update_labelled(b"kem_ciphertext", &server_hello.kem_ciphertext);
86 }
87
88 pub fn finalize(self) -> [u8; 32] {
89 let digest = self.hasher.finalize();
90 let mut out = [0u8; 32];
91 out.copy_from_slice(&digest);
92 out
93 }
94}
95
96impl Default for HandshakeTranscript {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102fn update_len_prefixed(hasher: &mut Sha256, data: &[u8]) {
103 hasher.update((data.len() as u64).to_be_bytes());
104 hasher.update(data);
105}
106
107fn transcript_bound_context(transcript_hash: &[u8; 32]) -> Vec<u8> {
108 let mut context = Vec::with_capacity(HANDSHAKE_CONTEXT.len() + transcript_hash.len());
109 context.extend_from_slice(HANDSHAKE_CONTEXT);
110 context.extend_from_slice(transcript_hash);
111 context
112}
113
114#[derive(Debug)]
115pub enum HandshakeError {
116 MissingClientState,
117 MissingServerState,
118 InvalidHandshakeState,
119 CryptoError(OqsError),
120}
121
122impl core::fmt::Display for HandshakeError {
123 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124 match self {
125 HandshakeError::MissingClientState => write!(f, "missing client handshake state"),
126 HandshakeError::MissingServerState => write!(f, "missing server handshake state"),
127 HandshakeError::InvalidHandshakeState => write!(f, "invalid handshake state"),
128 HandshakeError::CryptoError(err) => write!(f, "cryptographic operation failed: {err}"),
129 }
130 }
131}
132
133impl std::error::Error for HandshakeError {}
134
135impl From<OqsError> for HandshakeError {
136 fn from(value: OqsError) -> Self {
137 HandshakeError::CryptoError(value)
138 }
139}
140
141pub struct HybridClient {
142 kem: KemInstance,
143 state: Option<ClientHandshakeState>,
144}
145
146struct ClientHandshakeState {
147 x25519_secret: StaticSecret,
148 kem_secret: SecretKey,
149 client_hello: ClientHello,
150}
151
152impl HybridClient {
153 pub fn new() -> Self {
154 Self {
155 kem: KemInstance::new(KemAlgorithm::MlKem768),
156 state: None,
157 }
158 }
159
160 pub fn with_algorithm(algorithm: KemAlgorithm) -> Self {
161 Self {
162 kem: KemInstance::new(algorithm),
163 state: None,
164 }
165 }
166
167 pub fn start_handshake(&mut self) -> Result<ClientHello, HandshakeError> {
168 let client_x25519_secret = StaticSecret::random_from_rng(OsRng);
169 let client_x25519_public = X25519PublicKey::from(&client_x25519_secret);
170
171 let (client_kem_public, client_kem_secret) = self.kem.keypair()?;
172
173 let client_hello = ClientHello {
174 client_x25519_public: client_x25519_public.as_bytes().to_vec(),
175 client_kem_public: client_kem_public.as_bytes().to_vec(),
176 };
177
178 self.state = Some(ClientHandshakeState {
179 x25519_secret: client_x25519_secret,
180 kem_secret: client_kem_secret,
181 client_hello: client_hello.clone(),
182 });
183
184 Ok(client_hello)
185 }
186
187 pub fn finish(&mut self, server_hello: ServerHello) -> Result<SecureSession, HandshakeError> {
188 let state = self
189 .state
190 .take()
191 .ok_or(HandshakeError::MissingClientState)?;
192
193 if server_hello.server_x25519_public.len() != 32 || server_hello.kem_ciphertext.is_empty() {
194 return Err(HandshakeError::InvalidHandshakeState);
195 }
196
197 let server_public_bytes: [u8; 32] = server_hello
198 .server_x25519_public
199 .as_slice()
200 .try_into()
201 .map_err(|_| HandshakeError::InvalidHandshakeState)?;
202
203 let server_x25519_public = X25519PublicKey::from(server_public_bytes);
204 let classical_secret = state.x25519_secret.diffie_hellman(&server_x25519_public);
205
206 let pqc_secret = client_pqc_secret(
207 self.kem.algorithm(),
208 &server_hello.kem_ciphertext,
209 &state.kem_secret,
210 )?;
211
212 let mut transcript = HandshakeTranscript::new();
213 transcript.update_algorithm(self.kem.algorithm());
214 transcript.update_client_hello(&state.client_hello);
215 transcript.update_server_hello(&server_hello);
216 let transcript_hash = transcript.finalize();
217
218 let context = transcript_bound_context(&transcript_hash);
219
220 let hybrid_secret = derive_hybrid_secret(
221 pqc_secret.as_slice(),
222 classical_secret.as_bytes(),
223 context.as_slice(),
224 );
225
226 Ok(SecureSession::new(hybrid_secret.as_bytes().to_vec()))
227 }
228}
229
230impl Default for HybridClient {
231 fn default() -> Self {
232 Self::new()
233 }
234}
235
236pub struct HybridServer {
237 kem: KemInstance,
238 session: Option<SecureSession>,
239}
240
241impl HybridServer {
242 pub fn new() -> Self {
243 Self {
244 kem: KemInstance::new(KemAlgorithm::MlKem768),
245 session: None,
246 }
247 }
248
249 pub fn with_algorithm(algorithm: KemAlgorithm) -> Self {
250 Self {
251 kem: KemInstance::new(algorithm),
252 session: None,
253 }
254 }
255
256 pub fn respond(&mut self, client_hello: ClientHello) -> Result<ServerHello, HandshakeError> {
257 if client_hello.client_x25519_public.len() != 32
258 || client_hello.client_kem_public.is_empty()
259 {
260 return Err(HandshakeError::InvalidHandshakeState);
261 }
262
263 let client_public_bytes: [u8; 32] = client_hello
264 .client_x25519_public
265 .as_slice()
266 .try_into()
267 .map_err(|_| HandshakeError::InvalidHandshakeState)?;
268
269 let client_x25519_public = X25519PublicKey::from(client_public_bytes);
270
271 let server_x25519_secret = StaticSecret::random_from_rng(OsRng);
272 let server_x25519_public = X25519PublicKey::from(&server_x25519_secret);
273
274 let classical_secret = server_x25519_secret.diffie_hellman(&client_x25519_public);
275
276 let (kem_ciphertext, pqc_secret) =
277 server_pqc_secret(self.kem.algorithm(), &client_hello.client_kem_public)?;
278
279 let server_hello = ServerHello {
280 server_x25519_public: server_x25519_public.as_bytes().to_vec(),
281 kem_ciphertext,
282 };
283
284 let mut transcript = HandshakeTranscript::new();
285 transcript.update_algorithm(self.kem.algorithm());
286 transcript.update_client_hello(&client_hello);
287 transcript.update_server_hello(&server_hello);
288 let transcript_hash = transcript.finalize();
289
290 let context = transcript_bound_context(&transcript_hash);
291
292 let hybrid_secret = derive_hybrid_secret(
293 pqc_secret.as_slice(),
294 classical_secret.as_bytes(),
295 context.as_slice(),
296 );
297
298 self.session = Some(SecureSession::new(hybrid_secret.as_bytes().to_vec()));
299
300 Ok(server_hello)
301 }
302
303 pub fn session(&self) -> Result<&SecureSession, HandshakeError> {
304 self.session
305 .as_ref()
306 .ok_or(HandshakeError::MissingServerState)
307 }
308}
309
310impl Default for HybridServer {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316#[cfg(feature = "liboqs")]
317fn server_pqc_secret(
318 algorithm: KemAlgorithm,
319 client_kem_public: &[u8],
320) -> Result<(Vec<u8>, Vec<u8>), HandshakeError> {
321 use crate::kem::PublicKey;
322
323 let kem = KemInstance::new(algorithm);
324 let client_public_key = PublicKey::new(algorithm, client_kem_public.to_vec());
325
326 let (ciphertext, shared_secret) = kem.encapsulate(&client_public_key)?;
327
328 Ok((
329 ciphertext.as_bytes().to_vec(),
330 shared_secret.as_bytes().to_vec(),
331 ))
332}
333
334#[cfg(feature = "liboqs")]
335fn client_pqc_secret(
336 algorithm: KemAlgorithm,
337 kem_ciphertext: &[u8],
338 kem_secret: &SecretKey,
339) -> Result<Vec<u8>, HandshakeError> {
340 use crate::kem::Ciphertext;
341
342 let kem = KemInstance::new(algorithm);
343 let ciphertext = Ciphertext::new(algorithm, kem_ciphertext.to_vec());
344
345 let shared_secret = kem.decapsulate(&ciphertext, kem_secret)?;
346
347 Ok(shared_secret.as_bytes().to_vec())
348}
349
350#[cfg(not(feature = "liboqs"))]
351fn server_pqc_secret(
352 algorithm: KemAlgorithm,
353 client_kem_public: &[u8],
354) -> Result<(Vec<u8>, Vec<u8>), HandshakeError> {
355 let ciphertext = mock_ciphertext(algorithm, client_kem_public);
356 let shared_secret = mock_shared_secret(algorithm, &ciphertext);
357
358 Ok((ciphertext, shared_secret))
359}
360
361#[cfg(not(feature = "liboqs"))]
362fn client_pqc_secret(
363 algorithm: KemAlgorithm,
364 kem_ciphertext: &[u8],
365 kem_secret: &SecretKey,
366) -> Result<Vec<u8>, HandshakeError> {
367 let _ = kem_secret;
368
369 Ok(mock_shared_secret(algorithm, kem_ciphertext))
370}
371
372#[cfg(not(feature = "liboqs"))]
373fn mock_ciphertext(algorithm: KemAlgorithm, client_kem_public: &[u8]) -> Vec<u8> {
374 let mut ciphertext = vec![0u8; algorithm.ciphertext_len()];
375 let mut counter = 0u64;
376 let mut offset = 0usize;
377
378 while offset < ciphertext.len() {
379 let mut hasher = Sha256::new();
380 hasher.update(b"oqs-safe-v0.5.0-mock-ciphertext");
381 hasher.update(client_kem_public);
382 hasher.update(counter.to_le_bytes());
383
384 let block = hasher.finalize();
385 let take = core::cmp::min(block.len(), ciphertext.len() - offset);
386
387 ciphertext[offset..offset + take].copy_from_slice(&block[..take]);
388 offset += take;
389 counter += 1;
390 }
391
392 ciphertext
393}
394
395#[cfg(not(feature = "liboqs"))]
396fn mock_shared_secret(algorithm: KemAlgorithm, kem_ciphertext: &[u8]) -> Vec<u8> {
397 let mut hasher = Sha256::new();
398
399 hasher.update(b"oqs-safe-v0.5.0-mock-pqc-secret");
400 hasher.update(format!("{algorithm:?}").as_bytes());
401 hasher.update(kem_ciphertext);
402
403 hasher.finalize().to_vec()
404}
405
406#[cfg(test)]
407mod transcript_tests {
408 use super::*;
409
410 #[test]
411 fn same_transcript_inputs_produce_same_hash() {
412 let client_hello = ClientHello {
413 client_x25519_public: vec![1; 32],
414 client_kem_public: vec![2; 1184],
415 };
416
417 let server_hello = ServerHello {
418 server_x25519_public: vec![3; 32],
419 kem_ciphertext: vec![4; 1088],
420 };
421
422 let mut t1 = HandshakeTranscript::new();
423 t1.update_algorithm(KemAlgorithm::MlKem768);
424 t1.update_client_hello(&client_hello);
425 t1.update_server_hello(&server_hello);
426
427 let mut t2 = HandshakeTranscript::new();
428 t2.update_algorithm(KemAlgorithm::MlKem768);
429 t2.update_client_hello(&client_hello);
430 t2.update_server_hello(&server_hello);
431
432 assert_eq!(t1.finalize(), t2.finalize());
433 }
434
435 #[test]
436 fn transcript_hash_changes_when_client_hello_changes() {
437 let client_hello_a = ClientHello {
438 client_x25519_public: vec![1; 32],
439 client_kem_public: vec![2; 1184],
440 };
441
442 let client_hello_b = ClientHello {
443 client_x25519_public: vec![9; 32],
444 client_kem_public: vec![2; 1184],
445 };
446
447 let server_hello = ServerHello {
448 server_x25519_public: vec![3; 32],
449 kem_ciphertext: vec![4; 1088],
450 };
451
452 let mut t1 = HandshakeTranscript::new();
453 t1.update_algorithm(KemAlgorithm::MlKem768);
454 t1.update_client_hello(&client_hello_a);
455 t1.update_server_hello(&server_hello);
456
457 let mut t2 = HandshakeTranscript::new();
458 t2.update_algorithm(KemAlgorithm::MlKem768);
459 t2.update_client_hello(&client_hello_b);
460 t2.update_server_hello(&server_hello);
461
462 assert_ne!(t1.finalize(), t2.finalize());
463 }
464
465 #[test]
466 fn transcript_hash_changes_when_server_hello_changes() {
467 let client_hello = ClientHello {
468 client_x25519_public: vec![1; 32],
469 client_kem_public: vec![2; 1184],
470 };
471
472 let server_hello_a = ServerHello {
473 server_x25519_public: vec![3; 32],
474 kem_ciphertext: vec![4; 1088],
475 };
476
477 let server_hello_b = ServerHello {
478 server_x25519_public: vec![3; 32],
479 kem_ciphertext: vec![8; 1088],
480 };
481
482 let mut t1 = HandshakeTranscript::new();
483 t1.update_algorithm(KemAlgorithm::MlKem768);
484 t1.update_client_hello(&client_hello);
485 t1.update_server_hello(&server_hello_a);
486
487 let mut t2 = HandshakeTranscript::new();
488 t2.update_algorithm(KemAlgorithm::MlKem768);
489 t2.update_client_hello(&client_hello);
490 t2.update_server_hello(&server_hello_b);
491
492 assert_ne!(t1.finalize(), t2.finalize());
493 }
494
495 #[test]
496 fn transcript_hash_changes_when_algorithm_changes() {
497 let client_hello = ClientHello {
498 client_x25519_public: vec![1; 32],
499 client_kem_public: vec![2; 1184],
500 };
501
502 let server_hello = ServerHello {
503 server_x25519_public: vec![3; 32],
504 kem_ciphertext: vec![4; 1088],
505 };
506
507 let mut t1 = HandshakeTranscript::new();
508 t1.update_algorithm(KemAlgorithm::MlKem512);
509 t1.update_client_hello(&client_hello);
510 t1.update_server_hello(&server_hello);
511
512 let mut t2 = HandshakeTranscript::new();
513 t2.update_algorithm(KemAlgorithm::MlKem768);
514 t2.update_client_hello(&client_hello);
515 t2.update_server_hello(&server_hello);
516
517 assert_ne!(t1.finalize(), t2.finalize());
518 }
519
520 #[test]
521 fn transcript_bound_context_is_deterministic() {
522 let transcript_hash = [7u8; 32];
523
524 let context_a = transcript_bound_context(&transcript_hash);
525 let context_b = transcript_bound_context(&transcript_hash);
526
527 assert_eq!(context_a, context_b);
528 assert!(context_a.starts_with(HANDSHAKE_CONTEXT));
529 assert!(context_a.ends_with(&transcript_hash));
530 }
531}
532
533#[cfg(all(test, feature = "serialization"))]
534mod serialization_tests {
535 use super::*;
536
537 #[test]
538 fn client_hello_roundtrips_through_bytes() {
539 let client_hello = ClientHello {
540 client_x25519_public: vec![1; 32],
541 client_kem_public: vec![2; 1184],
542 };
543
544 let encoded = client_hello
545 .to_bytes()
546 .expect("client hello should serialize");
547
548 let decoded = ClientHello::from_bytes(&encoded).expect("client hello should deserialize");
549
550 assert_eq!(
551 decoded.client_x25519_public,
552 client_hello.client_x25519_public
553 );
554 assert_eq!(decoded.client_kem_public, client_hello.client_kem_public);
555 }
556
557 #[test]
558 fn server_hello_roundtrips_through_bytes() {
559 let server_hello = ServerHello {
560 server_x25519_public: vec![3; 32],
561 kem_ciphertext: vec![4; 1088],
562 };
563
564 let encoded = server_hello
565 .to_bytes()
566 .expect("server hello should serialize");
567
568 let decoded = ServerHello::from_bytes(&encoded).expect("server hello should deserialize");
569
570 assert_eq!(
571 decoded.server_x25519_public,
572 server_hello.server_x25519_public
573 );
574 assert_eq!(decoded.kem_ciphertext, server_hello.kem_ciphertext);
575 }
576
577 #[test]
578 fn invalid_client_hello_bytes_fail_to_deserialize() {
579 let invalid = b"not-a-valid-client-hello";
580
581 assert!(ClientHello::from_bytes(invalid).is_err());
582 }
583
584 #[test]
585 fn invalid_server_hello_bytes_fail_to_deserialize() {
586 let invalid = b"not-a-valid-server-hello";
587
588 assert!(ServerHello::from_bytes(invalid).is_err());
589 }
590}