@amritk/nish-x86_64-linux 0.15.0 → 0.17.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.
Files changed (57) hide show
  1. package/INSTALL.md +58 -40
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +67 -10
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +72 -26
  8. package/runtime/runtime-net.c +72 -19
  9. package/runtime/runtime-os.c +127 -45
  10. package/runtime/runtime-parallel.c +5 -1
  11. package/runtime/runtime-wasm.c +26 -2
  12. package/runtime/runtime.c +86 -14
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +1 -3
  15. package/std/README.md +192 -36
  16. package/std/crypto/aes.ts +104 -21
  17. package/std/crypto/base64url.ts +43 -17
  18. package/std/crypto/chacha20poly1305.ts +52 -31
  19. package/std/crypto/ct.ts +14 -3
  20. package/std/crypto/hkdf.ts +385 -8
  21. package/std/crypto/hmac.ts +325 -9
  22. package/std/crypto/p256.ts +153 -27
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +15 -3
  25. package/std/crypto/sha512.ts +16 -2
  26. package/std/crypto/x25519.ts +90 -37
  27. package/std/crypto/x509.ts +265 -65
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +905 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +392 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +653 -0
  45. package/std/net/quic-packet.ts +1474 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1603 -0
  48. package/std/net/quic.ts +3214 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +410 -0
  54. package/std/net/tls.ts +1368 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
@@ -24,6 +24,12 @@
24
24
  * which HMAC does not keep secret; a received tag is compared with
25
25
  * `timingSafeEqual`, never `===`.
26
26
  *
27
+ * `HmacSha256Scratch` and `HmacSha384Scratch` are the same computation in
28
+ * hashers the caller allocates once: `begin` keys them again in place and
29
+ * `finishInto` writes the tag into the caller's array, so a loop of them
30
+ * stores nothing of its own and an arena scope around it releases all it
31
+ * allocated (see "Caller-owned scratch" below).
32
+ *
27
33
  * Written from RFC 2104, not ported from another implementation.
28
34
  */
29
35
  import { SHA256_BLOCK, SHA256_SIZE, Sha256, sha256 } from "nish/crypto/sha256"
@@ -40,25 +46,33 @@ const HMAC_OPAD: i32 = 0x5c
40
46
  const HMAC_FROM: i32 = 0
41
47
 
42
48
  /**
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.
49
+ * `key[off .. off + len) XOR pad` over a whole block, `out`, with the key
50
+ * already no longer than the block: the key's bytes XOR `pad`, then `pad`
51
+ * alone where the zero padding of RFC 2104 §2 step (1) would be.
46
52
  */
47
- const hmacPadBlock = (key: u8[], blockSize: i32, pad: i32): u8[] => {
53
+ const hmacPadInto = (out: u8[], key: u8[], off: i32, len: i32, pad: i32): void => {
48
54
  const padByte: u8 = toU8(pad)
49
- const out: u8[] = new Array<u8>(blockSize)
50
55
  const outLength: i32 = toI32(out.length)
51
56
  const keyLength: i32 = toI32(key.length)
52
57
  // Bounded by both lengths, so both indices are proved in range.
53
58
  let i: i32 = 0
54
- while (i < keyLength && i < outLength) {
55
- out[i] = key[i] ^ padByte
59
+ while (i < len && i < outLength && off + i >= 0 && off + i < keyLength) {
60
+ out[i] = key[off + i] ^ padByte
56
61
  i += 1
57
62
  }
58
63
  while (i < outLength) {
59
64
  out[i] = padByte
60
65
  i += 1
61
66
  }
67
+ }
68
+
69
+ /**
70
+ * `key XOR pad` over a whole block of `blockSize` bytes, in a fresh array, with
71
+ * `key` already no longer than a block (`hmacPadInto`).
72
+ */
73
+ const hmacPadBlock = (key: u8[], blockSize: i32, pad: i32): u8[] => {
74
+ const out: u8[] = new Array<u8>(blockSize)
75
+ hmacPadInto(out, key, HMAC_FROM, toI32(key.length), pad)
62
76
  return out
63
77
  }
64
78
 
@@ -127,15 +141,31 @@ export class HmacSha384 {
127
141
  }
128
142
  }
129
143
 
130
- /** The HMAC-SHA-256 of all of `data` under `key`, as a fresh 32-byte array. */
144
+ /**
145
+ * The HMAC-SHA-256 of all of `data` under `key`, as a fresh 32-byte array.
146
+ *
147
+ * `data` longer than 2^31 - 1 bytes panics, as `sha256` does and for its
148
+ * reason: under `--number-mode f64` the tag would be of a prefix, and a tag
149
+ * that verifies every message sharing that prefix is a forgery. A key that
150
+ * long reaches `sha256` in the constructor and panics there.
151
+ */
131
152
  export const hmacSha256 = (key: u8[], data: u8[]): u8[] => {
153
+ if (data.length > 2147483647) {
154
+ panic("hmacSha256: a message longer than 2^31 - 1 bytes")
155
+ }
132
156
  const mac = new HmacSha256(key)
133
157
  mac.update(data, HMAC_FROM, toI32(data.length))
134
158
  return mac.digest()
135
159
  }
136
160
 
137
- /** The HMAC-SHA-384 of all of `data` under `key`, as a fresh 48-byte array. */
161
+ /**
162
+ * The HMAC-SHA-384 of all of `data` under `key`, as a fresh 48-byte array.
163
+ * `data` longer than 2^31 - 1 bytes panics, as in `hmacSha256`.
164
+ */
138
165
  export const hmacSha384 = (key: u8[], data: u8[]): u8[] => {
166
+ if (data.length > 2147483647) {
167
+ panic("hmacSha384: a message longer than 2^31 - 1 bytes")
168
+ }
139
169
  const mac = new HmacSha384(key)
140
170
  mac.update(data, HMAC_FROM, toI32(data.length))
141
171
  return mac.digest()
@@ -153,3 +183,289 @@ export const hmacSha256Verify = (key: u8[], data: u8[], tag: u8[]): boolean =>
153
183
  /** Whether `tag` is the HMAC-SHA-384 of `data` under `key`, compared as `hmacSha256Verify` does. */
154
184
  export const hmacSha384Verify = (key: u8[], data: u8[], tag: u8[]): boolean =>
155
185
  timingSafeEqual(hmacSha384(key, data), tag)
186
+
187
+ // ---- Caller-owned scratch --------------------------------------------------
188
+ //
189
+ // `HmacSha256` makes its two hashers in its constructor, and the one-shot
190
+ // functions above make an `HmacSha256` and a fresh tag on every call, so all
191
+ // of them store allocations of their own: memory no automatic arena scope and
192
+ // no `using a = arena()` block may release around them (docs/LANGUAGE.md,
193
+ // "`using a = arena()`", NL2424). The scratch below is the same computation in
194
+ // hashers the caller allocates once and keeps: `begin` keys it again by
195
+ // rewriting their state in place, and `finishInto` writes the tag into the
196
+ // caller's array. What it allocates on the way — the two digests a hasher
197
+ // answers — dies with the call that made it, so a caller may run it a
198
+ // thousand times inside one arena scope and leave the arena where it was.
199
+
200
+ /**
201
+ * Makes `to` the hasher `from` is — the same point in the same message, as
202
+ * `Sha256.copy` makes it — in a hasher the caller already holds, so that
203
+ * nothing is allocated. Copying from a hasher nothing has been fed starts `to`
204
+ * again. It is here rather than beside `Sha256` because the scratch below and
205
+ * the TLS transcript are what need it, and HMAC is the module both sit on.
206
+ */
207
+ export const hmacCopySha256 = (from: Sha256, to: Sha256): void => {
208
+ const fromState: u32[] = from.state
209
+ const toState: u32[] = to.state
210
+ for (let i: i32 = 0; i < toI32(fromState.length) && i < toI32(toState.length); i += 1) {
211
+ toState[i] = fromState[i]
212
+ }
213
+ const fromBlock: u8[] = from.block
214
+ const toBlock: u8[] = to.block
215
+ for (let i: i32 = 0; i < from.fill && i < toI32(fromBlock.length) && i < toI32(toBlock.length); i += 1) {
216
+ toBlock[i] = fromBlock[i]
217
+ }
218
+ to.fill = from.fill
219
+ to.total = from.total
220
+ to.finished = from.finished
221
+ }
222
+
223
+ /** `hmacCopySha256` for SHA-384: `to` made the hasher `from` is, in place. */
224
+ export const hmacCopySha384 = (from: Sha384, to: Sha384): void => {
225
+ const fromState: u64[] = from.engine.state
226
+ const toState: u64[] = to.engine.state
227
+ for (let i: i32 = 0; i < toI32(fromState.length) && i < toI32(toState.length); i += 1) {
228
+ toState[i] = fromState[i]
229
+ }
230
+ const fromBlock: u8[] = from.engine.block
231
+ const toBlock: u8[] = to.engine.block
232
+ for (
233
+ let i: i32 = 0;
234
+ i < from.engine.filled && i < toI32(fromBlock.length) && i < toI32(toBlock.length);
235
+ i += 1
236
+ ) {
237
+ toBlock[i] = fromBlock[i]
238
+ }
239
+ to.engine.filled = from.engine.filled
240
+ to.engine.count = from.engine.count
241
+ to.engine.finished = from.engine.finished
242
+ }
243
+
244
+ /**
245
+ * Zeroes everything `h` holds of the message it was fed: the pending block
246
+ * with `secureZero`, and the hash value and message schedule, which are words
247
+ * `secureZero` does not take, with ordinary stores. Those stand because the
248
+ * hasher stays reachable for the next `begin`. The hasher is left finished, so
249
+ * that feeding it before it is started again panics.
250
+ */
251
+ const hmacWipeSha256 = (h: Sha256): void => {
252
+ secureZero(h.block)
253
+ const state: u32[] = h.state
254
+ for (let i: i32 = 0; i < toI32(state.length); i += 1) {
255
+ state[i] = toU32(0)
256
+ }
257
+ const schedule: u32[] = h.schedule
258
+ for (let i: i32 = 0; i < toI32(schedule.length); i += 1) {
259
+ schedule[i] = toU32(0)
260
+ }
261
+ h.fill = 0
262
+ h.total = 0
263
+ h.finished = true
264
+ }
265
+
266
+ /** `hmacWipeSha256` for SHA-384. */
267
+ const hmacWipeSha384 = (h: Sha384): void => {
268
+ secureZero(h.engine.block)
269
+ const state: u64[] = h.engine.state
270
+ for (let i: i32 = 0; i < toI32(state.length); i += 1) {
271
+ state[i] = toU64(0)
272
+ }
273
+ const schedule: u64[] = h.engine.schedule
274
+ for (let i: i32 = 0; i < toI32(schedule.length); i += 1) {
275
+ schedule[i] = toU64(0)
276
+ }
277
+ h.engine.filled = 0
278
+ h.engine.count = toU64(0)
279
+ h.engine.finished = true
280
+ }
281
+
282
+ /** Whether `[off, off + len)` is a window inside an array of `length` elements. */
283
+ const hmacWindowFits = (length: i32, off: i32, len: i32): boolean =>
284
+ off >= 0 && len >= 0 && off <= length - len
285
+
286
+ /**
287
+ * HMAC-SHA-256 over hashers the caller owns: allocate one, then for each tag
288
+ * `begin` with the key, `update` with the message, and `finishInto` the
289
+ * caller's array. The tag is the one `hmacSha256` answers.
290
+ *
291
+ * const mac = new HmacSha256Scratch(); // once, outside the loop
292
+ * mac.begin(key, 0, key.length);
293
+ * mac.update(message, 0, message.length);
294
+ * mac.finishInto(tag, 0); // 32 bytes at tag[0]
295
+ *
296
+ * The key block is wiped with `secureZero` once both hashers have absorbed
297
+ * it, and so is every digest on the way; `wipe` zeroes the hashers too, for a
298
+ * caller done with the key. A key or tag window outside its array panics, as
299
+ * a message window does in `Sha256.update`.
300
+ */
301
+ export class HmacSha256Scratch {
302
+ /** H(K XOR ipad, …): keyed by `begin`, fed the message by `update`. */
303
+ inner: Sha256
304
+ /** H(K XOR opad, …): keyed by `begin`, fed the inner digest by `finishInto`. */
305
+ outer: Sha256
306
+ /** A hasher nothing is ever fed: the initial hash value `begin` copies from. */
307
+ fresh: Sha256
308
+ /** One key block XOR a pad, wiped as soon as it is absorbed. */
309
+ pad: u8[]
310
+ /** The SHA-256 of a key longer than a block, which RFC 2104 §2 keys with instead; wiped once used. */
311
+ keyHash: u8[]
312
+
313
+ constructor() {
314
+ this.inner = new Sha256()
315
+ this.outer = new Sha256()
316
+ this.fresh = new Sha256()
317
+ this.pad = new Array<u8>(SHA256_BLOCK)
318
+ this.keyHash = new Array<u8>(SHA256_SIZE)
319
+ }
320
+
321
+ /** Keys a new computation with `key[off .. off + len)`, a key longer than 64 bytes by its SHA-256 (RFC 2104 §2). */
322
+ begin(key: u8[], off: i32, len: i32): void {
323
+ if (!hmacWindowFits(toI32(key.length), off, len)) {
324
+ panic("HmacSha256Scratch: the key window is outside its array")
325
+ }
326
+ if (len <= SHA256_BLOCK) {
327
+ this.keyBlocks(key, off, len)
328
+ return
329
+ }
330
+ hmacCopySha256(this.fresh, this.inner)
331
+ this.inner.update(key, off, len)
332
+ const digest: u8[] = this.inner.digest()
333
+ for (let i: i32 = 0; i < toI32(digest.length) && i < toI32(this.keyHash.length); i += 1) {
334
+ this.keyHash[i] = digest[i]
335
+ }
336
+ secureZero(digest)
337
+ this.keyBlocks(this.keyHash, HMAC_FROM, SHA256_SIZE)
338
+ secureZero(this.keyHash)
339
+ }
340
+
341
+ /**
342
+ * Absorbs `key[off .. off + len)`, no longer than a block, XOR ipad into the
343
+ * inner hasher and XOR opad into the outer one, each started again, and
344
+ * wipes the block. A method of its own so that `begin` never keeps the
345
+ * caller's key in a local it reassigns, which would make the key look
346
+ * stored to the arena analysis.
347
+ */
348
+ keyBlocks(key: u8[], off: i32, len: i32): void {
349
+ hmacPadInto(this.pad, key, off, len, HMAC_IPAD)
350
+ hmacCopySha256(this.fresh, this.inner)
351
+ this.inner.update(this.pad, HMAC_FROM, SHA256_BLOCK)
352
+ hmacPadInto(this.pad, key, off, len, HMAC_OPAD)
353
+ hmacCopySha256(this.fresh, this.outer)
354
+ this.outer.update(this.pad, HMAC_FROM, SHA256_BLOCK)
355
+ secureZero(this.pad)
356
+ }
357
+
358
+ /** Absorbs `data[off .. off + len)`; a window outside `data` panics in `Sha256.update`. */
359
+ update(data: u8[], off: i32, len: i32): void {
360
+ this.inner.update(data, off, len)
361
+ }
362
+
363
+ /** Writes the 32-byte tag at `out[at]` and ends the computation; a window outside `out` panics. */
364
+ finishInto(out: u8[], at: i32): void {
365
+ if (!hmacWindowFits(toI32(out.length), at, SHA256_SIZE)) {
366
+ panic("HmacSha256Scratch: the tag window is outside its array")
367
+ }
368
+ const innerDigest: u8[] = this.inner.digest()
369
+ this.outer.update(innerDigest, HMAC_FROM, SHA256_SIZE)
370
+ secureZero(innerDigest)
371
+ const tag: u8[] = this.outer.digest()
372
+ for (let i: i32 = 0; i < SHA256_SIZE && i < toI32(tag.length) && at + i < toI32(out.length); i += 1) {
373
+ out[at + i] = tag[i]
374
+ }
375
+ secureZero(tag)
376
+ }
377
+
378
+ /** Zeroes both keyed hashers, for a caller done with the key; `begin` starts again. */
379
+ wipe(): void {
380
+ hmacWipeSha256(this.inner)
381
+ hmacWipeSha256(this.outer)
382
+ }
383
+ }
384
+
385
+ /**
386
+ * HMAC-SHA-384 over hashers the caller owns, with `HmacSha256Scratch`'s
387
+ * methods. The block is 128 bytes and the tag 48.
388
+ */
389
+ export class HmacSha384Scratch {
390
+ /** H(K XOR ipad, …): keyed by `begin`, fed the message by `update`. */
391
+ inner: Sha384
392
+ /** H(K XOR opad, …): keyed by `begin`, fed the inner digest by `finishInto`. */
393
+ outer: Sha384
394
+ /** A hasher nothing is ever fed: the initial hash value `begin` copies from. */
395
+ fresh: Sha384
396
+ /** One key block XOR a pad, wiped as soon as it is absorbed. */
397
+ pad: u8[]
398
+ /** The SHA-384 of a key longer than a block; wiped once used. */
399
+ keyHash: u8[]
400
+
401
+ constructor() {
402
+ this.inner = new Sha384()
403
+ this.outer = new Sha384()
404
+ this.fresh = new Sha384()
405
+ this.pad = new Array<u8>(SHA512_BLOCK)
406
+ this.keyHash = new Array<u8>(SHA384_SIZE)
407
+ }
408
+
409
+ /** Keys a new computation with `key[off .. off + len)`, a key longer than 128 bytes by its SHA-384 (RFC 2104 §2). */
410
+ begin(key: u8[], off: i32, len: i32): void {
411
+ if (!hmacWindowFits(toI32(key.length), off, len)) {
412
+ panic("HmacSha384Scratch: the key window is outside its array")
413
+ }
414
+ if (len <= SHA512_BLOCK) {
415
+ this.keyBlocks(key, off, len)
416
+ return
417
+ }
418
+ hmacCopySha384(this.fresh, this.inner)
419
+ this.inner.update(key, off, len)
420
+ const digest: u8[] = this.inner.digest()
421
+ for (let i: i32 = 0; i < toI32(digest.length) && i < toI32(this.keyHash.length); i += 1) {
422
+ this.keyHash[i] = digest[i]
423
+ }
424
+ secureZero(digest)
425
+ this.keyBlocks(this.keyHash, HMAC_FROM, SHA384_SIZE)
426
+ secureZero(this.keyHash)
427
+ }
428
+
429
+ /**
430
+ * Absorbs `key[off .. off + len)`, no longer than a block, XOR ipad into the
431
+ * inner hasher and XOR opad into the outer one, each started again, and
432
+ * wipes the block. A method of its own so that `begin` never keeps the
433
+ * caller's key in a local it reassigns, which would make the key look
434
+ * stored to the arena analysis.
435
+ */
436
+ keyBlocks(key: u8[], off: i32, len: i32): void {
437
+ hmacPadInto(this.pad, key, off, len, HMAC_IPAD)
438
+ hmacCopySha384(this.fresh, this.inner)
439
+ this.inner.update(this.pad, HMAC_FROM, SHA512_BLOCK)
440
+ hmacPadInto(this.pad, key, off, len, HMAC_OPAD)
441
+ hmacCopySha384(this.fresh, this.outer)
442
+ this.outer.update(this.pad, HMAC_FROM, SHA512_BLOCK)
443
+ secureZero(this.pad)
444
+ }
445
+
446
+ /** Absorbs `data[off .. off + len)`; a window outside `data` panics in the hasher. */
447
+ update(data: u8[], off: i32, len: i32): void {
448
+ this.inner.update(data, off, len)
449
+ }
450
+
451
+ /** Writes the 48-byte tag at `out[at]` and ends the computation; a window outside `out` panics. */
452
+ finishInto(out: u8[], at: i32): void {
453
+ if (!hmacWindowFits(toI32(out.length), at, SHA384_SIZE)) {
454
+ panic("HmacSha384Scratch: the tag window is outside its array")
455
+ }
456
+ const innerDigest: u8[] = this.inner.digest()
457
+ this.outer.update(innerDigest, HMAC_FROM, SHA384_SIZE)
458
+ secureZero(innerDigest)
459
+ const tag: u8[] = this.outer.digest()
460
+ for (let i: i32 = 0; i < SHA384_SIZE && i < toI32(tag.length) && at + i < toI32(out.length); i += 1) {
461
+ out[at + i] = tag[i]
462
+ }
463
+ secureZero(tag)
464
+ }
465
+
466
+ /** Zeroes both keyed hashers, for a caller done with the key; `begin` starts again. */
467
+ wipe(): void {
468
+ hmacWipeSha384(this.inner)
469
+ hmacWipeSha384(this.outer)
470
+ }
471
+ }
@@ -4,8 +4,10 @@
4
4
  *
5
5
  * import { p256PublicKey, p256SignSha256, p256VerifySha256 } from "nish/crypto/p256";
6
6
  *
7
+ * const priv: Secret<u8[]> = secret(readKey()); // nish:secret
7
8
  * const pub: u8[] | null = p256PublicKey(priv); // 65 bytes, 0x04 || X || Y
8
9
  * const sig: u8[] | null = p256SignSha256(priv, message); // 64 bytes, r || s
10
+ * wipe(priv);
9
11
  * const ok: boolean = p256VerifySha256(pub, message, sig);
10
12
  *
11
13
  * A private key is 32 big-endian bytes holding a scalar in [1, n); a public
@@ -73,12 +75,21 @@
73
75
  *
74
76
  * Verification multiplies only public values, and uses the same routine.
75
77
  *
78
+ * **The private key is a `Secret`** (`nish:secret`, docs/LANGUAGE.md
79
+ * "Secrets"). `p256PublicKey` and `p256Sign` read it only inside `expose` /
80
+ * `exposeWith`, whose function the checker holds to reaching no I/O and no C,
81
+ * and every intermediate the key or the nonce reaches is wiped before the
82
+ * signature is returned (ECC-2, docs/security/crypto-ecc.md). That is why RFC
83
+ * 6979's HMAC is written out over `Sha256` here (`p256Mac`) rather than taken
84
+ * from `nish/crypto/hmac`: its key blocks and hasher state have to be this
85
+ * module's to wipe.
86
+ *
76
87
  * Private names carry the `p256` prefix because a `std/` module's private
77
88
  * functions share the importing program's flat symbol namespace
78
89
  * (`docs/wp26-stdlib.md` §3e).
79
90
  */
80
- import { HmacSha256, hmacSha256 } from "nish/crypto/hmac"
81
- import { sha256 } from "nish/crypto/sha256"
91
+ import { Secret, expose, exposeWith, wipe } from "nish:secret"
92
+ import { Sha256, sha256 } from "nish/crypto/sha256"
82
93
  import { timingSafeEqual } from "nish/crypto/ct"
83
94
 
84
95
  /** The length in bytes of a private key: one scalar, big-endian. */
@@ -3784,6 +3795,23 @@ class P256PointScratch {
3784
3795
  }
3785
3796
  }
3786
3797
 
3798
+ /** Zero `p`'s three coordinates, with the store `nish:secret` keeps from the optimiser (ECC-2). */
3799
+ const p256WipePoint = (p: P256ProjectivePoint): void => {
3800
+ wipe(p.x)
3801
+ wipe(p.y)
3802
+ wipe(p.z)
3803
+ }
3804
+
3805
+ /** Zero every temporary a point operation left in `s`; `b` is the curve's public constant. */
3806
+ const p256WipeScratch = (s: P256PointScratch): void => {
3807
+ wipe(s.t0)
3808
+ wipe(s.t1)
3809
+ wipe(s.t2)
3810
+ wipe(s.t3)
3811
+ wipe(s.t4)
3812
+ p256WipePoint(s.sum)
3813
+ }
3814
+
3787
3815
  /** `out = p`, coordinate by coordinate. */
3788
3816
  const p256PointCopy = (out: P256ProjectivePoint, p: P256ProjectivePoint): void => {
3789
3817
  p256Copy(out.x, p.x)
@@ -3948,17 +3976,18 @@ const p256TableBuild = (table: u32[], p: P256ProjectivePoint, s: P256PointScratc
3948
3976
  * way, at an offset that is the caller's loop position, never the secret.
3949
3977
  */
3950
3978
  const p256TableMove = (out: u32[], table: u32[], at: i32, hit: u32): void => {
3979
+ const p: u32 = toU32(at)
3951
3980
  if (toI32(out.length) < 8 || at < 0 || at > toI32(table.length) - 8) {
3952
3981
  return
3953
3982
  }
3954
- out[0] = p256FiatCmovznzU32(hit, out[0], table[at])
3955
- out[1] = p256FiatCmovznzU32(hit, out[1], table[at + 1])
3956
- out[2] = p256FiatCmovznzU32(hit, out[2], table[at + 2])
3957
- out[3] = p256FiatCmovznzU32(hit, out[3], table[at + 3])
3958
- out[4] = p256FiatCmovznzU32(hit, out[4], table[at + 4])
3959
- out[5] = p256FiatCmovznzU32(hit, out[5], table[at + 5])
3960
- out[6] = p256FiatCmovznzU32(hit, out[6], table[at + 6])
3961
- out[7] = p256FiatCmovznzU32(hit, out[7], table[at + 7])
3983
+ out[0] = p256FiatCmovznzU32(hit, out[0], table[p])
3984
+ out[1] = p256FiatCmovznzU32(hit, out[1], table[p + 1])
3985
+ out[2] = p256FiatCmovznzU32(hit, out[2], table[p + 2])
3986
+ out[3] = p256FiatCmovznzU32(hit, out[3], table[p + 3])
3987
+ out[4] = p256FiatCmovznzU32(hit, out[4], table[p + 4])
3988
+ out[5] = p256FiatCmovznzU32(hit, out[5], table[p + 5])
3989
+ out[6] = p256FiatCmovznzU32(hit, out[6], table[p + 6])
3990
+ out[7] = p256FiatCmovznzU32(hit, out[7], table[p + 7])
3962
3991
  }
3963
3992
 
3964
3993
  /**
@@ -4014,6 +4043,11 @@ const p256ScalarMult = (
4014
4043
  p256PointAdd(acc, acc, entry, s)
4015
4044
  }
4016
4045
  p256PointCopy(out, acc)
4046
+ // The accumulator and the selected entry follow the scalar's digits, and a
4047
+ // signature's scalar is its nonce: both are wiped, and the table with them.
4048
+ wipe(table)
4049
+ p256WipePoint(acc)
4050
+ p256WipePoint(entry)
4017
4051
  }
4018
4052
 
4019
4053
  /** The base point G (SEC 2 §2.4.2), in the Montgomery domain with Z = 1. */
@@ -4133,6 +4167,7 @@ const p256ScalarInRange = (bytes: u8[]): boolean => {
4133
4167
  p256LimbsFromBytes(a, bytes, 0)
4134
4168
  const below: u32 = p256Below(a, p256Order())
4135
4169
  const zero: u32 = ctEq(p256FiatNonzero(a), 0) & 1
4170
+ wipe(a) // a private key's limbs, when `bytes` is one
4136
4171
  return (below & (zero ^ 1)) === 1
4137
4172
  }
4138
4173
 
@@ -4155,19 +4190,72 @@ const p256DigestScalar = (out: u32[], digest: u8[]): void => {
4155
4190
  p256ReduceOnce(out, a, p256Order())
4156
4191
  }
4157
4192
 
4193
+ /** RFC 2104's key block for SHA-256: `k` zero-extended to 64 bytes, each byte XOR `pad`. */
4194
+ const p256PadBlock = (k: u8[], pad: u8): u8[] => {
4195
+ const out: u8[] = new Array<u8>(64)
4196
+ const outLength: i32 = toI32(out.length)
4197
+ const keyLength: i32 = toI32(k.length)
4198
+ for (let i: i32 = 0; i < outLength; i++) {
4199
+ if (i < keyLength) {
4200
+ out[i] = k[i] ^ pad
4201
+ } else {
4202
+ out[i] = pad
4203
+ }
4204
+ }
4205
+ return out
4206
+ }
4207
+
4208
+ /** Zero what a SHA-256 in progress holds: its chaining value, its pending block and its schedule. */
4209
+ const p256WipeHash = (h: Sha256): void => {
4210
+ wipe(h.state)
4211
+ wipe(h.block)
4212
+ wipe(h.schedule)
4213
+ }
4214
+
4215
+ /**
4216
+ * HMAC-SHA-256 keyed by `k` over `a || b || c || d` (RFC 2104), the one MAC
4217
+ * RFC 6979 runs. It is written out over `Sha256` here rather than taken from
4218
+ * `nish/crypto/hmac` so that every buffer the key reaches — the two key
4219
+ * blocks, both hashers' state, block and schedule, and the inner digest — is
4220
+ * this function's to wipe before it returns (ECC-2). `k` is always 32 bytes,
4221
+ * under SHA-256's 64-byte block, so it is never hashed first.
4222
+ */
4223
+ const p256Mac = (k: u8[], a: u8[], b: u8[], c: u8[], d: u8[]): u8[] => {
4224
+ const ipad: u8[] = p256PadBlock(k, 0x36)
4225
+ const opad: u8[] = p256PadBlock(k, 0x5c)
4226
+ const inner = new Sha256()
4227
+ inner.update(ipad, P256_FROM, toI32(ipad.length))
4228
+ inner.update(a, P256_FROM, toI32(a.length))
4229
+ inner.update(b, P256_FROM, toI32(b.length))
4230
+ inner.update(c, P256_FROM, toI32(c.length))
4231
+ inner.update(d, P256_FROM, toI32(d.length))
4232
+ const innerDigest: u8[] = inner.digest()
4233
+ const outer = new Sha256()
4234
+ outer.update(opad, P256_FROM, toI32(opad.length))
4235
+ outer.update(innerDigest, P256_FROM, toI32(innerDigest.length))
4236
+ const tag: u8[] = outer.digest()
4237
+ wipe(ipad)
4238
+ wipe(opad)
4239
+ wipe(innerDigest)
4240
+ p256WipeHash(inner)
4241
+ p256WipeHash(outer)
4242
+ return tag
4243
+ }
4244
+
4158
4245
  /**
4159
4246
  * HMAC_K(V || sep || x || h), the key update of RFC 6979 §3.2 steps d and f
4160
4247
  * (with `sep` 0 and then 1) and, with `x` and `h` empty, step h.3.
4161
4248
  */
4162
4249
  const p256NonceKey = (k: u8[], v: u8[], sep: u8, x: u8[], h: u8[]): u8[] => {
4163
- const mac = new HmacSha256(k)
4164
4250
  const separator: u8[] = new Array<u8>(1)
4165
4251
  separator[0] = sep
4166
- mac.update(v, P256_FROM, toI32(v.length))
4167
- mac.update(separator, P256_FROM, toI32(separator.length))
4168
- mac.update(x, P256_FROM, toI32(x.length))
4169
- mac.update(h, P256_FROM, toI32(h.length))
4170
- return mac.digest()
4252
+ return p256Mac(k, v, separator, x, h)
4253
+ }
4254
+
4255
+ /** HMAC_K(V), RFC 6979 §3.2 steps e, g and h.2. */
4256
+ const p256NonceValue = (k: u8[], v: u8[]): u8[] => {
4257
+ const empty: u8[] = new Array<u8>(0)
4258
+ return p256Mac(k, v, empty, empty, empty)
4171
4259
  }
4172
4260
 
4173
4261
  /**
@@ -4192,10 +4280,20 @@ const p256SignScalar = (priv: u8[], z: u32[]): u8[] => {
4192
4280
  for (let i: i32 = 0; i < 32; i++) {
4193
4281
  v[i] = 1
4194
4282
  }
4195
- k = p256NonceKey(k, v, 0, priv, h1) // step d
4196
- v = hmacSha256(k, v) // step e
4197
- k = p256NonceKey(k, v, 1, priv, h1) // step f
4198
- v = hmacSha256(k, v) // step g
4283
+ // Each K and V is wiped as the next replaces it (ECC-2): both are derived
4284
+ // from the private key, and the last V is the nonce.
4285
+ let next: u8[] = p256NonceKey(k, v, 0, priv, h1) // step d
4286
+ wipe(k)
4287
+ k = next
4288
+ next = p256NonceValue(k, v) // step e
4289
+ wipe(v)
4290
+ v = next
4291
+ next = p256NonceKey(k, v, 1, priv, h1) // step f
4292
+ wipe(k)
4293
+ k = next
4294
+ next = p256NonceValue(k, v) // step g
4295
+ wipe(v)
4296
+ v = next
4199
4297
 
4200
4298
  const dM: u32[] = p256Limbs()
4201
4299
  p256LimbsFromBytes(dM, priv, 0)
@@ -4208,7 +4306,9 @@ const p256SignScalar = (priv: u8[], z: u32[]): u8[] => {
4208
4306
  const kM: u32[] = p256Limbs()
4209
4307
  const s: u32[] = p256Limbs()
4210
4308
  while (true) {
4211
- v = hmacSha256(k, v) // step h.2
4309
+ next = p256NonceValue(k, v) // step h.2
4310
+ wipe(v)
4311
+ v = next
4212
4312
  if (p256ScalarInRange(v)) {
4213
4313
  p256ScalarMult(point, v, g, scratch)
4214
4314
  p256AffineX(x, point)
@@ -4226,11 +4326,23 @@ const p256SignScalar = (priv: u8[], z: u32[]): u8[] => {
4226
4326
  const sig: u8[] = new Array<u8>(64)
4227
4327
  p256LimbsToBytes(sig, 0, r)
4228
4328
  p256LimbsToBytes(sig, 32, s)
4329
+ // ECC-2: the private key's limbs, the nonce and its inverse, RFC
4330
+ // 6979's K and V, the nonce point and the scratch it was built in.
4331
+ wipe(k)
4332
+ wipe(v)
4333
+ wipe(dM)
4334
+ wipe(kM)
4335
+ p256WipePoint(point)
4336
+ p256WipeScratch(scratch)
4229
4337
  return sig
4230
4338
  }
4231
4339
  }
4232
- k = p256NonceKey(k, v, 0, empty, empty) // step h.3
4233
- v = hmacSha256(k, v)
4340
+ next = p256NonceKey(k, v, 0, empty, empty) // step h.3
4341
+ wipe(k)
4342
+ k = next
4343
+ next = p256NonceValue(k, v)
4344
+ wipe(v)
4345
+ v = next
4234
4346
  }
4235
4347
  }
4236
4348
 
@@ -4239,12 +4351,17 @@ const p256SignScalar = (priv: u8[], z: u32[]): u8[] => {
4239
4351
  * encoding. Answers `null` unless `priv` is `P256_SCALAR_SIZE` bytes holding a
4240
4352
  * scalar in [1, n) — 0 and n and everything above are not private keys.
4241
4353
  */
4242
- export const p256PublicKey = (priv: u8[]): u8[] | null => {
4354
+ export const p256PublicKey = (priv: Secret<u8[]>): u8[] | null => expose(priv, p256PublicKeyExposed)
4355
+
4356
+ /** `p256PublicKey` on the plain key, for `expose` to run: the scratch the key's digits steered is wiped. */
4357
+ const p256PublicKeyExposed = (priv: u8[]): u8[] | null => {
4243
4358
  if (!p256ScalarInRange(priv)) {
4244
4359
  return null
4245
4360
  }
4246
4361
  const point = new P256ProjectivePoint()
4247
- p256ScalarMult(point, priv, p256Generator(), new P256PointScratch())
4362
+ const scratch = new P256PointScratch()
4363
+ p256ScalarMult(point, priv, p256Generator(), scratch)
4364
+ p256WipeScratch(scratch)
4248
4365
  return p256EncodePoint(point)
4249
4366
  }
4250
4367
 
@@ -4256,7 +4373,11 @@ export const p256PublicKey = (priv: u8[]): u8[] | null => {
4256
4373
  * not normalised to the lower half. Answers `null` for a private key
4257
4374
  * `p256PublicKey` would refuse.
4258
4375
  */
4259
- export const p256Sign = (priv: u8[], digest: u8[]): u8[] | null => {
4376
+ export const p256Sign = (priv: Secret<u8[]>, digest: u8[]): u8[] | null =>
4377
+ exposeWith(priv, digest, p256SignExposed)
4378
+
4379
+ /** `p256Sign` on the plain key, for `exposeWith` to run. */
4380
+ const p256SignExposed = (priv: u8[], digest: u8[]): u8[] | null => {
4260
4381
  if (!p256ScalarInRange(priv)) {
4261
4382
  return null
4262
4383
  }
@@ -4273,6 +4394,11 @@ export const p256Sign = (priv: u8[], digest: u8[]): u8[] | null => {
4273
4394
  * that is not a point on the curve, and a sum that is the identity all answer
4274
4395
  * `false`. Every value here is public, so the answer may be reached by
4275
4396
  * branches; the multiplications are the constant-time ones all the same.
4397
+ *
4398
+ * ECDSA is malleable, and so is this: when `(r, s)` verifies, `(r, n - s)`
4399
+ * verifies too, as FIPS 186-4 and SEC 1 have it. Nothing here asks for the
4400
+ * lower `s`, so a protocol that needs one signature per message and key (a
4401
+ * signature used as an identifier, say) has to refuse the upper half itself.
4276
4402
  */
4277
4403
  export const p256Verify = (pub: u8[], digest: u8[], sig: u8[]): boolean => {
4278
4404
  if (toI32(sig.length) !== 64) {
@@ -4330,7 +4456,7 @@ export const p256Verify = (pub: u8[], digest: u8[], sig: u8[]): boolean => {
4330
4456
  }
4331
4457
 
4332
4458
  /** `p256Sign` of the SHA-256 of `msg`: ECDSA with SHA-256 over the whole message. */
4333
- export const p256SignSha256 = (priv: u8[], msg: u8[]): u8[] | null => p256Sign(priv, sha256(msg))
4459
+ export const p256SignSha256 = (priv: Secret<u8[]>, msg: u8[]): u8[] | null => p256Sign(priv, sha256(msg))
4334
4460
 
4335
4461
  /** `p256Verify` of the SHA-256 of `msg`. */
4336
4462
  export const p256VerifySha256 = (pub: u8[], msg: u8[], sig: u8[]): boolean =>