we (web engine): Experimental web browser project to understand the limits of Claude
1//! TLS 1.3 handshake client (RFC 8446).
2//!
3//! Implements the full TLS 1.3 client handshake including:
4//! - ClientHello with required extensions
5//! - ServerHello processing and ECDHE key exchange
6//! - Encrypted handshake message processing
7//! - Certificate verification
8//! - Finished message exchange
9//! - TlsStream for application data
10
11use std::io::{self, Read, Write};
12
13use we_crypto::sha2::{sha256, sha384};
14use we_crypto::x25519::{x25519, x25519_base};
15use we_crypto::x509::{self, Certificate, DateTime};
16
17use super::key_schedule::{KeySchedule, TranscriptHash};
18use super::record::{
19 CipherSuite, ContentType, RecordCryptoState, RecordLayer, TlsError, TlsRecord,
20};
21
22// ---------------------------------------------------------------------------
23// Constants
24// ---------------------------------------------------------------------------
25
26/// TLS 1.2 legacy version (used in ClientHello).
27const LEGACY_VERSION: [u8; 2] = [0x03, 0x03];
28
29/// TLS 1.3 version identifier for supported_versions extension.
30const TLS13_VERSION: [u8; 2] = [0x03, 0x04];
31
32// Handshake message types (RFC 8446 §4)
33const HANDSHAKE_CLIENT_HELLO: u8 = 1;
34const HANDSHAKE_SERVER_HELLO: u8 = 2;
35const HANDSHAKE_ENCRYPTED_EXTENSIONS: u8 = 8;
36const HANDSHAKE_CERTIFICATE: u8 = 11;
37const HANDSHAKE_CERTIFICATE_VERIFY: u8 = 15;
38const HANDSHAKE_FINISHED: u8 = 20;
39
40// Extension types (RFC 8446 §4.2)
41const EXT_SERVER_NAME: u16 = 0;
42const EXT_SUPPORTED_GROUPS: u16 = 10;
43const EXT_SIGNATURE_ALGORITHMS: u16 = 13;
44const EXT_SUPPORTED_VERSIONS: u16 = 43;
45const EXT_KEY_SHARE: u16 = 51;
46
47// Named groups
48const GROUP_X25519: u16 = 0x001d;
49
50// Signature schemes (RFC 8446 §4.2.3)
51const SIG_RSA_PKCS1_SHA256: u16 = 0x0401;
52const SIG_RSA_PKCS1_SHA384: u16 = 0x0501;
53const SIG_RSA_PKCS1_SHA512: u16 = 0x0601;
54const SIG_ECDSA_SECP256R1_SHA256: u16 = 0x0403;
55const SIG_ECDSA_SECP384R1_SHA384: u16 = 0x0503;
56const SIG_RSA_PSS_RSAE_SHA256: u16 = 0x0804;
57const SIG_RSA_PSS_RSAE_SHA384: u16 = 0x0805;
58
59// Cipher suite wire values (RFC 8446 §B.4)
60const CS_AES_128_GCM_SHA256: [u8; 2] = [0x13, 0x01];
61const CS_AES_256_GCM_SHA384: [u8; 2] = [0x13, 0x02];
62const CS_CHACHA20_POLY1305_SHA256: [u8; 2] = [0x13, 0x03];
63
64// CertificateVerify context string (RFC 8446 §4.4.3)
65const CV_SERVER_CONTEXT: &[u8] = b"TLS 1.3, server CertificateVerify";
66
67// ---------------------------------------------------------------------------
68// Error types
69// ---------------------------------------------------------------------------
70
71/// Handshake-specific errors.
72#[derive(Debug)]
73pub enum HandshakeError {
74 /// TLS record layer error.
75 Tls(TlsError),
76 /// Unexpected handshake message type.
77 UnexpectedMessage(u8),
78 /// Server selected unsupported cipher suite.
79 UnsupportedCipherSuite,
80 /// Server selected unsupported version.
81 UnsupportedVersion,
82 /// Server did not provide a key share.
83 MissingKeyShare,
84 /// Server key share uses unsupported group.
85 UnsupportedGroup,
86 /// Missing required extension.
87 MissingExtension(&'static str),
88 /// Certificate chain is empty.
89 EmptyCertificateChain,
90 /// Certificate verification failed.
91 CertificateError(String),
92 /// CertificateVerify signature verification failed.
93 SignatureVerificationFailed,
94 /// Finished verify data mismatch.
95 FinishedMismatch,
96 /// Message too short or malformed.
97 Malformed(&'static str),
98 /// I/O error.
99 Io(io::Error),
100}
101
102impl std::fmt::Display for HandshakeError {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 match self {
105 Self::Tls(e) => write!(f, "TLS error: {e}"),
106 Self::UnexpectedMessage(t) => write!(f, "unexpected handshake message type: {t}"),
107 Self::UnsupportedCipherSuite => write!(f, "unsupported cipher suite"),
108 Self::UnsupportedVersion => write!(f, "unsupported TLS version"),
109 Self::MissingKeyShare => write!(f, "server did not provide key share"),
110 Self::UnsupportedGroup => write!(f, "unsupported key exchange group"),
111 Self::MissingExtension(ext) => write!(f, "missing extension: {ext}"),
112 Self::EmptyCertificateChain => write!(f, "empty certificate chain"),
113 Self::CertificateError(e) => write!(f, "certificate error: {e}"),
114 Self::SignatureVerificationFailed => write!(f, "signature verification failed"),
115 Self::FinishedMismatch => write!(f, "finished verify data mismatch"),
116 Self::Malformed(msg) => write!(f, "malformed message: {msg}"),
117 Self::Io(e) => write!(f, "I/O error: {e}"),
118 }
119 }
120}
121
122impl From<TlsError> for HandshakeError {
123 fn from(err: TlsError) -> Self {
124 HandshakeError::Tls(err)
125 }
126}
127
128impl From<io::Error> for HandshakeError {
129 fn from(err: io::Error) -> Self {
130 HandshakeError::Io(err)
131 }
132}
133
134type Result<T> = std::result::Result<T, HandshakeError>;
135
136// ---------------------------------------------------------------------------
137// Encoding helpers
138// ---------------------------------------------------------------------------
139
140fn push_u8(buf: &mut Vec<u8>, val: u8) {
141 buf.push(val);
142}
143
144fn push_u16(buf: &mut Vec<u8>, val: u16) {
145 buf.extend_from_slice(&val.to_be_bytes());
146}
147
148fn push_u24(buf: &mut Vec<u8>, val: u32) {
149 buf.push((val >> 16) as u8);
150 buf.push((val >> 8) as u8);
151 buf.push(val as u8);
152}
153
154fn push_bytes(buf: &mut Vec<u8>, data: &[u8]) {
155 buf.extend_from_slice(data);
156}
157
158fn read_u8(data: &[u8], offset: &mut usize) -> Result<u8> {
159 if *offset >= data.len() {
160 return Err(HandshakeError::Malformed("unexpected end of data"));
161 }
162 let val = data[*offset];
163 *offset += 1;
164 Ok(val)
165}
166
167fn read_u16(data: &[u8], offset: &mut usize) -> Result<u16> {
168 if *offset + 2 > data.len() {
169 return Err(HandshakeError::Malformed("unexpected end of data"));
170 }
171 let val = u16::from_be_bytes([data[*offset], data[*offset + 1]]);
172 *offset += 2;
173 Ok(val)
174}
175
176fn read_u24(data: &[u8], offset: &mut usize) -> Result<u32> {
177 if *offset + 3 > data.len() {
178 return Err(HandshakeError::Malformed("unexpected end of data"));
179 }
180 let val =
181 (data[*offset] as u32) << 16 | (data[*offset + 1] as u32) << 8 | data[*offset + 2] as u32;
182 *offset += 3;
183 Ok(val)
184}
185
186fn read_bytes<'a>(data: &'a [u8], offset: &mut usize, len: usize) -> Result<&'a [u8]> {
187 if *offset + len > data.len() {
188 return Err(HandshakeError::Malformed("unexpected end of data"));
189 }
190 let slice = &data[*offset..*offset + len];
191 *offset += len;
192 Ok(slice)
193}
194
195// ---------------------------------------------------------------------------
196// Random bytes generation (using std for now)
197// ---------------------------------------------------------------------------
198
199fn random_bytes(buf: &mut [u8]) {
200 // Read from /dev/urandom for random bytes.
201 // This is available on macOS (which is our only target).
202 let mut f = std::fs::File::open("/dev/urandom").expect("failed to open /dev/urandom");
203 f.read_exact(buf).expect("failed to read /dev/urandom");
204}
205
206// ---------------------------------------------------------------------------
207// ClientHello construction
208// ---------------------------------------------------------------------------
209
210/// Build a ClientHello handshake message.
211///
212/// Returns (handshake_message, x25519_private_key).
213fn build_client_hello(server_name: &str) -> (Vec<u8>, [u8; 32]) {
214 // Generate X25519 ephemeral keypair
215 let mut private_key = [0u8; 32];
216 random_bytes(&mut private_key);
217 let public_key = x25519_base(&private_key);
218
219 // Generate random
220 let mut client_random = [0u8; 32];
221 random_bytes(&mut client_random);
222
223 // Generate legacy session ID (32 random bytes)
224 let mut session_id = [0u8; 32];
225 random_bytes(&mut session_id);
226
227 // Build ClientHello body
228 let mut body = Vec::with_capacity(512);
229
230 // Protocol version: TLS 1.2 (legacy)
231 push_bytes(&mut body, &LEGACY_VERSION);
232
233 // Random (32 bytes)
234 push_bytes(&mut body, &client_random);
235
236 // Legacy session ID (length-prefixed)
237 push_u8(&mut body, 32);
238 push_bytes(&mut body, &session_id);
239
240 // Cipher suites
241 push_u16(&mut body, 6); // 3 suites * 2 bytes
242 push_bytes(&mut body, &CS_AES_128_GCM_SHA256);
243 push_bytes(&mut body, &CS_AES_256_GCM_SHA384);
244 push_bytes(&mut body, &CS_CHACHA20_POLY1305_SHA256);
245
246 // Compression methods: only null
247 push_u8(&mut body, 1); // length
248 push_u8(&mut body, 0); // null
249
250 // Extensions
251 let extensions = build_extensions(server_name, &public_key);
252 push_u16(&mut body, extensions.len() as u16);
253 push_bytes(&mut body, &extensions);
254
255 // Wrap in handshake header
256 let mut msg = Vec::with_capacity(4 + body.len());
257 push_u8(&mut msg, HANDSHAKE_CLIENT_HELLO);
258 push_u24(&mut msg, body.len() as u32);
259 push_bytes(&mut msg, &body);
260
261 (msg, private_key)
262}
263
264fn build_extensions(server_name: &str, x25519_public: &[u8; 32]) -> Vec<u8> {
265 let mut exts = Vec::with_capacity(256);
266
267 // SNI extension (server_name)
268 {
269 let name_bytes = server_name.as_bytes();
270 // ServerNameList: list_length(2) + type(1) + name_length(2) + name
271 let sni_data_len = 2 + 1 + 2 + name_bytes.len();
272 push_u16(&mut exts, EXT_SERVER_NAME);
273 push_u16(&mut exts, sni_data_len as u16);
274 // ServerNameList length
275 push_u16(&mut exts, (1 + 2 + name_bytes.len()) as u16);
276 push_u8(&mut exts, 0); // host_name type
277 push_u16(&mut exts, name_bytes.len() as u16);
278 push_bytes(&mut exts, name_bytes);
279 }
280
281 // supported_versions extension
282 {
283 push_u16(&mut exts, EXT_SUPPORTED_VERSIONS);
284 push_u16(&mut exts, 3); // extension data length
285 push_u8(&mut exts, 2); // list length
286 push_bytes(&mut exts, &TLS13_VERSION);
287 }
288
289 // supported_groups extension
290 {
291 push_u16(&mut exts, EXT_SUPPORTED_GROUPS);
292 push_u16(&mut exts, 4); // extension data length
293 push_u16(&mut exts, 2); // list length
294 push_u16(&mut exts, GROUP_X25519);
295 }
296
297 // key_share extension
298 {
299 // KeyShareEntry: group(2) + key_length(2) + key(32)
300 let entry_len = 2 + 2 + 32;
301 push_u16(&mut exts, EXT_KEY_SHARE);
302 push_u16(&mut exts, (2 + entry_len) as u16); // extension data length
303 push_u16(&mut exts, entry_len as u16); // client_shares length
304 push_u16(&mut exts, GROUP_X25519);
305 push_u16(&mut exts, 32);
306 push_bytes(&mut exts, x25519_public);
307 }
308
309 // signature_algorithms extension
310 {
311 let sig_algs = [
312 SIG_ECDSA_SECP256R1_SHA256,
313 SIG_ECDSA_SECP384R1_SHA384,
314 SIG_RSA_PSS_RSAE_SHA256,
315 SIG_RSA_PSS_RSAE_SHA384,
316 SIG_RSA_PKCS1_SHA256,
317 SIG_RSA_PKCS1_SHA384,
318 SIG_RSA_PKCS1_SHA512,
319 ];
320 let list_len = sig_algs.len() * 2;
321 push_u16(&mut exts, EXT_SIGNATURE_ALGORITHMS);
322 push_u16(&mut exts, (2 + list_len) as u16);
323 push_u16(&mut exts, list_len as u16);
324 for alg in sig_algs {
325 push_u16(&mut exts, alg);
326 }
327 }
328
329 exts
330}
331
332// ---------------------------------------------------------------------------
333// ServerHello parsing
334// ---------------------------------------------------------------------------
335
336struct ServerHelloResult {
337 cipher_suite: CipherSuite,
338 server_x25519_public: [u8; 32],
339}
340
341fn parse_server_hello(data: &[u8]) -> Result<ServerHelloResult> {
342 let mut offset = 0;
343
344 // legacy_version (2)
345 let _legacy_version = read_bytes(data, &mut offset, 2)?;
346
347 // random (32)
348 let _random = read_bytes(data, &mut offset, 32)?;
349
350 // legacy_session_id_echo
351 let session_id_len = read_u8(data, &mut offset)? as usize;
352 let _session_id = read_bytes(data, &mut offset, session_id_len)?;
353
354 // cipher_suite (2)
355 let cs_bytes = read_bytes(data, &mut offset, 2)?;
356 let cipher_suite = match [cs_bytes[0], cs_bytes[1]] {
357 CS_AES_128_GCM_SHA256 => CipherSuite::Aes128Gcm,
358 CS_AES_256_GCM_SHA384 => CipherSuite::Aes256Gcm,
359 CS_CHACHA20_POLY1305_SHA256 => CipherSuite::Chacha20Poly1305,
360 _ => return Err(HandshakeError::UnsupportedCipherSuite),
361 };
362
363 // legacy_compression_method (1)
364 let _compression = read_u8(data, &mut offset)?;
365
366 // Extensions
367 let extensions_len = read_u16(data, &mut offset)? as usize;
368 let extensions_end = offset + extensions_len;
369
370 let mut server_key: Option<[u8; 32]> = None;
371 let mut has_supported_versions = false;
372
373 while offset < extensions_end {
374 let ext_type = read_u16(data, &mut offset)?;
375 let ext_len = read_u16(data, &mut offset)? as usize;
376 let ext_data = read_bytes(data, &mut offset, ext_len)?;
377
378 match ext_type {
379 EXT_KEY_SHARE => {
380 let mut eoff = 0;
381 let group = read_u16(ext_data, &mut eoff)?;
382 if group != GROUP_X25519 {
383 return Err(HandshakeError::UnsupportedGroup);
384 }
385 let key_len = read_u16(ext_data, &mut eoff)? as usize;
386 if key_len != 32 {
387 return Err(HandshakeError::Malformed("invalid x25519 key length"));
388 }
389 let key_data = read_bytes(ext_data, &mut eoff, 32)?;
390 let mut key = [0u8; 32];
391 key.copy_from_slice(key_data);
392 server_key = Some(key);
393 }
394 EXT_SUPPORTED_VERSIONS => {
395 if ext_data.len() >= 2 && ext_data[0] == 0x03 && ext_data[1] == 0x04 {
396 has_supported_versions = true;
397 }
398 }
399 _ => {} // Ignore unknown extensions
400 }
401 }
402
403 if !has_supported_versions {
404 return Err(HandshakeError::UnsupportedVersion);
405 }
406
407 let server_x25519_public = server_key.ok_or(HandshakeError::MissingKeyShare)?;
408
409 Ok(ServerHelloResult {
410 cipher_suite,
411 server_x25519_public,
412 })
413}
414
415// ---------------------------------------------------------------------------
416// Encrypted handshake message parsing
417// ---------------------------------------------------------------------------
418
419fn parse_encrypted_extensions(data: &[u8]) -> Result<()> {
420 let mut offset = 0;
421 let _extensions_len = read_u16(data, &mut offset)?;
422 // We don't require any specific encrypted extensions for now.
423 // Just validate the format is parseable.
424 Ok(())
425}
426
427/// Parse a Certificate handshake message (RFC 8446 §4.4.2).
428/// Returns the list of DER-encoded certificates.
429fn parse_certificate_message(data: &[u8]) -> Result<Vec<Vec<u8>>> {
430 let mut offset = 0;
431
432 // certificate_request_context (opaque <0..255>)
433 let ctx_len = read_u8(data, &mut offset)? as usize;
434 let _ctx = read_bytes(data, &mut offset, ctx_len)?;
435
436 // certificate_list length (3 bytes)
437 let list_len = read_u24(data, &mut offset)? as usize;
438 let list_end = offset + list_len;
439
440 let mut certs = Vec::new();
441 while offset < list_end {
442 // cert_data length (3 bytes)
443 let cert_len = read_u24(data, &mut offset)? as usize;
444 let cert_data = read_bytes(data, &mut offset, cert_len)?;
445 certs.push(cert_data.to_vec());
446
447 // extensions length (2 bytes per cert entry)
448 let ext_len = read_u16(data, &mut offset)? as usize;
449 let _ext = read_bytes(data, &mut offset, ext_len)?;
450 }
451
452 if certs.is_empty() {
453 return Err(HandshakeError::EmptyCertificateChain);
454 }
455
456 Ok(certs)
457}
458
459/// Parse and verify a CertificateVerify message (RFC 8446 §4.4.3).
460fn verify_certificate_verify(
461 data: &[u8],
462 cert: &Certificate,
463 transcript_hash: &[u8],
464) -> Result<()> {
465 let mut offset = 0;
466
467 let scheme = read_u16(data, &mut offset)?;
468 let sig_len = read_u16(data, &mut offset)? as usize;
469 let signature = read_bytes(data, &mut offset, sig_len)?;
470
471 // Build the content to verify:
472 // 64 spaces + context string + 0x00 + transcript hash
473 let mut content = Vec::with_capacity(64 + CV_SERVER_CONTEXT.len() + 1 + transcript_hash.len());
474 content.extend_from_slice(&[0x20u8; 64]);
475 content.extend_from_slice(CV_SERVER_CONTEXT);
476 content.push(0x00);
477 content.extend_from_slice(transcript_hash);
478
479 match scheme {
480 SIG_RSA_PKCS1_SHA256 => {
481 let pubkey = we_crypto::rsa::RsaPublicKey::from_der(&cert.subject_public_key_info)
482 .map_err(|e| HandshakeError::CertificateError(format!("RSA key: {e:?}")))?;
483 pubkey
484 .verify_pkcs1v15(we_crypto::rsa::HashAlgorithm::Sha256, &content, signature)
485 .map_err(|_| HandshakeError::SignatureVerificationFailed)?;
486 }
487 SIG_RSA_PKCS1_SHA384 => {
488 let pubkey = we_crypto::rsa::RsaPublicKey::from_der(&cert.subject_public_key_info)
489 .map_err(|e| HandshakeError::CertificateError(format!("RSA key: {e:?}")))?;
490 pubkey
491 .verify_pkcs1v15(we_crypto::rsa::HashAlgorithm::Sha384, &content, signature)
492 .map_err(|_| HandshakeError::SignatureVerificationFailed)?;
493 }
494 SIG_RSA_PKCS1_SHA512 => {
495 let pubkey = we_crypto::rsa::RsaPublicKey::from_der(&cert.subject_public_key_info)
496 .map_err(|e| HandshakeError::CertificateError(format!("RSA key: {e:?}")))?;
497 pubkey
498 .verify_pkcs1v15(we_crypto::rsa::HashAlgorithm::Sha512, &content, signature)
499 .map_err(|_| HandshakeError::SignatureVerificationFailed)?;
500 }
501 SIG_ECDSA_SECP256R1_SHA256 => {
502 let pubkey =
503 we_crypto::ecdsa::EcdsaPublicKey::from_spki_der(&cert.subject_public_key_info)
504 .map_err(|e| HandshakeError::CertificateError(format!("ECDSA key: {e:?}")))?;
505 let sig = we_crypto::ecdsa::EcdsaSignature::from_der(signature)
506 .map_err(|e| HandshakeError::CertificateError(format!("ECDSA sig: {e:?}")))?;
507 // Hash the content with SHA-256 and verify
508 let hash = sha256(&content);
509 pubkey
510 .verify_prehashed(&hash, &sig)
511 .map_err(|_| HandshakeError::SignatureVerificationFailed)?;
512 }
513 SIG_ECDSA_SECP384R1_SHA384 => {
514 let pubkey =
515 we_crypto::ecdsa::EcdsaPublicKey::from_spki_der(&cert.subject_public_key_info)
516 .map_err(|e| HandshakeError::CertificateError(format!("ECDSA key: {e:?}")))?;
517 let sig = we_crypto::ecdsa::EcdsaSignature::from_der(signature)
518 .map_err(|e| HandshakeError::CertificateError(format!("ECDSA sig: {e:?}")))?;
519 let hash = sha384(&content);
520 pubkey
521 .verify_prehashed(&hash, &sig)
522 .map_err(|_| HandshakeError::SignatureVerificationFailed)?;
523 }
524 SIG_RSA_PSS_RSAE_SHA256 | SIG_RSA_PSS_RSAE_SHA384 => {
525 // RSA-PSS is common in TLS 1.3. For now, we accept the connection
526 // if we can't verify PSS signatures, but we should implement it.
527 // TODO: Implement RSA-PSS signature verification
528 // For now, skip verification for PSS schemes.
529 // This is a known limitation.
530 return Err(HandshakeError::SignatureVerificationFailed);
531 }
532 _ => {
533 return Err(HandshakeError::SignatureVerificationFailed);
534 }
535 }
536
537 Ok(())
538}
539
540// ---------------------------------------------------------------------------
541// Handshake message reading
542// ---------------------------------------------------------------------------
543
544/// Read the next handshake message from the record layer.
545/// Returns (handshake_type, body_bytes, full_handshake_message).
546fn read_handshake_message<S: Read + Write>(
547 record_layer: &mut RecordLayer<S>,
548) -> Result<(u8, Vec<u8>, Vec<u8>)> {
549 let record = record_layer.read_record()?;
550
551 if record.content_type != ContentType::Handshake {
552 return Err(HandshakeError::Malformed("expected handshake record"));
553 }
554
555 let data = &record.data;
556 if data.len() < 4 {
557 return Err(HandshakeError::Malformed("handshake message too short"));
558 }
559
560 let msg_type = data[0];
561 let body_len = (data[1] as usize) << 16 | (data[2] as usize) << 8 | data[3] as usize;
562
563 if data.len() < 4 + body_len {
564 return Err(HandshakeError::Malformed(
565 "handshake message body truncated",
566 ));
567 }
568
569 let body = data[4..4 + body_len].to_vec();
570 let full_msg = data[..4 + body_len].to_vec();
571
572 Ok((msg_type, body, full_msg))
573}
574
575// ---------------------------------------------------------------------------
576// TlsStream
577// ---------------------------------------------------------------------------
578
579/// A TLS-encrypted stream over a transport.
580///
581/// Provides `Read` and `Write` for application data after a successful
582/// TLS 1.3 handshake.
583pub struct TlsStream<S> {
584 record_layer: RecordLayer<S>,
585 read_buffer: Vec<u8>,
586 read_offset: usize,
587}
588
589impl<S: Read + Write> TlsStream<S> {
590 /// Read decrypted application data.
591 pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
592 // If we have buffered data, return from that first
593 if self.read_offset < self.read_buffer.len() {
594 let available = &self.read_buffer[self.read_offset..];
595 let to_copy = available.len().min(buf.len());
596 buf[..to_copy].copy_from_slice(&available[..to_copy]);
597 self.read_offset += to_copy;
598 if self.read_offset >= self.read_buffer.len() {
599 self.read_buffer.clear();
600 self.read_offset = 0;
601 }
602 return Ok(to_copy);
603 }
604
605 // Read next record
606 let record = self.record_layer.read_record()?;
607 match record.content_type {
608 ContentType::ApplicationData => {
609 let to_copy = record.data.len().min(buf.len());
610 buf[..to_copy].copy_from_slice(&record.data[..to_copy]);
611 if to_copy < record.data.len() {
612 self.read_buffer = record.data;
613 self.read_offset = to_copy;
614 }
615 Ok(to_copy)
616 }
617 ContentType::Alert => {
618 if record.data.len() >= 2 && record.data[1] == 0 {
619 // close_notify
620 Ok(0)
621 } else {
622 Err(HandshakeError::Tls(TlsError::DecryptionFailed))
623 }
624 }
625 _ => Err(HandshakeError::Malformed("unexpected record type")),
626 }
627 }
628
629 /// Write application data.
630 pub fn write(&mut self, data: &[u8]) -> Result<usize> {
631 let record = TlsRecord::new(ContentType::ApplicationData, data.to_vec());
632 self.record_layer.write_record(&record)?;
633 Ok(data.len())
634 }
635
636 /// Write all application data.
637 pub fn write_all(&mut self, data: &[u8]) -> Result<()> {
638 let record = TlsRecord::new(ContentType::ApplicationData, data.to_vec());
639 self.record_layer.write_record(&record)?;
640 Ok(())
641 }
642
643 /// Send a close_notify alert and shut down the TLS connection.
644 pub fn close(&mut self) -> Result<()> {
645 self.record_layer.send_close_notify()?;
646 Ok(())
647 }
648
649 /// Get a reference to the underlying stream.
650 pub fn stream(&self) -> &S {
651 self.record_layer.stream()
652 }
653}
654
655// ---------------------------------------------------------------------------
656// Handshake state machine
657// ---------------------------------------------------------------------------
658
659/// Perform a TLS 1.3 handshake over the given stream.
660///
661/// Returns a `TlsStream` ready for application data.
662pub fn connect<S: Read + Write>(stream: S, server_name: &str) -> Result<TlsStream<S>> {
663 let mut record_layer = RecordLayer::new(stream);
664
665 // Step 1: Build and send ClientHello
666 let (client_hello_msg, x25519_private) = build_client_hello(server_name);
667
668 let ch_record = TlsRecord::new(ContentType::Handshake, client_hello_msg.clone());
669 record_layer.write_record(&ch_record)?;
670
671 // Step 2: Read ServerHello
672 let (sh_type, sh_body, sh_full) = read_handshake_message(&mut record_layer)?;
673 if sh_type != HANDSHAKE_SERVER_HELLO {
674 return Err(HandshakeError::UnexpectedMessage(sh_type));
675 }
676
677 let sh = parse_server_hello(&sh_body)?;
678
679 // Step 3: Set up transcript hash and key schedule
680 let mut transcript = TranscriptHash::new(sh.cipher_suite);
681 transcript.update(&client_hello_msg);
682 transcript.update(&sh_full);
683
684 // Compute ECDHE shared secret
685 let shared_secret = x25519(&x25519_private, &sh.server_x25519_public);
686
687 // Derive handshake secrets
688 let mut key_schedule = KeySchedule::new(sh.cipher_suite, None);
689 key_schedule.derive_handshake_secrets(&shared_secret, &transcript.current_hash());
690
691 // Step 4: Switch to handshake encryption for reading
692 let server_hs_keys = key_schedule.server_handshake_keys();
693 let server_iv: [u8; 12] = server_hs_keys.iv.try_into().expect("IV must be 12 bytes");
694 record_layer.set_read_crypto(RecordCryptoState::new(
695 sh.cipher_suite,
696 server_hs_keys.key,
697 server_iv,
698 ));
699
700 // Step 5: Read EncryptedExtensions
701 let (ee_type, ee_body, ee_full) = read_handshake_message(&mut record_layer)?;
702 if ee_type != HANDSHAKE_ENCRYPTED_EXTENSIONS {
703 return Err(HandshakeError::UnexpectedMessage(ee_type));
704 }
705 parse_encrypted_extensions(&ee_body)?;
706 transcript.update(&ee_full);
707
708 // Step 6: Read Certificate
709 let (cert_type, cert_body, cert_full) = read_handshake_message(&mut record_layer)?;
710 if cert_type != HANDSHAKE_CERTIFICATE {
711 return Err(HandshakeError::UnexpectedMessage(cert_type));
712 }
713 let cert_ders = parse_certificate_message(&cert_body)?;
714 transcript.update(&cert_full);
715
716 // Parse certificates
717 let mut certs = Vec::with_capacity(cert_ders.len());
718 for der in &cert_ders {
719 let cert = Certificate::from_der(der)
720 .map_err(|e| HandshakeError::CertificateError(format!("{e:?}")))?;
721 certs.push(cert);
722 }
723
724 // Validate certificate chain
725 let now = current_datetime();
726 let root_store = x509::root_ca_store()
727 .map_err(|e| HandshakeError::CertificateError(format!("root CA store: {e:?}")))?;
728 x509::validate_chain(&certs, &root_store, &now)
729 .map_err(|e| HandshakeError::CertificateError(format!("{e:?}")))?;
730
731 // Verify server name matches certificate
732 verify_server_name(&certs[0], server_name)?;
733
734 // Step 7: Read CertificateVerify
735 let (cv_type, cv_body, cv_full) = read_handshake_message(&mut record_layer)?;
736 if cv_type != HANDSHAKE_CERTIFICATE_VERIFY {
737 return Err(HandshakeError::UnexpectedMessage(cv_type));
738 }
739
740 // Verify CertificateVerify against transcript hash up to Certificate
741 let cv_transcript_hash = transcript.current_hash();
742 verify_certificate_verify(&cv_body, &certs[0], &cv_transcript_hash)?;
743 transcript.update(&cv_full);
744
745 // Step 8: Read server Finished
746 let (fin_type, fin_body, fin_full) = read_handshake_message(&mut record_layer)?;
747 if fin_type != HANDSHAKE_FINISHED {
748 return Err(HandshakeError::UnexpectedMessage(fin_type));
749 }
750
751 // Verify server Finished
752 let server_finished_hash = transcript.current_hash();
753 let expected_verify_data = key_schedule.compute_finished_verify_data(
754 key_schedule.server_handshake_traffic_secret().unwrap(),
755 &server_finished_hash,
756 );
757
758 if fin_body != expected_verify_data {
759 return Err(HandshakeError::FinishedMismatch);
760 }
761 transcript.update(&fin_full);
762
763 // Step 9: Switch write to handshake encryption and send client Finished
764 let client_hs_keys = key_schedule.client_handshake_keys();
765 let client_hs_iv: [u8; 12] = client_hs_keys.iv.try_into().expect("IV must be 12 bytes");
766 record_layer.set_write_crypto(RecordCryptoState::new(
767 sh.cipher_suite,
768 client_hs_keys.key,
769 client_hs_iv,
770 ));
771
772 // Compute and send client Finished
773 let client_finished_hash = transcript.current_hash();
774 let client_verify_data = key_schedule.compute_finished_verify_data(
775 key_schedule.client_handshake_traffic_secret().unwrap(),
776 &client_finished_hash,
777 );
778
779 let mut client_finished_msg = Vec::with_capacity(4 + client_verify_data.len());
780 push_u8(&mut client_finished_msg, HANDSHAKE_FINISHED);
781 push_u24(&mut client_finished_msg, client_verify_data.len() as u32);
782 push_bytes(&mut client_finished_msg, &client_verify_data);
783
784 let finished_record = TlsRecord::new(ContentType::Handshake, client_finished_msg.clone());
785 record_layer.write_record(&finished_record)?;
786 transcript.update(&client_finished_msg);
787
788 // Step 10: Derive application keys
789 // RFC 8446 §7.1: application traffic secrets use the transcript hash through
790 // server Finished (client_finished_hash), NOT including client Finished.
791 key_schedule.derive_app_secrets(&client_finished_hash);
792
793 let client_app_keys = key_schedule.client_app_keys();
794 let client_app_iv: [u8; 12] = client_app_keys.iv.try_into().expect("IV must be 12 bytes");
795 record_layer.set_write_crypto(RecordCryptoState::new(
796 sh.cipher_suite,
797 client_app_keys.key,
798 client_app_iv,
799 ));
800
801 let server_app_keys = key_schedule.server_app_keys();
802 let server_app_iv: [u8; 12] = server_app_keys.iv.try_into().expect("IV must be 12 bytes");
803 record_layer.set_read_crypto(RecordCryptoState::new(
804 sh.cipher_suite,
805 server_app_keys.key,
806 server_app_iv,
807 ));
808
809 Ok(TlsStream {
810 record_layer,
811 read_buffer: Vec::new(),
812 read_offset: 0,
813 })
814}
815
816// ---------------------------------------------------------------------------
817// Server name verification
818// ---------------------------------------------------------------------------
819
820fn verify_server_name(cert: &Certificate, server_name: &str) -> Result<()> {
821 // Check Subject Alternative Names first
822 for san in &cert.extensions.subject_alt_names {
823 if let x509::SubjectAltName::DnsName(dns) = san {
824 if matches_hostname(dns, server_name) {
825 return Ok(());
826 }
827 }
828 }
829
830 // Fall back to Common Name
831 if let Some(cn) = &cert.subject.common_name {
832 if matches_hostname(cn, server_name) {
833 return Ok(());
834 }
835 }
836
837 Err(HandshakeError::CertificateError(format!(
838 "server name '{server_name}' does not match certificate"
839 )))
840}
841
842/// Match a hostname against a pattern (supports leading wildcard).
843fn matches_hostname(pattern: &str, hostname: &str) -> bool {
844 let pattern = pattern.to_ascii_lowercase();
845 let hostname = hostname.to_ascii_lowercase();
846
847 if pattern == hostname {
848 return true;
849 }
850
851 // Wildcard matching: *.example.com matches foo.example.com
852 if let Some(suffix) = pattern.strip_prefix("*.") {
853 if let Some(rest) = hostname.strip_suffix(suffix) {
854 // The wildcard must match exactly one non-empty label
855 if rest.ends_with('.') && rest.len() > 1 && !rest[..rest.len() - 1].contains('.') {
856 return true;
857 }
858 }
859 }
860
861 false
862}
863
864// ---------------------------------------------------------------------------
865// Utility
866// ---------------------------------------------------------------------------
867
868fn current_datetime() -> DateTime {
869 // We use a fixed recent time since we don't have access to system time
870 // in a portable way without external crates. In practice this would
871 // use std::time::SystemTime.
872 //
873 // For now, use SystemTime to compute the actual date.
874 use std::time::{SystemTime, UNIX_EPOCH};
875 let duration = SystemTime::now()
876 .duration_since(UNIX_EPOCH)
877 .unwrap_or_default();
878 let secs = duration.as_secs();
879
880 // Simple conversion from unix timestamp to date components
881 let days = secs / 86400;
882 let time_of_day = secs % 86400;
883 let hour = (time_of_day / 3600) as u8;
884 let minute = ((time_of_day % 3600) / 60) as u8;
885 let second = (time_of_day % 60) as u8;
886
887 // Days since 1970-01-01
888 let (year, month, day) = days_to_date(days);
889
890 DateTime::new(year, month, day, hour, minute, second)
891}
892
893fn days_to_date(days_since_epoch: u64) -> (u16, u8, u8) {
894 // Algorithm from Howard Hinnant's chrono-compatible date algorithms
895 let z = days_since_epoch + 719468;
896 let era = z / 146097;
897 let doe = z - era * 146097;
898 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
899 let y = yoe + era * 400;
900 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
901 let mp = (5 * doy + 2) / 153;
902 let d = doy - (153 * mp + 2) / 5 + 1;
903 let m = if mp < 10 { mp + 3 } else { mp - 9 };
904 let y = if m <= 2 { y + 1 } else { y };
905 (y as u16, m as u8, d as u8)
906}
907
908// ---------------------------------------------------------------------------
909// Tests
910// ---------------------------------------------------------------------------
911
912#[cfg(test)]
913mod tests {
914 use super::*;
915
916 // -- Encoding helpers --
917
918 #[test]
919 fn push_u8_works() {
920 let mut buf = Vec::new();
921 push_u8(&mut buf, 0x42);
922 assert_eq!(buf, vec![0x42]);
923 }
924
925 #[test]
926 fn push_u16_works() {
927 let mut buf = Vec::new();
928 push_u16(&mut buf, 0x1234);
929 assert_eq!(buf, vec![0x12, 0x34]);
930 }
931
932 #[test]
933 fn push_u24_works() {
934 let mut buf = Vec::new();
935 push_u24(&mut buf, 0x123456);
936 assert_eq!(buf, vec![0x12, 0x34, 0x56]);
937 }
938
939 #[test]
940 fn read_u8_works() {
941 let data = [0x42, 0x43];
942 let mut offset = 0;
943 assert_eq!(read_u8(&data, &mut offset).unwrap(), 0x42);
944 assert_eq!(offset, 1);
945 assert_eq!(read_u8(&data, &mut offset).unwrap(), 0x43);
946 assert_eq!(offset, 2);
947 }
948
949 #[test]
950 fn read_u16_works() {
951 let data = [0x12, 0x34];
952 let mut offset = 0;
953 assert_eq!(read_u16(&data, &mut offset).unwrap(), 0x1234);
954 assert_eq!(offset, 2);
955 }
956
957 #[test]
958 fn read_u24_works() {
959 let data = [0x12, 0x34, 0x56];
960 let mut offset = 0;
961 assert_eq!(read_u24(&data, &mut offset).unwrap(), 0x123456);
962 assert_eq!(offset, 3);
963 }
964
965 #[test]
966 fn read_past_end_fails() {
967 let data = [0x42];
968 let mut offset = 0;
969 assert!(read_u16(&data, &mut offset).is_err());
970 }
971
972 #[test]
973 fn read_bytes_works() {
974 let data = [1, 2, 3, 4, 5];
975 let mut offset = 1;
976 let slice = read_bytes(&data, &mut offset, 3).unwrap();
977 assert_eq!(slice, &[2, 3, 4]);
978 assert_eq!(offset, 4);
979 }
980
981 // -- ClientHello construction --
982
983 #[test]
984 fn client_hello_has_correct_type() {
985 let (msg, _) = build_client_hello("example.com");
986 assert_eq!(msg[0], HANDSHAKE_CLIENT_HELLO);
987 }
988
989 #[test]
990 fn client_hello_has_valid_length() {
991 let (msg, _) = build_client_hello("example.com");
992 let body_len = (msg[1] as usize) << 16 | (msg[2] as usize) << 8 | msg[3] as usize;
993 assert_eq!(msg.len(), 4 + body_len);
994 }
995
996 #[test]
997 fn client_hello_starts_with_legacy_version() {
998 let (msg, _) = build_client_hello("example.com");
999 assert_eq!(msg[4], 0x03);
1000 assert_eq!(msg[5], 0x03);
1001 }
1002
1003 #[test]
1004 fn client_hello_has_32_byte_random() {
1005 let (msg1, _) = build_client_hello("example.com");
1006 let (msg2, _) = build_client_hello("example.com");
1007 // Random bytes start at offset 6 (after type(1) + length(3) + version(2))
1008 let random1 = &msg1[6..38];
1009 let random2 = &msg2[6..38];
1010 // They should almost certainly differ (random)
1011 // But in tests /dev/urandom might give same bytes... just check length
1012 assert_eq!(random1.len(), 32);
1013 assert_eq!(random2.len(), 32);
1014 }
1015
1016 #[test]
1017 fn client_hello_has_session_id() {
1018 let (msg, _) = build_client_hello("example.com");
1019 // session_id_len at offset 38
1020 assert_eq!(msg[38], 32); // 32-byte session ID
1021 }
1022
1023 #[test]
1024 fn client_hello_has_cipher_suites() {
1025 let (msg, _) = build_client_hello("example.com");
1026 // After version(2) + random(32) + session_id_len(1) + session_id(32)
1027 let cs_offset = 4 + 2 + 32 + 1 + 32;
1028 let cs_len = u16::from_be_bytes([msg[cs_offset], msg[cs_offset + 1]]);
1029 assert_eq!(cs_len, 6); // 3 suites * 2 bytes
1030 }
1031
1032 #[test]
1033 fn client_hello_returns_private_key() {
1034 let (_, private_key) = build_client_hello("example.com");
1035 assert_eq!(private_key.len(), 32);
1036 }
1037
1038 // -- ServerHello parsing --
1039
1040 fn build_test_server_hello(suite: [u8; 2], x25519_key: &[u8; 32]) -> Vec<u8> {
1041 let mut body = Vec::new();
1042 // legacy version
1043 push_bytes(&mut body, &LEGACY_VERSION);
1044 // random
1045 push_bytes(&mut body, &[0u8; 32]);
1046 // session_id echo (empty)
1047 push_u8(&mut body, 0);
1048 // cipher suite
1049 push_bytes(&mut body, &suite);
1050 // compression
1051 push_u8(&mut body, 0);
1052
1053 // Extensions
1054 let mut exts = Vec::new();
1055 // supported_versions
1056 push_u16(&mut exts, EXT_SUPPORTED_VERSIONS);
1057 push_u16(&mut exts, 2);
1058 push_bytes(&mut exts, &TLS13_VERSION);
1059 // key_share
1060 push_u16(&mut exts, EXT_KEY_SHARE);
1061 push_u16(&mut exts, 36); // group(2) + len(2) + key(32)
1062 push_u16(&mut exts, GROUP_X25519);
1063 push_u16(&mut exts, 32);
1064 push_bytes(&mut exts, x25519_key);
1065
1066 push_u16(&mut body, exts.len() as u16);
1067 push_bytes(&mut body, &exts);
1068
1069 body
1070 }
1071
1072 #[test]
1073 fn parse_server_hello_aes128() {
1074 let key = [0x42u8; 32];
1075 let body = build_test_server_hello(CS_AES_128_GCM_SHA256, &key);
1076 let result = parse_server_hello(&body).unwrap();
1077 assert_eq!(result.cipher_suite, CipherSuite::Aes128Gcm);
1078 assert_eq!(result.server_x25519_public, key);
1079 }
1080
1081 #[test]
1082 fn parse_server_hello_aes256() {
1083 let key = [0x43u8; 32];
1084 let body = build_test_server_hello(CS_AES_256_GCM_SHA384, &key);
1085 let result = parse_server_hello(&body).unwrap();
1086 assert_eq!(result.cipher_suite, CipherSuite::Aes256Gcm);
1087 assert_eq!(result.server_x25519_public, key);
1088 }
1089
1090 #[test]
1091 fn parse_server_hello_chacha() {
1092 let key = [0x44u8; 32];
1093 let body = build_test_server_hello(CS_CHACHA20_POLY1305_SHA256, &key);
1094 let result = parse_server_hello(&body).unwrap();
1095 assert_eq!(result.cipher_suite, CipherSuite::Chacha20Poly1305);
1096 }
1097
1098 #[test]
1099 fn parse_server_hello_unsupported_suite() {
1100 let key = [0x42u8; 32];
1101 let mut body = build_test_server_hello(CS_AES_128_GCM_SHA256, &key);
1102 // Corrupt cipher suite
1103 let cs_offset = 2 + 32 + 1;
1104 body[cs_offset] = 0xFF;
1105 body[cs_offset + 1] = 0xFF;
1106 assert!(parse_server_hello(&body).is_err());
1107 }
1108
1109 #[test]
1110 fn parse_server_hello_missing_version() {
1111 let key = [0x42u8; 32];
1112 let mut body = Vec::new();
1113 push_bytes(&mut body, &LEGACY_VERSION);
1114 push_bytes(&mut body, &[0u8; 32]);
1115 push_u8(&mut body, 0);
1116 push_bytes(&mut body, &CS_AES_128_GCM_SHA256);
1117 push_u8(&mut body, 0);
1118 // Only key_share, no supported_versions
1119 let mut exts = Vec::new();
1120 push_u16(&mut exts, EXT_KEY_SHARE);
1121 push_u16(&mut exts, 36);
1122 push_u16(&mut exts, GROUP_X25519);
1123 push_u16(&mut exts, 32);
1124 push_bytes(&mut exts, &key);
1125 push_u16(&mut body, exts.len() as u16);
1126 push_bytes(&mut body, &exts);
1127 assert!(parse_server_hello(&body).is_err());
1128 }
1129
1130 #[test]
1131 fn parse_server_hello_missing_key_share() {
1132 let mut body = Vec::new();
1133 push_bytes(&mut body, &LEGACY_VERSION);
1134 push_bytes(&mut body, &[0u8; 32]);
1135 push_u8(&mut body, 0);
1136 push_bytes(&mut body, &CS_AES_128_GCM_SHA256);
1137 push_u8(&mut body, 0);
1138 // Only supported_versions, no key_share
1139 let mut exts = Vec::new();
1140 push_u16(&mut exts, EXT_SUPPORTED_VERSIONS);
1141 push_u16(&mut exts, 2);
1142 push_bytes(&mut exts, &TLS13_VERSION);
1143 push_u16(&mut body, exts.len() as u16);
1144 push_bytes(&mut body, &exts);
1145 assert!(parse_server_hello(&body).is_err());
1146 }
1147
1148 // -- Certificate message parsing --
1149
1150 #[test]
1151 fn parse_empty_certificate_fails() {
1152 // certificate_request_context = empty, certificate_list = empty
1153 let mut data = Vec::new();
1154 push_u8(&mut data, 0); // empty context
1155 push_u24(&mut data, 0); // empty list
1156 assert!(parse_certificate_message(&data).is_err());
1157 }
1158
1159 #[test]
1160 fn parse_certificate_message_single_cert() {
1161 let fake_cert = vec![0x30, 0x82, 0x01, 0x00]; // fake DER
1162 let mut data = Vec::new();
1163 push_u8(&mut data, 0); // empty context
1164 // certificate list
1165 let entry_len = 3 + fake_cert.len() + 2; // cert_len(3) + cert + ext_len(2)
1166 push_u24(&mut data, entry_len as u32);
1167 push_u24(&mut data, fake_cert.len() as u32);
1168 push_bytes(&mut data, &fake_cert);
1169 push_u16(&mut data, 0); // no extensions
1170
1171 let certs = parse_certificate_message(&data).unwrap();
1172 assert_eq!(certs.len(), 1);
1173 assert_eq!(certs[0], fake_cert);
1174 }
1175
1176 #[test]
1177 fn parse_certificate_message_two_certs() {
1178 let cert1 = vec![0x30, 0x01];
1179 let cert2 = vec![0x30, 0x02, 0x03];
1180 let mut data = Vec::new();
1181 push_u8(&mut data, 0); // empty context
1182
1183 let entry1_len = 3 + cert1.len() + 2;
1184 let entry2_len = 3 + cert2.len() + 2;
1185 push_u24(&mut data, (entry1_len + entry2_len) as u32);
1186
1187 push_u24(&mut data, cert1.len() as u32);
1188 push_bytes(&mut data, &cert1);
1189 push_u16(&mut data, 0);
1190
1191 push_u24(&mut data, cert2.len() as u32);
1192 push_bytes(&mut data, &cert2);
1193 push_u16(&mut data, 0);
1194
1195 let certs = parse_certificate_message(&data).unwrap();
1196 assert_eq!(certs.len(), 2);
1197 assert_eq!(certs[0], cert1);
1198 assert_eq!(certs[1], cert2);
1199 }
1200
1201 // -- EncryptedExtensions parsing --
1202
1203 #[test]
1204 fn parse_encrypted_extensions_empty() {
1205 let mut data = Vec::new();
1206 push_u16(&mut data, 0); // empty extensions
1207 assert!(parse_encrypted_extensions(&data).is_ok());
1208 }
1209
1210 // -- Hostname matching --
1211
1212 #[test]
1213 fn exact_hostname_match() {
1214 assert!(matches_hostname("example.com", "example.com"));
1215 assert!(matches_hostname("Example.COM", "example.com"));
1216 }
1217
1218 #[test]
1219 fn hostname_mismatch() {
1220 assert!(!matches_hostname("example.com", "other.com"));
1221 assert!(!matches_hostname("example.com", "sub.example.com"));
1222 }
1223
1224 #[test]
1225 fn wildcard_hostname_match() {
1226 assert!(matches_hostname("*.example.com", "www.example.com"));
1227 assert!(matches_hostname("*.example.com", "mail.example.com"));
1228 }
1229
1230 #[test]
1231 fn wildcard_no_deep_match() {
1232 // Wildcard should match only one label
1233 assert!(!matches_hostname("*.example.com", "a.b.example.com"));
1234 }
1235
1236 #[test]
1237 fn wildcard_no_bare_match() {
1238 assert!(!matches_hostname("*.example.com", "example.com"));
1239 }
1240
1241 // -- Date conversion --
1242
1243 #[test]
1244 fn days_to_date_epoch() {
1245 let (y, m, d) = days_to_date(0);
1246 assert_eq!((y, m, d), (1970, 1, 1));
1247 }
1248
1249 #[test]
1250 fn days_to_date_known() {
1251 // 2026-03-12 = 20524 days since epoch
1252 let (y, m, d) = days_to_date(20524);
1253 assert_eq!((y, m, d), (2026, 3, 12));
1254 }
1255
1256 #[test]
1257 fn days_to_date_2000() {
1258 // 2000-01-01 = 10957 days since epoch
1259 let (y, m, d) = days_to_date(10957);
1260 assert_eq!((y, m, d), (2000, 1, 1));
1261 }
1262
1263 // -- Error display --
1264
1265 #[test]
1266 fn handshake_error_display() {
1267 let err = HandshakeError::UnsupportedCipherSuite;
1268 assert_eq!(err.to_string(), "unsupported cipher suite");
1269 }
1270
1271 #[test]
1272 fn handshake_error_from_tls() {
1273 let tls_err = TlsError::RecordOverflow;
1274 let hs_err = HandshakeError::from(tls_err);
1275 assert!(matches!(hs_err, HandshakeError::Tls(_)));
1276 }
1277
1278 #[test]
1279 fn handshake_error_from_io() {
1280 let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "broken");
1281 let hs_err = HandshakeError::from(io_err);
1282 assert!(matches!(hs_err, HandshakeError::Io(_)));
1283 }
1284
1285 // -- Extensions building --
1286
1287 #[test]
1288 fn extensions_contain_sni() {
1289 let key = [0u8; 32];
1290 let exts = build_extensions("example.com", &key);
1291 // First extension should be SNI (type 0x0000)
1292 assert_eq!(exts[0], 0x00);
1293 assert_eq!(exts[1], 0x00);
1294 }
1295
1296 #[test]
1297 fn extensions_contain_supported_versions() {
1298 let key = [0u8; 32];
1299 let exts = build_extensions("example.com", &key);
1300 // Search for supported_versions extension type (0x002b = 43)
1301 let mut found = false;
1302 let mut i = 0;
1303 while i + 4 <= exts.len() {
1304 let ext_type = u16::from_be_bytes([exts[i], exts[i + 1]]);
1305 let ext_len = u16::from_be_bytes([exts[i + 2], exts[i + 3]]) as usize;
1306 if ext_type == EXT_SUPPORTED_VERSIONS {
1307 found = true;
1308 break;
1309 }
1310 i += 4 + ext_len;
1311 }
1312 assert!(found, "supported_versions extension not found");
1313 }
1314
1315 #[test]
1316 fn extensions_contain_key_share() {
1317 let key = [0x42u8; 32];
1318 let exts = build_extensions("example.com", &key);
1319 let mut found = false;
1320 let mut i = 0;
1321 while i + 4 <= exts.len() {
1322 let ext_type = u16::from_be_bytes([exts[i], exts[i + 1]]);
1323 let ext_len = u16::from_be_bytes([exts[i + 2], exts[i + 3]]) as usize;
1324 if ext_type == EXT_KEY_SHARE {
1325 found = true;
1326 // Verify the key is in there
1327 // ext_data: list_len(2) + group(2) + key_len(2) + key(32)
1328 let key_start = i + 4 + 2 + 2 + 2;
1329 assert_eq!(&exts[key_start..key_start + 32], &key);
1330 break;
1331 }
1332 i += 4 + ext_len;
1333 }
1334 assert!(found, "key_share extension not found");
1335 }
1336
1337 #[test]
1338 fn extensions_contain_sig_algorithms() {
1339 let key = [0u8; 32];
1340 let exts = build_extensions("example.com", &key);
1341 let mut found = false;
1342 let mut i = 0;
1343 while i + 4 <= exts.len() {
1344 let ext_type = u16::from_be_bytes([exts[i], exts[i + 1]]);
1345 let ext_len = u16::from_be_bytes([exts[i + 2], exts[i + 3]]) as usize;
1346 if ext_type == EXT_SIGNATURE_ALGORITHMS {
1347 found = true;
1348 break;
1349 }
1350 i += 4 + ext_len;
1351 }
1352 assert!(found, "signature_algorithms extension not found");
1353 }
1354
1355 // -- Build and parse roundtrip --
1356
1357 #[test]
1358 fn server_hello_roundtrip_with_session_id() {
1359 let key = [0x55u8; 32];
1360 let mut body = Vec::new();
1361 push_bytes(&mut body, &LEGACY_VERSION);
1362 push_bytes(&mut body, &[0xAAu8; 32]); // random
1363 // 32-byte session ID echo
1364 push_u8(&mut body, 32);
1365 push_bytes(&mut body, &[0xBBu8; 32]);
1366 push_bytes(&mut body, &CS_AES_128_GCM_SHA256);
1367 push_u8(&mut body, 0); // compression
1368 // Extensions
1369 let mut exts = Vec::new();
1370 push_u16(&mut exts, EXT_SUPPORTED_VERSIONS);
1371 push_u16(&mut exts, 2);
1372 push_bytes(&mut exts, &TLS13_VERSION);
1373 push_u16(&mut exts, EXT_KEY_SHARE);
1374 push_u16(&mut exts, 36);
1375 push_u16(&mut exts, GROUP_X25519);
1376 push_u16(&mut exts, 32);
1377 push_bytes(&mut exts, &key);
1378 push_u16(&mut body, exts.len() as u16);
1379 push_bytes(&mut body, &exts);
1380
1381 let result = parse_server_hello(&body).unwrap();
1382 assert_eq!(result.cipher_suite, CipherSuite::Aes128Gcm);
1383 assert_eq!(result.server_x25519_public, key);
1384 }
1385
1386 // -- current_datetime sanity check --
1387
1388 #[test]
1389 fn current_datetime_reasonable() {
1390 let dt = current_datetime();
1391 assert!(dt.year >= 2024);
1392 assert!((1..=12).contains(&dt.month));
1393 assert!((1..=31).contains(&dt.day));
1394 }
1395}