@amritk/nish-x86_64-linux 0.12.0 → 0.14.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 +13 -13
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +85 -1
- package/runtime/nish.h +62 -11
- package/runtime/nish.mjs +38 -3
- package/runtime/runtime-host.c +178 -0
- package/runtime/{runtime_os.c → runtime-os.c} +16 -1
- package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
- package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
- package/runtime/runtime.c +5 -5
- package/runtime/shim.mjs +128 -6
- package/scripts/build.sh +19 -13
- package/std/README.md +42 -2
- package/std/collections.ts +194 -188
- package/std/crypto/base64url.ts +145 -0
- package/std/crypto/ct.ts +64 -0
- package/std/crypto/hkdf.ts +118 -0
- package/std/crypto/hmac.ts +155 -0
- package/std/crypto/sha256.ts +444 -0
- package/std/crypto/sha512.ts +510 -0
- package/std/crypto/x25519.ts +494 -0
- package/std/json.ts +136 -136
- package/std/map.ts +9 -7
- package/std/pair.ts +2 -2
- package/std/testing.ts +67 -67
- package/std/text.ts +54 -54
- package/std/threads.ts +126 -38
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/base64url` — RFC 4648 §5 base64url, unpadded, as JWS and the
|
|
3
|
+
* relay's grants spell a key or a tag inside a URL or a header.
|
|
4
|
+
*
|
|
5
|
+
* The alphabet is `A-Z a-z 0-9 - _`, and there is no `=`: the length of the
|
|
6
|
+
* text already says how many bytes the last group holds (RFC 4648 §3.2 lets a
|
|
7
|
+
* specification that knows its lengths drop the padding, and RFC 7515 §2 does).
|
|
8
|
+
*
|
|
9
|
+
* **Decoding is strict, so that each byte string has exactly one spelling.**
|
|
10
|
+
* `base64urlDecode` answers `null` for a `=` anywhere, for any character outside
|
|
11
|
+
* the alphabet (whitespace included), for a length of 1 mod 4 — six bits, not a
|
|
12
|
+
* byte — and for a final character whose unused low bits are not zero (RFC 4648
|
|
13
|
+
* §3.5). A lenient decoder would let `AB` and `AA` both mean `00`, and a token
|
|
14
|
+
* compared or cached by its text would then have two identities.
|
|
15
|
+
*
|
|
16
|
+
* **Neither direction indexes or branches on the data.** What is encoded here
|
|
17
|
+
* is usually a key, a nonce or a MAC, and a lookup table indexed by a secret
|
|
18
|
+
* sextet leaves its trace in the cache. So a sextet becomes a character, and a
|
|
19
|
+
* character a sextet, by arithmetic on range masks: `(lo - 1 - c) & (c - hi - 1)`
|
|
20
|
+
* is negative exactly when `lo <= c <= hi`, and an arithmetic shift by 31 turns
|
|
21
|
+
* that sign into an all-ones or all-zeros mask. A malformed character is not
|
|
22
|
+
* refused where it is found either: the verdict is ORed into one word and read
|
|
23
|
+
* once, after the whole text. The text's length is public and is tested first.
|
|
24
|
+
*
|
|
25
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
26
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* All ones when `lo <= c <= hi`, else zero, without a branch. `c` is a byte or
|
|
31
|
+
* a sextet, so neither subtraction can overflow.
|
|
32
|
+
*/
|
|
33
|
+
const base64urlRangeMask = (c: i32, lo: i32, hi: i32): i32 => ((lo - 1 - c) & (c - hi - 1)) >> 31
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The URL-alphabet character for the sextet `v` (`0 <= v <= 63`).
|
|
37
|
+
*
|
|
38
|
+
* It starts from `'A' + v` and adds, for each range `v` has passed, the step
|
|
39
|
+
* from the previous range's first character to this one's: 26 lands on `a`, 52
|
|
40
|
+
* on `0`, 62 on `-` and 63 on `_`.
|
|
41
|
+
*/
|
|
42
|
+
const base64urlCharOf = (v: i32): i32 =>
|
|
43
|
+
65 +
|
|
44
|
+
v +
|
|
45
|
+
(base64urlRangeMask(v, 26, 63) & 6) -
|
|
46
|
+
(base64urlRangeMask(v, 52, 63) & 75) -
|
|
47
|
+
(base64urlRangeMask(v, 62, 63) & 13) +
|
|
48
|
+
(base64urlRangeMask(v, 63, 63) & 49)
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The sextet the byte `c` stands for, or `-1` when `c` is not in the alphabet.
|
|
52
|
+
* Each range contributes its value under its own mask, and a byte in none of
|
|
53
|
+
* them has every bit set by the final OR.
|
|
54
|
+
*/
|
|
55
|
+
const base64urlSextetOf = (c: i32): i32 => {
|
|
56
|
+
const upper: i32 = base64urlRangeMask(c, 65, 90)
|
|
57
|
+
const lower: i32 = base64urlRangeMask(c, 97, 122)
|
|
58
|
+
const digit: i32 = base64urlRangeMask(c, 48, 57)
|
|
59
|
+
const dash: i32 = base64urlRangeMask(c, 45, 45)
|
|
60
|
+
const underscore: i32 = base64urlRangeMask(c, 95, 95)
|
|
61
|
+
const value: i32 =
|
|
62
|
+
(upper & (c - 65)) | (lower & (c - 71)) | (digit & (c + 4)) | (dash & 62) | (underscore & 63)
|
|
63
|
+
return value | ~(upper | lower | digit | dash | underscore)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* `data` as unpadded base64url: four characters per three bytes, and two or
|
|
68
|
+
* three for a short final group.
|
|
69
|
+
*
|
|
70
|
+
* Bytes go into a bit accumulator eight at a time and come out six at a time,
|
|
71
|
+
* so one index walks the input and the only branches are on how many bits are
|
|
72
|
+
* waiting — which depends on the position, never on the bytes.
|
|
73
|
+
*/
|
|
74
|
+
export const base64urlEncode = (data: u8[]): string => {
|
|
75
|
+
const parts: string[] = []
|
|
76
|
+
let acc: i32 = 0
|
|
77
|
+
let bits: i32 = 0
|
|
78
|
+
for (let k: i32 = 0; k < toI32(data.length); k++) {
|
|
79
|
+
// At most four bits wait from the bytes before, so twelve are live here;
|
|
80
|
+
// the mask keeps the accumulator from growing without bound.
|
|
81
|
+
acc = ((acc << 8) | toI32(data[k])) & 0xfff
|
|
82
|
+
bits += 8
|
|
83
|
+
while (bits >= 6) {
|
|
84
|
+
bits -= 6
|
|
85
|
+
parts.push(String.fromCharCode(base64urlCharOf((acc >> bits) & 63)))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Two or four bits of a final byte are left: they go out as the high bits of
|
|
89
|
+
// one more sextet, filled out with zeros.
|
|
90
|
+
if (bits > 0) {
|
|
91
|
+
parts.push(String.fromCharCode(base64urlCharOf((acc << (6 - bits)) & 63)))
|
|
92
|
+
}
|
|
93
|
+
return parts.join("")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The bytes `text` spells in unpadded base64url, or `null` when it is not the
|
|
98
|
+
* one canonical spelling of any byte string — see the module comment for the
|
|
99
|
+
* four refusals.
|
|
100
|
+
*
|
|
101
|
+
* The same accumulator as `base64urlEncode`, run the other way: six bits in per
|
|
102
|
+
* character, eight out per byte.
|
|
103
|
+
*/
|
|
104
|
+
export const base64urlDecode = (text: string): u8[] | null => {
|
|
105
|
+
const n: i32 = toI32(text.length)
|
|
106
|
+
const tail: i32 = n & 3
|
|
107
|
+
// Six bits cannot finish a byte, so a single character after the last full
|
|
108
|
+
// group spells nothing.
|
|
109
|
+
if (tail === 1) {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
// Three bytes per full group of four, and one fewer than the characters in a
|
|
113
|
+
// short final group; `n * 3 / 4` would overflow for a text over 700 MB.
|
|
114
|
+
const out: u8[] = new Array<u8>((n >> 2) * 3 + (tail === 0 ? 0 : tail - 1))
|
|
115
|
+
const outLen: i32 = toI32(out.length)
|
|
116
|
+
// All ones once any character is outside the alphabet (its sextet of `-1`
|
|
117
|
+
// shifted right by 31), and non-zero once the text ends on a bit that
|
|
118
|
+
// encodes no byte.
|
|
119
|
+
let bad: i32 = 0
|
|
120
|
+
let acc: i32 = 0
|
|
121
|
+
let bits: i32 = 0
|
|
122
|
+
let j: i32 = 0
|
|
123
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
124
|
+
const v: i32 = base64urlSextetOf(toI32(text.charCodeAt(k)))
|
|
125
|
+
bad = bad | (v >> 31)
|
|
126
|
+
acc = ((acc << 6) | (v & 63)) & 0xfff
|
|
127
|
+
bits += 6
|
|
128
|
+
if (bits >= 8) {
|
|
129
|
+
bits -= 8
|
|
130
|
+
// Always true — `out` was sized from the same length — but written out
|
|
131
|
+
// so that the store keeps no bounds check.
|
|
132
|
+
if (j >= 0 && j < outLen) {
|
|
133
|
+
out[j] = toU8((acc >> bits) & 255)
|
|
134
|
+
}
|
|
135
|
+
j++
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// The two or four bits left over belong to no byte, and must be zero so that
|
|
139
|
+
// `AA` is the only spelling of a zero byte and `AB` is refused (RFC 4648 §3.5).
|
|
140
|
+
bad = bad | (acc & ((1 << bits) - 1))
|
|
141
|
+
if (bad !== 0) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
return out
|
|
145
|
+
}
|
package/std/crypto/ct.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/ct` — comparing secrets without telling the clock where they differ.
|
|
3
|
+
*
|
|
4
|
+
* An `===` loop over two MACs stops at the first differing byte, so the time it
|
|
5
|
+
* takes says how many leading bytes an attacker already guessed right, and a
|
|
6
|
+
* tag can be recovered one byte at a time. These functions read every byte of
|
|
7
|
+
* the window whatever it holds: each pair is XORed, the differences are ORed
|
|
8
|
+
* into one accumulator, and that accumulator is tested once, after the loop.
|
|
9
|
+
*
|
|
10
|
+
* What is *not* secret is checked first and may branch: the two lengths, and
|
|
11
|
+
* whether a window lies inside its array. A caller comparing a received tag
|
|
12
|
+
* against an expected one already knows both lengths, so refusing a mismatch
|
|
13
|
+
* early leaks nothing the attacker did not send.
|
|
14
|
+
*
|
|
15
|
+
* The names are `timingSafeEqual*`, after Node's `crypto.timingSafeEqual`,
|
|
16
|
+
* rather than `ctEq`: that one is reserved for the builtin WP34 N6 will add,
|
|
17
|
+
* together with the disassembly check that proves the loop stays branch-free
|
|
18
|
+
* after LLVM has seen it. Until then this is the discipline and not a proof.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether `a` and `b` hold the same bytes, reading all of them.
|
|
23
|
+
*
|
|
24
|
+
* Arrays of different lengths answer `false` at once, because a length is
|
|
25
|
+
* public; arrays of one length are compared in full, with no early exit.
|
|
26
|
+
*/
|
|
27
|
+
export const timingSafeEqual = (a: u8[], b: u8[]): boolean => {
|
|
28
|
+
const n: i32 = toI32(a.length)
|
|
29
|
+
if (n !== toI32(b.length)) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
let diff: i32 = 0
|
|
33
|
+
// Bounded by both lengths, which are equal here, so the prover drops both
|
|
34
|
+
// bounds checks rather than trusting the comparison above.
|
|
35
|
+
for (let i: i32 = 0; i < n && i < toI32(b.length); i++) {
|
|
36
|
+
diff = diff | toI32(a[i] ^ b[i])
|
|
37
|
+
}
|
|
38
|
+
return diff === 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether the `len` bytes of `a` from `aOff` equal the `len` bytes of `b` from
|
|
43
|
+
* `bOff`, reading all of them.
|
|
44
|
+
*
|
|
45
|
+
* A window that does not lie inside its array — a negative offset or length,
|
|
46
|
+
* or `off + len` past the end — answers `false` instead of panicking on the
|
|
47
|
+
* first out-of-range read. That is a decision about the caller's position: a
|
|
48
|
+
* verifier handed a truncated record should say "not equal", not take the
|
|
49
|
+
* process down, and the bounds are public, so testing them first is safe.
|
|
50
|
+
*/
|
|
51
|
+
export const timingSafeEqualAt = (a: u8[], aOff: i32, b: u8[], bOff: i32, len: i32): boolean => {
|
|
52
|
+
const aLen: i32 = toI32(a.length)
|
|
53
|
+
const bLen: i32 = toI32(b.length)
|
|
54
|
+
// Written as `off > length - len` rather than `off + len > length`, so that
|
|
55
|
+
// a huge offset cannot wrap round to a small sum and pass.
|
|
56
|
+
if (aOff < 0 || bOff < 0 || len < 0 || aOff > aLen - len || bOff > bLen - len) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
let diff: i32 = 0
|
|
60
|
+
for (let k: i32 = 0; k < len; k++) {
|
|
61
|
+
diff = diff | toI32(a[aOff + k] ^ b[bOff + k])
|
|
62
|
+
}
|
|
63
|
+
return diff === 0
|
|
64
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/hkdf` — HKDF as RFC 5869 defines it, over HMAC-SHA-256 and
|
|
3
|
+
* HMAC-SHA-384.
|
|
4
|
+
*
|
|
5
|
+
* Two steps, each its own function, because TLS 1.3 calls them separately:
|
|
6
|
+
*
|
|
7
|
+
* PRK = HKDF-Extract(salt, IKM) = HMAC-Hash(salt, IKM) (§2.2)
|
|
8
|
+
* OKM = HKDF-Expand(PRK, info, L) = T(1) | T(2) | … truncated to L bytes (§2.3)
|
|
9
|
+
* T(i) = HMAC-Hash(PRK, T(i-1) | info | i), T(0) empty, i one byte
|
|
10
|
+
*
|
|
11
|
+
* import { hkdfExtractSha256, hkdfExpandSha256 } from "nish/crypto/hkdf";
|
|
12
|
+
*
|
|
13
|
+
* const prk: u8[] = hkdfExtractSha256(salt, secret);
|
|
14
|
+
* const okm: u8[] | null = hkdfExpandSha256(prk, info, 42);
|
|
15
|
+
*
|
|
16
|
+
* The counter `i` is one byte, so `L` is at most 255 × HashLen (§2.3): 8160
|
|
17
|
+
* bytes for SHA-256 and 12240 for SHA-384. A longer `L`, or a negative one,
|
|
18
|
+
* answers `null` rather than panicking, because `L` usually comes from a
|
|
19
|
+
* protocol field; `L = 0` answers an empty array.
|
|
20
|
+
*
|
|
21
|
+
* HKDF-Expand-Label, TLS 1.3's wrapper around `expand`, belongs with TLS and is
|
|
22
|
+
* not here. Written from RFC 5869, not ported from another implementation.
|
|
23
|
+
*/
|
|
24
|
+
import { HmacSha256, HmacSha384, hmacSha256, hmacSha384 } from "nish/crypto/hmac"
|
|
25
|
+
import { SHA256_SIZE } from "nish/crypto/sha256"
|
|
26
|
+
import { SHA384_SIZE } from "nish/crypto/sha512"
|
|
27
|
+
|
|
28
|
+
/** The largest block counter, and so the most blocks `expand` can make (RFC 5869 §2.3). */
|
|
29
|
+
const HKDF_MAX_BLOCKS: i32 = 255
|
|
30
|
+
|
|
31
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
32
|
+
const HKDF_FROM: i32 = 0
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The salt `extract` keys HMAC with: `salt` itself, or HashLen zero bytes when
|
|
36
|
+
* it is empty (RFC 5869 §2.2, "if not provided"). HMAC zero-pads a short key to
|
|
37
|
+
* a block anyway, so the two give one PRK; the zeros are written out so the
|
|
38
|
+
* code says what the RFC says.
|
|
39
|
+
*/
|
|
40
|
+
const hkdfSalt = (salt: u8[], hashLen: i32): u8[] =>
|
|
41
|
+
toI32(salt.length) === 0 ? new Array<u8>(hashLen) : salt
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Copies `block[0 .. n)` to `out[at .. at + n)`, where `n` is as much of the
|
|
45
|
+
* block as `out` still has room for. Answers the new fill of `out`.
|
|
46
|
+
*/
|
|
47
|
+
const hkdfAppend = (out: u8[], at: i32, block: u8[]): i32 => {
|
|
48
|
+
const outLength: i32 = toI32(out.length)
|
|
49
|
+
const blockLength: i32 = toI32(block.length)
|
|
50
|
+
let k: i32 = 0
|
|
51
|
+
while (k < blockLength && at + k < outLength) {
|
|
52
|
+
out[at + k] = block[k]
|
|
53
|
+
k += 1
|
|
54
|
+
}
|
|
55
|
+
return at + k
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** HKDF-Extract with HMAC-SHA-256 (RFC 5869 §2.2): the 32-byte PRK. */
|
|
59
|
+
export const hkdfExtractSha256 = (salt: u8[], ikm: u8[]): u8[] => hmacSha256(hkdfSalt(salt, SHA256_SIZE), ikm)
|
|
60
|
+
|
|
61
|
+
/** HKDF-Extract with HMAC-SHA-384 (RFC 5869 §2.2): the 48-byte PRK. */
|
|
62
|
+
export const hkdfExtractSha384 = (salt: u8[], ikm: u8[]): u8[] => hmacSha384(hkdfSalt(salt, SHA384_SIZE), ikm)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* HKDF-Expand with HMAC-SHA-256 (RFC 5869 §2.3): `length` bytes of output
|
|
66
|
+
* keying material from `prk` and `info`, or `null` when `length` is negative
|
|
67
|
+
* or past 255 × 32.
|
|
68
|
+
*/
|
|
69
|
+
export const hkdfExpandSha256 = (prk: u8[], info: u8[], length: i32): u8[] | null => {
|
|
70
|
+
if (length < 0 || length > HKDF_MAX_BLOCKS * SHA256_SIZE) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
const out: u8[] = new Array<u8>(length)
|
|
74
|
+
const counter: u8[] = new Array<u8>(1)
|
|
75
|
+
const counterLength: i32 = 1
|
|
76
|
+
let previous: u8[] = []
|
|
77
|
+
let at: i32 = 0
|
|
78
|
+
let i: i32 = 1
|
|
79
|
+
while (at < length) {
|
|
80
|
+
const mac = new HmacSha256(prk)
|
|
81
|
+
mac.update(previous, HKDF_FROM, toI32(previous.length))
|
|
82
|
+
mac.update(info, HKDF_FROM, toI32(info.length))
|
|
83
|
+
counter[0] = toU8(i)
|
|
84
|
+
mac.update(counter, HKDF_FROM, counterLength)
|
|
85
|
+
previous = mac.digest()
|
|
86
|
+
at = hkdfAppend(out, at, previous)
|
|
87
|
+
i += 1
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* HKDF-Expand with HMAC-SHA-384 (RFC 5869 §2.3): `length` bytes of output
|
|
94
|
+
* keying material from `prk` and `info`, or `null` when `length` is negative
|
|
95
|
+
* or past 255 × 48.
|
|
96
|
+
*/
|
|
97
|
+
export const hkdfExpandSha384 = (prk: u8[], info: u8[], length: i32): u8[] | null => {
|
|
98
|
+
if (length < 0 || length > HKDF_MAX_BLOCKS * SHA384_SIZE) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
const out: u8[] = new Array<u8>(length)
|
|
102
|
+
const counter: u8[] = new Array<u8>(1)
|
|
103
|
+
const counterLength: i32 = 1
|
|
104
|
+
let previous: u8[] = []
|
|
105
|
+
let at: i32 = 0
|
|
106
|
+
let i: i32 = 1
|
|
107
|
+
while (at < length) {
|
|
108
|
+
const mac = new HmacSha384(prk)
|
|
109
|
+
mac.update(previous, HKDF_FROM, toI32(previous.length))
|
|
110
|
+
mac.update(info, HKDF_FROM, toI32(info.length))
|
|
111
|
+
counter[0] = toU8(i)
|
|
112
|
+
mac.update(counter, HKDF_FROM, counterLength)
|
|
113
|
+
previous = mac.digest()
|
|
114
|
+
at = hkdfAppend(out, at, previous)
|
|
115
|
+
i += 1
|
|
116
|
+
}
|
|
117
|
+
return out
|
|
118
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/hmac` — HMAC as RFC 2104 defines it, over SHA-256 and SHA-384.
|
|
3
|
+
*
|
|
4
|
+
* H(K XOR opad, H(K XOR ipad, text))
|
|
5
|
+
*
|
|
6
|
+
* `K` is the key zero-padded to the hash's block size, or first hashed when it
|
|
7
|
+
* is longer than a block (RFC 2104 §2); `ipad` is the byte 0x36 and `opad` the
|
|
8
|
+
* byte 0x5c, repeated a block long. The key is absorbed into both hashers in
|
|
9
|
+
* the constructor, so a `HmacSha256` is a keyed inner hash waiting for the
|
|
10
|
+
* message and a keyed outer hash waiting for the inner digest.
|
|
11
|
+
*
|
|
12
|
+
* import { HmacSha256, hmacSha256, hmacSha256Verify } from "nish/crypto/hmac";
|
|
13
|
+
*
|
|
14
|
+
* const tag: u8[] = hmacSha256(key, message);
|
|
15
|
+
* const ok: boolean = hmacSha256Verify(key, message, received);
|
|
16
|
+
*
|
|
17
|
+
* const mac = new HmacSha256(key);
|
|
18
|
+
* mac.update(record, off, len);
|
|
19
|
+
* const tag2: u8[] = mac.digest();
|
|
20
|
+
*
|
|
21
|
+
* Like the hashers underneath, a `digest` ends the computation: a later
|
|
22
|
+
* `update` or a second `digest` panics in the hasher it reaches, and so does
|
|
23
|
+
* a window outside its buffer. The only branch on the key is on its length,
|
|
24
|
+
* which HMAC does not keep secret; a received tag is compared with
|
|
25
|
+
* `timingSafeEqual`, never `===`.
|
|
26
|
+
*
|
|
27
|
+
* Written from RFC 2104, not ported from another implementation.
|
|
28
|
+
*/
|
|
29
|
+
import { SHA256_BLOCK, SHA256_SIZE, Sha256, sha256 } from "nish/crypto/sha256"
|
|
30
|
+
import { SHA384_SIZE, SHA512_BLOCK, Sha384, sha384 } from "nish/crypto/sha512"
|
|
31
|
+
import { timingSafeEqual } from "nish/crypto/ct"
|
|
32
|
+
|
|
33
|
+
/** RFC 2104 §2's `ipad`, the byte XORed into the key for the inner hash. */
|
|
34
|
+
const HMAC_IPAD: i32 = 0x36
|
|
35
|
+
|
|
36
|
+
/** RFC 2104 §2's `opad`, the byte XORed into the key for the outer hash. */
|
|
37
|
+
const HMAC_OPAD: i32 = 0x5c
|
|
38
|
+
|
|
39
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
40
|
+
const HMAC_FROM: i32 = 0
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `key XOR pad` over a whole block of `blockSize` bytes, with `key` already no
|
|
44
|
+
* longer than a block: the key's bytes XOR `pad`, then `pad` alone where the
|
|
45
|
+
* zero padding of RFC 2104 §2 step (1) would be.
|
|
46
|
+
*/
|
|
47
|
+
const hmacPadBlock = (key: u8[], blockSize: i32, pad: i32): u8[] => {
|
|
48
|
+
const padByte: u8 = toU8(pad)
|
|
49
|
+
const out: u8[] = new Array<u8>(blockSize)
|
|
50
|
+
const outLength: i32 = toI32(out.length)
|
|
51
|
+
const keyLength: i32 = toI32(key.length)
|
|
52
|
+
// Bounded by both lengths, so both indices are proved in range.
|
|
53
|
+
let i: i32 = 0
|
|
54
|
+
while (i < keyLength && i < outLength) {
|
|
55
|
+
out[i] = key[i] ^ padByte
|
|
56
|
+
i += 1
|
|
57
|
+
}
|
|
58
|
+
while (i < outLength) {
|
|
59
|
+
out[i] = padByte
|
|
60
|
+
i += 1
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* HMAC-SHA-256 in progress (RFC 2104 with RFC 4231's SHA-256): feed the
|
|
67
|
+
* message with `update`, as many windows as it takes, and take the 32-byte
|
|
68
|
+
* tag with `digest`.
|
|
69
|
+
*/
|
|
70
|
+
export class HmacSha256 {
|
|
71
|
+
/** H(K XOR ipad, …), fed the key block in the constructor and the message after. */
|
|
72
|
+
inner: Sha256
|
|
73
|
+
/** H(K XOR opad, …), fed the key block in the constructor and the inner digest last. */
|
|
74
|
+
outer: Sha256
|
|
75
|
+
|
|
76
|
+
/** Keys the computation. A key longer than 64 bytes is replaced by its SHA-256 (RFC 2104 §2). */
|
|
77
|
+
constructor(key: u8[]) {
|
|
78
|
+
const k: u8[] = toI32(key.length) > SHA256_BLOCK ? sha256(key) : key
|
|
79
|
+
this.inner = new Sha256()
|
|
80
|
+
this.inner.update(hmacPadBlock(k, SHA256_BLOCK, HMAC_IPAD), HMAC_FROM, SHA256_BLOCK)
|
|
81
|
+
this.outer = new Sha256()
|
|
82
|
+
this.outer.update(hmacPadBlock(k, SHA256_BLOCK, HMAC_OPAD), HMAC_FROM, SHA256_BLOCK)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Absorbs `data[off .. off + len)`; a window outside `data` panics in `Sha256.update`. */
|
|
86
|
+
update(data: u8[], off: i32, len: i32): void {
|
|
87
|
+
this.inner.update(data, off, len)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The 32-byte tag, in a fresh array. Ends the computation. */
|
|
91
|
+
digest(): u8[] {
|
|
92
|
+
const innerDigest: u8[] = this.inner.digest()
|
|
93
|
+
this.outer.update(innerDigest, HMAC_FROM, SHA256_SIZE)
|
|
94
|
+
return this.outer.digest()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* HMAC-SHA-384 in progress (RFC 2104 with RFC 4231's SHA-384), with
|
|
100
|
+
* `HmacSha256`'s methods. The block is SHA-384's 128 bytes and the tag 48.
|
|
101
|
+
*/
|
|
102
|
+
export class HmacSha384 {
|
|
103
|
+
/** H(K XOR ipad, …), fed the key block in the constructor and the message after. */
|
|
104
|
+
inner: Sha384
|
|
105
|
+
/** H(K XOR opad, …), fed the key block in the constructor and the inner digest last. */
|
|
106
|
+
outer: Sha384
|
|
107
|
+
|
|
108
|
+
/** Keys the computation. A key longer than 128 bytes is replaced by its SHA-384 (RFC 2104 §2). */
|
|
109
|
+
constructor(key: u8[]) {
|
|
110
|
+
const k: u8[] = toI32(key.length) > SHA512_BLOCK ? sha384(key) : key
|
|
111
|
+
this.inner = new Sha384()
|
|
112
|
+
this.inner.update(hmacPadBlock(k, SHA512_BLOCK, HMAC_IPAD), HMAC_FROM, SHA512_BLOCK)
|
|
113
|
+
this.outer = new Sha384()
|
|
114
|
+
this.outer.update(hmacPadBlock(k, SHA512_BLOCK, HMAC_OPAD), HMAC_FROM, SHA512_BLOCK)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Absorbs `data[off .. off + len)`; a window outside `data` panics in `Sha384.update`. */
|
|
118
|
+
update(data: u8[], off: i32, len: i32): void {
|
|
119
|
+
this.inner.update(data, off, len)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The 48-byte tag, in a fresh array. Ends the computation. */
|
|
123
|
+
digest(): u8[] {
|
|
124
|
+
const innerDigest: u8[] = this.inner.digest()
|
|
125
|
+
this.outer.update(innerDigest, HMAC_FROM, SHA384_SIZE)
|
|
126
|
+
return this.outer.digest()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The HMAC-SHA-256 of all of `data` under `key`, as a fresh 32-byte array. */
|
|
131
|
+
export const hmacSha256 = (key: u8[], data: u8[]): u8[] => {
|
|
132
|
+
const mac = new HmacSha256(key)
|
|
133
|
+
mac.update(data, HMAC_FROM, toI32(data.length))
|
|
134
|
+
return mac.digest()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The HMAC-SHA-384 of all of `data` under `key`, as a fresh 48-byte array. */
|
|
138
|
+
export const hmacSha384 = (key: u8[], data: u8[]): u8[] => {
|
|
139
|
+
const mac = new HmacSha384(key)
|
|
140
|
+
mac.update(data, HMAC_FROM, toI32(data.length))
|
|
141
|
+
return mac.digest()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Whether `tag` is the HMAC-SHA-256 of `data` under `key`, compared by
|
|
146
|
+
* `timingSafeEqual`. A tag that is not 32 bytes answers `false` without a
|
|
147
|
+
* byte compare, since its length is public; one that is is compared in full,
|
|
148
|
+
* so the time taken does not say how many leading bytes were right.
|
|
149
|
+
*/
|
|
150
|
+
export const hmacSha256Verify = (key: u8[], data: u8[], tag: u8[]): boolean =>
|
|
151
|
+
timingSafeEqual(hmacSha256(key, data), tag)
|
|
152
|
+
|
|
153
|
+
/** Whether `tag` is the HMAC-SHA-384 of `data` under `key`, compared as `hmacSha256Verify` does. */
|
|
154
|
+
export const hmacSha384Verify = (key: u8[], data: u8[], tag: u8[]): boolean =>
|
|
155
|
+
timingSafeEqual(hmacSha384(key, data), tag)
|