@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
|
@@ -85,9 +85,18 @@ const POLY1305_HIGH_BIT: i32 = 0x1000000
|
|
|
85
85
|
/** `x` rotated left by `n` bits, `n` in 1 to 31. */
|
|
86
86
|
const chacha20Rotl = (x: u32, n: u32): u32 => (x << n) | (x >>> (32 - n))
|
|
87
87
|
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The little-endian `u32` at `buf[at]` to `buf[at + 3]`. The offsets are
|
|
90
|
+
* summed in `u32`, which wraps by definition, rather than as checked `i32`
|
|
91
|
+
* sums: every caller passes a window inside `buf`, a wrapped offset would
|
|
92
|
+
* index past it rather than reach another byte, and a check is a branch that
|
|
93
|
+
* `tests/cases/ct_asm_chacha20poly1305` refuses. `poly1305Le64`,
|
|
94
|
+
* `poly1305Block` and `chacha20Poly1305TagMatch` sum theirs the same way.
|
|
95
|
+
*/
|
|
96
|
+
const chacha20Word = (buf: u8[], at: i32): u32 => {
|
|
97
|
+
const p: u32 = toU32(at)
|
|
98
|
+
return toU32(buf[p]) | (toU32(buf[p + 1]) << 8) | (toU32(buf[p + 2]) << 16) | (toU32(buf[p + 3]) << 24)
|
|
99
|
+
}
|
|
91
100
|
|
|
92
101
|
/**
|
|
93
102
|
* The sixteen-word state of RFC 8439 §2.3: the four constant words
|
|
@@ -346,15 +355,19 @@ export const chacha20 = (key: u8[], counter: u32, nonce: u8[], data: u8[]): u8[]
|
|
|
346
355
|
}
|
|
347
356
|
|
|
348
357
|
/** The little-endian `u64` at `buf[at]` to `buf[at + 7]`. */
|
|
349
|
-
const poly1305Le64 = (buf: u8[], at: i32): u64 =>
|
|
350
|
-
|
|
351
|
-
(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
const poly1305Le64 = (buf: u8[], at: i32): u64 => {
|
|
359
|
+
const p: u32 = toU32(at)
|
|
360
|
+
return (
|
|
361
|
+
toU64(buf[p]) |
|
|
362
|
+
(toU64(buf[p + 1]) << 8) |
|
|
363
|
+
(toU64(buf[p + 2]) << 16) |
|
|
364
|
+
(toU64(buf[p + 3]) << 24) |
|
|
365
|
+
(toU64(buf[p + 4]) << 32) |
|
|
366
|
+
(toU64(buf[p + 5]) << 40) |
|
|
367
|
+
(toU64(buf[p + 6]) << 48) |
|
|
368
|
+
(toU64(buf[p + 7]) << 56)
|
|
369
|
+
)
|
|
370
|
+
}
|
|
358
371
|
|
|
359
372
|
/**
|
|
360
373
|
* `r` from the first 16 bytes of a one-time key, clamped as RFC 8439 §2.5
|
|
@@ -397,7 +410,7 @@ const poly1305Clamp = (key: u8[], r: u64[]): void => {
|
|
|
397
410
|
*/
|
|
398
411
|
const poly1305Block = (h: u64[], r: u64[], m: u8[], at: i32, high: u64): void => {
|
|
399
412
|
const lo: u64 = poly1305Le64(m, at)
|
|
400
|
-
const hi: u64 = poly1305Le64(m, at + 8)
|
|
413
|
+
const hi: u64 = poly1305Le64(m, toI32(toU32(at) + 8))
|
|
401
414
|
const h0: u64 = h[0] + (lo & 0x3ffffff)
|
|
402
415
|
const h1: u64 = h[1] + ((lo >>> 26) & 0x3ffffff)
|
|
403
416
|
const h2: u64 = h[2] + (((lo >>> 52) | (hi << 12)) & 0x3ffffff)
|
|
@@ -602,23 +615,24 @@ const chacha20Poly1305Tag = (polyKey: u8[], aad: u8[], ct: u8[], at: i32, len: i
|
|
|
602
615
|
* disassembly check; the caller has checked that `sealed` holds the window.
|
|
603
616
|
*/
|
|
604
617
|
const chacha20Poly1305TagMatch = (tag: u8[], sealed: u8[], at: i32): u32 => {
|
|
618
|
+
const p: u32 = toU32(at)
|
|
605
619
|
const diff: u32 =
|
|
606
|
-
toU32(tag[0] ^ sealed[
|
|
607
|
-
toU32(tag[1] ^ sealed[
|
|
608
|
-
toU32(tag[2] ^ sealed[
|
|
609
|
-
toU32(tag[3] ^ sealed[
|
|
610
|
-
toU32(tag[4] ^ sealed[
|
|
611
|
-
toU32(tag[5] ^ sealed[
|
|
612
|
-
toU32(tag[6] ^ sealed[
|
|
613
|
-
toU32(tag[7] ^ sealed[
|
|
614
|
-
toU32(tag[8] ^ sealed[
|
|
615
|
-
toU32(tag[9] ^ sealed[
|
|
616
|
-
toU32(tag[10] ^ sealed[
|
|
617
|
-
toU32(tag[11] ^ sealed[
|
|
618
|
-
toU32(tag[12] ^ sealed[
|
|
619
|
-
toU32(tag[13] ^ sealed[
|
|
620
|
-
toU32(tag[14] ^ sealed[
|
|
621
|
-
toU32(tag[15] ^ sealed[
|
|
620
|
+
toU32(tag[0] ^ sealed[p]) |
|
|
621
|
+
toU32(tag[1] ^ sealed[p + 1]) |
|
|
622
|
+
toU32(tag[2] ^ sealed[p + 2]) |
|
|
623
|
+
toU32(tag[3] ^ sealed[p + 3]) |
|
|
624
|
+
toU32(tag[4] ^ sealed[p + 4]) |
|
|
625
|
+
toU32(tag[5] ^ sealed[p + 5]) |
|
|
626
|
+
toU32(tag[6] ^ sealed[p + 6]) |
|
|
627
|
+
toU32(tag[7] ^ sealed[p + 7]) |
|
|
628
|
+
toU32(tag[8] ^ sealed[p + 8]) |
|
|
629
|
+
toU32(tag[9] ^ sealed[p + 9]) |
|
|
630
|
+
toU32(tag[10] ^ sealed[p + 10]) |
|
|
631
|
+
toU32(tag[11] ^ sealed[p + 11]) |
|
|
632
|
+
toU32(tag[12] ^ sealed[p + 12]) |
|
|
633
|
+
toU32(tag[13] ^ sealed[p + 13]) |
|
|
634
|
+
toU32(tag[14] ^ sealed[p + 14]) |
|
|
635
|
+
toU32(tag[15] ^ sealed[p + 15])
|
|
622
636
|
return ctEq(diff, 0)
|
|
623
637
|
}
|
|
624
638
|
|
package/std/crypto/ct.ts
CHANGED
|
@@ -62,9 +62,14 @@ export const timingSafeEqualAt = (a: u8[], aOff: i32, b: u8[], bOff: i32, len: i
|
|
|
62
62
|
if (aOff < 0 || bOff < 0 || len < 0 || aOff > aLen - len || bOff > bLen - len) {
|
|
63
63
|
return false
|
|
64
64
|
}
|
|
65
|
+
// The offsets are summed in `u32`, which wraps by definition, rather than
|
|
66
|
+
// as checked `i32` sums: the test above already put every `off + k` inside
|
|
67
|
+
// its array, so neither can wrap, and a check would only add a branch.
|
|
68
|
+
const aAt: u32 = toU32(aOff)
|
|
69
|
+
const bAt: u32 = toU32(bOff)
|
|
65
70
|
let diff: i32 = 0
|
|
66
71
|
for (let k: i32 = 0; k < len; k++) {
|
|
67
|
-
diff = diff | toI32(a[
|
|
72
|
+
diff = diff | toI32(a[aAt + toU32(k)] ^ b[bAt + toU32(k)])
|
|
68
73
|
}
|
|
69
74
|
return diff === 0
|
|
70
75
|
}
|
package/std/crypto/hkdf.ts
CHANGED
|
@@ -22,10 +22,60 @@
|
|
|
22
22
|
* shorter than HashLen, which §2.3 does not allow: an empty one is a key
|
|
23
23
|
* anybody can compute with.
|
|
24
24
|
*
|
|
25
|
-
* HKDF-Expand-Label
|
|
26
|
-
*
|
|
25
|
+
* HKDF-Expand-Label is TLS 1.3's wrapper around `expand` (RFC 8446 §7.1), and
|
|
26
|
+
* QUIC derives its packet keys with it too (RFC 9001 §5.1), so it is here once
|
|
27
|
+
* for both rather than in each:
|
|
28
|
+
*
|
|
29
|
+
* HKDF-Expand-Label(Secret, Label, Context, Length) =
|
|
30
|
+
* HKDF-Expand(Secret, HkdfLabel, Length)
|
|
31
|
+
* struct {
|
|
32
|
+
* uint16 length = Length;
|
|
33
|
+
* opaque label<7..255> = "tls13 " + Label;
|
|
34
|
+
* opaque context<0..255> = Context;
|
|
35
|
+
* } HkdfLabel;
|
|
36
|
+
*
|
|
37
|
+
* const key: u8[] = hkdfExpandLabelSha256(secret, "quic key", [], 16);
|
|
38
|
+
*
|
|
39
|
+
* `label` is given without the `"tls13 "` prefix, which the function adds. Its
|
|
40
|
+
* arguments come from the program rather than from the peer — a label is a
|
|
41
|
+
* literal, a length a key or hash size, a context a transcript hash — so one
|
|
42
|
+
* out of range is a bug in the caller, and it panics rather than answering
|
|
43
|
+
* `null`: a length below 0 or above 255, a label empty or longer than 249
|
|
44
|
+
* bytes (the struct's 7 to 255 less the prefix), a context longer than 255
|
|
45
|
+
* bytes, and a secret shorter than HashLen.
|
|
46
|
+
*
|
|
47
|
+
* **Caller-owned scratch.** The functions above make their HMAC objects and
|
|
48
|
+
* their output fresh on every call, which stores allocations no arena scope
|
|
49
|
+
* may release around them. `hkdfExtractInto`, `hkdfExpandInto` and
|
|
50
|
+
* `hkdfExpandLabelInto` compute the same bytes in an `HkdfScratch` the caller
|
|
51
|
+
* allocates once, over the hash `hashLength` names (32 for SHA-256, 48 for
|
|
52
|
+
* SHA-384), and write them into the caller's array, so that a loop of key
|
|
53
|
+
* derivations inside one `using a = arena()` block leaves the arena where it
|
|
54
|
+
* was. Each wipes the scratch before it returns, its last block and the
|
|
55
|
+
* keyed hashers of the hash it ran over, so no key material outlives the
|
|
56
|
+
* call there:
|
|
57
|
+
*
|
|
58
|
+
* const kdf = new HkdfScratch(); // once
|
|
59
|
+
* hkdfExtractInto(kdf, 32, salt, ikm, prk, 0); // 32 bytes at prk[0]
|
|
60
|
+
* hkdfExpandLabelInto(kdf, 32, prk, "key", context, 0, 0, key, 0, 16);
|
|
61
|
+
*
|
|
62
|
+
* They refuse what the functions above refuse, the same way: `hkdfExpandInto`
|
|
63
|
+
* answers `false` where `hkdfExpandSha256` answers `null`, and
|
|
64
|
+
* `hkdfExpandLabelInto` panics where `hkdfExpandLabelSha256` does. A hash
|
|
65
|
+
* length other than 32 or 48, or an output window outside its array, is the
|
|
66
|
+
* caller's bug and panics.
|
|
67
|
+
*
|
|
68
|
+
* Written from RFC 5869 and RFC 8446 §7.1, not ported from another
|
|
69
|
+
* implementation.
|
|
27
70
|
*/
|
|
28
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
HmacSha256,
|
|
73
|
+
HmacSha256Scratch,
|
|
74
|
+
HmacSha384,
|
|
75
|
+
HmacSha384Scratch,
|
|
76
|
+
hmacSha256,
|
|
77
|
+
hmacSha384,
|
|
78
|
+
} from "nish/crypto/hmac"
|
|
29
79
|
import { SHA256_SIZE } from "nish/crypto/sha256"
|
|
30
80
|
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
31
81
|
|
|
@@ -35,6 +85,9 @@ const HKDF_MAX_BLOCKS: i32 = 255
|
|
|
35
85
|
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
36
86
|
const HKDF_FROM: i32 = 0
|
|
37
87
|
|
|
88
|
+
/** What HKDF-Expand-Label puts before every label (RFC 8446 §7.1). */
|
|
89
|
+
const HKDF_LABEL_PREFIX: string = "tls13 "
|
|
90
|
+
|
|
38
91
|
/**
|
|
39
92
|
* The salt `extract` keys HMAC with: `salt` itself, or HashLen zero bytes when
|
|
40
93
|
* it is empty (RFC 5869 §2.2, "if not provided"). HMAC zero-pads a short key to
|
|
@@ -132,3 +185,311 @@ export const hkdfExpandSha384 = (prk: u8[], info: u8[], length: i32): u8[] | nul
|
|
|
132
185
|
}
|
|
133
186
|
return out
|
|
134
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The `HkdfLabel` struct of RFC 8446 §7.1, the `info` HKDF-Expand-Label hands
|
|
191
|
+
* to `expand`, or a panic naming `caller` when `length`, `label` or `context`
|
|
192
|
+
* is outside the range the module docs give: 249 is `label<7..255>`'s 255 less
|
|
193
|
+
* the prefix's 6 bytes. The lengths of `label` and `context` are compared as
|
|
194
|
+
* the `number`s they are, before any `toI32`, which would saturate them under
|
|
195
|
+
* `--number-mode f64`; so the bounds are literals rather than `i32` constants.
|
|
196
|
+
*/
|
|
197
|
+
const hkdfLabel = (caller: string, label: string, context: u8[], length: i32): u8[] => {
|
|
198
|
+
if (length < 0 || length > 255) {
|
|
199
|
+
panic(`${caller}: a length of ${length} bytes, outside 0 to 255`)
|
|
200
|
+
}
|
|
201
|
+
if (label.length === 0 || label.length > 249) {
|
|
202
|
+
panic(`${caller}: a label of ${toI32(label.length)} bytes, outside 1 to 249`)
|
|
203
|
+
}
|
|
204
|
+
if (context.length > 255) {
|
|
205
|
+
panic(`${caller}: a context of more than 255 bytes`)
|
|
206
|
+
}
|
|
207
|
+
const full: string = `${HKDF_LABEL_PREFIX}${label}`
|
|
208
|
+
const fullLength: i32 = toI32(full.length)
|
|
209
|
+
const contextLength: i32 = toI32(context.length)
|
|
210
|
+
const out: u8[] = new Array<u8>(4 + fullLength + contextLength)
|
|
211
|
+
out[0] = toU8(length >> 8)
|
|
212
|
+
out[1] = toU8(length & 0xff)
|
|
213
|
+
out[2] = toU8(fullLength)
|
|
214
|
+
for (let k: i32 = 0; k < fullLength; k += 1) {
|
|
215
|
+
out[3 + k] = toU8(toI32(full.charCodeAt(k)))
|
|
216
|
+
}
|
|
217
|
+
out[3 + fullLength] = toU8(contextLength)
|
|
218
|
+
hkdfAppend(out, 4 + fullLength, context)
|
|
219
|
+
return out
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* HKDF-Expand-Label with HMAC-SHA-256 (RFC 8446 §7.1): `length` bytes from
|
|
224
|
+
* `secret`, `"tls13 " + label` and `context`. Panics on an argument out of the
|
|
225
|
+
* range the module docs give, a secret shorter than 32 bytes among them.
|
|
226
|
+
*/
|
|
227
|
+
export const hkdfExpandLabelSha256 = (secret: u8[], label: string, context: u8[], length: i32): u8[] => {
|
|
228
|
+
const okm: u8[] | null = hkdfExpandSha256(
|
|
229
|
+
secret,
|
|
230
|
+
hkdfLabel("hkdfExpandLabelSha256", label, context, length),
|
|
231
|
+
length
|
|
232
|
+
)
|
|
233
|
+
if (okm === null) {
|
|
234
|
+
panic("hkdfExpandLabelSha256: a secret shorter than 32 bytes")
|
|
235
|
+
}
|
|
236
|
+
return okm
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* HKDF-Expand-Label with HMAC-SHA-384 (RFC 8446 §7.1): `length` bytes from
|
|
241
|
+
* `secret`, `"tls13 " + label` and `context`. Panics on an argument out of the
|
|
242
|
+
* range the module docs give, a secret shorter than 48 bytes among them.
|
|
243
|
+
*/
|
|
244
|
+
export const hkdfExpandLabelSha384 = (secret: u8[], label: string, context: u8[], length: i32): u8[] => {
|
|
245
|
+
const okm: u8[] | null = hkdfExpandSha384(
|
|
246
|
+
secret,
|
|
247
|
+
hkdfLabel("hkdfExpandLabelSha384", label, context, length),
|
|
248
|
+
length
|
|
249
|
+
)
|
|
250
|
+
if (okm === null) {
|
|
251
|
+
panic("hkdfExpandLabelSha384: a secret shorter than 48 bytes")
|
|
252
|
+
}
|
|
253
|
+
return okm
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---- Caller-owned scratch ----------------------------------------------------
|
|
257
|
+
|
|
258
|
+
/** The longest `HkdfLabel`: two length bytes, `label<7..255>` and `context<0..255>`, each with its length byte. */
|
|
259
|
+
const HKDF_LABEL_MAX: i32 = 514
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* What the `*Into` functions compute in: an HMAC scratch for each hash, the
|
|
263
|
+
* previous output block T(i - 1), and the `HkdfLabel` HKDF-Expand-Label
|
|
264
|
+
* builds. Allocate one per long-lived owner — a connection slot, say — and
|
|
265
|
+
* hand it to every derivation; it carries nothing from one call to the next.
|
|
266
|
+
*/
|
|
267
|
+
export class HkdfScratch {
|
|
268
|
+
sha256: HmacSha256Scratch
|
|
269
|
+
sha384: HmacSha384Scratch
|
|
270
|
+
/** T(i - 1), HashLen bytes of it; wiped before every function returns. */
|
|
271
|
+
previous: u8[]
|
|
272
|
+
/** The one-byte block counter `i`. */
|
|
273
|
+
counter: u8[]
|
|
274
|
+
/** HKDF-Expand-Label's `info`, built here: public, so it is not wiped. */
|
|
275
|
+
label: u8[]
|
|
276
|
+
|
|
277
|
+
constructor() {
|
|
278
|
+
this.sha256 = new HmacSha256Scratch()
|
|
279
|
+
this.sha384 = new HmacSha384Scratch()
|
|
280
|
+
this.previous = new Array<u8>(SHA384_SIZE)
|
|
281
|
+
this.counter = new Array<u8>(1)
|
|
282
|
+
this.label = new Array<u8>(HKDF_LABEL_MAX)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Zeroes the key material the last derivation left: T(i - 1) and both keyed hashers. */
|
|
286
|
+
wipe(): void {
|
|
287
|
+
secureZero(this.previous)
|
|
288
|
+
this.sha256.wipe()
|
|
289
|
+
this.sha384.wipe()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Panics unless `hashLength` is 32 or 48, the two hashes HKDF runs over here. */
|
|
294
|
+
const hkdfCheckHash = (caller: string, hashLength: i32): void => {
|
|
295
|
+
if (hashLength !== SHA256_SIZE && hashLength !== SHA384_SIZE) {
|
|
296
|
+
panic(`${caller}: a hash length of ${hashLength}, not 32 or 48`)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Panics unless `out[at .. at + length)` is a window inside `out`. */
|
|
301
|
+
const hkdfCheckOutput = (caller: string, out: u8[], at: i32, length: i32): void => {
|
|
302
|
+
if (at < 0 || length < 0 || at > toI32(out.length) - length) {
|
|
303
|
+
panic(`${caller}: the output window is outside its array`)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Zeroes what the last derivation over `hashLength` left in `s`: T(i - 1) and that hash's keyed hashers. */
|
|
308
|
+
const hkdfWipe = (s: HkdfScratch, hashLength: i32): void => {
|
|
309
|
+
secureZero(s.previous)
|
|
310
|
+
if (hashLength === SHA384_SIZE) {
|
|
311
|
+
s.sha384.wipe()
|
|
312
|
+
} else {
|
|
313
|
+
s.sha256.wipe()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Keys the scratch's HMAC for `hashLength` with `key[off .. off + len)`. */
|
|
318
|
+
const hkdfMacBegin = (s: HkdfScratch, hashLength: i32, key: u8[], off: i32, len: i32): void => {
|
|
319
|
+
if (hashLength === SHA384_SIZE) {
|
|
320
|
+
s.sha384.begin(key, off, len)
|
|
321
|
+
} else {
|
|
322
|
+
s.sha256.begin(key, off, len)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Feeds `data[off .. off + len)` to the scratch's HMAC for `hashLength`. */
|
|
327
|
+
const hkdfMacUpdate = (s: HkdfScratch, hashLength: i32, data: u8[], off: i32, len: i32): void => {
|
|
328
|
+
if (hashLength === SHA384_SIZE) {
|
|
329
|
+
s.sha384.update(data, off, len)
|
|
330
|
+
} else {
|
|
331
|
+
s.sha256.update(data, off, len)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Writes the HMAC for `hashLength`'s tag at `out[at]`. */
|
|
336
|
+
const hkdfMacFinish = (s: HkdfScratch, hashLength: i32, out: u8[], at: i32): void => {
|
|
337
|
+
if (hashLength === SHA384_SIZE) {
|
|
338
|
+
s.sha384.finishInto(out, at)
|
|
339
|
+
} else {
|
|
340
|
+
s.sha256.finishInto(out, at)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* HKDF-Extract (RFC 5869 §2.2) over the hash `hashLength` names, the PRK's
|
|
346
|
+
* HashLen bytes written at `out[at]`. An empty `salt` keys HMAC with no bytes,
|
|
347
|
+
* which HMAC pads to the block with zeros, exactly as the HashLen zeros §2.2
|
|
348
|
+
* asks for would be. An `ikm` longer than 2^31 - 1 bytes panics, as
|
|
349
|
+
* `hmacSha256` does.
|
|
350
|
+
*/
|
|
351
|
+
export const hkdfExtractInto = (
|
|
352
|
+
s: HkdfScratch,
|
|
353
|
+
hashLength: i32,
|
|
354
|
+
salt: u8[],
|
|
355
|
+
ikm: u8[],
|
|
356
|
+
out: u8[],
|
|
357
|
+
at: i32
|
|
358
|
+
): void => {
|
|
359
|
+
hkdfCheckHash("hkdfExtractInto", hashLength)
|
|
360
|
+
hkdfCheckOutput("hkdfExtractInto", out, at, hashLength)
|
|
361
|
+
if (ikm.length > 2147483647) {
|
|
362
|
+
panic("hkdfExtractInto: input keying material longer than 2^31 - 1 bytes")
|
|
363
|
+
}
|
|
364
|
+
hkdfMacBegin(s, hashLength, salt, HKDF_FROM, toI32(salt.length))
|
|
365
|
+
hkdfMacUpdate(s, hashLength, ikm, HKDF_FROM, toI32(ikm.length))
|
|
366
|
+
hkdfMacFinish(s, hashLength, out, at)
|
|
367
|
+
hkdfWipe(s, hashLength)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* The T(1) | T(2) | … loop of RFC 5869 §2.3, `length` bytes of it at
|
|
372
|
+
* `out[at]`, with `info` the first `infoLen` bytes of `info`.
|
|
373
|
+
* The callers have checked every bound.
|
|
374
|
+
*/
|
|
375
|
+
const hkdfExpandWindow = (
|
|
376
|
+
s: HkdfScratch,
|
|
377
|
+
hashLength: i32,
|
|
378
|
+
prk: u8[],
|
|
379
|
+
info: u8[],
|
|
380
|
+
infoLen: i32,
|
|
381
|
+
out: u8[],
|
|
382
|
+
at: i32,
|
|
383
|
+
length: i32
|
|
384
|
+
): void => {
|
|
385
|
+
const counterLength: i32 = 1
|
|
386
|
+
let done: i32 = 0
|
|
387
|
+
let i: i32 = 1
|
|
388
|
+
while (done < length) {
|
|
389
|
+
hkdfMacBegin(s, hashLength, prk, HKDF_FROM, toI32(prk.length))
|
|
390
|
+
if (i > 1) {
|
|
391
|
+
hkdfMacUpdate(s, hashLength, s.previous, HKDF_FROM, hashLength)
|
|
392
|
+
}
|
|
393
|
+
hkdfMacUpdate(s, hashLength, info, HKDF_FROM, infoLen)
|
|
394
|
+
s.counter[0] = toU8(i)
|
|
395
|
+
hkdfMacUpdate(s, hashLength, s.counter, HKDF_FROM, counterLength)
|
|
396
|
+
hkdfMacFinish(s, hashLength, s.previous, HKDF_FROM)
|
|
397
|
+
for (let k: i32 = 0; k < hashLength && done < length && k < toI32(s.previous.length); k += 1) {
|
|
398
|
+
out[at + done] = s.previous[k]
|
|
399
|
+
done += 1
|
|
400
|
+
}
|
|
401
|
+
i += 1
|
|
402
|
+
}
|
|
403
|
+
hkdfWipe(s, hashLength)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* HKDF-Expand (RFC 5869 §2.3) over the hash `hashLength` names: `length`
|
|
408
|
+
* bytes from `prk` and `info`, written at `out[at]`. `false`, writing nothing,
|
|
409
|
+
* where `hkdfExpandSha256` answers `null`: a `length` negative or past
|
|
410
|
+
* 255 × HashLen, an `info` longer than 2^31 - 1 bytes, or a `prk` shorter than
|
|
411
|
+
* HashLen.
|
|
412
|
+
*/
|
|
413
|
+
export const hkdfExpandInto = (
|
|
414
|
+
s: HkdfScratch,
|
|
415
|
+
hashLength: i32,
|
|
416
|
+
prk: u8[],
|
|
417
|
+
info: u8[],
|
|
418
|
+
out: u8[],
|
|
419
|
+
at: i32,
|
|
420
|
+
length: i32
|
|
421
|
+
): boolean => {
|
|
422
|
+
hkdfCheckHash("hkdfExpandInto", hashLength)
|
|
423
|
+
if (
|
|
424
|
+
length < 0 ||
|
|
425
|
+
length > HKDF_MAX_BLOCKS * hashLength ||
|
|
426
|
+
info.length > 2147483647 ||
|
|
427
|
+
toI32(prk.length) < hashLength
|
|
428
|
+
) {
|
|
429
|
+
return false
|
|
430
|
+
}
|
|
431
|
+
hkdfCheckOutput("hkdfExpandInto", out, at, length)
|
|
432
|
+
hkdfExpandWindow(s, hashLength, prk, info, toI32(info.length), out, at, length)
|
|
433
|
+
return true
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* HKDF-Expand-Label (RFC 8446 §7.1) over the hash `hashLength` names: `length`
|
|
438
|
+
* bytes from `secret`, `"tls13 " + label` and the context
|
|
439
|
+
* `context[contextOff .. contextOff + contextLen)`, written at `out[at]`. It
|
|
440
|
+
* panics where `hkdfExpandLabelSha256` does — a length outside 0 to 255, a
|
|
441
|
+
* label empty or past 249 bytes, a context past 255, a secret shorter than
|
|
442
|
+
* HashLen — and on a context window outside its array.
|
|
443
|
+
*/
|
|
444
|
+
export const hkdfExpandLabelInto = (
|
|
445
|
+
s: HkdfScratch,
|
|
446
|
+
hashLength: i32,
|
|
447
|
+
secret: u8[],
|
|
448
|
+
label: string,
|
|
449
|
+
context: u8[],
|
|
450
|
+
contextOff: i32,
|
|
451
|
+
contextLen: i32,
|
|
452
|
+
out: u8[],
|
|
453
|
+
at: i32,
|
|
454
|
+
length: i32
|
|
455
|
+
): void => {
|
|
456
|
+
hkdfCheckHash("hkdfExpandLabelInto", hashLength)
|
|
457
|
+
if (length < 0 || length > 255) {
|
|
458
|
+
panic(`hkdfExpandLabelInto: a length of ${length} bytes, outside 0 to 255`)
|
|
459
|
+
}
|
|
460
|
+
if (label.length === 0 || label.length > 249) {
|
|
461
|
+
panic(`hkdfExpandLabelInto: a label of ${toI32(label.length)} bytes, outside 1 to 249`)
|
|
462
|
+
}
|
|
463
|
+
if (
|
|
464
|
+
contextOff < 0 ||
|
|
465
|
+
contextLen < 0 ||
|
|
466
|
+
contextOff > toI32(context.length) - contextLen ||
|
|
467
|
+
contextLen > 255
|
|
468
|
+
) {
|
|
469
|
+
panic("hkdfExpandLabelInto: the context window is outside its array or longer than 255 bytes")
|
|
470
|
+
}
|
|
471
|
+
if (toI32(secret.length) < hashLength) {
|
|
472
|
+
panic("hkdfExpandLabelInto: a secret shorter than HashLen")
|
|
473
|
+
}
|
|
474
|
+
hkdfCheckOutput("hkdfExpandLabelInto", out, at, length)
|
|
475
|
+
// The `HkdfLabel` struct, as `hkdfLabel` builds it, into the scratch.
|
|
476
|
+
const prefixLength: i32 = toI32(HKDF_LABEL_PREFIX.length)
|
|
477
|
+
const labelLength: i32 = toI32(label.length)
|
|
478
|
+
const fullLength: i32 = prefixLength + labelLength
|
|
479
|
+
const info: u8[] = s.label
|
|
480
|
+
info[0] = toU8(length >> 8)
|
|
481
|
+
info[1] = toU8(length & 0xff)
|
|
482
|
+
info[2] = toU8(fullLength)
|
|
483
|
+
for (let k: i32 = 0; k < prefixLength && 3 + k < toI32(info.length); k += 1) {
|
|
484
|
+
info[3 + k] = toU8(toI32(HKDF_LABEL_PREFIX.charCodeAt(k)))
|
|
485
|
+
}
|
|
486
|
+
for (let k: i32 = 0; k < labelLength && 3 + prefixLength + k < toI32(info.length); k += 1) {
|
|
487
|
+
info[3 + prefixLength + k] = toU8(toI32(label.charCodeAt(k)))
|
|
488
|
+
}
|
|
489
|
+
const contextAt: i32 = 3 + fullLength
|
|
490
|
+
info[contextAt] = toU8(contextLen)
|
|
491
|
+
for (let k: i32 = 0; k < contextLen && contextAt + 1 + k < toI32(info.length); k += 1) {
|
|
492
|
+
info[contextAt + 1 + k] = context[contextOff + k]
|
|
493
|
+
}
|
|
494
|
+
hkdfExpandWindow(s, hashLength, secret, info, contextAt + 1 + contextLen, out, at, length)
|
|
495
|
+
}
|