nx_tls13_server_session_emit_cv.nx source
↩ module page · 347 lines · 12781 B
1// nx_tls13_server_session_emit_cv.nx -- stone 7.5 of TLS server arc.
2//
3// Emits the server CertificateVerify handshake message: ECDSA-P256
4// signature over a transcript-bound context, wrapped as an
5// AEAD-encrypted record.
6//
7// Per RFC 8446 §4.4.3, the signed content is:
8// [0x20 × 64] (octet 0x20 = space, 64 bytes)
9// "TLS 1.3, server CertificateVerify" (33 bytes, the string)
10// 0x00 (separator)
11// transcript_hash (32 bytes, SHA-256)
12// ----- 130 bytes total -----
13//
14// Signature: ECDSA-P256-SHA256. The signing primitive
15// nx_ecdsa_p256_sign hashes input itself.
16//
17// Wire encoding:
18// HT_CERTIFICATE_VERIFY (0x0f) (1 byte)
19// u24 body_len (3 bytes)
20// u16 signature_scheme = 0x0403 (ecdsa_secp256r1_sha256)
21// u16 sig_len (DER signature length)
22// DER-encoded ECDSA(r, s) (~70-72 bytes)
23//
24// State machine: requires CERT_SENT (i.e. EE has been emitted; the
25// Certificate stone 6.5 is logically before this but is queued, so
26// in v1 the substrate skips cert -- THIS IS HONESTLY ANONYMOUS-CERT
27// STILL). Advances to CV_SENT. emit_sf (patched this commit) now
28// accepts both CERT_SENT and CV_SENT.
29//
30// Honest scope statement: a real client interop'ing with a real
31// public CA chain would expect Certificate to precede this message.
32// This commit ships the SIGNATURE pipeline (key schedule + transcript
33// + ECDSA + DER + AEAD record), composable with stone 6.5 when it
34// ships. The signing primitive is KAT'd against RFC 6979 vectors.
35//
36// license_tier: ORIGINAL
37
38import "nx_syscalls.nx"
39import "nx_u256.nx"
40import "nx_sha256.nx"
41import "nx_ecdsa_p256_sign.nx"
42import "nx_tls13.nx"
43import "nx_tls13_record.nx"
44import "nx_tls13_transcript.nx"
45import "nx_tls13_server_session.nx"
46// V-HOST-2+1 Path B: Ed25519 sig support
47import "nx_ed25519_signature.nx"
48
49const NX_TLS13_SIG_SCHEME_ECDSA_P256_SHA256: i64 = 0x0403
50const NX_TLS13_CV_TRANSCRIPT_LEN: i64 = 32 // SHA-256 hash output
51const NX_TLS13_CV_CONTEXT_LEN: i64 = 130 // 64 + 33 + 1 + 32
52
53// DER-encode an ECDSA-P256 signature (r, s). Returns total bytes
54// written to out. out_cap must be >= 72 for safety.
55func encode_ecdsa_p256_sig_der(r_bytes: *u8, s_bytes: *u8,
56 out: *u8) -> i64 {
57 out[0] = 0x30 // SEQUENCE tag
58 let total_len_pos: i64 = 1
59 var pos: i64 = 2
60
61 // r INTEGER
62 out[pos] = 0x02
63 pos = pos + 1
64 let r_len_pos: i64 = pos
65 pos = pos + 1
66 var r_pad: i64 = 0
67 if ((r_bytes[0] as i64) & 0x80) != 0 {
68 out[pos] = 0
69 pos = pos + 1
70 r_pad = 1
71 }
72 var ri: i64 = 0
73 while ri < 32 {
74 out[pos + ri] = r_bytes[ri]
75 ri = ri + 1
76 }
77 pos = pos + 32
78 out[r_len_pos] = ((32 + r_pad) & 0xff) as u8
79
80 // s INTEGER
81 out[pos] = 0x02
82 pos = pos + 1
83 let s_len_pos: i64 = pos
84 pos = pos + 1
85 var s_pad: i64 = 0
86 if ((s_bytes[0] as i64) & 0x80) != 0 {
87 out[pos] = 0
88 pos = pos + 1
89 s_pad = 1
90 }
91 var si: i64 = 0
92 while si < 32 {
93 out[pos + si] = s_bytes[si]
94 si = si + 1
95 }
96 pos = pos + 32
97 out[s_len_pos] = ((32 + s_pad) & 0xff) as u8
98
99 // Backfill total_len = (pos - 2) (everything after the tag+len pair)
100 out[total_len_pos] = ((pos - 2) & 0xff) as u8
101 return pos
102}
103
104// Build the 130-byte CertificateVerify signed-content context.
105// Lays it into the caller's buffer; returns bytes written (130).
106func build_cv_context(transcript_hash: *u8, out: *u8) -> i64 {
107 var i: i64 = 0
108 while i < 64 {
109 out[i] = 0x20 as u8 // ASCII space
110 i = i + 1
111 }
112 let lbl: *u8 = "TLS 1.3, server CertificateVerify"
113 var j: i64 = 0
114 while j < 33 {
115 out[64 + j] = lbl[j]
116 j = j + 1
117 }
118 out[97] = 0
119 var k: i64 = 0
120 while k < 32 {
121 out[98 + k] = transcript_hash[k]
122 k = k + 1
123 }
124 return 130
125}
126
127// Emit the CertificateVerify handshake message as an AEAD-encrypted
128// record under server_hs_traffic_key + iv. Returns total bytes
129// written or negative -(NX_TLS13_SSESSION_*) verdict.
130//
131// server_ecdsa_priv_be: caller-supplied 32-byte big-endian ECDSA-P256
132// private key. v1 takes it as a param; future
133// stones may store it in the session struct.
134
135func nx_tls13_server_session_emit_cv(
136 session: *Tls13ServerSession,
137 server_ecdsa_priv_be: *u8,
138 out: *u8, out_cap: i64
139) -> i64 {
140 if (session as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
141 if (out as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
142 if (server_ecdsa_priv_be as i64) == 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
143 if session.state != NX_TLS13_SSTATE_CERT_SENT {
144 return 0 - NX_TLS13_SSESSION_BAD_STATE
145 }
146 if (session.server_hs_traffic_key as i64) == 0 {
147 return 0 - NX_TLS13_SSESSION_INTERNAL
148 }
149
150 // 1. Transcript snapshot (over CH..EE so far)
151 let th: *u8 = sys_mmap(NX_TLS13_CV_TRANSCRIPT_LEN)
152 let r_th: i64 = nx_tls13_transcript_snapshot(session.transcript, th)
153 if r_th != NX_TLS13_TX_VERDICT_OK { return 0 - NX_TLS13_SSESSION_INTERNAL }
154
155 // 2. Build the 130-byte signed-content context
156 let ctx: *u8 = sys_mmap(NX_TLS13_CV_CONTEXT_LEN)
157 build_cv_context(th, ctx)
158
159 // 3. SHA-256 the context to produce the digest to sign
160 let digest: *u8 = sys_mmap(32)
161 let sha: *Sha256 = sys_mmap(512) as *Sha256
162 sha256_init(sha)
163 sha256_update(sha, ctx, NX_TLS13_CV_CONTEXT_LEN)
164 sha256_final(sha, digest)
165
166 // 4. Load priv key + digest into u256 limbs for signing
167 let priv_limbs: *i64 = u256_alloc()
168 let digest_limbs: *i64 = u256_alloc()
169 u256_load_be(priv_limbs, server_ecdsa_priv_be)
170 u256_load_be(digest_limbs, digest)
171
172 // 5. Sign
173 let r_limbs: *i64 = u256_alloc()
174 let s_limbs: *i64 = u256_alloc()
175 let sv: i64 = nx_ecdsa_p256_sign(priv_limbs, digest_limbs, r_limbs, s_limbs)
176 if sv != NX_ECDSA_SIGN_OK { return 0 - NX_TLS13_SSESSION_INTERNAL }
177
178 // 6. Convert r,s back to big-endian bytes
179 let r_be: *u8 = sys_mmap(32)
180 let s_be: *u8 = sys_mmap(32)
181 u256_store_be(r_be, r_limbs)
182 u256_store_be(s_be, s_limbs)
183
184 // 7. DER-encode r,s
185 let der: *u8 = sys_mmap(80)
186 let der_len: i64 = encode_ecdsa_p256_sig_der(r_be, s_be, der)
187
188 // 8. Build CertificateVerify handshake message
189 // HT(1) + u24_len(3) + sig_scheme(2) + sig_len(2) + DER(der_len)
190 let body_len: i64 = 2 + 2 + der_len // sig_scheme + sig_len + DER
191 let inner_len: i64 = 4 + body_len // HT + u24_len prefix + body
192 let inner: *u8 = sys_mmap(inner_len)
193 inner[0] = HT_CERTIFICATE_VERIFY & 0xff
194 inner[1] = ((body_len >> 16) & 0xff) as u8
195 inner[2] = ((body_len >> 8) & 0xff) as u8
196 inner[3] = ( body_len & 0xff) as u8
197 inner[4] = ((NX_TLS13_SIG_SCHEME_ECDSA_P256_SHA256 >> 8) & 0xff) as u8
198 inner[5] = ( NX_TLS13_SIG_SCHEME_ECDSA_P256_SHA256 & 0xff) as u8
199 inner[6] = ((der_len >> 8) & 0xff) as u8
200 inner[7] = ( der_len & 0xff) as u8
201 var di: i64 = 0
202 while di < der_len {
203 inner[8 + di] = der[di]
204 di = di + 1
205 }
206
207 // 9. AEAD-encrypt as record
208 let total_needed: i64 = 5 + inner_len + 1 + 16
209 if out_cap < total_needed { return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW }
210 let header_buf: *u8 = sys_mmap(5)
211 let ct_buf: *u8 = sys_mmap(inner_len + 17)
212 let tag_buf: *u8 = sys_mmap(16)
213 let rv: i64 = nx_tls13_record_encrypt_v2(
214 session.cipher_suite,
215 session.server_hs_traffic_key,
216 session.server_hs_iv,
217 session.server_seq,
218 inner, inner_len,
219 CT_HANDSHAKE, 0,
220 header_buf, ct_buf, tag_buf)
221 if rv != NX_TLS13_REC_VERDICT_OK {
222 return 0 - NX_TLS13_SSESSION_INTERNAL
223 }
224
225 var w: i64 = 0
226 var hi: i64 = 0
227 while hi < 5 { out[w + hi] = header_buf[hi]; hi = hi + 1 }
228 w = w + 5
229 let ct_len: i64 = inner_len + 1
230 var ci: i64 = 0
231 while ci < ct_len { out[w + ci] = ct_buf[ci]; ci = ci + 1 }
232 w = w + ct_len
233 var ti: i64 = 0
234 while ti < 16 { out[w + ti] = tag_buf[ti]; ti = ti + 1 }
235 w = w + 16
236
237 // 10. Update transcript with the CV plaintext, advance state + seq
238 nx_tls13_transcript_update(session.transcript, inner, inner_len)
239 session.server_seq = session.server_seq + 1
240 session.state = NX_TLS13_SSTATE_CV_SENT
241 return w
242}
243
244// ===== V-HOST-2+1-1 Path B: Ed25519 CertificateVerify =================================================
245//
246// Per RFC 8446 §4.2.3 + RFC 8410: signature_scheme ed25519 = 0x0807.
247// Per RFC 8446 §4.4.3: signature is computed over the 130-byte context
248// directly (Ed25519 internally does SHA-512 + scalar mult; no separate
249// outer hash step like ECDSA).
250//
251// Parallel to nx_tls13_server_session_emit_cv (above); ZERO churn to
252// existing function for backward compat. Caller picks which to use
253// based on cert's signing key type.
254//
255// Composes existing primitives: ed25519_sign_full (RFC 8032 §5.1.6) +
256// nx_tls13_transcript_snapshot + build_cv_context +
257// nx_tls13_record_encrypt + transcript_update.
258
259const NX_TLS13_SIG_SCHEME_ED25519: i64 = 0x0807
260
261func nx_tls13_server_session_emit_cv_ed25519(
262 session: *Tls13ServerSession,
263 server_ed25519_priv_32: *u8,
264 out: *u8, out_cap: i64
265) -> i64 {
266 if (session as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
267 if (out as i64) == 0 { return 0 - NX_TLS13_SSESSION_BAD_STATE }
268 if (server_ed25519_priv_32 as i64) == 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
269 if session.state != NX_TLS13_SSTATE_CERT_SENT {
270 return 0 - NX_TLS13_SSESSION_BAD_STATE
271 }
272 if (session.server_hs_traffic_key as i64) == 0 {
273 return 0 - NX_TLS13_SSESSION_INTERNAL
274 }
275
276 // 1. Transcript snapshot (over CH..EE so far)
277 let th: *u8 = sys_mmap(NX_TLS13_CV_TRANSCRIPT_LEN)
278 let r_th: i64 = nx_tls13_transcript_snapshot(session.transcript, th)
279 if r_th != NX_TLS13_TX_VERDICT_OK { return 0 - NX_TLS13_SSESSION_INTERNAL }
280
281 // 2. Build the 130-byte signed-content context
282 let ctx: *u8 = sys_mmap(NX_TLS13_CV_CONTEXT_LEN)
283 build_cv_context(th, ctx)
284
285 // 3. SKIP intermediate hash -- Ed25519 signs the context bytes directly
286 // (RFC 8446 §4.4.3 + RFC 8032 §5.1.6: Ed25519 internally PHF over msg)
287
288 // 4. Sign 130-byte context -> 64-byte sig (R || S)
289 let sig_64: *u8 = sys_mmap(64)
290 let sv: i64 = ed25519_sign_full(server_ed25519_priv_32, ctx, NX_TLS13_CV_CONTEXT_LEN, sig_64)
291 if sv != 0 { return 0 - NX_TLS13_SSESSION_INTERNAL }
292
293 // 5. Build CertificateVerify handshake message
294 // HT(1) + u24_len(3) + sig_scheme(2)=0x0807 + sig_len(2)=64 + sig(64)
295 let body_len: i64 = 2 + 2 + 64
296 let inner_len: i64 = 4 + body_len
297 let inner: *u8 = sys_mmap(inner_len)
298 inner[0] = HT_CERTIFICATE_VERIFY & 0xff
299 inner[1] = ((body_len >> 16) & 0xff) as u8
300 inner[2] = ((body_len >> 8) & 0xff) as u8
301 inner[3] = ( body_len & 0xff) as u8
302 inner[4] = ((NX_TLS13_SIG_SCHEME_ED25519 >> 8) & 0xff) as u8
303 inner[5] = ( NX_TLS13_SIG_SCHEME_ED25519 & 0xff) as u8
304 inner[6] = ((64 >> 8) & 0xff) as u8
305 inner[7] = ( 64 & 0xff) as u8
306 var si: i64 = 0
307 while si < 64 {
308 inner[8 + si] = sig_64[si]
309 si = si + 1
310 }
311
312 // 6. AEAD-encrypt as record (identical to ECDSA path)
313 let total_needed: i64 = 5 + inner_len + 1 + 16
314 if out_cap < total_needed { return 0 - NX_TLS13_SSESSION_BUF_OVERFLOW }
315 let header_buf: *u8 = sys_mmap(5)
316 let ct_buf: *u8 = sys_mmap(inner_len + 17)
317 let tag_buf: *u8 = sys_mmap(16)
318 let rv: i64 = nx_tls13_record_encrypt_v2(
319 session.cipher_suite,
320 session.server_hs_traffic_key,
321 session.server_hs_iv,
322 session.server_seq,
323 inner, inner_len,
324 CT_HANDSHAKE, 0,
325 header_buf, ct_buf, tag_buf)
326 if rv != NX_TLS13_REC_VERDICT_OK {
327 return 0 - NX_TLS13_SSESSION_INTERNAL
328 }
329
330 var w: i64 = 0
331 var hi: i64 = 0
332 while hi < 5 { out[w + hi] = header_buf[hi]; hi = hi + 1 }
333 w = w + 5
334 let ct_len: i64 = inner_len + 1
335 var ci: i64 = 0
336 while ci < ct_len { out[w + ci] = ct_buf[ci]; ci = ci + 1 }
337 w = w + ct_len
338 var ti: i64 = 0
339 while ti < 16 { out[w + ti] = tag_buf[ti]; ti = ti + 1 }
340 w = w + 16
341
342 // 7. Update transcript + advance state + seq
343 nx_tls13_transcript_update(session.transcript, inner, inner_len)
344 session.server_seq = session.server_seq + 1
345 session.state = NX_TLS13_SSTATE_CV_SENT
346 return w
347}