@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
package/std/crypto/aes.ts CHANGED
@@ -59,10 +59,11 @@
59
59
  *
60
60
  * **What is refused.** A key of any length but 16 or 32 bytes (AES-192 is out
61
61
  * of scope), a block or a sample that is not 16 bytes, an empty IV (SP 800-38D
62
- * §5.2.1.1 asks for at least one bit), a sealed input shorter than its tag and
63
- * a plaintext too long for its sealed form to be an array answer `null`; so
64
- * does a tag that does not verify. An IV of any other non-zero
65
- * length is hashed into the first counter block as §7.1 says.
62
+ * §5.2.1.1 asks for at least one bit), a sealed input shorter than its tag, a
63
+ * plaintext too long for its sealed form to be an array, and an `AesKey`
64
+ * `aesKey` did not make answer `null`; so does a tag that does not verify. An
65
+ * IV of any other non-zero length is hashed into the first counter block as
66
+ * §7.1 says.
66
67
  *
67
68
  * Private names carry the `aes` / `ghash` prefix because a `std/` module's
68
69
  * private functions share the importing program's flat symbol namespace
@@ -80,6 +81,12 @@ const AES_BATCH: i32 = 64
80
81
  /**
81
82
  * An AES key, expanded: the round keys already bitsliced, and GCM's hash key
82
83
  * H = E(K, 0^128), so a key used for many messages is expanded once.
84
+ *
85
+ * Make one with `aesKey`. The constructor and the fields are public only
86
+ * because the language has no private ones, and a key built or edited by hand
87
+ * is not trusted: one whose shape is not what `aesKey` makes answers `null`
88
+ * everywhere (`aesKeyUsable`), and one whose H is zero opens nothing
89
+ * (`aesGcmOpen`).
83
90
  */
84
91
  export class AesKey {
85
92
  /** Nr: 10 for AES-128, 14 for AES-256. */
@@ -406,14 +413,15 @@ const aesMixColumns = (q: u64[]): void => {
406
413
 
407
414
  /** AddRoundKey (FIPS 197 §5.1.4): the eight words of `rk` from `at` into the state. */
408
415
  const aesAddRoundKey = (q: u64[], rk: u64[], at: i32): void => {
409
- q[0] = q[0] ^ rk[at]
410
- q[1] = q[1] ^ rk[at + 1]
411
- q[2] = q[2] ^ rk[at + 2]
412
- q[3] = q[3] ^ rk[at + 3]
413
- q[4] = q[4] ^ rk[at + 4]
414
- q[5] = q[5] ^ rk[at + 5]
415
- q[6] = q[6] ^ rk[at + 6]
416
- q[7] = q[7] ^ rk[at + 7]
416
+ const p: u32 = toU32(at)
417
+ q[0] = q[0] ^ rk[p]
418
+ q[1] = q[1] ^ rk[p + 1]
419
+ q[2] = q[2] ^ rk[p + 2]
420
+ q[3] = q[3] ^ rk[p + 3]
421
+ q[4] = q[4] ^ rk[p + 4]
422
+ q[5] = q[5] ^ rk[p + 5]
423
+ q[6] = q[6] ^ rk[p + 6]
424
+ q[7] = q[7] ^ rk[p + 7]
417
425
  }
418
426
 
419
427
  /**
@@ -574,6 +582,19 @@ const aesCopyPrefix = (dst: u8[], src: u8[], count: i32): void => {
574
582
  }
575
583
  }
576
584
 
585
+ /**
586
+ * Whether `key` has the shape `aesKey` makes: 10 or 14 rounds and the
587
+ * `(rounds + 1) * 8` round-key words the cipher reads. A key of another shape
588
+ * could only have been built or edited by hand, and would index past its round
589
+ * keys and panic, so every entry point answers `null` for it instead. The
590
+ * branch is on the shape, which is not secret.
591
+ */
592
+ const aesKeyUsable = (key: AesKey): boolean => {
593
+ const rounds: i32 = key.rounds
594
+ const words: i32 = (rounds + 1) * 8
595
+ return (rounds === 10 || rounds === 14) && toI32(key.roundKeys.length) === words
596
+ }
597
+
577
598
  /**
578
599
  * The first 16 bytes of `block` encrypted, in the first 16 bytes of a fresh
579
600
  * batch; the rest of the batch is the cipher of zeros and is not read.
@@ -603,9 +624,51 @@ export const aesKey = (key: u8[]): AesKey | null => {
603
624
  return expanded
604
625
  }
605
626
 
627
+ /**
628
+ * `aesKey` into an `AesKey` the caller already holds: the key schedule of a
629
+ * 16-byte (AES-128) or 32-byte (AES-256) `key`, bitsliced, and H = E(K,
630
+ * 0^128) for GCM, written into `out`, whose `roundKeys` must already be the
631
+ * `8 * (rounds + 1)` words that key length needs (88 or 120). It answers
632
+ * whether it did; a key of another length, or a schedule array of the wrong
633
+ * size for it, answers `false` and leaves `out` as it was.
634
+ *
635
+ * `aesKey` makes a fresh `AesKey` and stores its schedule into it, which no
636
+ * arena scope may be put around; this stores numbers only, so a caller that
637
+ * keeps one `AesKey` per connection installs every key inside a `using a =
638
+ * arena()` block and leaves the arena where it was. The schedule and H are
639
+ * secret: the caller wipes `out` when it is done with the key. The working
640
+ * copy of the schedule is zeroed before it is released, by stores that stand
641
+ * no better than `aesKey`'s own temporaries do.
642
+ */
643
+ export const aesKeyInto = (key: u8[], out: AesKey): boolean => {
644
+ const length: i32 = toI32(key.length)
645
+ if (length !== 16 && length !== 32) {
646
+ return false
647
+ }
648
+ const rounds: i32 = length === 16 ? 10 : 14
649
+ const roundKeys: u64[] = out.roundKeys
650
+ const words: i32 = (rounds + 1) * 8
651
+ if (toI32(roundKeys.length) !== words) {
652
+ return false
653
+ }
654
+ const expanded: u64[] = aesExpand(key, rounds)
655
+ for (let i: i32 = 0; i < toI32(roundKeys.length) && i < toI32(expanded.length); i++) {
656
+ roundKeys[i] = expanded[i]
657
+ expanded[i] = 0
658
+ }
659
+ out.rounds = rounds
660
+ const buf: u8[] = aesEncryptOne(out, [])
661
+ out.hHi = aesLoad64(buf, 0, 8)
662
+ out.hLo = aesLoad64(buf, 8, 16)
663
+ for (let i: i32 = 0; i < toI32(buf.length); i++) {
664
+ buf[i] = 0
665
+ }
666
+ return true
667
+ }
668
+
606
669
  /** One block encrypted (FIPS 197 §5.1, ECB): 16 bytes in, 16 fresh bytes out, or `null` for another length. */
607
670
  export const aesEncryptBlock = (key: AesKey, block: u8[]): u8[] | null => {
608
- if (toI32(block.length) !== AES_BLOCK) {
671
+ if (!aesKeyUsable(key) || toI32(block.length) !== AES_BLOCK) {
609
672
  return null
610
673
  }
611
674
  const out: u8[] = new Array<u8>(AES_BLOCK)
@@ -619,7 +682,7 @@ export const aesEncryptBlock = (key: AesKey, block: u8[]): u8[] | null => {
619
682
  * a sample of another length.
620
683
  */
621
684
  export const aesHeaderMask = (key: AesKey, sample: u8[]): u8[] | null => {
622
- if (toI32(sample.length) !== AES_BLOCK) {
685
+ if (!aesKeyUsable(key) || toI32(sample.length) !== AES_BLOCK) {
623
686
  return null
624
687
  }
625
688
  const mask: u8[] = new Array<u8>(5)
@@ -781,6 +844,17 @@ const aesGcmJ0 = (key: AesKey, iv: u8[]): u8[] => {
781
844
  return j0
782
845
  }
783
846
 
847
+ /**
848
+ * Whether GCTR can encrypt `length` bytes from inc32(J0) without its 32-bit
849
+ * counter coming back round to J0's, whose block masks the tag: SP 800-38D
850
+ * §5.2.1.1 caps a plaintext at 2^32 - 2 blocks (2^39 - 256 bits). An array
851
+ * length is below 2^31, so this never refuses today; it is here so that the
852
+ * bound is stated where the counter is, rather than left to the width of an
853
+ * array length.
854
+ */
855
+ const aesGcmLengthFits = (length: i32): boolean =>
856
+ (toI64(length) + toI64(15)) >> toI64(4) <= (toI64(1) << toI64(32)) - toI64(2)
857
+
784
858
  /**
785
859
  * GCTR (SP 800-38D §6.5) from inc32(J0): `dst[i] = src[i] ⊕ keystream[i]` for
786
860
  * `i` below `length`, four blocks per pass of the cipher. The counter is the
@@ -844,16 +918,16 @@ export const aesGcmTagMask = (tagHi: u64, tagLo: u64, gotHi: u64, gotLo: u64): u
844
918
  /**
845
919
  * GCM authenticated encryption (SP 800-38D §7.1): the ciphertext followed by
846
920
  * the 16-byte tag, in one fresh array. The IV may be any non-zero length; 12
847
- * bytes is the fast and usual one. `null` for an empty IV, and for a
848
- * plaintext longer than 2^31 - 17 bytes, whose sealed form would not fit in an
849
- * array.
921
+ * bytes is the fast and usual one. `null` for an empty IV, for a key
922
+ * `aesKey` did not make, and for a plaintext longer than 2^31 - 17 bytes,
923
+ * whose sealed form would not fit in an array.
850
924
  */
851
925
  export const aesGcmSeal = (key: AesKey, iv: u8[], aad: u8[], plaintext: u8[]): u8[] | null => {
852
926
  const length: i32 = toI32(plaintext.length)
853
927
  // 2^31 - 17: the longest plaintext whose sealed form, sixteen bytes longer,
854
928
  // is still an array length.
855
929
  const longest: i32 = 0x7fffffef
856
- if (toI32(iv.length) === 0 || length > longest) {
930
+ if (!aesKeyUsable(key) || toI32(iv.length) === 0 || length > longest || !aesGcmLengthFits(length)) {
857
931
  return null
858
932
  }
859
933
  const j0: u8[] = aesGcmJ0(key, iv)
@@ -869,18 +943,22 @@ export const aesGcmSeal = (key: AesKey, iv: u8[], aad: u8[], plaintext: u8[]): u
869
943
  /**
870
944
  * GCM authenticated decryption (SP 800-38D §7.2): `sealed` is the ciphertext
871
945
  * followed by its 16-byte tag, and the answer is the plaintext, or `null` when
872
- * the tag does not verify, the IV is empty or `sealed` is shorter than a tag.
946
+ * the tag does not verify, the IV is empty, `sealed` is shorter than a tag or
947
+ * the key is not one `aesKey` made.
873
948
  * The tag is computed over the received ciphertext and compared in constant
874
949
  * time before anything is decrypted, so a forgery yields no plaintext at all.
875
950
  */
876
951
  export const aesGcmOpen = (key: AesKey, iv: u8[], aad: u8[], sealed: u8[]): u8[] | null => {
877
952
  const total: i32 = toI32(sealed.length)
878
- if (toI32(iv.length) === 0 || total < AES_GCM_TAG_SIZE) {
953
+ if (!aesKeyUsable(key) || toI32(iv.length) === 0 || total < AES_GCM_TAG_SIZE) {
879
954
  return null
880
955
  }
881
956
  // `total` is an array length and at least a tag, so neither this nor the
882
957
  // tag's second word at `length + 8` can leave the range of an `i32`.
883
958
  const length: i32 = total - AES_GCM_TAG_SIZE
959
+ if (!aesGcmLengthFits(length)) {
960
+ return null
961
+ }
884
962
  const j0: u8[] = aesGcmJ0(key, iv)
885
963
  const tag: u64[] = new Array<u64>(2)
886
964
  aesGcmTag(tag, key, j0, aad, sealed, length)
@@ -890,7 +968,12 @@ export const aesGcmOpen = (key: AesKey, iv: u8[], aad: u8[], sealed: u8[]): u8[]
890
968
  aesLoad64(sealed, length, total),
891
969
  aesLoad64(sealed, length + 8, total)
892
970
  )
893
- if (same === toU64(0)) {
971
+ // Under H = 0 GHASH is zero for every input, so the tag E(K, J0) would
972
+ // authenticate any ciphertext and AAD. Only a key built or edited by hand
973
+ // has it (for one from `aesKey` the chance is 2^-128), and such a key opens
974
+ // nothing. H is secret, so this is folded into the mask, not branched on.
975
+ const hashKeySet: u64 = ~ctEq(key.hHi | key.hLo, toU64(0))
976
+ if ((same & hashKeySet) === toU64(0)) {
894
977
  return null
895
978
  }
896
979
  const out: u8[] = new Array<u8>(length)
@@ -9,9 +9,10 @@
9
9
  * **Decoding is strict, so that each byte string has exactly one spelling.**
10
10
  * `base64urlDecode` answers `null` for a `=` anywhere, for any character outside
11
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.
12
+ * byte — for a final character whose unused low bits are not zero (RFC 4648
13
+ * §3.5), and for a text longer than 2^31 - 1 bytes. A lenient decoder would let
14
+ * `AB` and `AA` both mean `00`, and a token compared or cached by its text
15
+ * would then have two identities.
15
16
  *
16
17
  * **Neither direction indexes or branches on the data.** What is encoded here
17
18
  * is usually a key, a nonce or a MAC, and a lookup table indexed by a secret
@@ -28,9 +29,14 @@
28
29
 
29
30
  /**
30
31
  * 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
+ * a sextet, so neither subtraction could leave `i32`; they are written in
33
+ * `u32`, which wraps by definition, because a signed one would be checked for
34
+ * an overflow it cannot have, and the check is a branch on the secret. The
35
+ * sign bit `>> 31` reads is the same either way. `base64urlCharOf` and
36
+ * `base64urlSextetOf` do their sums in `u32` for the same reason.
32
37
  */
33
- const base64urlRangeMask = (c: i32, lo: i32, hi: i32): i32 => ((lo - 1 - c) & (c - hi - 1)) >> 31
38
+ const base64urlRangeMask = (c: i32, lo: i32, hi: i32): i32 =>
39
+ toI32((toU32(lo) - 1 - toU32(c)) & (toU32(c) - toU32(hi) - 1)) >> 31
34
40
 
35
41
  /**
36
42
  * The URL-alphabet character for the sextet `v` (`0 <= v <= 63`).
@@ -40,12 +46,14 @@ const base64urlRangeMask = (c: i32, lo: i32, hi: i32): i32 => ((lo - 1 - c) & (c
40
46
  * on `0`, 62 on `-` and 63 on `_`.
41
47
  */
42
48
  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
+ toI32(
50
+ 65 +
51
+ toU32(v) +
52
+ toU32(base64urlRangeMask(v, 26, 63) & 6) -
53
+ toU32(base64urlRangeMask(v, 52, 63) & 75) -
54
+ toU32(base64urlRangeMask(v, 62, 63) & 13) +
55
+ toU32(base64urlRangeMask(v, 63, 63) & 49)
56
+ )
49
57
 
50
58
  /**
51
59
  * The sextet the byte `c` stands for, or `-1` when `c` is not in the alphabet.
@@ -59,7 +67,11 @@ const base64urlSextetOf = (c: i32): i32 => {
59
67
  const dash: i32 = base64urlRangeMask(c, 45, 45)
60
68
  const underscore: i32 = base64urlRangeMask(c, 95, 95)
61
69
  const value: i32 =
62
- (upper & (c - 65)) | (lower & (c - 71)) | (digit & (c + 4)) | (dash & 62) | (underscore & 63)
70
+ (upper & toI32(toU32(c) - 65)) |
71
+ (lower & toI32(toU32(c) - 71)) |
72
+ (digit & toI32(toU32(c) + 4)) |
73
+ (dash & 62) |
74
+ (underscore & 63)
63
75
  return value | ~(upper | lower | digit | dash | underscore)
64
76
  }
65
77
 
@@ -69,12 +81,20 @@ const base64urlSextetOf = (c: i32): i32 => {
69
81
  *
70
82
  * Bytes go into a bit accumulator eight at a time and come out six at a time,
71
83
  * 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.
84
+ * waiting — which depends on the position, never on the bytes. An array longer
85
+ * than 2^31 - 1 bytes panics.
73
86
  */
74
87
  export const base64urlEncode = (data: u8[]): string => {
88
+ // Refused before `toI32`, which saturates a longer length under
89
+ // `--number-mode f64` and would end the loop at a prefix.
90
+ if (data.length > 2147483647) {
91
+ panic("base64urlEncode: more than 2^31 - 1 bytes")
92
+ }
75
93
  const parts: string[] = []
76
94
  let acc: i32 = 0
77
- let bits: i32 = 0
95
+ // Unsigned, so that counting bits in and out is never checked for an
96
+ // overflow: it is never more than twelve.
97
+ let bits: u32 = 0
78
98
  for (let k: i32 = 0; k < toI32(data.length); k++) {
79
99
  // At most four bits wait from the bytes before, so twelve are live here;
80
100
  // the mask keeps the accumulator from growing without bound.
@@ -82,13 +102,13 @@ export const base64urlEncode = (data: u8[]): string => {
82
102
  bits += 8
83
103
  while (bits >= 6) {
84
104
  bits -= 6
85
- parts.push(String.fromCharCode(base64urlCharOf((acc >> bits) & 63)))
105
+ parts.push(String.fromCharCode(base64urlCharOf((acc >> toI32(bits)) & 63)))
86
106
  }
87
107
  }
88
108
  // Two or four bits of a final byte are left: they go out as the high bits of
89
109
  // one more sextet, filled out with zeros.
90
110
  if (bits > 0) {
91
- parts.push(String.fromCharCode(base64urlCharOf((acc << (6 - bits)) & 63)))
111
+ parts.push(String.fromCharCode(base64urlCharOf((acc << toI32(6 - bits)) & 63)))
92
112
  }
93
113
  return parts.join("")
94
114
  }
@@ -96,12 +116,18 @@ export const base64urlEncode = (data: u8[]): string => {
96
116
  /**
97
117
  * The bytes `text` spells in unpadded base64url, or `null` when it is not the
98
118
  * one canonical spelling of any byte string — see the module comment for the
99
- * four refusals.
119
+ * five refusals.
100
120
  *
101
121
  * The same accumulator as `base64urlEncode`, run the other way: six bits in per
102
122
  * character, eight out per byte.
103
123
  */
104
124
  export const base64urlDecode = (text: string): u8[] | null => {
125
+ // A text past 2^31 - 1 bytes is refused before `toI32`, which would
126
+ // saturate it under `--number-mode f64` and decode only a prefix: a second
127
+ // spelling of that prefix's bytes, which strictness exists to refuse.
128
+ if (text.length > 2147483647) {
129
+ return null
130
+ }
105
131
  const n: i32 = toI32(text.length)
106
132
  const tail: i32 = n & 3
107
133
  // Six bits cannot finish a byte, so a single character after the last full
@@ -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
- /** The little-endian `u32` at `buf[at]` to `buf[at + 3]`. */
89
- const chacha20Word = (buf: u8[], at: i32): u32 =>
90
- toU32(buf[at]) | (toU32(buf[at + 1]) << 8) | (toU32(buf[at + 2]) << 16) | (toU32(buf[at + 3]) << 24)
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
@@ -274,7 +283,10 @@ const chacha20Poly1305Setup = (state: u32[]): u8[] => {
274
283
  * Whether the blocks `len` bytes need, from `counter` on, fit before the
275
284
  * 32-bit counter wraps.
276
285
  * RFC 8439 §2.4 leaves a wrap undefined, and a wrapped counter repeats the
277
- * keystream, so a request that would need one answers `null` instead.
286
+ * keystream, so a request that would need one answers `null` instead. The
287
+ * AEAD asks it too, from counter 1: an array length is below 2^31, so that
288
+ * never refuses today, but the bound is then stated where the counter is
289
+ * rather than left to the width of an array length.
278
290
  */
279
291
  const chacha20CounterFits = (counter: u32, len: i32): boolean => {
280
292
  const blocks: i64 = (toI64(len) + toI64(63)) >> toI64(6)
@@ -343,15 +355,19 @@ export const chacha20 = (key: u8[], counter: u32, nonce: u8[], data: u8[]): u8[]
343
355
  }
344
356
 
345
357
  /** The little-endian `u64` at `buf[at]` to `buf[at + 7]`. */
346
- const poly1305Le64 = (buf: u8[], at: i32): u64 =>
347
- toU64(buf[at]) |
348
- (toU64(buf[at + 1]) << 8) |
349
- (toU64(buf[at + 2]) << 16) |
350
- (toU64(buf[at + 3]) << 24) |
351
- (toU64(buf[at + 4]) << 32) |
352
- (toU64(buf[at + 5]) << 40) |
353
- (toU64(buf[at + 6]) << 48) |
354
- (toU64(buf[at + 7]) << 56)
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
+ }
355
371
 
356
372
  /**
357
373
  * `r` from the first 16 bytes of a one-time key, clamped as RFC 8439 §2.5
@@ -394,7 +410,7 @@ const poly1305Clamp = (key: u8[], r: u64[]): void => {
394
410
  */
395
411
  const poly1305Block = (h: u64[], r: u64[], m: u8[], at: i32, high: u64): void => {
396
412
  const lo: u64 = poly1305Le64(m, at)
397
- const hi: u64 = poly1305Le64(m, at + 8)
413
+ const hi: u64 = poly1305Le64(m, toI32(toU32(at) + 8))
398
414
  const h0: u64 = h[0] + (lo & 0x3ffffff)
399
415
  const h1: u64 = h[1] + ((lo >>> 26) & 0x3ffffff)
400
416
  const h2: u64 = h[2] + (((lo >>> 52) | (hi << 12)) & 0x3ffffff)
@@ -599,23 +615,24 @@ const chacha20Poly1305Tag = (polyKey: u8[], aad: u8[], ct: u8[], at: i32, len: i
599
615
  * disassembly check; the caller has checked that `sealed` holds the window.
600
616
  */
601
617
  const chacha20Poly1305TagMatch = (tag: u8[], sealed: u8[], at: i32): u32 => {
618
+ const p: u32 = toU32(at)
602
619
  const diff: u32 =
603
- toU32(tag[0] ^ sealed[at]) |
604
- toU32(tag[1] ^ sealed[at + 1]) |
605
- toU32(tag[2] ^ sealed[at + 2]) |
606
- toU32(tag[3] ^ sealed[at + 3]) |
607
- toU32(tag[4] ^ sealed[at + 4]) |
608
- toU32(tag[5] ^ sealed[at + 5]) |
609
- toU32(tag[6] ^ sealed[at + 6]) |
610
- toU32(tag[7] ^ sealed[at + 7]) |
611
- toU32(tag[8] ^ sealed[at + 8]) |
612
- toU32(tag[9] ^ sealed[at + 9]) |
613
- toU32(tag[10] ^ sealed[at + 10]) |
614
- toU32(tag[11] ^ sealed[at + 11]) |
615
- toU32(tag[12] ^ sealed[at + 12]) |
616
- toU32(tag[13] ^ sealed[at + 13]) |
617
- toU32(tag[14] ^ sealed[at + 14]) |
618
- toU32(tag[15] ^ sealed[at + 15])
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])
619
636
  return ctEq(diff, 0)
620
637
  }
621
638
 
@@ -634,7 +651,8 @@ export const chacha20Poly1305Seal = (key: u8[], nonce: u8[], aad: u8[], plaintex
634
651
  if (
635
652
  toI32(key.length) !== CHACHA20_KEY_SIZE ||
636
653
  toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE ||
637
- len > longest
654
+ len > longest ||
655
+ !chacha20CounterFits(1, len)
638
656
  ) {
639
657
  return null
640
658
  }
@@ -668,6 +686,9 @@ export const chacha20Poly1305Open = (key: u8[], nonce: u8[], aad: u8[], sealed:
668
686
  return null
669
687
  }
670
688
  const len: i32 = total - POLY1305_TAG_SIZE
689
+ if (!chacha20CounterFits(1, len)) {
690
+ return null
691
+ }
671
692
  const state: u32[] = chacha20State(key, 0, nonce, 0)
672
693
  const polyKey: u8[] = chacha20Poly1305Setup(state)
673
694
  const tag: u8[] = chacha20Poly1305Tag(polyKey, aad, sealed, 0, len)
package/std/crypto/ct.ts CHANGED
@@ -23,12 +23,18 @@
23
23
  *
24
24
  * Arrays of different lengths answer `false` at once, because a length is
25
25
  * public; arrays of one length are compared in full, with no early exit.
26
+ *
27
+ * The lengths are compared as the `number`s they are, before any `toI32`:
28
+ * under `--number-mode f64` `toI32` saturates at 2^31 - 1, so two arrays past
29
+ * it would have looked the same length and been compared only that far. An
30
+ * array longer than that answers `false` too — no `i32` index reaches its end,
31
+ * and a compare that cannot read every byte must not say "equal".
26
32
  */
27
33
  export const timingSafeEqual = (a: u8[], b: u8[]): boolean => {
28
- const n: i32 = toI32(a.length)
29
- if (n !== toI32(b.length)) {
34
+ if (a.length !== b.length || a.length > 2147483647) {
30
35
  return false
31
36
  }
37
+ const n: i32 = toI32(a.length)
32
38
  let diff: i32 = 0
33
39
  // Bounded by both lengths, which are equal here, so the prover drops both
34
40
  // bounds checks rather than trusting the comparison above.
@@ -56,9 +62,14 @@ export const timingSafeEqualAt = (a: u8[], aOff: i32, b: u8[], bOff: i32, len: i
56
62
  if (aOff < 0 || bOff < 0 || len < 0 || aOff > aLen - len || bOff > bLen - len) {
57
63
  return false
58
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)
59
70
  let diff: i32 = 0
60
71
  for (let k: i32 = 0; k < len; k++) {
61
- diff = diff | toI32(a[aOff + k] ^ b[bOff + k])
72
+ diff = diff | toI32(a[aAt + toU32(k)] ^ b[bAt + toU32(k)])
62
73
  }
63
74
  return diff === 0
64
75
  }