1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Transport Layer Security (TLS 1.2 / TLS 1.3) (RFC 8446 (TLS 1.3) / RFC 5246 (TLS 1.2) / RFC 6066 (SNI) / RFC 7301 (ALPN)) Layer 7 wire packet builder.
//!
//! This module synthesizes deterministic on-the-wire Transport Layer Security (TLS 1.2 / TLS 1.3) frames over TCP port 443.
//! The wire encoder constructs a 5-byte TLS Record Layer header (8-bit ContentType: `21` Alert, `22` Handshake, `23` ApplicationData; 16-bit Legacy Record Version e.g. `0x0303`; 16-bit Length) encapsulating a 4-byte Handshake header (8-bit HandshakeType: `1` ClientHello, `2` ServerHello, `11` Certificate, `20` Finished; 24-bit Length), 32-byte Random nonce, CipherSuite list (`0x1301` TLS_AES_128_GCM_SHA256, `0x1302` TLS_AES_256_GCM_SHA384), and SNI (`0x0000`) / ALPN (`0x0010`) extensions. When optional fields in [`TlsSpec`]
//! are omitted in the `.fasm` source, RFC-compliant default values are automatically populated.
//!
//! Canonical `.fasm` syntax:
//! `a -> b TLS [CLIENT_HELLO | SERVER_HELLO | CERTIFICATE | FINISHED | APP_DATA | ALERT] [RECORD_VERSION <u16>] [HANDSHAKE_VERSION <u16>] [SNI <str>] [CIPHERSUITE <u16>] [ALPN <str>]`
use crate::ast::*;
use crate::builders::WireContext;
use alloc::vec::Vec;
/// Builds an RFC 8446 TLS 1.3 handshake record payload.
/// Syntax mapping: `TLS [CLIENT_HELLO | SERVER_HELLO | CERTIFICATE | FINISHED | APP_DATA | ALERT] [RECORD_VERSION <u16>] [HANDSHAKE_VERSION <u16>] [SNI <str>] [CIPHERSUITE <u16>] [ALPN <str>]`
#[doc(alias = "TLS")]
#[doc(alias = "TLS13")]
#[doc(alias = "RFC8446")]
#[doc(alias = "SNI")]
#[doc(alias = "ALPN")]
#[doc(alias = "CIPHERSUITE")]
pub fn build_tls_payload(
_ctx: &WireContext,
spec: &TlsSpec,
payload_bytes: &[u8],
) -> Vec<u8> {
let rec_version = spec.record_version.unwrap_or(0x0303); // TLS 1.2 compatibility layer
let hs_version = spec
.handshake_version
.or(spec.record_version)
.unwrap_or(0x0303);
let cs = spec.ciphersuite.unwrap_or(0x1301); // TLS_AES_128_GCM_SHA256
if spec.is_alert {
let body: &[u8] = if !payload_bytes.is_empty() {
payload_bytes
} else {
&[2, 40] // Level 2 (Fatal), Description 40 (handshake_failure)
};
let mut rec = Vec::with_capacity(5 + body.len());
rec.push(21); // ContentType: Alert (21)
rec.extend_from_slice(&rec_version.to_be_bytes());
rec.extend_from_slice(&(body.len() as u16).to_be_bytes());
rec.extend_from_slice(body);
return rec;
}
if spec.is_app_data {
let body: &[u8] = if !payload_bytes.is_empty() {
payload_bytes
} else {
b"\x00\x01\x02\x03\x04\x05\x06\x07"
};
let mut rec = Vec::with_capacity(5 + body.len());
rec.push(23); // ContentType: ApplicationData (23)
rec.extend_from_slice(&rec_version.to_be_bytes());
rec.extend_from_slice(&(body.len() as u16).to_be_bytes());
rec.extend_from_slice(body);
return rec;
}
// Build shared TLS Extensions (RFC 6066 SNI 0x0000 and RFC 7301 ALPN 0x0010)
let mut exts = Vec::new();
if let Some(sni) = spec.sni {
let sni_bytes = sni.as_bytes();
exts.extend_from_slice(&0u16.to_be_bytes()); // Extension Type 0: server_name
let ext_len = (sni_bytes.len() + 5) as u16;
exts.extend_from_slice(&ext_len.to_be_bytes());
let list_len = (sni_bytes.len() + 3) as u16;
exts.extend_from_slice(&list_len.to_be_bytes());
exts.push(0); // HostName Type 0
exts.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
exts.extend_from_slice(sni_bytes);
}
if let Some(alpn) = spec.alpn {
let alpn_bytes = alpn.as_bytes();
exts.extend_from_slice(&0x0010u16.to_be_bytes()); // Extension Type 16: application_layer_protocol_negotiation
let ext_len = (alpn_bytes.len() + 3) as u16;
exts.extend_from_slice(&ext_len.to_be_bytes());
let list_len = (alpn_bytes.len() + 1) as u16;
exts.extend_from_slice(&list_len.to_be_bytes());
exts.push(alpn_bytes.len() as u8);
exts.extend_from_slice(alpn_bytes);
}
let hs_type: u8 = if spec.is_client_hello {
1 // ClientHello
} else if spec.is_server_hello {
2 // ServerHello
} else if spec.is_certificate {
11 // Certificate
} else if spec.is_finished {
20 // Finished
} else {
1 // ClientHello (default)
};
let mut hs_body = Vec::new();
if hs_type == 2 {
// ServerHello Handshake
hs_body.extend_from_slice(&hs_version.to_be_bytes());
hs_body.extend_from_slice(&[0xaa; 32]); // Server Random
hs_body.push(0); // Session ID length: 0
hs_body.extend_from_slice(&cs.to_be_bytes());
hs_body.push(0); // Compression Method 0 (null)
hs_body.extend_from_slice(&(exts.len() as u16).to_be_bytes());
hs_body.extend_from_slice(&exts);
} else if hs_type == 11 {
// Certificate Handshake
if !payload_bytes.is_empty() {
hs_body.extend_from_slice(payload_bytes);
} else {
hs_body.extend_from_slice(&[0x00, 0x00, 0x00]); // Empty certificate_list
}
} else if hs_type == 20 {
// Finished Handshake
if !payload_bytes.is_empty() {
hs_body.extend_from_slice(payload_bytes);
} else {
hs_body.extend_from_slice(&[0xdd; 32]); // 32-byte verify_data
}
} else {
// ClientHello Handshake (hs_type == 1)
hs_body.extend_from_slice(&hs_version.to_be_bytes());
hs_body.extend_from_slice(&[0x11; 32]); // Client Random
hs_body.push(0); // Session ID length: 0
// Cipher Suites (4 bytes: primary + fallback)
hs_body.extend_from_slice(&4u16.to_be_bytes());
hs_body.extend_from_slice(&cs.to_be_bytes());
hs_body.extend_from_slice(&0x1302u16.to_be_bytes()); // TLS_AES_256_GCM_SHA384
// Compression Methods
hs_body.push(1); // 1 method
hs_body.push(0); // null
hs_body.extend_from_slice(&(exts.len() as u16).to_be_bytes());
hs_body.extend_from_slice(&exts);
}
let hs_len = hs_body.len() as u32;
let mut hs = alloc::vec![
hs_type,
(hs_len >> 16) as u8,
(hs_len >> 8) as u8,
hs_len as u8,
];
hs.extend_from_slice(&hs_body);
if (hs_type == 1 || hs_type == 2) && !payload_bytes.is_empty() {
hs.extend_from_slice(payload_bytes);
}
let rec_len = hs.len() as u16;
let mut rec = Vec::with_capacity(5 + hs.len());
rec.push(22); // ContentType: Handshake (22)
rec.extend_from_slice(&rec_version.to_be_bytes());
rec.extend_from_slice(&rec_len.to_be_bytes());
rec.extend_from_slice(&hs);
rec
}