@amritk/nish 0.13.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.
@@ -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)