nx_tls13_handshake.nx source
↩ module page · 212 lines · 8713 B
1// nx_tls13_handshake.nx -- TLS 1.3 handshake orchestrator (Gap L sub-1).
2//
3// Phase 0b §L of the Nishi TLS 1.3 stack per
4// docs/NISHI_TLS13_GAP_AUDIT.md. Composes the shipped primitives
5// (ECDHE + transcript + schedule + KDF) into the small set of
6// state-machine functions a TLS 1.3 client needs.
7//
8// This is NOT yet a full nx_tls13_connect() with sockets. It is
9// the IN-MEMORY orchestrator that takes byte buffers + keys in and
10// produces the next byte buffers + keys out. A future thin
11// wrapper drives this from a TCP socket. Separating logic from
12// transport keeps the substrate testable end-to-end without
13// network, matching the RFC 8448 §3 KAT pattern.
14//
15// What it does today:
16// - tls13_handshake_compute_handshake_keys: given client X25519
17// private key + server X25519 public key + the running
18// transcript hash state (already-fed with CH+SH), derives
19// handshake_secret + client/server handshake_traffic_secrets
20// + their AEAD keys + IVs. This is the moment in the
21// handshake where everything past ServerHello becomes
22// encrypted.
23// - tls13_handshake_compute_application_keys: given handshake_secret
24// + transcript-state at the post-server-Finished point,
25// derives master_secret + client/server app_traffic_secrets
26// + their AEAD keys + IVs. This is the moment the handshake
27// completes and application data starts flowing.
28//
29// What it doesn't do yet:
30// - record-layer I/O (caller still drives nx_tls13_record
31// directly for now; integration ships when the full state
32// machine lands)
33// - HRR retry loop (the HRR detection + transcript replace are
34// both shipped; an outer state-machine retries with the
35// server-chosen group from HRR's key_share extension)
36// - PSK / resumption path (Gap M)
37//
38// KAT verified:
39// - RFC 8448 §3 ECDHE computation:
40// x25519(client_priv=49af...5005, server_pub=c982...1f0f)
41// = 8bd4054f...492d
42// - RFC 8448 §3 handshake_secret derivation chain end-to-end
43// (no longer plugging in a hardcoded ECDHE shared as in
44// nx_tls13_schedule_test; the shared comes from real X25519)
45//
46// Composes with:
47// - nx_x25519 (ECDHE)
48// - nx_tls13_schedule (key schedule cascade)
49// - nx_tls13_kdf (HKDF-Expand-Label, Derive-Secret)
50// - nx_tls13_transcript (caller manages the running hash state)
51//
52// license_tier: INDEPENDENT_REDERIVE
53// genealogy_id: international-research-sources/ietf/rfc_8446
54// lineage_id: nishi_tls13_handshake_orchestrator_q10
55
56// nx_safety_envelope:
57// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
58// sil_target: SIL1
59// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
60// verdict: NOT_YET_EVALUATED
61
62import "nx_syscalls.nx"
63import "nx_x25519.nx"
64import "nx_tls13_kdf.nx"
65import "nx_tls13_schedule.nx"
66
67const NX_TLS13_HS_KEY_BYTES_AEAD: i64 = 32 // ChaCha20-Poly1305 / AES-256-GCM key
68const NX_TLS13_HS_IV_BYTES_AEAD: i64 = 12 // AEAD nonce / iv
69const NX_TLS13_HS_HASH_LEN_SHA256: i64 = 32
70
71const NX_TLS13_HS_VERDICT_OK: i64 = 1
72const NX_TLS13_HS_VERDICT_N: i64 = 2
73
74// Compute the full handshake-phase key set for both directions.
75//
76// Inputs:
77// client_priv32 = our ephemeral X25519 private key (caller
78// generates via nx_x25519_ephemeral)
79// server_pub32 = peer ephemeral X25519 public key (caller
80// parsed from ServerHello key_share)
81// transcript_hash32 = current transcript hash AFTER feeding both
82// ClientHello and ServerHello (caller
83// snapshotted via nx_tls13_transcript_snapshot)
84// empty_hash32 = SHA-256 of empty string; caller-supplied so
85// this function stays hash-agnostic
86//
87// Outputs (each is 32 bytes for SHA-256 cipher suite):
88// out_handshake_secret = the long-form HKDF-Extract output
89// out_chts = client_handshake_traffic_secret
90// out_shts = server_handshake_traffic_secret
91// out_client_hs_key = AEAD key for client->server records
92// out_client_hs_iv = AEAD IV for client->server records
93// out_server_hs_key = AEAD key for server->client records
94// out_server_hs_iv = AEAD IV for server->client records
95//
96// Returns NX_TLS13_HS_VERDICT_OK.
97func tls13_handshake_compute_handshake_keys(
98 client_priv32: *u8, server_pub32: *u8,
99 transcript_hash32: *u8,
100 empty_hash32: *u8,
101 out_handshake_secret: *u8,
102 out_chts: *u8, out_shts: *u8,
103 out_client_hs_key: *u8, out_client_hs_iv: *u8,
104 out_server_hs_key: *u8, out_server_hs_iv: *u8
105) -> i64 {
106 // 1. ECDHE: compute shared secret = X25519(client_priv, server_pub).
107 let ecdhe: *u8 = sys_mmap(64)
108 x25519(client_priv32, server_pub32, ecdhe)
109
110 // 2. Compute the no-PSK early_secret = HKDF-Extract(0, 0^32).
111 let zeros: *u8 = sys_mmap(64)
112 let early_secret: *u8 = sys_mmap(64)
113 tls13_early_secret(zeros, NX_TLS13_HS_HASH_LEN_SHA256, early_secret)
114
115 // 3. derived_1 = Derive-Secret(early_secret, "derived", empty_hash)
116 let derived_1: *u8 = sys_mmap(64)
117 tls13_derived(early_secret, empty_hash32, NX_TLS13_HS_HASH_LEN_SHA256, derived_1)
118
119 // 4. handshake_secret = HKDF-Extract(derived_1, ECDHE_shared)
120 tls13_handshake_secret(
121 derived_1, NX_TLS13_HS_HASH_LEN_SHA256,
122 ecdhe, 32,
123 out_handshake_secret
124 )
125
126 // 5. client_handshake_traffic_secret =
127 // Derive-Secret(handshake_secret, "c hs traffic", transcript_hash)
128 tls13_traffic_secret(
129 out_handshake_secret,
130 NX_TLS13_LABEL_C_HS_TRAFFIC,
131 transcript_hash32, NX_TLS13_HS_HASH_LEN_SHA256,
132 out_chts
133 )
134
135 // 6. server_handshake_traffic_secret = same with "s hs traffic"
136 tls13_traffic_secret(
137 out_handshake_secret,
138 NX_TLS13_LABEL_S_HS_TRAFFIC,
139 transcript_hash32, NX_TLS13_HS_HASH_LEN_SHA256,
140 out_shts
141 )
142
143 // 7. Per-direction AEAD key + IV derivation.
144 tls13_traffic_key(out_chts, NX_TLS13_HS_KEY_BYTES_AEAD, out_client_hs_key)
145 tls13_traffic_iv (out_chts, NX_TLS13_HS_IV_BYTES_AEAD, out_client_hs_iv)
146 tls13_traffic_key(out_shts, NX_TLS13_HS_KEY_BYTES_AEAD, out_server_hs_key)
147 tls13_traffic_iv (out_shts, NX_TLS13_HS_IV_BYTES_AEAD, out_server_hs_iv)
148
149 return NX_TLS13_HS_VERDICT_OK
150}
151
152// Compute application-phase keys after server Finished has been
153// verified and the transcript has been advanced through it.
154//
155// Inputs:
156// handshake_secret = from tls13_handshake_compute_handshake_keys
157// transcript_hash32 = snapshot AFTER ServerHello..server_Finished
158// (per RFC 8446 §7.1, the master_secret-derived
159// secrets read the transcript ending at SF)
160// empty_hash32 = SHA-256("") as above
161//
162// Outputs:
163// out_master_secret
164// out_cats = client_application_traffic_secret_0
165// out_sats = server_application_traffic_secret_0
166// out_client_app_key + iv
167// out_server_app_key + iv
168//
169// Returns NX_TLS13_HS_VERDICT_OK.
170func tls13_handshake_compute_application_keys(
171 handshake_secret: *u8,
172 transcript_hash32: *u8,
173 empty_hash32: *u8,
174 out_master_secret: *u8,
175 out_cats: *u8, out_sats: *u8,
176 out_client_app_key: *u8, out_client_app_iv: *u8,
177 out_server_app_key: *u8, out_server_app_iv: *u8
178) -> i64 {
179 // derived_2 = Derive-Secret(handshake_secret, "derived", empty)
180 let derived_2: *u8 = sys_mmap(64)
181 tls13_derived(handshake_secret, empty_hash32, NX_TLS13_HS_HASH_LEN_SHA256, derived_2)
182
183 // master_secret = HKDF-Extract(derived_2, 0^32)
184 tls13_master_secret(derived_2, NX_TLS13_HS_HASH_LEN_SHA256, out_master_secret)
185
186 // client_application_traffic_secret_0 + server_application_traffic_secret_0
187 tls13_traffic_secret(
188 out_master_secret, NX_TLS13_LABEL_C_AP_TRAFFIC,
189 transcript_hash32, NX_TLS13_HS_HASH_LEN_SHA256,
190 out_cats
191 )
192 tls13_traffic_secret(
193 out_master_secret, NX_TLS13_LABEL_S_AP_TRAFFIC,
194 transcript_hash32, NX_TLS13_HS_HASH_LEN_SHA256,
195 out_sats
196 )
197
198 // App AEAD keys + IVs.
199 tls13_traffic_key(out_cats, NX_TLS13_HS_KEY_BYTES_AEAD, out_client_app_key)
200 tls13_traffic_iv (out_cats, NX_TLS13_HS_IV_BYTES_AEAD, out_client_app_iv)
201 tls13_traffic_key(out_sats, NX_TLS13_HS_KEY_BYTES_AEAD, out_server_app_key)
202 tls13_traffic_iv (out_sats, NX_TLS13_HS_IV_BYTES_AEAD, out_server_app_iv)
203
204 return NX_TLS13_HS_VERDICT_OK
205}
206
207// Sealed-enum validity gate.
208func nx_tls13_hs_verdict_is_valid(v: i64) -> i64 {
209 if v < 0 { return 0 }
210 if v >= NX_TLS13_HS_VERDICT_N { return 0 }
211 return 1
212}