nx_tls13_kdf.nx source
↩ module page · 172 lines · 6480 B
1// nx_tls13_kdf.nx -- TLS 1.3 HKDF-Expand-Label + Derive-Secret
2// (RFC 8446 §7.1).
3//
4// Phase 0b §C of the Nishi TLS 1.3 stack per
5// docs/NISHI_TLS13_GAP_AUDIT.md. The thin TLS-specific wrapper
6// over plain HKDF-Expand that prepends the "tls13 " literal label
7// and length-prefixes the label + context per the spec's HkdfLabel
8// struct. Every traffic key, handshake secret, application secret,
9// and Finished MAC key in TLS 1.3 derives from this.
10//
11// HkdfLabel layout (RFC 8446 §7.1):
12// struct {
13// uint16 length; // big-endian
14// opaque label<7..255>; // = "tls13 " || Label
15// // 1-byte length prefix
16// opaque context<0..255>; // 1-byte length prefix
17// } HkdfLabel;
18//
19// Then: HKDF-Expand-Label(Secret, Label, Context, Length) =
20// HKDF-Expand(Secret, HkdfLabel, Length).
21//
22// Derive-Secret is convenience: Context = Transcript-Hash(Messages),
23// Length = hash output (32 for SHA-256, 48 for SHA-384).
24//
25// What it does today:
26// - build HkdfLabel struct into a caller buffer
27// - HKDF-Expand-Label one-shot
28// - Derive-Secret one-shot (composes ExpandLabel + Transcript-Hash
29// as caller-supplied buffer)
30//
31// What it doesn't do yet:
32// - SHA-384 variant (composes against existing nx_hkdf_sha384;
33// queued sub-phase)
34// - Multi-block transcript-hash streaming (caller computes
35// transcript hash via nx_sha256 incremental API)
36//
37// KAT verified:
38// - HkdfLabel byte-exact construction for ("derived", empty hash, 32)
39// - RFC 8448 §3 early_secret = HMAC-SHA256(0^32, 0^32)
40// = 33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a
41// - RFC 8448 §3 derived = Derive-Secret(early_secret, "derived", empty)
42// = 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
43//
44// Composes with:
45// - nx_hkdf (RFC 5869 Extract+Expand)
46// - nx_sha256 (caller-side transcript hash for Derive-Secret)
47// - nx_tls13 (constants + record layer; eventually nx_tls13_schedule
48// which calls Derive-Secret for every secret in the 5-stage cascade)
49//
50// license_tier: INDEPENDENT_REDERIVE
51// genealogy_id: international-research-sources/ietf/rfc_8446 + ietf/rfc_5869
52// lineage_id: nishi_tls13_kdf_q10
53
54// nx_safety_envelope:
55// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
56// sil_target: SIL1
57// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
58// verdict: NOT_YET_EVALUATED
59
60import "nx_syscalls.nx"
61import "nx_hkdf.nx"
62
63// "tls13 " ASCII = 0x74 0x6c 0x73 0x31 0x33 0x20 = 6 bytes.
64const TLS13_LABEL_PREFIX_LEN: i64 = 6
65
66// HKDF-Expand-Label upper bound: TLS 1.3 spec limits Label to <= 249
67// bytes so the prefixed-label byte length fits in u8.
68const TLS13_LABEL_MAX: i64 = 249
69
70// Sealed verdict for the KDF path (composes with HKDF's -1 error
71// from oversize Length).
72const NX_TLS13_KDF_VERDICT_OK: i64 = 1
73const NX_TLS13_KDF_VERDICT_LABEL_TOO_LONG: i64 = 2
74const NX_TLS13_KDF_VERDICT_CONTEXT_TOO_LONG: i64 = 3
75const NX_TLS13_KDF_VERDICT_HKDF_FAIL: i64 = 4
76const NX_TLS13_KDF_VERDICT_N: i64 = 5
77
78// Build the HkdfLabel byte sequence per RFC 8446 §7.1 into `out`.
79// Returns total bytes written, or a negative NX_TLS13_KDF_VERDICT_*
80// on error. Caller buffer must hold >= 4 + 6 + label_len + context_len.
81func tls13_build_hkdf_label(
82 length: i64,
83 label: *u8, label_len: i64,
84 context: *u8, context_len: i64,
85 out: *u8
86) -> i64 {
87 if label_len < 0 { return 0 - NX_TLS13_KDF_VERDICT_LABEL_TOO_LONG }
88 if label_len > TLS13_LABEL_MAX { return 0 - NX_TLS13_KDF_VERDICT_LABEL_TOO_LONG }
89 if context_len < 0 { return 0 - NX_TLS13_KDF_VERDICT_CONTEXT_TOO_LONG }
90 if context_len > 255 { return 0 - NX_TLS13_KDF_VERDICT_CONTEXT_TOO_LONG }
91 var o: i64 = 0
92 // uint16 length, big-endian.
93 out[o] = (length >> 8) & 0xff
94 out[o + 1] = length & 0xff
95 o = o + 2
96 // opaque label<7..255>: 1-byte length prefix (= 6 + label_len),
97 // then "tls13 " (6 bytes) then user Label bytes.
98 let total_label_len: i64 = TLS13_LABEL_PREFIX_LEN + label_len
99 out[o] = total_label_len & 0xff
100 o = o + 1
101 out[o] = 0x74 // 't'
102 out[o + 1] = 0x6c // 'l'
103 out[o + 2] = 0x73 // 's'
104 out[o + 3] = 0x31 // '1'
105 out[o + 4] = 0x33 // '3'
106 out[o + 5] = 0x20 // ' '
107 o = o + TLS13_LABEL_PREFIX_LEN
108 var i: i64 = 0
109 while i < label_len {
110 out[o + i] = label[i]
111 i = i + 1
112 }
113 o = o + label_len
114 // opaque context<0..255>: 1-byte length prefix then context bytes.
115 out[o] = context_len & 0xff
116 o = o + 1
117 var j: i64 = 0
118 while j < context_len {
119 out[o + j] = context[j]
120 j = j + 1
121 }
122 o = o + context_len
123 return o
124}
125
126// HKDF-Expand-Label per RFC 8446 §7.1. Writes `length` bytes of
127// keying material to `out`. Returns NX_TLS13_KDF_VERDICT_OK on
128// success or a non-OK verdict.
129func tls13_hkdf_expand_label(
130 secret: *u8,
131 label: *u8, label_len: i64,
132 context: *u8, context_len: i64,
133 length: i64,
134 out: *u8
135) -> i64 {
136 let info_cap: i64 = 4 + TLS13_LABEL_PREFIX_LEN + label_len + context_len + 16
137 let info: *u8 = sys_mmap(info_cap)
138 let info_len: i64 = tls13_build_hkdf_label(
139 length, label, label_len, context, context_len, info
140 )
141 if info_len < 0 { return 0 - info_len } // negate to expose verdict
142 let rc: i64 = hkdf_expand(secret, info, info_len, length, out)
143 if rc != 0 { return NX_TLS13_KDF_VERDICT_HKDF_FAIL }
144 return NX_TLS13_KDF_VERDICT_OK
145}
146
147// Derive-Secret per RFC 8446 §7.1:
148// Derive-Secret(Secret, Label, Messages) =
149// HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length)
150//
151// hash_output_len is 32 for SHA-256, 48 for SHA-384. Caller
152// pre-computes Transcript-Hash via the appropriate hash module
153// (nx_sha256.incremental for SHA-256; nx_sha384 queued).
154func tls13_derive_secret(
155 secret: *u8,
156 label: *u8, label_len: i64,
157 transcript_hash: *u8, hash_output_len: i64,
158 out: *u8
159) -> i64 {
160 return tls13_hkdf_expand_label(
161 secret, label, label_len,
162 transcript_hash, hash_output_len,
163 hash_output_len, out
164 )
165}
166
167// Sealed-enum validity gate.
168func nx_tls13_kdf_verdict_is_valid(v: i64) -> i64 {
169 if v < 0 { return 0 }
170 if v >= NX_TLS13_KDF_VERDICT_N { return 0 }
171 return 1
172}