@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.
- package/INSTALL.md +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- 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
|
|
63
|
-
*
|
|
64
|
-
* does a tag that does not verify. An
|
|
65
|
-
* length is hashed into the first counter block as
|
|
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
|
-
|
|
410
|
-
q[
|
|
411
|
-
q[
|
|
412
|
-
q[
|
|
413
|
-
q[
|
|
414
|
-
q[
|
|
415
|
-
q[
|
|
416
|
-
q[
|
|
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,
|
|
848
|
-
* plaintext longer than 2^31 - 17 bytes,
|
|
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
|
|
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
|
-
|
|
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)
|
package/std/crypto/base64url.ts
CHANGED
|
@@ -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 —
|
|
13
|
-
* §3.5)
|
|
14
|
-
* compared or cached by its text
|
|
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
|
|
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 =>
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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)) |
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The little-endian `u32` at `buf[at]` to `buf[at + 3]`. The offsets are
|
|
90
|
+
* summed in `u32`, which wraps by definition, rather than as checked `i32`
|
|
91
|
+
* sums: every caller passes a window inside `buf`, a wrapped offset would
|
|
92
|
+
* index past it rather than reach another byte, and a check is a branch that
|
|
93
|
+
* `tests/cases/ct_asm_chacha20poly1305` refuses. `poly1305Le64`,
|
|
94
|
+
* `poly1305Block` and `chacha20Poly1305TagMatch` sum theirs the same way.
|
|
95
|
+
*/
|
|
96
|
+
const chacha20Word = (buf: u8[], at: i32): u32 => {
|
|
97
|
+
const p: u32 = toU32(at)
|
|
98
|
+
return toU32(buf[p]) | (toU32(buf[p + 1]) << 8) | (toU32(buf[p + 2]) << 16) | (toU32(buf[p + 3]) << 24)
|
|
99
|
+
}
|
|
91
100
|
|
|
92
101
|
/**
|
|
93
102
|
* The sixteen-word state of RFC 8439 §2.3: the four constant words
|
|
@@ -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
|
-
|
|
348
|
-
(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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[
|
|
604
|
-
toU32(tag[1] ^ sealed[
|
|
605
|
-
toU32(tag[2] ^ sealed[
|
|
606
|
-
toU32(tag[3] ^ sealed[
|
|
607
|
-
toU32(tag[4] ^ sealed[
|
|
608
|
-
toU32(tag[5] ^ sealed[
|
|
609
|
-
toU32(tag[6] ^ sealed[
|
|
610
|
-
toU32(tag[7] ^ sealed[
|
|
611
|
-
toU32(tag[8] ^ sealed[
|
|
612
|
-
toU32(tag[9] ^ sealed[
|
|
613
|
-
toU32(tag[10] ^ sealed[
|
|
614
|
-
toU32(tag[11] ^ sealed[
|
|
615
|
-
toU32(tag[12] ^ sealed[
|
|
616
|
-
toU32(tag[13] ^ sealed[
|
|
617
|
-
toU32(tag[14] ^ sealed[
|
|
618
|
-
toU32(tag[15] ^ sealed[
|
|
620
|
+
toU32(tag[0] ^ sealed[p]) |
|
|
621
|
+
toU32(tag[1] ^ sealed[p + 1]) |
|
|
622
|
+
toU32(tag[2] ^ sealed[p + 2]) |
|
|
623
|
+
toU32(tag[3] ^ sealed[p + 3]) |
|
|
624
|
+
toU32(tag[4] ^ sealed[p + 4]) |
|
|
625
|
+
toU32(tag[5] ^ sealed[p + 5]) |
|
|
626
|
+
toU32(tag[6] ^ sealed[p + 6]) |
|
|
627
|
+
toU32(tag[7] ^ sealed[p + 7]) |
|
|
628
|
+
toU32(tag[8] ^ sealed[p + 8]) |
|
|
629
|
+
toU32(tag[9] ^ sealed[p + 9]) |
|
|
630
|
+
toU32(tag[10] ^ sealed[p + 10]) |
|
|
631
|
+
toU32(tag[11] ^ sealed[p + 11]) |
|
|
632
|
+
toU32(tag[12] ^ sealed[p + 12]) |
|
|
633
|
+
toU32(tag[13] ^ sealed[p + 13]) |
|
|
634
|
+
toU32(tag[14] ^ sealed[p + 14]) |
|
|
635
|
+
toU32(tag[15] ^ sealed[p + 15])
|
|
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
|
-
|
|
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[
|
|
72
|
+
diff = diff | toI32(a[aAt + toU32(k)] ^ b[bAt + toU32(k)])
|
|
62
73
|
}
|
|
63
74
|
return diff === 0
|
|
64
75
|
}
|