1#![allow(dead_code)]
16
17#[cfg(feature = "alloc")]
18extern crate alloc;
19
20#[cfg(feature = "alloc")]
21use alloc::vec::Vec;
22
23pub mod certificate;
24pub mod cipher;
25pub mod handshake;
26pub mod record;
27
28pub use certificate::{TrustStore, X509Certificate};
30pub use cipher::{
31 hkdf_expand, hkdf_expand_label, hkdf_extract, hmac_sha256, x25519_keypair, x25519_shared_secret,
32};
33pub use handshake::{
34 ClientHello, HandshakeEngine, HandshakeState, HandshakeType, NamedGroup, ServerHello,
35 SignatureScheme,
36};
37pub use record::{
38 decrypt_record, encrypt_record, ContentType, FragmentBuffer, RecordHeader, TlsRecord,
39};
40
41pub(crate) const MAX_RECORD_SIZE: usize = 16384;
47
48pub(crate) const TLS_13_VERSION: u16 = 0x0304;
50
51pub(crate) const TLS_LEGACY_VERSION: u16 = 0x0303;
53
54pub(crate) const AEAD_TAG_LEN: usize = 16;
56
57pub(crate) const HASH_LEN: usize = 32;
59
60pub(crate) const X25519_KEY_LEN: usize = 32;
62
63pub(crate) const NONCE_LEN: usize = 12;
65
66pub(crate) const AES_128_KEY_LEN: usize = 16;
68
69pub(crate) const CHACHA20_KEY_LEN: usize = 32;
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum CipherSuite {
79 Aes128GcmSha256,
81 ChaCha20Poly1305Sha256,
83}
84
85impl CipherSuite {
86 pub fn code(&self) -> u16 {
88 match self {
89 Self::Aes128GcmSha256 => 0x1301,
90 Self::ChaCha20Poly1305Sha256 => 0x1303,
91 }
92 }
93
94 pub fn from_code(code: u16) -> Option<Self> {
96 match code {
97 0x1301 => Some(Self::Aes128GcmSha256),
98 0x1303 => Some(Self::ChaCha20Poly1305Sha256),
99 _ => None,
100 }
101 }
102
103 pub fn key_len(&self) -> usize {
105 match self {
106 Self::Aes128GcmSha256 => AES_128_KEY_LEN,
107 Self::ChaCha20Poly1305Sha256 => CHACHA20_KEY_LEN,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118#[repr(u8)]
119pub enum AlertLevel {
120 Warning = 1,
121 Fatal = 2,
122}
123
124impl AlertLevel {
125 fn from_u8(v: u8) -> Option<Self> {
126 match v {
127 1 => Some(Self::Warning),
128 2 => Some(Self::Fatal),
129 _ => None,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136#[repr(u8)]
137pub enum AlertDescription {
138 CloseNotify = 0,
139 UnexpectedMessage = 10,
140 BadRecordMac = 20,
141 RecordOverflow = 22,
142 HandshakeFailure = 40,
143 BadCertificate = 42,
144 CertificateRevoked = 44,
145 CertificateExpired = 45,
146 CertificateUnknown = 46,
147 IllegalParameter = 47,
148 UnknownCa = 48,
149 DecodeError = 50,
150 DecryptError = 51,
151 ProtocolVersion = 70,
152 InsufficientSecurity = 71,
153 InternalError = 80,
154 MissingExtension = 109,
155 UnsupportedExtension = 110,
156 UnrecognizedName = 112,
157 NoApplicationProtocol = 120,
158}
159
160impl AlertDescription {
161 fn from_u8(v: u8) -> Option<Self> {
162 match v {
163 0 => Some(Self::CloseNotify),
164 10 => Some(Self::UnexpectedMessage),
165 20 => Some(Self::BadRecordMac),
166 22 => Some(Self::RecordOverflow),
167 40 => Some(Self::HandshakeFailure),
168 42 => Some(Self::BadCertificate),
169 44 => Some(Self::CertificateRevoked),
170 45 => Some(Self::CertificateExpired),
171 46 => Some(Self::CertificateUnknown),
172 47 => Some(Self::IllegalParameter),
173 48 => Some(Self::UnknownCa),
174 50 => Some(Self::DecodeError),
175 51 => Some(Self::DecryptError),
176 70 => Some(Self::ProtocolVersion),
177 71 => Some(Self::InsufficientSecurity),
178 80 => Some(Self::InternalError),
179 109 => Some(Self::MissingExtension),
180 110 => Some(Self::UnsupportedExtension),
181 112 => Some(Self::UnrecognizedName),
182 120 => Some(Self::NoApplicationProtocol),
183 _ => None,
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub struct TlsAlert {
191 pub level: AlertLevel,
192 pub description: AlertDescription,
193}
194
195impl TlsAlert {
196 pub fn new(level: AlertLevel, description: AlertDescription) -> Self {
197 Self { level, description }
198 }
199
200 pub fn encode(&self) -> [u8; 2] {
201 [self.level as u8, self.description as u8]
202 }
203
204 pub fn decode(data: &[u8]) -> Option<Self> {
205 if data.len() < 2 {
206 return None;
207 }
208 Some(Self {
209 level: AlertLevel::from_u8(data[0])?,
210 description: AlertDescription::from_u8(data[1])?,
211 })
212 }
213
214 pub fn is_fatal(&self) -> bool {
216 self.level == AlertLevel::Fatal
217 }
218}
219
220#[derive(Debug, Clone)]
226pub struct SessionTicket {
227 pub lifetime: u32,
229 pub age_add: u32,
231 pub nonce: Vec<u8>,
233 pub ticket: Vec<u8>,
235 pub cipher_suite: CipherSuite,
237 pub resumption_secret: [u8; HASH_LEN],
239 pub created_at: u64,
241}
242
243impl SessionTicket {
244 pub fn from_message(
246 data: &[u8],
247 cipher_suite: CipherSuite,
248 resumption_secret: [u8; HASH_LEN],
249 now: u64,
250 ) -> Option<Self> {
251 if data.len() < 4 {
252 return None;
253 }
254 if data[0] != HandshakeType::NewSessionTicket as u8 {
255 return None;
256 }
257 let payload = &data[4..];
258 if payload.len() < 12 {
259 return None;
260 }
261
262 let lifetime = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
263 let age_add = u32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
264
265 let nonce_len = payload[8] as usize;
266 if 9 + nonce_len + 2 > payload.len() {
267 return None;
268 }
269 let nonce = payload[9..9 + nonce_len].to_vec();
270
271 let ticket_pos = 9 + nonce_len;
272 let ticket_len =
273 u16::from_be_bytes([payload[ticket_pos], payload[ticket_pos + 1]]) as usize;
274 let ticket_start = ticket_pos + 2;
275 if ticket_start + ticket_len > payload.len() {
276 return None;
277 }
278 let ticket = payload[ticket_start..ticket_start + ticket_len].to_vec();
279
280 Some(Self {
281 lifetime,
282 age_add,
283 nonce,
284 ticket,
285 cipher_suite,
286 resumption_secret,
287 created_at: now,
288 })
289 }
290
291 pub fn derive_psk(&self) -> [u8; HASH_LEN] {
293 let expanded = hkdf_expand_label(
294 &self.resumption_secret,
295 b"resumption",
296 &self.nonce,
297 HASH_LEN,
298 );
299 let mut psk = [0u8; HASH_LEN];
300 psk.copy_from_slice(&expanded);
301 psk
302 }
303
304 pub fn is_expired(&self, now: u64) -> bool {
306 now.saturating_sub(self.created_at) > self.lifetime as u64
307 }
308}
309
310pub struct SessionStore {
312 tickets: Vec<SessionTicket>,
313 max_entries: usize,
314}
315
316impl SessionStore {
317 pub fn new(max_entries: usize) -> Self {
318 Self {
319 tickets: Vec::new(),
320 max_entries,
321 }
322 }
323
324 pub fn store(&mut self, ticket: SessionTicket) {
326 if self.tickets.len() >= self.max_entries {
327 self.tickets.remove(0);
328 }
329 self.tickets.push(ticket);
330 }
331
332 pub fn find_valid(&self, now: u64) -> Option<&SessionTicket> {
334 self.tickets.iter().rev().find(|t| !t.is_expired(now))
335 }
336
337 pub fn prune(&mut self, now: u64) {
339 self.tickets.retain(|t| !t.is_expired(now));
340 }
341
342 pub fn count(&self) -> usize {
344 self.tickets.len()
345 }
346}
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum EarlyDataState {
353 NotAttempted,
355 Offered,
357 Accepted,
359 Rejected,
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum TlsError {
370 NotConnected,
372 Closed,
374 HandshakeError,
376 DecryptionFailed,
378 AlertReceived(AlertDescription),
380 InvalidState,
382 BufferTooSmall,
384 DataTooLarge,
386}
387
388pub struct TlsConnection {
394 engine: HandshakeEngine,
396 send_seq: u64,
398 recv_seq: u64,
400 client_write_key: Option<Vec<u8>>,
402 server_write_key: Option<Vec<u8>>,
404 client_write_iv: Option<[u8; NONCE_LEN]>,
406 server_write_iv: Option<[u8; NONCE_LEN]>,
408 fragment_buf: FragmentBuffer,
410 app_data_buf: Vec<u8>,
412 session_store: SessionStore,
414 pub early_data: EarlyDataState,
416 closed: bool,
418 pub last_alert: Option<TlsAlert>,
420}
421
422impl Default for TlsConnection {
423 fn default() -> Self {
424 Self::new()
425 }
426}
427
428impl TlsConnection {
429 pub fn new() -> Self {
431 Self {
432 engine: HandshakeEngine::new(),
433 send_seq: 0,
434 recv_seq: 0,
435 client_write_key: None,
436 server_write_key: None,
437 client_write_iv: None,
438 server_write_iv: None,
439 fragment_buf: FragmentBuffer::new(),
440 app_data_buf: Vec::new(),
441 session_store: SessionStore::new(4),
442 early_data: EarlyDataState::NotAttempted,
443 closed: false,
444 last_alert: None,
445 }
446 }
447
448 pub fn connect(&mut self, random: [u8; 32]) -> Result<Vec<u8>, TlsError> {
450 let ch_msg = self
451 .engine
452 .build_client_hello(random)
453 .ok_or(TlsError::InvalidState)?;
454
455 let record = TlsRecord::new(ContentType::Handshake, ch_msg);
456 Ok(record.encode())
457 }
458
459 pub fn process_incoming(&mut self, data: &[u8]) -> Result<Vec<u8>, TlsError> {
462 let mut response = Vec::new();
463 let mut pos = 0;
464
465 while pos < data.len() {
466 let (record, consumed) =
467 TlsRecord::decode(&data[pos..]).ok_or(TlsError::DecryptionFailed)?;
468 pos += consumed;
469
470 match record.content_type {
471 ContentType::Alert => {
472 if let Some(alert) = TlsAlert::decode(&record.fragment) {
473 self.last_alert = Some(alert);
474 if alert.is_fatal() {
475 self.closed = true;
476 return Err(TlsError::AlertReceived(alert.description));
477 }
478 if alert.description == AlertDescription::CloseNotify {
479 self.closed = true;
480 }
481 }
482 }
483 ContentType::Handshake => {
484 if let Some(msg) = self.fragment_buf.append(&record) {
485 self.process_handshake_message(&msg, &mut response)?;
486 }
487 }
488 ContentType::ApplicationData => {
489 if self.engine.state == HandshakeState::Connected {
490 let cipher = self.engine.cipher_suite.ok_or(TlsError::NotConnected)?;
492 let iv = self
493 .server_write_iv
494 .as_ref()
495 .ok_or(TlsError::NotConnected)?;
496 let key = self
497 .server_write_key
498 .as_ref()
499 .ok_or(TlsError::NotConnected)?;
500 if let Some(plain) = decrypt_record(&record, key, iv, self.recv_seq, cipher)
501 {
502 self.recv_seq += 1;
503 self.app_data_buf.extend_from_slice(&plain.fragment);
504 } else {
505 return Err(TlsError::DecryptionFailed);
506 }
507 }
508 }
509 ContentType::ChangeCipherSpec => {
510 }
512 }
513 }
514
515 Ok(response)
516 }
517
518 fn process_handshake_message(
520 &mut self,
521 msg: &[u8],
522 response: &mut Vec<u8>,
523 ) -> Result<(), TlsError> {
524 if msg.is_empty() {
525 return Err(TlsError::HandshakeError);
526 }
527
528 let hs_type = HandshakeType::from_u8(msg[0]).ok_or(TlsError::HandshakeError)?;
529
530 match hs_type {
531 HandshakeType::ServerHello => {
532 self.engine
533 .process_server_hello(msg)
534 .ok_or(TlsError::HandshakeError)?;
535 }
536 HandshakeType::EncryptedExtensions => {
537 self.engine
538 .process_encrypted_extensions(msg)
539 .ok_or(TlsError::HandshakeError)?;
540 }
541 HandshakeType::Certificate => {
542 self.engine
543 .process_certificate(msg)
544 .ok_or(TlsError::HandshakeError)?;
545 }
546 HandshakeType::CertificateVerify => {
547 self.engine
548 .process_certificate_verify(msg)
549 .ok_or(TlsError::HandshakeError)?;
550 }
551 HandshakeType::Finished => {
552 self.engine
553 .process_finished(msg)
554 .ok_or(TlsError::HandshakeError)?;
555
556 self.derive_traffic_keys()?;
558
559 if let Some(client_finished) = self.engine.build_client_finished() {
561 let record = TlsRecord::new(ContentType::Handshake, client_finished);
562 response.extend_from_slice(&record.encode());
563 }
564 }
565 HandshakeType::NewSessionTicket => {
566 if let Some(cipher) = self.engine.cipher_suite {
568 let resumption_secret = self
569 .engine
570 .client_app_traffic_secret
571 .unwrap_or([0u8; HASH_LEN]);
572 if let Some(ticket) =
573 SessionTicket::from_message(msg, cipher, resumption_secret, 0)
574 {
575 self.session_store.store(ticket);
576 }
577 }
578 }
579 _ => {
580 return Err(TlsError::HandshakeError);
581 }
582 }
583
584 Ok(())
585 }
586
587 fn derive_traffic_keys(&mut self) -> Result<(), TlsError> {
589 let cipher = self.engine.cipher_suite.ok_or(TlsError::NotConnected)?;
590 let key_len = cipher.key_len();
591
592 let c_secret = self
594 .engine
595 .client_app_traffic_secret
596 .as_ref()
597 .ok_or(TlsError::NotConnected)?;
598 self.client_write_key = Some(hkdf_expand_label(c_secret, b"key", &[], key_len));
599 let c_iv = hkdf_expand_label(c_secret, b"iv", &[], NONCE_LEN);
600 let mut civ = [0u8; NONCE_LEN];
601 civ.copy_from_slice(&c_iv);
602 self.client_write_iv = Some(civ);
603
604 let s_secret = self
606 .engine
607 .server_app_traffic_secret
608 .as_ref()
609 .ok_or(TlsError::NotConnected)?;
610 self.server_write_key = Some(hkdf_expand_label(s_secret, b"key", &[], key_len));
611 let s_iv = hkdf_expand_label(s_secret, b"iv", &[], NONCE_LEN);
612 let mut siv = [0u8; NONCE_LEN];
613 siv.copy_from_slice(&s_iv);
614 self.server_write_iv = Some(siv);
615
616 Ok(())
617 }
618
619 pub fn send(&mut self, data: &[u8]) -> Result<Vec<u8>, TlsError> {
621 if self.closed {
622 return Err(TlsError::Closed);
623 }
624 if self.engine.state != HandshakeState::Connected {
625 return Err(TlsError::NotConnected);
626 }
627
628 let cipher = self.engine.cipher_suite.ok_or(TlsError::NotConnected)?;
629 let key = self
630 .client_write_key
631 .as_ref()
632 .ok_or(TlsError::NotConnected)?;
633 let iv = self
634 .client_write_iv
635 .as_ref()
636 .ok_or(TlsError::NotConnected)?;
637
638 let mut output = Vec::new();
639
640 for chunk in data.chunks(MAX_RECORD_SIZE) {
642 let record = TlsRecord::new(ContentType::ApplicationData, chunk.to_vec());
643 let encrypted = encrypt_record(&record, key, iv, self.send_seq, cipher)
644 .ok_or(TlsError::DecryptionFailed)?;
645 self.send_seq += 1;
646 output.extend_from_slice(&encrypted.encode());
647 }
648
649 Ok(output)
650 }
651
652 pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, TlsError> {
654 if self.app_data_buf.is_empty() {
655 if self.closed {
656 return Err(TlsError::Closed);
657 }
658 return Ok(0);
659 }
660
661 let copy_len = core::cmp::min(buf.len(), self.app_data_buf.len());
662 buf[..copy_len].copy_from_slice(&self.app_data_buf[..copy_len]);
663 self.app_data_buf = self.app_data_buf[copy_len..].to_vec();
664 Ok(copy_len)
665 }
666
667 pub fn close(&mut self) -> Result<Vec<u8>, TlsError> {
669 if self.closed {
670 return Err(TlsError::Closed);
671 }
672 self.closed = true;
673
674 let alert = TlsAlert::new(AlertLevel::Warning, AlertDescription::CloseNotify);
675 let alert_bytes = alert.encode().to_vec();
676
677 if self.engine.state == HandshakeState::Connected {
678 let cipher = self.engine.cipher_suite.ok_or(TlsError::NotConnected)?;
679 let key = self
680 .client_write_key
681 .as_ref()
682 .ok_or(TlsError::NotConnected)?;
683 let iv = self
684 .client_write_iv
685 .as_ref()
686 .ok_or(TlsError::NotConnected)?;
687
688 let record = TlsRecord::new(ContentType::Alert, alert_bytes);
689 let encrypted = encrypt_record(&record, key, iv, self.send_seq, cipher)
690 .ok_or(TlsError::DecryptionFailed)?;
691 self.send_seq += 1;
692 Ok(encrypted.encode())
693 } else {
694 let record = TlsRecord::new(ContentType::Alert, alert_bytes);
695 Ok(record.encode())
696 }
697 }
698
699 pub fn state(&self) -> HandshakeState {
701 self.engine.state
702 }
703
704 pub fn is_connected(&self) -> bool {
706 self.engine.state == HandshakeState::Connected && !self.closed
707 }
708
709 pub fn cipher_suite(&self) -> Option<CipherSuite> {
711 self.engine.cipher_suite
712 }
713
714 pub fn session_ticket_count(&self) -> usize {
716 self.session_store.count()
717 }
718}
719
720#[cfg(test)]
725mod tests {
726 #[allow(unused_imports)]
727 use alloc::vec;
728
729 use super::{
730 certificate::asn1_parse_tlv,
731 cipher::{
732 aes128_gcm_decrypt, aes128_gcm_encrypt, chacha20_crypt, chacha20_poly1305_decrypt,
733 chacha20_poly1305_encrypt, x25519_scalar_mult,
734 },
735 record::compute_nonce,
736 *,
737 };
738
739 #[test]
742 fn test_record_header_encode_decode() {
743 let header = RecordHeader {
744 content_type: ContentType::Handshake,
745 legacy_version: TLS_LEGACY_VERSION,
746 length: 256,
747 };
748 let mut buf = [0u8; 5];
749 let n = header.encode(&mut buf);
750 assert_eq!(n, 5);
751
752 let decoded = RecordHeader::decode(&buf).unwrap();
753 assert_eq!(decoded.content_type, ContentType::Handshake);
754 assert_eq!(decoded.legacy_version, TLS_LEGACY_VERSION);
755 assert_eq!(decoded.length, 256);
756 }
757
758 #[test]
759 fn test_record_encode_decode() {
760 let data = vec![1, 2, 3, 4, 5];
761 let record = TlsRecord::new(ContentType::ApplicationData, data.clone());
762 let encoded = record.encode();
763
764 let (decoded, consumed) = TlsRecord::decode(&encoded).unwrap();
765 assert_eq!(consumed, encoded.len());
766 assert_eq!(decoded.content_type, ContentType::ApplicationData);
767 assert_eq!(decoded.fragment, data);
768 }
769
770 #[test]
771 fn test_record_header_rejects_oversized() {
772 let mut buf = [0u8; 5];
773 buf[0] = ContentType::ApplicationData as u8;
774 buf[1..3].copy_from_slice(&TLS_LEGACY_VERSION.to_be_bytes());
775 let too_large = (MAX_RECORD_SIZE + AEAD_TAG_LEN + 2) as u16;
777 buf[3..5].copy_from_slice(&too_large.to_be_bytes());
778 assert!(RecordHeader::decode(&buf).is_none());
779 }
780
781 #[test]
782 fn test_content_type_roundtrip() {
783 assert_eq!(
784 ContentType::from_u8(20),
785 Some(ContentType::ChangeCipherSpec)
786 );
787 assert_eq!(ContentType::from_u8(21), Some(ContentType::Alert));
788 assert_eq!(ContentType::from_u8(22), Some(ContentType::Handshake));
789 assert_eq!(ContentType::from_u8(23), Some(ContentType::ApplicationData));
790 assert_eq!(ContentType::from_u8(99), None);
791 }
792
793 #[test]
796 fn test_handshake_initial_state() {
797 let engine = HandshakeEngine::new();
798 assert_eq!(engine.state, HandshakeState::Start);
799 }
800
801 #[test]
802 fn test_handshake_client_hello_advances_state() {
803 let mut engine = HandshakeEngine::new();
804 let random = [0x42u8; 32];
805 let msg = engine.build_client_hello(random);
806 assert!(msg.is_some());
807 assert_eq!(engine.state, HandshakeState::WaitServerHello);
808 }
809
810 #[test]
811 fn test_handshake_rejects_wrong_state() {
812 let mut engine = HandshakeEngine::new();
813 assert!(engine.process_server_hello(&[]).is_none());
815 }
816
817 #[test]
818 fn test_cipher_suite_code_roundtrip() {
819 assert_eq!(
820 CipherSuite::from_code(0x1301),
821 Some(CipherSuite::Aes128GcmSha256)
822 );
823 assert_eq!(
824 CipherSuite::from_code(0x1303),
825 Some(CipherSuite::ChaCha20Poly1305Sha256)
826 );
827 assert_eq!(CipherSuite::from_code(0x9999), None);
828 assert_eq!(CipherSuite::Aes128GcmSha256.code(), 0x1301);
829 assert_eq!(CipherSuite::ChaCha20Poly1305Sha256.code(), 0x1303);
830 }
831
832 #[test]
833 fn test_client_hello_encoding() {
834 let ch = ClientHello {
835 random: [0xAA; 32],
836 session_id: [0; 32],
837 cipher_suites: vec![CipherSuite::Aes128GcmSha256],
838 key_share_public: [0xBB; 32],
839 };
840 let encoded = ch.encode();
841 assert_eq!(encoded[0], HandshakeType::ClientHello as u8);
843 assert_eq!(&encoded[6..38], &[0xAA; 32]);
845 }
846
847 #[test]
850 fn test_hkdf_extract_rfc5869_vector1() {
851 let ikm = [0x0bu8; 22];
853 let salt = [
854 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
855 ];
856 let prk = hkdf_extract(&salt, &ikm);
857
858 let expected: [u8; 32] = [
859 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b,
860 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a,
861 0xd7, 0xc2, 0xb3, 0xe5,
862 ];
863 assert_eq!(prk, expected);
864 }
865
866 #[test]
867 fn test_hkdf_expand_rfc5869_vector1() {
868 let prk: [u8; 32] = [
869 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b,
870 0xba, 0x63, 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a,
871 0xd7, 0xc2, 0xb3, 0xe5,
872 ];
873 let info = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
874 let okm = hkdf_expand(&prk, &info, 42);
875
876 let expected: [u8; 42] = [
877 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
878 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
879 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
880 ];
881 assert_eq!(&okm[..], &expected[..]);
882 }
883
884 #[test]
887 fn test_x25519_basepoint_multiplication() {
888 let scalar: [u8; 32] = [
890 0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46,
891 0x5e, 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44,
892 0xba, 0x44, 0x9a, 0xc4,
893 ];
894 let expected: [u8; 32] = [
896 0x1c, 0x9f, 0xd8, 0x8f, 0x45, 0x60, 0x6d, 0x93, 0x2a, 0x80, 0xc7, 0x18, 0x24, 0xae,
897 0x15, 0x1d, 0x15, 0xd7, 0x3e, 0x77, 0xde, 0x38, 0xe8, 0xe0, 0x00, 0x85, 0x2e, 0x61,
898 0x4f, 0xae, 0x70, 0x19,
899 ];
900
901 let x25519_basepoint: [u8; 32] = {
902 let mut b = [0u8; 32];
903 b[0] = 9;
904 b
905 };
906 let result = x25519_scalar_mult(&scalar, &x25519_basepoint);
907 assert_eq!(result, expected);
908 }
909
910 #[test]
911 fn test_x25519_shared_secret_symmetry() {
912 let alice_sk: [u8; 32] = {
914 let mut k = [0u8; 32];
915 for (i, b) in k.iter_mut().enumerate() {
916 *b = (i as u8).wrapping_mul(7).wrapping_add(3);
917 }
918 k
919 };
920 let bob_sk: [u8; 32] = {
921 let mut k = [0u8; 32];
922 for (i, b) in k.iter_mut().enumerate() {
923 *b = (i as u8).wrapping_mul(13).wrapping_add(17);
924 }
925 k
926 };
927
928 let x25519_basepoint: [u8; 32] = {
929 let mut b = [0u8; 32];
930 b[0] = 9;
931 b
932 };
933 let alice_pk = x25519_scalar_mult(&alice_sk, &x25519_basepoint);
934 let bob_pk = x25519_scalar_mult(&bob_sk, &x25519_basepoint);
935
936 let alice_shared = x25519_shared_secret(&alice_sk, &bob_pk);
937 let bob_shared = x25519_shared_secret(&bob_sk, &alice_pk);
938
939 assert_eq!(alice_shared, bob_shared);
940 }
941
942 #[test]
945 fn test_chacha20_encrypt_decrypt_roundtrip() {
946 let key = [0x42u8; 32];
947 let nonce = [0x01u8; 12];
948 let plaintext = b"Hello, TLS 1.3 from VeridianOS!";
949
950 let ciphertext = chacha20_crypt(&key, &nonce, 1, plaintext);
951 assert_ne!(&ciphertext[..], &plaintext[..]);
952
953 let decrypted = chacha20_crypt(&key, &nonce, 1, &ciphertext);
954 assert_eq!(&decrypted[..], &plaintext[..]);
955 }
956
957 #[test]
958 fn test_chacha20_poly1305_roundtrip() {
959 let key = [0x42u8; 32];
960 let nonce = [0x01u8; 12];
961 let aad = b"additional authenticated data";
962 let plaintext = b"secret message for TLS";
963
964 let ct = chacha20_poly1305_encrypt(&key, &nonce, aad, plaintext);
965 assert_eq!(ct.len(), plaintext.len() + AEAD_TAG_LEN);
966
967 let pt = chacha20_poly1305_decrypt(&key, &nonce, aad, &ct).unwrap();
968 assert_eq!(&pt[..], &plaintext[..]);
969 }
970
971 #[test]
972 fn test_chacha20_poly1305_tamper_detection() {
973 let key = [0x42u8; 32];
974 let nonce = [0x01u8; 12];
975 let aad = b"aad";
976 let plaintext = b"secret";
977
978 let mut ct = chacha20_poly1305_encrypt(&key, &nonce, aad, plaintext);
979 ct[0] ^= 0xFF;
981 assert!(chacha20_poly1305_decrypt(&key, &nonce, aad, &ct).is_none());
982 }
983
984 #[test]
987 fn test_aes128_gcm_roundtrip() {
988 let key = [0x42u8; 16];
989 let nonce = [0x01u8; 12];
990 let aad = b"additional data";
991 let plaintext = b"AES-GCM test message for TLS";
992
993 let ct = aes128_gcm_encrypt(&key, &nonce, aad, plaintext);
994 assert_eq!(ct.len(), plaintext.len() + AEAD_TAG_LEN);
995
996 let pt = aes128_gcm_decrypt(&key, &nonce, aad, &ct).unwrap();
997 assert_eq!(&pt[..], &plaintext[..]);
998 }
999
1000 #[test]
1001 fn test_aes128_gcm_tamper_detection() {
1002 let key = [0x42u8; 16];
1003 let nonce = [0x01u8; 12];
1004 let aad = b"aad";
1005 let plaintext = b"secret";
1006
1007 let mut ct = aes128_gcm_encrypt(&key, &nonce, aad, plaintext);
1008 ct[0] ^= 0xFF;
1009 assert!(aes128_gcm_decrypt(&key, &nonce, aad, &ct).is_none());
1010 }
1011
1012 #[test]
1015 fn test_alert_encode_decode() {
1016 let alert = TlsAlert::new(AlertLevel::Fatal, AlertDescription::HandshakeFailure);
1017 let encoded = alert.encode();
1018 assert_eq!(encoded, [2, 40]);
1019
1020 let decoded = TlsAlert::decode(&encoded).unwrap();
1021 assert_eq!(decoded.level, AlertLevel::Fatal);
1022 assert_eq!(decoded.description, AlertDescription::HandshakeFailure);
1023 assert!(decoded.is_fatal());
1024 }
1025
1026 #[test]
1027 fn test_alert_close_notify() {
1028 let alert = TlsAlert::new(AlertLevel::Warning, AlertDescription::CloseNotify);
1029 assert!(!alert.is_fatal());
1030 let encoded = alert.encode();
1031 let decoded = TlsAlert::decode(&encoded).unwrap();
1032 assert_eq!(decoded.description, AlertDescription::CloseNotify);
1033 }
1034
1035 #[test]
1038 fn test_session_store_lifecycle() {
1039 let mut store = SessionStore::new(2);
1040 assert_eq!(store.count(), 0);
1041
1042 let ticket1 = SessionTicket {
1043 lifetime: 3600,
1044 age_add: 0,
1045 nonce: vec![1],
1046 ticket: vec![0xAA; 16],
1047 cipher_suite: CipherSuite::Aes128GcmSha256,
1048 resumption_secret: [0x11; 32],
1049 created_at: 100,
1050 };
1051 let ticket2 = SessionTicket {
1052 lifetime: 3600,
1053 age_add: 0,
1054 nonce: vec![2],
1055 ticket: vec![0xBB; 16],
1056 cipher_suite: CipherSuite::ChaCha20Poly1305Sha256,
1057 resumption_secret: [0x22; 32],
1058 created_at: 200,
1059 };
1060
1061 store.store(ticket1);
1062 store.store(ticket2);
1063 assert_eq!(store.count(), 2);
1064
1065 let found = store.find_valid(300).unwrap();
1067 assert_eq!(found.nonce, vec![2]);
1068 }
1069
1070 #[test]
1071 fn test_session_ticket_expiry() {
1072 let mut store = SessionStore::new(4);
1073 let ticket = SessionTicket {
1074 lifetime: 100,
1075 age_add: 0,
1076 nonce: vec![1],
1077 ticket: vec![0xAA; 8],
1078 cipher_suite: CipherSuite::Aes128GcmSha256,
1079 resumption_secret: [0; 32],
1080 created_at: 1000,
1081 };
1082
1083 store.store(ticket);
1084 assert!(store.find_valid(1050).is_some()); assert!(store.find_valid(1200).is_none()); store.prune(1200);
1088 assert_eq!(store.count(), 0);
1089 }
1090
1091 #[test]
1092 fn test_session_store_eviction() {
1093 let mut store = SessionStore::new(2);
1094
1095 for i in 0..3 {
1096 store.store(SessionTicket {
1097 lifetime: 3600,
1098 age_add: 0,
1099 nonce: vec![i as u8],
1100 ticket: vec![i as u8; 8],
1101 cipher_suite: CipherSuite::Aes128GcmSha256,
1102 resumption_secret: [0; 32],
1103 created_at: i as u64,
1104 });
1105 }
1106
1107 assert_eq!(store.count(), 2);
1109 }
1110
1111 #[test]
1114 fn test_asn1_parse_tlv_short_form() {
1115 let data = [0x30, 0x03, 0x01, 0x02, 0x03];
1117 let (tag, start, len) = asn1_parse_tlv(&data).unwrap();
1118 assert_eq!(tag, 0x30);
1119 assert_eq!(start, 2);
1120 assert_eq!(len, 3);
1121 }
1122
1123 #[test]
1124 fn test_asn1_parse_tlv_long_form() {
1125 let mut data = vec![0x30, 0x81, 0x80];
1127 data.extend_from_slice(&[0u8; 128]);
1128 let (tag, start, len) = asn1_parse_tlv(&data).unwrap();
1129 assert_eq!(tag, 0x30);
1130 assert_eq!(start, 3);
1131 assert_eq!(len, 128);
1132 }
1133
1134 #[test]
1135 fn test_trust_store_empty_chain_rejected() {
1136 let store = TrustStore::new();
1137 assert!(!store.validate_chain(&[]));
1138 }
1139
1140 #[test]
1143 fn test_encrypted_record_roundtrip_chacha() {
1144 let key = [0x42u8; 32];
1145 let iv = [0x01u8; 12];
1146 let plaintext = b"application data payload";
1147
1148 let record = TlsRecord::new(ContentType::ApplicationData, plaintext.to_vec());
1149 let encrypted =
1150 encrypt_record(&record, &key, &iv, 0, CipherSuite::ChaCha20Poly1305Sha256).unwrap();
1151 let decrypted = decrypt_record(
1152 &encrypted,
1153 &key,
1154 &iv,
1155 0,
1156 CipherSuite::ChaCha20Poly1305Sha256,
1157 )
1158 .unwrap();
1159
1160 assert_eq!(decrypted.content_type, ContentType::ApplicationData);
1161 assert_eq!(&decrypted.fragment[..], &plaintext[..]);
1162 }
1163
1164 #[test]
1165 fn test_encrypted_record_roundtrip_aes() {
1166 let key = [0x42u8; 16];
1167 let iv = [0x01u8; 12];
1168 let plaintext = b"AES encrypted record data";
1169
1170 let record = TlsRecord::new(ContentType::Handshake, plaintext.to_vec());
1171 let encrypted =
1172 encrypt_record(&record, &key, &iv, 5, CipherSuite::Aes128GcmSha256).unwrap();
1173
1174 assert_eq!(encrypted.content_type, ContentType::ApplicationData);
1176
1177 let decrypted =
1178 decrypt_record(&encrypted, &key, &iv, 5, CipherSuite::Aes128GcmSha256).unwrap();
1179 assert_eq!(decrypted.content_type, ContentType::Handshake);
1180 assert_eq!(&decrypted.fragment[..], &plaintext[..]);
1181 }
1182
1183 #[test]
1186 fn test_tls_connection_initial_state() {
1187 let conn = TlsConnection::new();
1188 assert_eq!(conn.state(), HandshakeState::Start);
1189 assert!(!conn.is_connected());
1190 assert!(conn.cipher_suite().is_none());
1191 }
1192
1193 #[test]
1194 fn test_tls_connection_connect_produces_record() {
1195 let mut conn = TlsConnection::new();
1196 let result = conn.connect([0x42; 32]);
1197 assert!(result.is_ok());
1198 let record_bytes = result.unwrap();
1199 assert!(record_bytes.len() > 5);
1201 assert_eq!(record_bytes[0], ContentType::Handshake as u8);
1202 assert_eq!(conn.state(), HandshakeState::WaitServerHello);
1203 }
1204
1205 #[test]
1206 fn test_tls_connection_send_before_connect() {
1207 let mut conn = TlsConnection::new();
1208 let result = conn.send(b"hello");
1209 assert_eq!(result, Err(TlsError::NotConnected));
1210 }
1211
1212 #[test]
1213 fn test_tls_connection_close_before_connect() {
1214 let mut conn = TlsConnection::new();
1215 let result = conn.close();
1216 assert!(result.is_ok());
1218 assert!(conn.closed);
1219 }
1220
1221 #[test]
1224 fn test_compute_nonce() {
1225 let iv = [
1226 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1227 ];
1228 let nonce = compute_nonce(&iv, 1);
1229 assert_eq!(nonce[0], 0x00);
1231 assert_eq!(nonce[11], 0x0b ^ 0x01);
1232 }
1233
1234 #[test]
1237 fn test_hmac_sha256_rfc4231_vector1() {
1238 let key = [0x0bu8; 20];
1240 let data = b"Hi There";
1241 let mac = hmac_sha256(&key, data);
1242 let expected: [u8; 32] = [
1243 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b,
1244 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
1245 0x2e, 0x32, 0xcf, 0xf7,
1246 ];
1247 assert_eq!(mac, expected);
1248 }
1249}