we (web engine): Experimental web browser project to understand the limits of Claude
1//! TLS 1.3 record layer (RFC 8446 §5).
2//!
3//! Handles framing, encryption, and decryption of TLS records over a TCP stream.
4
5use std::io::{self, Read, Write};
6
7// ---------------------------------------------------------------------------
8// Constants
9// ---------------------------------------------------------------------------
10
11/// TLS 1.2 legacy version used in record headers (RFC 8446 §5.1).
12const LEGACY_VERSION: [u8; 2] = [0x03, 0x03];
13
14/// Maximum plaintext fragment size: 2^14 = 16384 bytes.
15const MAX_PLAINTEXT_LENGTH: usize = 16384;
16
17/// Maximum ciphertext overhead: 256 bytes (tag + inner content type + padding).
18const MAX_CIPHERTEXT_OVERHEAD: usize = 256;
19
20/// Maximum ciphertext fragment size: 2^14 + 256.
21const MAX_CIPHERTEXT_LENGTH: usize = MAX_PLAINTEXT_LENGTH + MAX_CIPHERTEXT_OVERHEAD;
22
23/// AEAD tag size for all TLS 1.3 cipher suites.
24const TAG_SIZE: usize = 16;
25
26/// Record header size: content type (1) + version (2) + length (2).
27const RECORD_HEADER_SIZE: usize = 5;
28
29// ---------------------------------------------------------------------------
30// Content types (RFC 8446 §5.1)
31// ---------------------------------------------------------------------------
32
33/// TLS record content types.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(u8)]
36pub enum ContentType {
37 ChangeCipherSpec = 20,
38 Alert = 21,
39 Handshake = 22,
40 ApplicationData = 23,
41}
42
43impl ContentType {
44 fn from_u8(v: u8) -> Result<Self> {
45 match v {
46 20 => Ok(ContentType::ChangeCipherSpec),
47 21 => Ok(ContentType::Alert),
48 22 => Ok(ContentType::Handshake),
49 23 => Ok(ContentType::ApplicationData),
50 _ => Err(TlsError::UnknownContentType(v)),
51 }
52 }
53}
54
55// ---------------------------------------------------------------------------
56// Alert protocol (RFC 8446 §6)
57// ---------------------------------------------------------------------------
58
59/// TLS alert severity level.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[repr(u8)]
62pub enum AlertLevel {
63 Warning = 1,
64 Fatal = 2,
65}
66
67impl AlertLevel {
68 fn from_u8(v: u8) -> Result<Self> {
69 match v {
70 1 => Ok(AlertLevel::Warning),
71 2 => Ok(AlertLevel::Fatal),
72 _ => Err(TlsError::MalformedAlert),
73 }
74 }
75}
76
77/// TLS alert descriptions (subset covering TLS 1.3).
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[repr(u8)]
80pub enum AlertDescription {
81 CloseNotify = 0,
82 UnexpectedMessage = 10,
83 BadRecordMac = 20,
84 RecordOverflow = 22,
85 HandshakeFailure = 40,
86 BadCertificate = 42,
87 CertificateRevoked = 44,
88 CertificateExpired = 45,
89 CertificateUnknown = 46,
90 IllegalParameter = 47,
91 UnknownCa = 48,
92 AccessDenied = 49,
93 DecodeError = 50,
94 DecryptError = 51,
95 ProtocolVersion = 70,
96 InsufficientSecurity = 71,
97 InternalError = 80,
98 MissingExtension = 109,
99 UnsupportedExtension = 110,
100 UnrecognizedName = 112,
101 BadCertificateStatusResponse = 113,
102 NoApplicationProtocol = 120,
103}
104
105impl AlertDescription {
106 fn from_u8(v: u8) -> Result<Self> {
107 match v {
108 0 => Ok(AlertDescription::CloseNotify),
109 10 => Ok(AlertDescription::UnexpectedMessage),
110 20 => Ok(AlertDescription::BadRecordMac),
111 22 => Ok(AlertDescription::RecordOverflow),
112 40 => Ok(AlertDescription::HandshakeFailure),
113 42 => Ok(AlertDescription::BadCertificate),
114 44 => Ok(AlertDescription::CertificateRevoked),
115 45 => Ok(AlertDescription::CertificateExpired),
116 46 => Ok(AlertDescription::CertificateUnknown),
117 47 => Ok(AlertDescription::IllegalParameter),
118 48 => Ok(AlertDescription::UnknownCa),
119 49 => Ok(AlertDescription::AccessDenied),
120 50 => Ok(AlertDescription::DecodeError),
121 51 => Ok(AlertDescription::DecryptError),
122 70 => Ok(AlertDescription::ProtocolVersion),
123 71 => Ok(AlertDescription::InsufficientSecurity),
124 80 => Ok(AlertDescription::InternalError),
125 109 => Ok(AlertDescription::MissingExtension),
126 110 => Ok(AlertDescription::UnsupportedExtension),
127 112 => Ok(AlertDescription::UnrecognizedName),
128 113 => Ok(AlertDescription::BadCertificateStatusResponse),
129 120 => Ok(AlertDescription::NoApplicationProtocol),
130 _ => Err(TlsError::MalformedAlert),
131 }
132 }
133}
134
135/// A parsed TLS alert message.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct Alert {
138 pub level: AlertLevel,
139 pub description: AlertDescription,
140}
141
142impl Alert {
143 /// Create a new alert.
144 pub fn new(level: AlertLevel, description: AlertDescription) -> Self {
145 Self { level, description }
146 }
147
148 /// Create a close_notify alert.
149 pub fn close_notify() -> Self {
150 Self::new(AlertLevel::Warning, AlertDescription::CloseNotify)
151 }
152
153 /// Encode the alert to bytes.
154 pub fn encode(&self) -> [u8; 2] {
155 [self.level as u8, self.description as u8]
156 }
157
158 /// Parse an alert from bytes.
159 pub fn parse(data: &[u8]) -> Result<Self> {
160 if data.len() < 2 {
161 return Err(TlsError::MalformedAlert);
162 }
163 Ok(Self {
164 level: AlertLevel::from_u8(data[0])?,
165 description: AlertDescription::from_u8(data[1])?,
166 })
167 }
168
169 /// Returns true if this is a fatal alert.
170 pub fn is_fatal(&self) -> bool {
171 self.level == AlertLevel::Fatal
172 }
173}
174
175// ---------------------------------------------------------------------------
176// Error types
177// ---------------------------------------------------------------------------
178
179/// TLS record layer errors.
180#[derive(Debug)]
181pub enum TlsError {
182 /// Unknown content type byte.
183 UnknownContentType(u8),
184 /// Record exceeds maximum allowed size.
185 RecordOverflow,
186 /// AEAD decryption failed (bad MAC).
187 DecryptionFailed,
188 /// Malformed alert message.
189 MalformedAlert,
190 /// Received a fatal alert from the peer.
191 AlertReceived(Alert),
192 /// An I/O error occurred.
193 Io(io::Error),
194}
195
196impl std::fmt::Display for TlsError {
197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198 match self {
199 Self::UnknownContentType(v) => write!(f, "unknown TLS content type: {v}"),
200 Self::RecordOverflow => write!(f, "TLS record overflow"),
201 Self::DecryptionFailed => write!(f, "TLS AEAD decryption failed"),
202 Self::MalformedAlert => write!(f, "malformed TLS alert"),
203 Self::AlertReceived(alert) => {
204 write!(f, "TLS alert received: {:?}", alert.description)
205 }
206 Self::Io(e) => write!(f, "TLS I/O error: {e}"),
207 }
208 }
209}
210
211impl From<io::Error> for TlsError {
212 fn from(err: io::Error) -> Self {
213 TlsError::Io(err)
214 }
215}
216
217pub type Result<T> = std::result::Result<T, TlsError>;
218
219// ---------------------------------------------------------------------------
220// Cipher suite abstraction
221// ---------------------------------------------------------------------------
222
223/// Supported TLS 1.3 cipher suites.
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum CipherSuite {
226 Aes128Gcm,
227 Aes256Gcm,
228 Chacha20Poly1305,
229}
230
231impl CipherSuite {
232 /// Key length in bytes.
233 pub fn key_len(&self) -> usize {
234 match self {
235 CipherSuite::Aes128Gcm => 16,
236 CipherSuite::Aes256Gcm => 32,
237 CipherSuite::Chacha20Poly1305 => 32,
238 }
239 }
240
241 /// IV (nonce base) length in bytes.
242 pub fn iv_len(&self) -> usize {
243 12 // All TLS 1.3 suites use 12-byte nonces
244 }
245}
246
247// ---------------------------------------------------------------------------
248// TLS record
249// ---------------------------------------------------------------------------
250
251/// A plaintext TLS record.
252#[derive(Debug, Clone)]
253pub struct TlsRecord {
254 pub content_type: ContentType,
255 pub data: Vec<u8>,
256}
257
258impl TlsRecord {
259 /// Create a new TLS record.
260 pub fn new(content_type: ContentType, data: Vec<u8>) -> Self {
261 Self { content_type, data }
262 }
263}
264
265// ---------------------------------------------------------------------------
266// Plaintext record I/O
267// ---------------------------------------------------------------------------
268
269/// Read a single plaintext TLS record from a stream.
270pub fn read_record<R: Read>(stream: &mut R) -> Result<TlsRecord> {
271 // Read 5-byte header
272 let mut header = [0u8; RECORD_HEADER_SIZE];
273 stream.read_exact(&mut header)?;
274
275 let content_type = ContentType::from_u8(header[0])?;
276 // We accept any legacy version in the header (RFC 8446 §5.1)
277 let length = u16::from_be_bytes([header[3], header[4]]) as usize;
278
279 if length > MAX_CIPHERTEXT_LENGTH {
280 return Err(TlsError::RecordOverflow);
281 }
282
283 let mut data = vec![0u8; length];
284 stream.read_exact(&mut data)?;
285
286 Ok(TlsRecord { content_type, data })
287}
288
289/// Write a single plaintext TLS record to a stream.
290pub fn write_record<W: Write>(stream: &mut W, record: &TlsRecord) -> Result<()> {
291 if record.data.len() > MAX_PLAINTEXT_LENGTH {
292 return Err(TlsError::RecordOverflow);
293 }
294
295 let mut header = [0u8; RECORD_HEADER_SIZE];
296 header[0] = record.content_type as u8;
297 header[1..3].copy_from_slice(&LEGACY_VERSION);
298 header[3..5].copy_from_slice(&(record.data.len() as u16).to_be_bytes());
299
300 stream.write_all(&header)?;
301 stream.write_all(&record.data)?;
302 stream.flush()?;
303
304 Ok(())
305}
306
307// ---------------------------------------------------------------------------
308// Nonce construction (RFC 8446 §5.3)
309// ---------------------------------------------------------------------------
310
311/// Construct a per-record nonce by XORing the base IV with the sequence number.
312///
313/// The sequence number is left-padded with zeros to match the IV length,
314/// then XORed with the base IV.
315fn make_nonce(iv: &[u8; 12], seq: u64) -> [u8; 12] {
316 let mut nonce = *iv;
317 let seq_bytes = seq.to_be_bytes(); // 8 bytes
318 // XOR seq into the last 8 bytes of the 12-byte nonce
319 for i in 0..8 {
320 nonce[4 + i] ^= seq_bytes[i];
321 }
322 nonce
323}
324
325// ---------------------------------------------------------------------------
326// AEAD encrypt/decrypt wrappers
327// ---------------------------------------------------------------------------
328
329fn aead_encrypt(
330 suite: CipherSuite,
331 key: &[u8],
332 nonce: &[u8; 12],
333 plaintext: &[u8],
334 aad: &[u8],
335) -> (Vec<u8>, [u8; 16]) {
336 match suite {
337 CipherSuite::Aes128Gcm => {
338 let key: [u8; 16] = key.try_into().expect("AES-128 key must be 16 bytes");
339 we_crypto::aes_gcm::aes128_gcm_encrypt(&key, nonce, plaintext, aad)
340 }
341 CipherSuite::Aes256Gcm => {
342 let key: [u8; 32] = key.try_into().expect("AES-256 key must be 32 bytes");
343 we_crypto::aes_gcm::aes256_gcm_encrypt(&key, nonce, plaintext, aad)
344 }
345 CipherSuite::Chacha20Poly1305 => {
346 let key: [u8; 32] = key.try_into().expect("ChaCha20 key must be 32 bytes");
347 we_crypto::chacha20_poly1305::chacha20_poly1305_encrypt(&key, nonce, plaintext, aad)
348 }
349 }
350}
351
352fn aead_decrypt(
353 suite: CipherSuite,
354 key: &[u8],
355 nonce: &[u8; 12],
356 ciphertext: &[u8],
357 aad: &[u8],
358 tag: &[u8; 16],
359) -> Option<Vec<u8>> {
360 match suite {
361 CipherSuite::Aes128Gcm => {
362 let key: [u8; 16] = key.try_into().expect("AES-128 key must be 16 bytes");
363 we_crypto::aes_gcm::aes128_gcm_decrypt(&key, nonce, ciphertext, aad, tag)
364 }
365 CipherSuite::Aes256Gcm => {
366 let key: [u8; 32] = key.try_into().expect("AES-256 key must be 32 bytes");
367 we_crypto::aes_gcm::aes256_gcm_decrypt(&key, nonce, ciphertext, aad, tag)
368 }
369 CipherSuite::Chacha20Poly1305 => {
370 let key: [u8; 32] = key.try_into().expect("ChaCha20 key must be 32 bytes");
371 we_crypto::chacha20_poly1305::chacha20_poly1305_decrypt(
372 &key, nonce, ciphertext, aad, tag,
373 )
374 }
375 }
376}
377
378// ---------------------------------------------------------------------------
379// Encrypted record layer (RFC 8446 §5.2)
380// ---------------------------------------------------------------------------
381
382/// State for encrypting/decrypting TLS 1.3 records.
383///
384/// Each direction (read/write) needs its own `RecordCryptoState` with
385/// independent keys, IVs, and sequence numbers.
386pub struct RecordCryptoState {
387 suite: CipherSuite,
388 key: Vec<u8>,
389 iv: [u8; 12],
390 seq: u64,
391}
392
393impl RecordCryptoState {
394 /// Create a new record crypto state.
395 pub fn new(suite: CipherSuite, key: Vec<u8>, iv: [u8; 12]) -> Self {
396 assert_eq!(key.len(), suite.key_len());
397 Self {
398 suite,
399 key,
400 iv,
401 seq: 0,
402 }
403 }
404
405 /// Encrypt a TLS record (RFC 8446 §5.2).
406 ///
407 /// Builds TLSInnerPlaintext (content ∥ content_type ∥ zeros),
408 /// then encrypts with AEAD using the record header as AAD.
409 /// Returns the encrypted TLS record with outer type ApplicationData.
410 pub fn encrypt(&mut self, record: &TlsRecord) -> Result<TlsRecord> {
411 if record.data.len() > MAX_PLAINTEXT_LENGTH {
412 return Err(TlsError::RecordOverflow);
413 }
414
415 // Build TLSInnerPlaintext: content ∥ content_type (no padding)
416 let mut inner = Vec::with_capacity(record.data.len() + 1);
417 inner.extend_from_slice(&record.data);
418 inner.push(record.content_type as u8);
419
420 // Construct nonce
421 let nonce = make_nonce(&self.iv, self.seq);
422
423 // The AAD is the record header of the *outer* record:
424 // type(23) ∥ legacy_version(0x0303) ∥ length(encrypted_len)
425 // encrypted_len = inner.len() + tag_size
426 let encrypted_len = inner.len() + TAG_SIZE;
427 if encrypted_len > MAX_CIPHERTEXT_LENGTH {
428 return Err(TlsError::RecordOverflow);
429 }
430
431 let mut aad = [0u8; RECORD_HEADER_SIZE];
432 aad[0] = ContentType::ApplicationData as u8;
433 aad[1..3].copy_from_slice(&LEGACY_VERSION);
434 aad[3..5].copy_from_slice(&(encrypted_len as u16).to_be_bytes());
435
436 let (ciphertext, tag) = aead_encrypt(self.suite, &self.key, &nonce, &inner, &aad);
437
438 // Combine ciphertext + tag
439 let mut encrypted = ciphertext;
440 encrypted.extend_from_slice(&tag);
441
442 self.seq = self.seq.wrapping_add(1);
443
444 Ok(TlsRecord {
445 content_type: ContentType::ApplicationData,
446 data: encrypted,
447 })
448 }
449
450 /// Decrypt a TLS record (RFC 8446 §5.2).
451 ///
452 /// Expects the outer content type to be ApplicationData.
453 /// Decrypts, strips padding and inner content type.
454 /// Returns the decrypted record with the real content type.
455 pub fn decrypt(&mut self, record: &TlsRecord) -> Result<TlsRecord> {
456 if record.content_type != ContentType::ApplicationData {
457 return Err(TlsError::UnknownContentType(record.content_type as u8));
458 }
459
460 if record.data.len() < TAG_SIZE + 1 {
461 // Need at least tag + 1 byte for inner content type
462 return Err(TlsError::DecryptionFailed);
463 }
464
465 if record.data.len() > MAX_CIPHERTEXT_LENGTH {
466 return Err(TlsError::RecordOverflow);
467 }
468
469 // Split ciphertext and tag
470 let ct_len = record.data.len() - TAG_SIZE;
471 let ciphertext = &record.data[..ct_len];
472 let tag: [u8; 16] = record.data[ct_len..].try_into().expect("tag is 16 bytes");
473
474 // Construct nonce
475 let nonce = make_nonce(&self.iv, self.seq);
476
477 // Build AAD: the record header as received
478 let mut aad = [0u8; RECORD_HEADER_SIZE];
479 aad[0] = ContentType::ApplicationData as u8;
480 aad[1..3].copy_from_slice(&LEGACY_VERSION);
481 aad[3..5].copy_from_slice(&(record.data.len() as u16).to_be_bytes());
482
483 let inner = aead_decrypt(self.suite, &self.key, &nonce, ciphertext, &aad, &tag)
484 .ok_or(TlsError::DecryptionFailed)?;
485
486 self.seq = self.seq.wrapping_add(1);
487
488 // Strip padding zeros and extract inner content type
489 // TLSInnerPlaintext = content ∥ ContentType ∥ zeros
490 // Find the last non-zero byte (the content type)
491 let ct_pos = inner
492 .iter()
493 .rposition(|&b| b != 0)
494 .ok_or(TlsError::DecryptionFailed)?;
495
496 let content_type = ContentType::from_u8(inner[ct_pos])?;
497 let data = inner[..ct_pos].to_vec();
498
499 if data.len() > MAX_PLAINTEXT_LENGTH {
500 return Err(TlsError::RecordOverflow);
501 }
502
503 Ok(TlsRecord { content_type, data })
504 }
505}
506
507// ---------------------------------------------------------------------------
508// RecordLayer: combines plaintext and encrypted record I/O
509// ---------------------------------------------------------------------------
510
511/// The TLS record layer, handling both plaintext and encrypted records.
512pub struct RecordLayer<S> {
513 stream: S,
514 write_state: Option<RecordCryptoState>,
515 read_state: Option<RecordCryptoState>,
516}
517
518impl<S: Read + Write> RecordLayer<S> {
519 /// Create a new record layer in plaintext mode.
520 pub fn new(stream: S) -> Self {
521 Self {
522 stream,
523 write_state: None,
524 read_state: None,
525 }
526 }
527
528 /// Enable encryption for writing.
529 pub fn set_write_crypto(&mut self, state: RecordCryptoState) {
530 self.write_state = Some(state);
531 }
532
533 /// Enable encryption for reading.
534 pub fn set_read_crypto(&mut self, state: RecordCryptoState) {
535 self.read_state = Some(state);
536 }
537
538 /// Get a reference to the underlying stream.
539 pub fn stream(&self) -> &S {
540 &self.stream
541 }
542
543 /// Get a mutable reference to the underlying stream.
544 pub fn stream_mut(&mut self) -> &mut S {
545 &mut self.stream
546 }
547
548 /// Write a TLS record, encrypting if crypto state is set.
549 pub fn write_record(&mut self, record: &TlsRecord) -> Result<()> {
550 let record_to_write = match &mut self.write_state {
551 Some(state) => state.encrypt(record)?,
552 None => record.clone(),
553 };
554 write_record(&mut self.stream, &record_to_write)
555 }
556
557 /// Read a TLS record, decrypting if crypto state is set.
558 pub fn read_record(&mut self) -> Result<TlsRecord> {
559 let raw = read_record(&mut self.stream)?;
560 match &mut self.read_state {
561 Some(state) if raw.content_type == ContentType::ApplicationData => state.decrypt(&raw),
562 _ => Ok(raw),
563 }
564 }
565
566 /// Send an alert.
567 pub fn send_alert(&mut self, alert: Alert) -> Result<()> {
568 let record = TlsRecord::new(ContentType::Alert, alert.encode().to_vec());
569 self.write_record(&record)
570 }
571
572 /// Send a close_notify alert.
573 pub fn send_close_notify(&mut self) -> Result<()> {
574 self.send_alert(Alert::close_notify())
575 }
576
577 /// Consume the record layer and return the underlying stream.
578 pub fn into_inner(self) -> S {
579 self.stream
580 }
581}
582
583// ---------------------------------------------------------------------------
584// Tests
585// ---------------------------------------------------------------------------
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use std::io::Cursor;
591
592 // -- ContentType tests --
593
594 #[test]
595 fn content_type_from_u8_valid() {
596 assert_eq!(
597 ContentType::from_u8(20).unwrap(),
598 ContentType::ChangeCipherSpec
599 );
600 assert_eq!(ContentType::from_u8(21).unwrap(), ContentType::Alert);
601 assert_eq!(ContentType::from_u8(22).unwrap(), ContentType::Handshake);
602 assert_eq!(
603 ContentType::from_u8(23).unwrap(),
604 ContentType::ApplicationData
605 );
606 }
607
608 #[test]
609 fn content_type_from_u8_invalid() {
610 assert!(ContentType::from_u8(0).is_err());
611 assert!(ContentType::from_u8(19).is_err());
612 assert!(ContentType::from_u8(24).is_err());
613 assert!(ContentType::from_u8(255).is_err());
614 }
615
616 // -- Alert tests --
617
618 #[test]
619 fn alert_encode_decode() {
620 let alert = Alert::new(AlertLevel::Fatal, AlertDescription::HandshakeFailure);
621 let bytes = alert.encode();
622 assert_eq!(bytes, [2, 40]);
623 let parsed = Alert::parse(&bytes).unwrap();
624 assert_eq!(parsed, alert);
625 }
626
627 #[test]
628 fn alert_close_notify() {
629 let alert = Alert::close_notify();
630 assert_eq!(alert.level, AlertLevel::Warning);
631 assert_eq!(alert.description, AlertDescription::CloseNotify);
632 assert!(!alert.is_fatal());
633 assert_eq!(alert.encode(), [1, 0]);
634 }
635
636 #[test]
637 fn alert_fatal() {
638 let alert = Alert::new(AlertLevel::Fatal, AlertDescription::InternalError);
639 assert!(alert.is_fatal());
640 }
641
642 #[test]
643 fn alert_parse_too_short() {
644 assert!(Alert::parse(&[1]).is_err());
645 assert!(Alert::parse(&[]).is_err());
646 }
647
648 #[test]
649 fn alert_parse_invalid_level() {
650 assert!(Alert::parse(&[0, 0]).is_err());
651 assert!(Alert::parse(&[3, 0]).is_err());
652 }
653
654 // -- Nonce construction tests --
655
656 #[test]
657 fn nonce_seq_zero() {
658 let iv = [0x01; 12];
659 let nonce = make_nonce(&iv, 0);
660 assert_eq!(nonce, iv);
661 }
662
663 #[test]
664 fn nonce_seq_one() {
665 let iv = [0u8; 12];
666 let nonce = make_nonce(&iv, 1);
667 let mut expected = [0u8; 12];
668 expected[11] = 1;
669 assert_eq!(nonce, expected);
670 }
671
672 #[test]
673 fn nonce_xor_correctness() {
674 let iv = [0xff; 12];
675 let nonce = make_nonce(&iv, 0x0102030405060708);
676 // Last 8 bytes: 0xff ^ seq_bytes
677 assert_eq!(nonce[0..4], [0xff, 0xff, 0xff, 0xff]); // untouched first 4
678 assert_eq!(nonce[4], 0xff ^ 0x01);
679 assert_eq!(nonce[5], 0xff ^ 0x02);
680 assert_eq!(nonce[6], 0xff ^ 0x03);
681 assert_eq!(nonce[7], 0xff ^ 0x04);
682 assert_eq!(nonce[8], 0xff ^ 0x05);
683 assert_eq!(nonce[9], 0xff ^ 0x06);
684 assert_eq!(nonce[10], 0xff ^ 0x07);
685 assert_eq!(nonce[11], 0xff ^ 0x08);
686 }
687
688 // -- Plaintext record I/O tests --
689
690 #[test]
691 fn write_read_plaintext_record() {
692 let record = TlsRecord::new(ContentType::Handshake, vec![0x01, 0x02, 0x03]);
693 let mut buf = Vec::new();
694 write_record(&mut buf, &record).unwrap();
695
696 // Verify wire format
697 assert_eq!(buf[0], 22); // Handshake
698 assert_eq!(buf[1..3], LEGACY_VERSION);
699 assert_eq!(buf[3..5], [0x00, 0x03]); // length = 3
700 assert_eq!(buf[5..], [0x01, 0x02, 0x03]);
701
702 // Read it back
703 let mut cursor = Cursor::new(&buf);
704 let read_back = read_record(&mut cursor).unwrap();
705 assert_eq!(read_back.content_type, ContentType::Handshake);
706 assert_eq!(read_back.data, vec![0x01, 0x02, 0x03]);
707 }
708
709 #[test]
710 fn write_read_empty_record() {
711 let record = TlsRecord::new(ContentType::ApplicationData, vec![]);
712 let mut buf = Vec::new();
713 write_record(&mut buf, &record).unwrap();
714
715 let mut cursor = Cursor::new(&buf);
716 let read_back = read_record(&mut cursor).unwrap();
717 assert_eq!(read_back.content_type, ContentType::ApplicationData);
718 assert!(read_back.data.is_empty());
719 }
720
721 #[test]
722 fn write_record_overflow() {
723 let record = TlsRecord::new(
724 ContentType::ApplicationData,
725 vec![0u8; MAX_PLAINTEXT_LENGTH + 1],
726 );
727 let mut buf = Vec::new();
728 let result = write_record(&mut buf, &record);
729 assert!(result.is_err());
730 }
731
732 #[test]
733 fn read_record_overflow() {
734 // Craft a header claiming more than MAX_CIPHERTEXT_LENGTH
735 let mut buf = vec![23u8]; // ApplicationData
736 buf.extend_from_slice(&LEGACY_VERSION);
737 let bad_len = (MAX_CIPHERTEXT_LENGTH + 1) as u16;
738 buf.extend_from_slice(&bad_len.to_be_bytes());
739 let mut cursor = Cursor::new(&buf);
740 let result = read_record(&mut cursor);
741 assert!(result.is_err());
742 }
743
744 #[test]
745 fn read_record_truncated_header() {
746 let buf = vec![22u8, 0x03]; // Only 2 bytes of header
747 let mut cursor = Cursor::new(&buf);
748 let result = read_record(&mut cursor);
749 assert!(result.is_err());
750 }
751
752 #[test]
753 fn read_record_truncated_body() {
754 // Header says 10 bytes but only 5 are available
755 let mut buf = vec![22u8]; // Handshake
756 buf.extend_from_slice(&LEGACY_VERSION);
757 buf.extend_from_slice(&10u16.to_be_bytes());
758 buf.extend_from_slice(&[0u8; 5]); // Only 5 of 10 bytes
759 let mut cursor = Cursor::new(&buf);
760 let result = read_record(&mut cursor);
761 assert!(result.is_err());
762 }
763
764 #[test]
765 fn write_read_multiple_records() {
766 let records = vec![
767 TlsRecord::new(ContentType::Handshake, vec![1, 2, 3]),
768 TlsRecord::new(ContentType::ApplicationData, vec![4, 5]),
769 TlsRecord::new(ContentType::Alert, vec![1, 0]),
770 ];
771
772 let mut buf = Vec::new();
773 for r in &records {
774 write_record(&mut buf, r).unwrap();
775 }
776
777 let mut cursor = Cursor::new(&buf);
778 for expected in &records {
779 let read_back = read_record(&mut cursor).unwrap();
780 assert_eq!(read_back.content_type, expected.content_type);
781 assert_eq!(read_back.data, expected.data);
782 }
783 }
784
785 // -- Encrypted record tests --
786
787 fn test_keys(suite: CipherSuite) -> (Vec<u8>, [u8; 12]) {
788 let key = vec![0x42u8; suite.key_len()];
789 let iv = [0x01u8; 12];
790 (key, iv)
791 }
792
793 #[test]
794 fn encrypt_decrypt_roundtrip_aes128() {
795 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
796 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key.clone(), iv);
797 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
798
799 let original = TlsRecord::new(ContentType::Handshake, b"hello TLS".to_vec());
800 let encrypted = enc.encrypt(&original).unwrap();
801 assert_eq!(encrypted.content_type, ContentType::ApplicationData);
802 assert_ne!(encrypted.data, original.data);
803
804 let decrypted = dec.decrypt(&encrypted).unwrap();
805 assert_eq!(decrypted.content_type, ContentType::Handshake);
806 assert_eq!(decrypted.data, b"hello TLS");
807 }
808
809 #[test]
810 fn encrypt_decrypt_roundtrip_aes256() {
811 let (key, iv) = test_keys(CipherSuite::Aes256Gcm);
812 let mut enc = RecordCryptoState::new(CipherSuite::Aes256Gcm, key.clone(), iv);
813 let mut dec = RecordCryptoState::new(CipherSuite::Aes256Gcm, key, iv);
814
815 let original = TlsRecord::new(ContentType::ApplicationData, b"data".to_vec());
816 let encrypted = enc.encrypt(&original).unwrap();
817 let decrypted = dec.decrypt(&encrypted).unwrap();
818 assert_eq!(decrypted.content_type, ContentType::ApplicationData);
819 assert_eq!(decrypted.data, b"data");
820 }
821
822 #[test]
823 fn encrypt_decrypt_roundtrip_chacha20() {
824 let (key, iv) = test_keys(CipherSuite::Chacha20Poly1305);
825 let mut enc = RecordCryptoState::new(CipherSuite::Chacha20Poly1305, key.clone(), iv);
826 let mut dec = RecordCryptoState::new(CipherSuite::Chacha20Poly1305, key, iv);
827
828 let original = TlsRecord::new(ContentType::Handshake, b"chacha test".to_vec());
829 let encrypted = enc.encrypt(&original).unwrap();
830 let decrypted = dec.decrypt(&encrypted).unwrap();
831 assert_eq!(decrypted.content_type, ContentType::Handshake);
832 assert_eq!(decrypted.data, b"chacha test");
833 }
834
835 #[test]
836 fn sequence_number_increments() {
837 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
838 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key.clone(), iv);
839 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
840
841 for i in 0..5u8 {
842 let original = TlsRecord::new(ContentType::ApplicationData, vec![i]);
843 let encrypted = enc.encrypt(&original).unwrap();
844 let decrypted = dec.decrypt(&encrypted).unwrap();
845 assert_eq!(decrypted.data, vec![i]);
846 }
847 }
848
849 #[test]
850 fn out_of_order_decryption_fails() {
851 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
852 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key.clone(), iv);
853 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
854
855 let r1 = enc
856 .encrypt(&TlsRecord::new(ContentType::ApplicationData, vec![1]))
857 .unwrap();
858 let r2 = enc
859 .encrypt(&TlsRecord::new(ContentType::ApplicationData, vec![2]))
860 .unwrap();
861
862 // Decrypt r2 first (out of order) should fail
863 assert!(dec.decrypt(&r2).is_err());
864 // r1 should still work
865 let d1 = dec.decrypt(&r1).unwrap();
866 assert_eq!(d1.data, vec![1]);
867 }
868
869 #[test]
870 fn tampered_ciphertext_rejected() {
871 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
872 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key.clone(), iv);
873 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
874
875 let original = TlsRecord::new(ContentType::ApplicationData, b"sensitive".to_vec());
876 let mut encrypted = enc.encrypt(&original).unwrap();
877 encrypted.data[0] ^= 0xff; // Tamper with ciphertext
878
879 assert!(dec.decrypt(&encrypted).is_err());
880 }
881
882 #[test]
883 fn content_type_hidden_in_encrypted_record() {
884 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
885 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
886
887 // Encrypt a Handshake record
888 let original = TlsRecord::new(ContentType::Handshake, vec![0x01]);
889 let encrypted = enc.encrypt(&original).unwrap();
890
891 // Outer type must be ApplicationData (content type hiding)
892 assert_eq!(encrypted.content_type, ContentType::ApplicationData);
893 }
894
895 #[test]
896 fn encrypt_empty_record() {
897 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
898 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key.clone(), iv);
899 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
900
901 let original = TlsRecord::new(ContentType::ApplicationData, vec![]);
902 let encrypted = enc.encrypt(&original).unwrap();
903 let decrypted = dec.decrypt(&encrypted).unwrap();
904 assert_eq!(decrypted.content_type, ContentType::ApplicationData);
905 assert!(decrypted.data.is_empty());
906 }
907
908 #[test]
909 fn encrypt_record_overflow() {
910 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
911 let mut enc = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
912
913 let big = TlsRecord::new(
914 ContentType::ApplicationData,
915 vec![0u8; MAX_PLAINTEXT_LENGTH + 1],
916 );
917 assert!(enc.encrypt(&big).is_err());
918 }
919
920 #[test]
921 fn decrypt_non_appdata_rejected() {
922 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
923 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
924
925 let bad = TlsRecord::new(ContentType::Handshake, vec![0, 1, 2]);
926 assert!(dec.decrypt(&bad).is_err());
927 }
928
929 #[test]
930 fn decrypt_too_short_rejected() {
931 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
932 let mut dec = RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv);
933
934 // Less than TAG_SIZE + 1
935 let bad = TlsRecord::new(ContentType::ApplicationData, vec![0u8; TAG_SIZE]);
936 assert!(dec.decrypt(&bad).is_err());
937 }
938
939 // -- RecordLayer integration tests --
940
941 #[test]
942 fn record_layer_plaintext_roundtrip() {
943 let mut buf = Vec::new();
944
945 // Write
946 {
947 let cursor = Cursor::new(&mut buf);
948 let mut layer = RecordLayer::new(cursor);
949 layer
950 .write_record(&TlsRecord::new(
951 ContentType::Handshake,
952 b"client hello".to_vec(),
953 ))
954 .unwrap();
955 }
956
957 // Read
958 {
959 let cursor = Cursor::new(buf.clone());
960 let mut layer = RecordLayer::new(cursor);
961 let record = layer.read_record().unwrap();
962 assert_eq!(record.content_type, ContentType::Handshake);
963 assert_eq!(record.data, b"client hello");
964 }
965 }
966
967 #[test]
968 fn record_layer_encrypted_roundtrip() {
969 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
970 let mut buf = Vec::new();
971
972 // Write encrypted
973 {
974 let cursor = Cursor::new(&mut buf);
975 let mut layer = RecordLayer::new(cursor);
976 layer.set_write_crypto(RecordCryptoState::new(
977 CipherSuite::Aes128Gcm,
978 key.clone(),
979 iv,
980 ));
981 layer
982 .write_record(&TlsRecord::new(
983 ContentType::ApplicationData,
984 b"encrypted data".to_vec(),
985 ))
986 .unwrap();
987 }
988
989 // Read encrypted
990 {
991 let cursor = Cursor::new(buf.clone());
992 let mut layer = RecordLayer::new(cursor);
993 layer.set_read_crypto(RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv));
994 let record = layer.read_record().unwrap();
995 assert_eq!(record.content_type, ContentType::ApplicationData);
996 assert_eq!(record.data, b"encrypted data");
997 }
998 }
999
1000 #[test]
1001 fn record_layer_send_close_notify() {
1002 let mut buf = Vec::new();
1003 {
1004 let cursor = Cursor::new(&mut buf);
1005 let mut layer = RecordLayer::new(cursor);
1006 layer.send_close_notify().unwrap();
1007 }
1008
1009 let cursor = Cursor::new(buf.clone());
1010 let mut layer = RecordLayer::new(cursor);
1011 let record = layer.read_record().unwrap();
1012 assert_eq!(record.content_type, ContentType::Alert);
1013 let alert = Alert::parse(&record.data).unwrap();
1014 assert_eq!(alert.description, AlertDescription::CloseNotify);
1015 assert!(!alert.is_fatal());
1016 }
1017
1018 #[test]
1019 fn record_layer_plaintext_then_encrypted() {
1020 let (key, iv) = test_keys(CipherSuite::Aes128Gcm);
1021 let mut buf = Vec::new();
1022
1023 // Write: one plaintext, then switch to encrypted
1024 {
1025 let cursor = Cursor::new(&mut buf);
1026 let mut layer = RecordLayer::new(cursor);
1027
1028 // Plaintext handshake
1029 layer
1030 .write_record(&TlsRecord::new(ContentType::Handshake, b"hello".to_vec()))
1031 .unwrap();
1032
1033 // Switch to encrypted
1034 layer.set_write_crypto(RecordCryptoState::new(
1035 CipherSuite::Aes128Gcm,
1036 key.clone(),
1037 iv,
1038 ));
1039
1040 // Encrypted application data
1041 layer
1042 .write_record(&TlsRecord::new(
1043 ContentType::ApplicationData,
1044 b"secret".to_vec(),
1045 ))
1046 .unwrap();
1047 }
1048
1049 // Read: one plaintext, then switch to encrypted
1050 {
1051 let cursor = Cursor::new(buf.clone());
1052 let mut layer = RecordLayer::new(cursor);
1053
1054 // Read plaintext
1055 let r1 = layer.read_record().unwrap();
1056 assert_eq!(r1.content_type, ContentType::Handshake);
1057 assert_eq!(r1.data, b"hello");
1058
1059 // Switch to encrypted
1060 layer.set_read_crypto(RecordCryptoState::new(CipherSuite::Aes128Gcm, key, iv));
1061
1062 // Read encrypted
1063 let r2 = layer.read_record().unwrap();
1064 assert_eq!(r2.content_type, ContentType::ApplicationData);
1065 assert_eq!(r2.data, b"secret");
1066 }
1067 }
1068
1069 // -- CipherSuite tests --
1070
1071 #[test]
1072 fn cipher_suite_key_lengths() {
1073 assert_eq!(CipherSuite::Aes128Gcm.key_len(), 16);
1074 assert_eq!(CipherSuite::Aes256Gcm.key_len(), 32);
1075 assert_eq!(CipherSuite::Chacha20Poly1305.key_len(), 32);
1076 }
1077
1078 #[test]
1079 fn cipher_suite_iv_lengths() {
1080 assert_eq!(CipherSuite::Aes128Gcm.iv_len(), 12);
1081 assert_eq!(CipherSuite::Aes256Gcm.iv_len(), 12);
1082 assert_eq!(CipherSuite::Chacha20Poly1305.iv_len(), 12);
1083 }
1084
1085 // -- Error Display tests --
1086
1087 #[test]
1088 fn tls_error_display() {
1089 assert_eq!(
1090 TlsError::UnknownContentType(99).to_string(),
1091 "unknown TLS content type: 99"
1092 );
1093 assert_eq!(TlsError::RecordOverflow.to_string(), "TLS record overflow");
1094 assert_eq!(
1095 TlsError::DecryptionFailed.to_string(),
1096 "TLS AEAD decryption failed"
1097 );
1098 assert_eq!(TlsError::MalformedAlert.to_string(), "malformed TLS alert");
1099 }
1100
1101 #[test]
1102 fn tls_error_from_io() {
1103 let io_err = io::Error::new(io::ErrorKind::BrokenPipe, "broken");
1104 let tls_err = TlsError::from(io_err);
1105 assert!(matches!(tls_err, TlsError::Io(_)));
1106 }
1107}