@amritk/nish-x86_64-linux 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +50 -13
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +12 -0
- package/runtime/runtime.c +66 -18
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +14 -12
- package/std/README.md +168 -36
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +618 -34
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/json.ts +297 -41
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +911 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +462 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +929 -0
- package/std/net/quic-packet.ts +1698 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1609 -0
- package/std/net/quic.ts +3578 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +414 -0
- package/std/net/tls.ts +1437 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
- package/std/text.ts +77 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/tls/schedule` — the TLS 1.3 key schedule and transcript hash
|
|
3
|
+
* (RFC 8446 §4.4.1, §7.1, §7.3) for the three cipher suites `nish/net/tls`
|
|
4
|
+
* negotiates.
|
|
5
|
+
*
|
|
6
|
+
* The schedule is a chain of HKDF steps over the suite's hash, SHA-256 for
|
|
7
|
+
* `TLS_AES_128_GCM_SHA256` and `TLS_CHACHA20_POLY1305_SHA256` and SHA-384 for
|
|
8
|
+
* `TLS_AES_256_GCM_SHA384`. Without a PSK it runs:
|
|
9
|
+
*
|
|
10
|
+
* early = HKDF-Extract(0, 0)
|
|
11
|
+
* handshake = HKDF-Extract(Derive-Secret(early, "derived", ""), ECDHE)
|
|
12
|
+
* c/s hs traffic = Derive-Secret(handshake, "c hs traffic" / "s hs traffic", CH..SH)
|
|
13
|
+
* master = HKDF-Extract(Derive-Secret(handshake, "derived", ""), 0)
|
|
14
|
+
* c/s ap traffic, exporter = Derive-Secret(master, "c ap traffic" / "s ap traffic" / "exp master", CH..server Finished)
|
|
15
|
+
*
|
|
16
|
+
* Every function takes the hash length (32 or 48) rather than a suite, because
|
|
17
|
+
* that is all that changes between them; `tlsSuiteHashLength` turns a suite
|
|
18
|
+
* into one. The traffic key and IV are here too, because T2's record layer
|
|
19
|
+
* needs them and RFC 8448 prints them beside the secrets.
|
|
20
|
+
*
|
|
21
|
+
* **In place.** Each step also has an `*Into` form that computes in an
|
|
22
|
+
* `HkdfScratch` the caller owns and writes into the caller's array, and
|
|
23
|
+
* `TlsTranscript` resets and reads its hash in hashers it allocated once, so
|
|
24
|
+
* a handshake that keeps its state in a connection slot can run the whole
|
|
25
|
+
* schedule inside `using a = arena()` blocks and leave the arena where it was
|
|
26
|
+
* (TLS-3 in `docs/security/tls.md`). The scratch is wiped by every step.
|
|
27
|
+
*
|
|
28
|
+
* **Secrets are not wiped.** Every function here answers or holds a secret as
|
|
29
|
+
* plain bytes, and each stays in arena memory until that memory is reused.
|
|
30
|
+
* `secureZero`, the store no optimiser removes, is on `main`, and `std/` may
|
|
31
|
+
* call it once a release ships it (CLAUDE.md §Security, TLS-1 in
|
|
32
|
+
* `docs/security/tls.md`).
|
|
33
|
+
*
|
|
34
|
+
* Written from RFC 8446, not ported from another implementation.
|
|
35
|
+
*/
|
|
36
|
+
import {
|
|
37
|
+
HkdfScratch,
|
|
38
|
+
hkdfExpandLabelInto,
|
|
39
|
+
hkdfExpandLabelSha256,
|
|
40
|
+
hkdfExpandLabelSha384,
|
|
41
|
+
hkdfExtractInto,
|
|
42
|
+
hkdfExtractSha256,
|
|
43
|
+
hkdfExtractSha384,
|
|
44
|
+
} from "nish/crypto/hkdf"
|
|
45
|
+
import { hmacCopySha256, hmacCopySha384, hmacSha256, hmacSha384 } from "nish/crypto/hmac"
|
|
46
|
+
import { tlsHexInto } from "nish/net/tls/codec"
|
|
47
|
+
import { SHA256_SIZE, Sha256, sha256 } from "nish/crypto/sha256"
|
|
48
|
+
import { SHA384_SIZE, Sha384, sha384 } from "nish/crypto/sha512"
|
|
49
|
+
|
|
50
|
+
/** `TLS_AES_128_GCM_SHA256` (RFC 8446 §B.4). */
|
|
51
|
+
export const TLS_AES_128_GCM_SHA256: i32 = 0x1301
|
|
52
|
+
/** `TLS_AES_256_GCM_SHA384` (RFC 8446 §B.4). */
|
|
53
|
+
export const TLS_AES_256_GCM_SHA384: i32 = 0x1302
|
|
54
|
+
/** `TLS_CHACHA20_POLY1305_SHA256` (RFC 8446 §B.4). */
|
|
55
|
+
export const TLS_CHACHA20_POLY1305_SHA256: i32 = 0x1303
|
|
56
|
+
|
|
57
|
+
/** The length of every TLS 1.3 AEAD nonce, and so of every traffic IV (RFC 8446 §5.3). */
|
|
58
|
+
export const TLS_IV_SIZE: i32 = 12
|
|
59
|
+
|
|
60
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
61
|
+
const TLS_SCHEDULE_FROM: i32 = 0
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The hash length of `suite`: 32 for the two SHA-256 suites, 48 for
|
|
65
|
+
* `TLS_AES_256_GCM_SHA384`, and 0 for any suite this module does not
|
|
66
|
+
* negotiate, which is how a caller tells the two apart.
|
|
67
|
+
*/
|
|
68
|
+
export const tlsSuiteHashLength = (suite: i32): i32 => {
|
|
69
|
+
switch (suite) {
|
|
70
|
+
case TLS_AES_128_GCM_SHA256:
|
|
71
|
+
return SHA256_SIZE
|
|
72
|
+
case TLS_CHACHA20_POLY1305_SHA256:
|
|
73
|
+
return SHA256_SIZE
|
|
74
|
+
case TLS_AES_256_GCM_SHA384:
|
|
75
|
+
return SHA384_SIZE
|
|
76
|
+
default:
|
|
77
|
+
return 0
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The AEAD key length of `suite`: 16 for AES-128-GCM, 32 for the other two, 0 for an unknown suite. */
|
|
82
|
+
export const tlsSuiteKeyLength = (suite: i32): i32 => {
|
|
83
|
+
switch (suite) {
|
|
84
|
+
case TLS_AES_128_GCM_SHA256:
|
|
85
|
+
return 16
|
|
86
|
+
case TLS_CHACHA20_POLY1305_SHA256:
|
|
87
|
+
return 32
|
|
88
|
+
case TLS_AES_256_GCM_SHA384:
|
|
89
|
+
return 32
|
|
90
|
+
default:
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* HKDF-Expand-Label (RFC 8446 §7.1) over the hash `hashLength` names: `length`
|
|
97
|
+
* bytes from `secret`, the label (without its `"tls13 "` prefix, which is
|
|
98
|
+
* added) and `context`.
|
|
99
|
+
*/
|
|
100
|
+
export const tlsExpandLabel = (
|
|
101
|
+
hashLength: i32,
|
|
102
|
+
secret: u8[],
|
|
103
|
+
label: string,
|
|
104
|
+
context: u8[],
|
|
105
|
+
length: i32
|
|
106
|
+
): u8[] =>
|
|
107
|
+
hashLength === SHA384_SIZE
|
|
108
|
+
? hkdfExpandLabelSha384(secret, label, context, length)
|
|
109
|
+
: hkdfExpandLabelSha256(secret, label, context, length)
|
|
110
|
+
|
|
111
|
+
/** HKDF-Extract (RFC 5869 §2.2) over the hash `hashLength` names; an empty salt is HashLen zeros. */
|
|
112
|
+
export const tlsExtract = (hashLength: i32, salt: u8[], ikm: u8[]): u8[] =>
|
|
113
|
+
hashLength === SHA384_SIZE ? hkdfExtractSha384(salt, ikm) : hkdfExtractSha256(salt, ikm)
|
|
114
|
+
|
|
115
|
+
/** The hash of the empty string, the transcript `Derive-Secret(., "derived", "")` hashes. */
|
|
116
|
+
export const tlsEmptyHash = (hashLength: i32): u8[] => {
|
|
117
|
+
const none: u8[] = []
|
|
118
|
+
return hashLength === SHA384_SIZE ? sha384(none) : sha256(none)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Derive-Secret (RFC 8446 §7.1): Expand-Label of `secret` with `label` and a transcript hash, HashLen bytes. */
|
|
122
|
+
export const tlsDeriveSecret = (hashLength: i32, secret: u8[], label: string, transcriptHash: u8[]): u8[] =>
|
|
123
|
+
tlsExpandLabel(hashLength, secret, label, transcriptHash, hashLength)
|
|
124
|
+
|
|
125
|
+
/** The early secret with no PSK: HKDF-Extract of HashLen zeros under a zero salt (RFC 8446 §7.1). */
|
|
126
|
+
export const tlsEarlySecret = (hashLength: i32): u8[] => {
|
|
127
|
+
const none: u8[] = []
|
|
128
|
+
return tlsExtract(hashLength, none, new Array<u8>(hashLength))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The handshake secret: HKDF-Extract of the ECDHE secret under `Derive-Secret(early, "derived", "")`. */
|
|
132
|
+
export const tlsHandshakeSecret = (hashLength: i32, earlySecret: u8[], ecdhe: u8[]): u8[] =>
|
|
133
|
+
tlsExtract(hashLength, tlsDeriveSecret(hashLength, earlySecret, "derived", tlsEmptyHash(hashLength)), ecdhe)
|
|
134
|
+
|
|
135
|
+
/** The master secret: HKDF-Extract of HashLen zeros under `Derive-Secret(handshake, "derived", "")`. */
|
|
136
|
+
export const tlsMasterSecret = (hashLength: i32, handshakeSecret: u8[]): u8[] =>
|
|
137
|
+
tlsExtract(
|
|
138
|
+
hashLength,
|
|
139
|
+
tlsDeriveSecret(hashLength, handshakeSecret, "derived", tlsEmptyHash(hashLength)),
|
|
140
|
+
new Array<u8>(hashLength)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A Finished message's `verify_data` (RFC 8446 §4.4.4): the HMAC of the
|
|
145
|
+
* transcript hash under `finished_key = Expand-Label(baseKey, "finished", "",
|
|
146
|
+
* HashLen)`, where `baseKey` is the sender's handshake traffic secret.
|
|
147
|
+
*/
|
|
148
|
+
export const tlsFinishedVerifyData = (hashLength: i32, baseKey: u8[], transcriptHash: u8[]): u8[] => {
|
|
149
|
+
const none: u8[] = []
|
|
150
|
+
const finishedKey: u8[] = tlsExpandLabel(hashLength, baseKey, "finished", none, hashLength)
|
|
151
|
+
return hashLength === SHA384_SIZE
|
|
152
|
+
? hmacSha384(finishedKey, transcriptHash)
|
|
153
|
+
: hmacSha256(finishedKey, transcriptHash)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The AEAD key a traffic secret gives `suite`'s record protection (RFC 8446 §7.3); empty for a suite this module does not negotiate. */
|
|
157
|
+
export const tlsTrafficKey = (suite: i32, secret: u8[]): u8[] => {
|
|
158
|
+
const none: u8[] = []
|
|
159
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
160
|
+
if (hashLength === 0) {
|
|
161
|
+
return none
|
|
162
|
+
}
|
|
163
|
+
return tlsExpandLabel(hashLength, secret, "key", none, tlsSuiteKeyLength(suite))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The 12-byte IV a traffic secret gives `suite`'s record protection (RFC 8446 §7.3); empty for a suite this module does not negotiate. */
|
|
167
|
+
export const tlsTrafficIv = (suite: i32, secret: u8[]): u8[] => {
|
|
168
|
+
const none: u8[] = []
|
|
169
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
170
|
+
if (hashLength === 0) {
|
|
171
|
+
return none
|
|
172
|
+
}
|
|
173
|
+
return tlsExpandLabel(hashLength, secret, "iv", none, TLS_IV_SIZE)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The running transcript hash of RFC 8446 §4.4.1: every handshake message,
|
|
178
|
+
* header included, in the order sent, over the negotiated suite's hash.
|
|
179
|
+
*
|
|
180
|
+
* `hash` reads the hash so far from a copy, so the transcript keeps going: the
|
|
181
|
+
* schedule takes it after ServerHello, after Certificate, after
|
|
182
|
+
* CertificateVerify and after the server's Finished. `restartWithMessageHash`
|
|
183
|
+
* is the HelloRetryRequest rule: the first ClientHello is replaced by the
|
|
184
|
+
* synthetic `message_hash` message holding its hash.
|
|
185
|
+
*
|
|
186
|
+
* Both hashers are kept so that the field types do not depend on the suite;
|
|
187
|
+
* only the one `hashLength` names is fed. So are a fresh pair, which `reset`
|
|
188
|
+
* starts the transcript again from, and a twin pair `hashInto` reads the hash
|
|
189
|
+
* through: all six are made once, so a transcript reused for the next
|
|
190
|
+
* connection with `reset` allocates nothing that outlives a call.
|
|
191
|
+
*/
|
|
192
|
+
export class TlsTranscript {
|
|
193
|
+
/** 32 for SHA-256, 48 for SHA-384. */
|
|
194
|
+
hashLength: i32
|
|
195
|
+
sha256: Sha256
|
|
196
|
+
sha384: Sha384
|
|
197
|
+
/** Never fed: the initial hash values `reset` copies from. */
|
|
198
|
+
fresh256: Sha256
|
|
199
|
+
fresh384: Sha384
|
|
200
|
+
/** Where `hashInto` finishes a copy of the running hash, so that the transcript goes on. */
|
|
201
|
+
twin256: Sha256
|
|
202
|
+
twin384: Sha384
|
|
203
|
+
|
|
204
|
+
constructor(hashLength: i32) {
|
|
205
|
+
this.hashLength = hashLength
|
|
206
|
+
this.sha256 = new Sha256()
|
|
207
|
+
this.sha384 = new Sha384()
|
|
208
|
+
this.fresh256 = new Sha256()
|
|
209
|
+
this.fresh384 = new Sha384()
|
|
210
|
+
this.twin256 = new Sha256()
|
|
211
|
+
this.twin384 = new Sha384()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Starts an empty transcript over the hash `hashLength` names, in the hashers it already has. */
|
|
215
|
+
reset(hashLength: i32): void {
|
|
216
|
+
this.hashLength = hashLength
|
|
217
|
+
hmacCopySha256(this.fresh256, this.sha256)
|
|
218
|
+
hmacCopySha384(this.fresh384, this.sha384)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Absorbs `data[off .. off + len)`, a whole handshake message or part of one. */
|
|
222
|
+
update(data: u8[], off: i32, len: i32): void {
|
|
223
|
+
if (this.hashLength === SHA384_SIZE) {
|
|
224
|
+
this.sha384.update(data, off, len)
|
|
225
|
+
} else {
|
|
226
|
+
this.sha256.update(data, off, len)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Writes the transcript hash of everything absorbed so far, `hashLength`
|
|
232
|
+
* bytes, at `out[at]`; the transcript goes on. What the digest allocates
|
|
233
|
+
* dies with the call. A window outside `out` writes what fits.
|
|
234
|
+
*/
|
|
235
|
+
hashInto(out: u8[], at: i32): void {
|
|
236
|
+
let digest: u8[] = []
|
|
237
|
+
if (this.hashLength === SHA384_SIZE) {
|
|
238
|
+
hmacCopySha384(this.sha384, this.twin384)
|
|
239
|
+
digest = this.twin384.digest()
|
|
240
|
+
} else {
|
|
241
|
+
hmacCopySha256(this.sha256, this.twin256)
|
|
242
|
+
digest = this.twin256.digest()
|
|
243
|
+
}
|
|
244
|
+
for (let k: i32 = 0; k < toI32(digest.length) && at + k >= 0 && at + k < toI32(out.length); k++) {
|
|
245
|
+
out[at + k] = digest[k]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The transcript hash of everything absorbed so far, in a fresh array; the transcript goes on. */
|
|
250
|
+
hash(): u8[] {
|
|
251
|
+
const out: u8[] = new Array<u8>(this.hashLength)
|
|
252
|
+
this.hashInto(out, TLS_SCHEDULE_FROM)
|
|
253
|
+
return out
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Replaces the transcript so far, which must be exactly the first
|
|
258
|
+
* ClientHello, by `message_hash` (type 254, a 24-bit length of HashLen,
|
|
259
|
+
* then Hash(ClientHello1)), as RFC 8446 §4.4.1 has a server do before it
|
|
260
|
+
* absorbs its HelloRetryRequest.
|
|
261
|
+
*/
|
|
262
|
+
restartWithMessageHash(): void {
|
|
263
|
+
const first: u8[] = new Array<u8>(SHA384_SIZE)
|
|
264
|
+
this.hashInto(first, TLS_SCHEDULE_FROM)
|
|
265
|
+
const header: u8[] = [toU8(254), toU8(0), toU8(0), toU8(this.hashLength)]
|
|
266
|
+
const headerLength: i32 = 4
|
|
267
|
+
this.reset(this.hashLength)
|
|
268
|
+
this.update(header, TLS_SCHEDULE_FROM, headerLength)
|
|
269
|
+
this.update(first, TLS_SCHEDULE_FROM, this.hashLength)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---- The schedule in place -------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
/** SHA-256 of the empty string, Derive-Secret's context for "derived" (RFC 8446 §7.1). */
|
|
276
|
+
const TLS_EMPTY_HASH_SHA256: string = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
277
|
+
/** SHA-384 of the empty string. */
|
|
278
|
+
const TLS_EMPTY_HASH_SHA384: string =
|
|
279
|
+
"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
|
|
280
|
+
|
|
281
|
+
/** Writes the hash of the empty string under the hash `hashLength` names, from the constants above, at `out[0]`. */
|
|
282
|
+
const tlsEmptyHashInto = (hashLength: i32, out: u8[]): void => {
|
|
283
|
+
tlsHexInto(
|
|
284
|
+
hashLength === SHA384_SIZE ? TLS_EMPTY_HASH_SHA384 : TLS_EMPTY_HASH_SHA256,
|
|
285
|
+
out,
|
|
286
|
+
TLS_SCHEDULE_FROM
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* `tlsDeriveSecret` in place: Derive-Secret (RFC 8446 §7.1) of `secret` with
|
|
292
|
+
* `label` and the transcript hash `transcriptHash[hashOff .. hashOff +
|
|
293
|
+
* hashLength)`, its HashLen bytes written at `out[0]`.
|
|
294
|
+
*/
|
|
295
|
+
export const tlsDeriveSecretInto = (
|
|
296
|
+
kdf: HkdfScratch,
|
|
297
|
+
hashLength: i32,
|
|
298
|
+
secret: u8[],
|
|
299
|
+
label: string,
|
|
300
|
+
transcriptHash: u8[],
|
|
301
|
+
hashOff: i32,
|
|
302
|
+
out: u8[]
|
|
303
|
+
): void => {
|
|
304
|
+
hkdfExpandLabelInto(
|
|
305
|
+
kdf,
|
|
306
|
+
hashLength,
|
|
307
|
+
secret,
|
|
308
|
+
label,
|
|
309
|
+
transcriptHash,
|
|
310
|
+
hashOff,
|
|
311
|
+
hashLength,
|
|
312
|
+
out,
|
|
313
|
+
TLS_SCHEDULE_FROM,
|
|
314
|
+
hashLength
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* `tlsMasterSecret` in place: HKDF-Extract of HashLen zeros under
|
|
320
|
+
* `Derive-Secret(handshake, "derived", "")`, written at `out[0]`. The salt is
|
|
321
|
+
* wiped before it returns.
|
|
322
|
+
*/
|
|
323
|
+
export const tlsMasterSecretInto = (
|
|
324
|
+
kdf: HkdfScratch,
|
|
325
|
+
hashLength: i32,
|
|
326
|
+
handshakeSecret: u8[],
|
|
327
|
+
out: u8[]
|
|
328
|
+
): void => {
|
|
329
|
+
const empty: u8[] = new Array<u8>(hashLength)
|
|
330
|
+
tlsEmptyHashInto(hashLength, empty)
|
|
331
|
+
const salt: u8[] = new Array<u8>(hashLength)
|
|
332
|
+
tlsDeriveSecretInto(kdf, hashLength, handshakeSecret, "derived", empty, TLS_SCHEDULE_FROM, salt)
|
|
333
|
+
hkdfExtractInto(kdf, hashLength, salt, new Array<u8>(hashLength), out, TLS_SCHEDULE_FROM)
|
|
334
|
+
secureZero(salt)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* `tlsFinishedVerifyData` in place: the HMAC of `transcriptHash[hashOff ..
|
|
339
|
+
* hashOff + hashLength)` under `finished_key = Expand-Label(baseKey,
|
|
340
|
+
* "finished", "", HashLen)` (RFC 8446 §4.4.4), written at `out[at]`. The
|
|
341
|
+
* finished key is wiped before it returns, and so is the scratch.
|
|
342
|
+
*/
|
|
343
|
+
export const tlsFinishedVerifyDataInto = (
|
|
344
|
+
kdf: HkdfScratch,
|
|
345
|
+
hashLength: i32,
|
|
346
|
+
baseKey: u8[],
|
|
347
|
+
transcriptHash: u8[],
|
|
348
|
+
hashOff: i32,
|
|
349
|
+
out: u8[],
|
|
350
|
+
at: i32
|
|
351
|
+
): void => {
|
|
352
|
+
const none: u8[] = []
|
|
353
|
+
const finishedKey: u8[] = new Array<u8>(hashLength)
|
|
354
|
+
hkdfExpandLabelInto(
|
|
355
|
+
kdf,
|
|
356
|
+
hashLength,
|
|
357
|
+
baseKey,
|
|
358
|
+
"finished",
|
|
359
|
+
none,
|
|
360
|
+
TLS_SCHEDULE_FROM,
|
|
361
|
+
TLS_SCHEDULE_FROM,
|
|
362
|
+
finishedKey,
|
|
363
|
+
TLS_SCHEDULE_FROM,
|
|
364
|
+
hashLength
|
|
365
|
+
)
|
|
366
|
+
if (hashLength === SHA384_SIZE) {
|
|
367
|
+
kdf.sha384.begin(finishedKey, TLS_SCHEDULE_FROM, hashLength)
|
|
368
|
+
kdf.sha384.update(transcriptHash, hashOff, hashLength)
|
|
369
|
+
kdf.sha384.finishInto(out, at)
|
|
370
|
+
} else {
|
|
371
|
+
kdf.sha256.begin(finishedKey, TLS_SCHEDULE_FROM, hashLength)
|
|
372
|
+
kdf.sha256.update(transcriptHash, hashOff, hashLength)
|
|
373
|
+
kdf.sha256.finishInto(out, at)
|
|
374
|
+
}
|
|
375
|
+
secureZero(finishedKey)
|
|
376
|
+
kdf.wipe()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* `tlsTrafficKey` and `tlsTrafficIv` in place: the AEAD key a traffic secret
|
|
381
|
+
* gives `suite` (RFC 8446 §7.3) written into `key`, which must be the suite's
|
|
382
|
+
* key length, and its 12-byte IV into `iv`. `false`, writing nothing, for a
|
|
383
|
+
* suite this module does not negotiate, a secret that is not its hash's
|
|
384
|
+
* length, or a key or IV array of the wrong length.
|
|
385
|
+
*/
|
|
386
|
+
export const tlsTrafficKeysInto = (
|
|
387
|
+
kdf: HkdfScratch,
|
|
388
|
+
suite: i32,
|
|
389
|
+
secret: u8[],
|
|
390
|
+
key: u8[],
|
|
391
|
+
iv: u8[]
|
|
392
|
+
): boolean => {
|
|
393
|
+
const none: u8[] = []
|
|
394
|
+
const hashLength: i32 = tlsSuiteHashLength(suite)
|
|
395
|
+
const keyLength: i32 = tlsSuiteKeyLength(suite)
|
|
396
|
+
if (
|
|
397
|
+
hashLength === 0 ||
|
|
398
|
+
toI32(secret.length) !== hashLength ||
|
|
399
|
+
toI32(key.length) !== keyLength ||
|
|
400
|
+
toI32(iv.length) !== TLS_IV_SIZE
|
|
401
|
+
) {
|
|
402
|
+
return false
|
|
403
|
+
}
|
|
404
|
+
hkdfExpandLabelInto(
|
|
405
|
+
kdf,
|
|
406
|
+
hashLength,
|
|
407
|
+
secret,
|
|
408
|
+
"key",
|
|
409
|
+
none,
|
|
410
|
+
TLS_SCHEDULE_FROM,
|
|
411
|
+
TLS_SCHEDULE_FROM,
|
|
412
|
+
key,
|
|
413
|
+
TLS_SCHEDULE_FROM,
|
|
414
|
+
keyLength
|
|
415
|
+
)
|
|
416
|
+
hkdfExpandLabelInto(
|
|
417
|
+
kdf,
|
|
418
|
+
hashLength,
|
|
419
|
+
secret,
|
|
420
|
+
"iv",
|
|
421
|
+
none,
|
|
422
|
+
TLS_SCHEDULE_FROM,
|
|
423
|
+
TLS_SCHEDULE_FROM,
|
|
424
|
+
iv,
|
|
425
|
+
TLS_SCHEDULE_FROM,
|
|
426
|
+
TLS_IV_SIZE
|
|
427
|
+
)
|
|
428
|
+
return true
|
|
429
|
+
}
|