@amritk/nish-x86_64-linux 0.15.0 → 0.16.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 +15 -6
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.h +39 -5
- package/runtime/runtime-host.c +29 -2
- package/runtime/runtime-net.c +15 -9
- package/runtime/runtime-os.c +77 -37
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +25 -2
- package/runtime/runtime.c +40 -6
- package/std/README.md +44 -19
- package/std/crypto/aes.ts +53 -13
- package/std/crypto/base64url.ts +18 -5
- package/std/crypto/chacha20poly1305.ts +9 -2
- package/std/crypto/ct.ts +8 -2
- package/std/crypto/hkdf.ts +21 -5
- package/std/crypto/hmac.ts +18 -2
- package/std/crypto/p256.ts +5 -0
- package/std/crypto/sha256.ts +12 -1
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +4 -3
- package/std/crypto/x509.ts +143 -26
package/std/README.md
CHANGED
|
@@ -32,19 +32,33 @@ performance gate compiles every module with no diagnostics under both
|
|
|
32
32
|
ChaCha20-Poly1305, AES-GCM, P-256 and X.509 also run their vectors in `f64`
|
|
33
33
|
(`crypto_*_f64`).
|
|
34
34
|
|
|
35
|
+
Every module here was security-audited (#363); the records are in
|
|
36
|
+
[`docs/security/`](../docs/security/README.md), and how to report a
|
|
37
|
+
vulnerability is in [`SECURITY.md`](../SECURITY.md). Two limits hold across the
|
|
38
|
+
modules. **Lengths stop at 2^31 − 1.** No array built by `push` or `new Array`,
|
|
39
|
+
and no array or string the runtime makes (a file read, a concatenation, a
|
|
40
|
+
template), can pass 2^31 − 1 elements or bytes, and the one-shot functions
|
|
41
|
+
below refuse a longer input as each row says. A string built by `join` still
|
|
42
|
+
can, and under `--number-mode i32` its `length` is then wrong, which nothing in
|
|
43
|
+
`std` can tell (CG-3, open): a program built in that mode must not hand these
|
|
44
|
+
functions anything built from such a string. **Secrets are not wiped.** Private keys,
|
|
45
|
+
nonces and intermediate state stay in arena memory after a call returns, until
|
|
46
|
+
that memory is reused, because the language has no store the optimiser is
|
|
47
|
+
barred from removing (ECC-2, X509-7, open).
|
|
48
|
+
|
|
35
49
|
| Module | What it is | Reproduces |
|
|
36
50
|
| --- | --- | --- |
|
|
37
|
-
| [`crypto/sha256.ts`](./crypto/sha256.ts) | `sha256(data)`, and `Sha256`, a streaming hasher: `update(buf, off, len)` over a window of a `u8[]`, `copy()` for the hash of a prefix while the original keeps going, and `digest()`, a fresh 32-byte array. `SHA256_SIZE` and `SHA256_BLOCK` | FIPS 180-4 §6.2 |
|
|
38
|
-
| [`crypto/sha512.ts`](./crypto/sha512.ts) | SHA-512 and SHA-384 on one compression function: `sha512` and `sha384`, and the streaming `Sha512` and `Sha384` with `Sha256`'s three methods; digests of 64 and 48 bytes. `SHA512_SIZE`, `SHA384_SIZE` and `SHA512_BLOCK` | FIPS 180-4 §6.4, §6.5 |
|
|
39
|
-
| [`crypto/hmac.ts`](./crypto/hmac.ts) | `hmacSha256` and `hmacSha384`, the streaming `HmacSha256` and `HmacSha384` (keyed in the constructor, then `update` and `digest`), and `hmacSha256Verify` / `hmacSha384Verify`, which compare a received tag with `timingSafeEqual
|
|
40
|
-
| [`crypto/hkdf.ts`](./crypto/hkdf.ts) | `hkdfExtractSha256` / `hkdfExtractSha384` (an empty salt is HashLen zeros) and `hkdfExpandSha256` / `hkdfExpandSha384`, which answer `null` for a length below zero or above 255 × HashLen. TLS 1.3's HKDF-Expand-Label is not here; it belongs with TLS | RFC 5869 §2, Appendix A |
|
|
41
|
-
| [`crypto/ct.ts`](./crypto/ct.ts) | `timingSafeEqual(a, b)`, which reads every byte whatever it holds, and `timingSafeEqualAt(a, aOff, b, bOff, len)` over two windows, which answers `false` for a window outside its array. Two lengths that differ answer `false` at once, because a length is public | — |
|
|
42
|
-
| [`crypto/base64url.ts`](./crypto/base64url.ts) | `base64urlEncode(data)` and `base64urlDecode(text)`, unpadded. Decoding is strict, so every byte string has one spelling: a `=`, a character outside the alphabet, a length of 1 mod 4 or nonzero unused low bits answer `null` | RFC 4648 §5, §10 |
|
|
43
|
-
| [`crypto/x25519.ts`](./crypto/x25519.ts) | `x25519(scalar, u)` and `x25519Base(scalar)`, on ten 25.5-bit limbs in `i64`. Either answers `null` unless its arguments are `X25519_SIZE` (32) bytes; the scalar is clamped on a copy | RFC 7748 §5.2, §6.1 |
|
|
44
|
-
| [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null` | RFC 8439 §2, RFC 9001 §5.4.4 |
|
|
45
|
-
| [`crypto/aes.ts`](./crypto/aes.ts) | AES-128 and AES-256, bitsliced four blocks at a time with the Boyar–Peralta S-box circuit, so no table is read: `aesKey(key)` expands a 16- or 32-byte key into an `AesKey`, then `aesEncryptBlock`, `aesGcmSeal` / `aesGcmOpen` (tag last, checked in full before anything is decrypted; any non-empty IV) and `aesHeaderMask`. AES-192 is out of scope, and a wrong length answers `null` | FIPS 197, SP 800-38D, RFC 9001 §5.4.3, Wycheproof `aes_gcm` |
|
|
46
|
-
| [`crypto/p256.ts`](./crypto/p256.ts) | ECDSA over P-256 with RFC 6979's deterministic nonces: `p256PublicKey(priv)` (65-byte uncompressed SEC1), `p256Sign` / `p256Verify` over a digest and `p256SignSha256` / `p256VerifySha256` over a message; a signature is `r` and `s`, 32 big-endian bytes each, not DER. A malformed key, a point off the curve or an `r` or `s` out of range answers `null` or `false`, never a panic | RFC 6979 A.2.5, Wycheproof `ecdsa_secp256r1_sha256` |
|
|
47
|
-
| [`crypto/x509.ts`](./crypto/x509.ts) | DER, PEM and X.509 over P-256: `pemToDer` / `derToPem` (RFC 7468, padded standard base64, strict labels), `x509ParseP256PrivateKey` (SEC1 or PKCS#8), `x509ParseCertificate` / `x509ParseChain` into an `X509Certificate`, `x509VerifySignature` (ecdsa-with-SHA256 under an issuer's key), `x509MintSelfSigned`, the 1-to-14-day self-signed P-256 certificate WebTransport's `serverCertificateHashes` accepts, and `x509CertificateHash`, its SHA-256. DER is read strictly — non-minimal lengths and integers, indefinite lengths and trailing bytes answer `null` | X.690, RFC 5280, RFC 7468, RFC 5915, RFC 5208 / 5958; certificates checked against OpenSSL |
|
|
51
|
+
| [`crypto/sha256.ts`](./crypto/sha256.ts) | `sha256(data)`, and `Sha256`, a streaming hasher: `update(buf, off, len)` over a window of a `u8[]`, `copy()` for the hash of a prefix while the original keeps going, and `digest()`, a fresh 32-byte array. `SHA256_SIZE` and `SHA256_BLOCK`. `sha256` panics on an array longer than 2^31 − 1 bytes | FIPS 180-4 §6.2 |
|
|
52
|
+
| [`crypto/sha512.ts`](./crypto/sha512.ts) | SHA-512 and SHA-384 on one compression function: `sha512` and `sha384`, and the streaming `Sha512` and `Sha384` with `Sha256`'s three methods; digests of 64 and 48 bytes. `SHA512_SIZE`, `SHA384_SIZE` and `SHA512_BLOCK`. The one-shot `sha512` and `sha384` panic on an array longer than 2^31 − 1 bytes | FIPS 180-4 §6.4, §6.5 |
|
|
53
|
+
| [`crypto/hmac.ts`](./crypto/hmac.ts) | `hmacSha256` and `hmacSha384`, the streaming `HmacSha256` and `HmacSha384` (keyed in the constructor, then `update` and `digest`), and `hmacSha256Verify` / `hmacSha384Verify`, which compare a received tag with `timingSafeEqual`. The one-shot functions panic on a message longer than 2^31 − 1 bytes | RFC 2104, RFC 4231 §4 |
|
|
54
|
+
| [`crypto/hkdf.ts`](./crypto/hkdf.ts) | `hkdfExtractSha256` / `hkdfExtractSha384` (an empty salt is HashLen zeros) and `hkdfExpandSha256` / `hkdfExpandSha384`, which answer `null` for a length below zero or above 255 × HashLen, for an `info` longer than 2^31 − 1 bytes, and for a PRK shorter than HashLen. TLS 1.3's HKDF-Expand-Label is not here; it belongs with TLS | RFC 5869 §2, Appendix A |
|
|
55
|
+
| [`crypto/ct.ts`](./crypto/ct.ts) | `timingSafeEqual(a, b)`, which reads every byte whatever it holds, and `timingSafeEqualAt(a, aOff, b, bOff, len)` over two windows, which answers `false` for a window outside its array. Two lengths that differ answer `false` at once, because a length is public, and so does an array longer than 2^31 − 1 bytes | — |
|
|
56
|
+
| [`crypto/base64url.ts`](./crypto/base64url.ts) | `base64urlEncode(data)` and `base64urlDecode(text)`, unpadded. Decoding is strict, so every byte string has one spelling: a `=`, a character outside the alphabet, a length of 1 mod 4 or nonzero unused low bits answer `null`, as does a text longer than 2^31 − 1 bytes; `base64urlEncode` panics on such an array | RFC 4648 §5, §10 |
|
|
57
|
+
| [`crypto/x25519.ts`](./crypto/x25519.ts) | `x25519(scalar, u)` and `x25519Base(scalar)`, on ten 25.5-bit limbs in `i64`. Either answers `null` unless its arguments are `X25519_SIZE` (32) bytes; the scalar is clamped on a copy | RFC 7748 §5.2, §6.1, Wycheproof `x25519_test` |
|
|
58
|
+
| [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null` | RFC 8439 §2, RFC 9001 §5.4.4, Wycheproof `chacha20_poly1305` (all 325 cases) |
|
|
59
|
+
| [`crypto/aes.ts`](./crypto/aes.ts) | AES-128 and AES-256, bitsliced four blocks at a time with the Boyar–Peralta S-box circuit, so no table is read: `aesKey(key)` expands a 16- or 32-byte key into an `AesKey`, then `aesEncryptBlock`, `aesGcmSeal` / `aesGcmOpen` (tag last, checked in full before anything is decrypted; any non-empty IV) and `aesHeaderMask`. AES-192 is out of scope, and a wrong length answers `null`, as does an `AesKey` not made by `aesKey`; one whose H is zero opens nothing | FIPS 197, SP 800-38D, RFC 9001 §5.4.3, Wycheproof `aes_gcm` (all 316 cases; the 103 AES-192 ones check that the key is refused) |
|
|
60
|
+
| [`crypto/p256.ts`](./crypto/p256.ts) | ECDSA over P-256 with RFC 6979's deterministic nonces: `p256PublicKey(priv)` (65-byte uncompressed SEC1), `p256Sign` / `p256Verify` over a digest and `p256SignSha256` / `p256VerifySha256` over a message; a signature is `r` and `s`, 32 big-endian bytes each, not DER (`crypto/x509.ts`'s `x509DerSignatureRS` converts one). A malformed key, a point off the curve or an `r` or `s` out of range answers `null` or `false`, never a panic. ECDSA is malleable, and `p256Verify` accepts a high `s`: when `(r, s)` verifies, so does `(r, n − s)` | RFC 6979 A.2.5, Wycheproof `ecdsa_secp256r1_sha256`, `_sha256_p1363`, `_sha512` and `_sha512_p1363` |
|
|
61
|
+
| [`crypto/x509.ts`](./crypto/x509.ts) | DER, PEM and X.509 over P-256: `pemToDer` / `derToPem` (RFC 7468, padded standard base64, strict labels), `x509ParseP256PrivateKey` (SEC1 or PKCS#8), `x509ParseCertificate` / `x509ParseChain` into an `X509Certificate`, `x509VerifySignature` (ecdsa-with-SHA256 under an issuer's key), `x509DerSignatureRS` (a DER ECDSA-Sig-Value as the `r || s` `crypto/p256.ts` takes), `x509MintSelfSigned`, the 1-to-14-day self-signed P-256 certificate WebTransport's `serverCertificateHashes` accepts, and `x509CertificateHash`, its SHA-256. DER is read strictly — non-minimal lengths and integers, indefinite lengths and trailing bytes answer `null`. `x509ParseCertificate` and `x509ParseChain` parse and do not validate: there is no path validation, no extension is read or exposed, and no clock is consulted. `pemToDer` refuses a file in which any block, of any label, is malformed. `x509MintSelfSigned` refuses a common name that is not UTF-8 or holds a NUL, and its key and serial must come from `crypto.getRandomValues`, which the module cannot call itself without refusing wasm32 (X509-6) | X.690, RFC 5280, RFC 7468, RFC 5915, RFC 5208 / 5958; certificates checked against OpenSSL |
|
|
48
62
|
|
|
49
63
|
Three rules hold across the modules:
|
|
50
64
|
|
|
@@ -55,8 +69,10 @@ Three rules hold across the modules:
|
|
|
55
69
|
that is itself spent, so any `update` or `digest` on it panics. Either way,
|
|
56
70
|
copy *before* `digest` when the computation has to go on.
|
|
57
71
|
- **An all-zero X25519 result is returned, not refused.** It is what a
|
|
58
|
-
low-order `u` gives, and RFC 7748 §6.1 leaves the check to the protocol
|
|
59
|
-
|
|
72
|
+
low-order `u` gives, and RFC 7748 §6.1 leaves the check to the protocol.
|
|
73
|
+
Nothing in this tree makes that check yet (TLS 1.3, WP34 T1, is planned to),
|
|
74
|
+
so every caller doing a key exchange must refuse an all-zero answer itself,
|
|
75
|
+
with `timingSafeEqual` against 32 zero bytes, since the answer is a secret.
|
|
60
76
|
- **Constant time by construction, and verified where the check reaches.** No
|
|
61
77
|
module branches on, or indexes by, a secret: comparisons OR the differences
|
|
62
78
|
into one word and test it once, the ladder swaps with a mask and always runs
|
|
@@ -65,13 +81,22 @@ Three rules hold across the modules:
|
|
|
65
81
|
rather than a table. Every branch is on a length, a loop counter or a bit
|
|
66
82
|
position. WP34 N6 checks that the machine code kept that shape:
|
|
67
83
|
`tests/ct-asm.js` disassembles golden fixtures on x86-64 and aarch64 and
|
|
68
|
-
refuses a branch, a call
|
|
69
|
-
|
|
84
|
+
refuses a branch, a call, a secret-addressed load, or a divide or square
|
|
85
|
+
root of a secret (whose latency follows its operands) in them. It reads what
|
|
86
|
+
`clang -O2` emits for each target's baseline CPU, and only the functions a
|
|
87
|
+
fixture names: the fixtures are copies of each module's straight-line cores,
|
|
88
|
+
so the module functions themselves, and every loop, rest on review.
|
|
89
|
+
[`docs/security/ct-verification.md`](../docs/security/ct-verification.md)
|
|
90
|
+
lists every secret-handling function no fixture reads. What it holds, per
|
|
91
|
+
module, is what that module's header says:
|
|
70
92
|
- `tests/cases/ct_asm_mac` pins the two loop shapes the others are built
|
|
71
|
-
from, a full tag compare and a masked table read
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
from, a full tag compare and a masked table read; `crypto/hmac.ts`'s
|
|
94
|
+
verifiers compare in the first shape.
|
|
95
|
+
- `tests/cases/ct_asm_k1_ct`: copies of `timingSafeEqual`'s loop at 32 and
|
|
96
|
+
48 bytes and `timingSafeEqualAt`'s at a 16-byte window.
|
|
97
|
+
- `tests/cases/ct_asm_k1_base64url`: the two character maps verbatim, and
|
|
98
|
+
one encode group and one decode group. The length-driven loops, the
|
|
99
|
+
decoder's store guard and the encoder's string building remain discipline.
|
|
75
100
|
- `tests/cases/ct_asm_x25519`: X25519's field multiply, square, add,
|
|
76
101
|
subtract and multiply by a24, its conditional swap, and one step of the
|
|
77
102
|
Montgomery ladder, the check following each call into the field functions.
|
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. */
|
|
@@ -574,6 +581,19 @@ const aesCopyPrefix = (dst: u8[], src: u8[], count: i32): void => {
|
|
|
574
581
|
}
|
|
575
582
|
}
|
|
576
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Whether `key` has the shape `aesKey` makes: 10 or 14 rounds and the
|
|
586
|
+
* `(rounds + 1) * 8` round-key words the cipher reads. A key of another shape
|
|
587
|
+
* could only have been built or edited by hand, and would index past its round
|
|
588
|
+
* keys and panic, so every entry point answers `null` for it instead. The
|
|
589
|
+
* branch is on the shape, which is not secret.
|
|
590
|
+
*/
|
|
591
|
+
const aesKeyUsable = (key: AesKey): boolean => {
|
|
592
|
+
const rounds: i32 = key.rounds
|
|
593
|
+
const words: i32 = (rounds + 1) * 8
|
|
594
|
+
return (rounds === 10 || rounds === 14) && toI32(key.roundKeys.length) === words
|
|
595
|
+
}
|
|
596
|
+
|
|
577
597
|
/**
|
|
578
598
|
* The first 16 bytes of `block` encrypted, in the first 16 bytes of a fresh
|
|
579
599
|
* batch; the rest of the batch is the cipher of zeros and is not read.
|
|
@@ -605,7 +625,7 @@ export const aesKey = (key: u8[]): AesKey | null => {
|
|
|
605
625
|
|
|
606
626
|
/** One block encrypted (FIPS 197 §5.1, ECB): 16 bytes in, 16 fresh bytes out, or `null` for another length. */
|
|
607
627
|
export const aesEncryptBlock = (key: AesKey, block: u8[]): u8[] | null => {
|
|
608
|
-
if (toI32(block.length) !== AES_BLOCK) {
|
|
628
|
+
if (!aesKeyUsable(key) || toI32(block.length) !== AES_BLOCK) {
|
|
609
629
|
return null
|
|
610
630
|
}
|
|
611
631
|
const out: u8[] = new Array<u8>(AES_BLOCK)
|
|
@@ -619,7 +639,7 @@ export const aesEncryptBlock = (key: AesKey, block: u8[]): u8[] | null => {
|
|
|
619
639
|
* a sample of another length.
|
|
620
640
|
*/
|
|
621
641
|
export const aesHeaderMask = (key: AesKey, sample: u8[]): u8[] | null => {
|
|
622
|
-
if (toI32(sample.length) !== AES_BLOCK) {
|
|
642
|
+
if (!aesKeyUsable(key) || toI32(sample.length) !== AES_BLOCK) {
|
|
623
643
|
return null
|
|
624
644
|
}
|
|
625
645
|
const mask: u8[] = new Array<u8>(5)
|
|
@@ -781,6 +801,17 @@ const aesGcmJ0 = (key: AesKey, iv: u8[]): u8[] => {
|
|
|
781
801
|
return j0
|
|
782
802
|
}
|
|
783
803
|
|
|
804
|
+
/**
|
|
805
|
+
* Whether GCTR can encrypt `length` bytes from inc32(J0) without its 32-bit
|
|
806
|
+
* counter coming back round to J0's, whose block masks the tag: SP 800-38D
|
|
807
|
+
* §5.2.1.1 caps a plaintext at 2^32 - 2 blocks (2^39 - 256 bits). An array
|
|
808
|
+
* length is below 2^31, so this never refuses today; it is here so that the
|
|
809
|
+
* bound is stated where the counter is, rather than left to the width of an
|
|
810
|
+
* array length.
|
|
811
|
+
*/
|
|
812
|
+
const aesGcmLengthFits = (length: i32): boolean =>
|
|
813
|
+
(toI64(length) + toI64(15)) >> toI64(4) <= (toI64(1) << toI64(32)) - toI64(2)
|
|
814
|
+
|
|
784
815
|
/**
|
|
785
816
|
* GCTR (SP 800-38D §6.5) from inc32(J0): `dst[i] = src[i] ⊕ keystream[i]` for
|
|
786
817
|
* `i` below `length`, four blocks per pass of the cipher. The counter is the
|
|
@@ -844,16 +875,16 @@ export const aesGcmTagMask = (tagHi: u64, tagLo: u64, gotHi: u64, gotLo: u64): u
|
|
|
844
875
|
/**
|
|
845
876
|
* GCM authenticated encryption (SP 800-38D §7.1): the ciphertext followed by
|
|
846
877
|
* 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.
|
|
878
|
+
* bytes is the fast and usual one. `null` for an empty IV, for a key
|
|
879
|
+
* `aesKey` did not make, and for a plaintext longer than 2^31 - 17 bytes,
|
|
880
|
+
* whose sealed form would not fit in an array.
|
|
850
881
|
*/
|
|
851
882
|
export const aesGcmSeal = (key: AesKey, iv: u8[], aad: u8[], plaintext: u8[]): u8[] | null => {
|
|
852
883
|
const length: i32 = toI32(plaintext.length)
|
|
853
884
|
// 2^31 - 17: the longest plaintext whose sealed form, sixteen bytes longer,
|
|
854
885
|
// is still an array length.
|
|
855
886
|
const longest: i32 = 0x7fffffef
|
|
856
|
-
if (toI32(iv.length) === 0 || length > longest) {
|
|
887
|
+
if (!aesKeyUsable(key) || toI32(iv.length) === 0 || length > longest || !aesGcmLengthFits(length)) {
|
|
857
888
|
return null
|
|
858
889
|
}
|
|
859
890
|
const j0: u8[] = aesGcmJ0(key, iv)
|
|
@@ -869,18 +900,22 @@ export const aesGcmSeal = (key: AesKey, iv: u8[], aad: u8[], plaintext: u8[]): u
|
|
|
869
900
|
/**
|
|
870
901
|
* GCM authenticated decryption (SP 800-38D §7.2): `sealed` is the ciphertext
|
|
871
902
|
* 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
|
|
903
|
+
* the tag does not verify, the IV is empty, `sealed` is shorter than a tag or
|
|
904
|
+
* the key is not one `aesKey` made.
|
|
873
905
|
* The tag is computed over the received ciphertext and compared in constant
|
|
874
906
|
* time before anything is decrypted, so a forgery yields no plaintext at all.
|
|
875
907
|
*/
|
|
876
908
|
export const aesGcmOpen = (key: AesKey, iv: u8[], aad: u8[], sealed: u8[]): u8[] | null => {
|
|
877
909
|
const total: i32 = toI32(sealed.length)
|
|
878
|
-
if (toI32(iv.length) === 0 || total < AES_GCM_TAG_SIZE) {
|
|
910
|
+
if (!aesKeyUsable(key) || toI32(iv.length) === 0 || total < AES_GCM_TAG_SIZE) {
|
|
879
911
|
return null
|
|
880
912
|
}
|
|
881
913
|
// `total` is an array length and at least a tag, so neither this nor the
|
|
882
914
|
// tag's second word at `length + 8` can leave the range of an `i32`.
|
|
883
915
|
const length: i32 = total - AES_GCM_TAG_SIZE
|
|
916
|
+
if (!aesGcmLengthFits(length)) {
|
|
917
|
+
return null
|
|
918
|
+
}
|
|
884
919
|
const j0: u8[] = aesGcmJ0(key, iv)
|
|
885
920
|
const tag: u64[] = new Array<u64>(2)
|
|
886
921
|
aesGcmTag(tag, key, j0, aad, sealed, length)
|
|
@@ -890,7 +925,12 @@ export const aesGcmOpen = (key: AesKey, iv: u8[], aad: u8[], sealed: u8[]): u8[]
|
|
|
890
925
|
aesLoad64(sealed, length, total),
|
|
891
926
|
aesLoad64(sealed, length + 8, total)
|
|
892
927
|
)
|
|
893
|
-
|
|
928
|
+
// Under H = 0 GHASH is zero for every input, so the tag E(K, J0) would
|
|
929
|
+
// authenticate any ciphertext and AAD. Only a key built or edited by hand
|
|
930
|
+
// has it (for one from `aesKey` the chance is 2^-128), and such a key opens
|
|
931
|
+
// nothing. H is secret, so this is folded into the mask, not branched on.
|
|
932
|
+
const hashKeySet: u64 = ~ctEq(key.hHi | key.hLo, toU64(0))
|
|
933
|
+
if ((same & hashKeySet) === toU64(0)) {
|
|
894
934
|
return null
|
|
895
935
|
}
|
|
896
936
|
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
|
|
@@ -69,9 +70,15 @@ const base64urlSextetOf = (c: i32): i32 => {
|
|
|
69
70
|
*
|
|
70
71
|
* Bytes go into a bit accumulator eight at a time and come out six at a time,
|
|
71
72
|
* so one index walks the input and the only branches are on how many bits are
|
|
72
|
-
* waiting — which depends on the position, never on the bytes.
|
|
73
|
+
* waiting — which depends on the position, never on the bytes. An array longer
|
|
74
|
+
* than 2^31 - 1 bytes panics.
|
|
73
75
|
*/
|
|
74
76
|
export const base64urlEncode = (data: u8[]): string => {
|
|
77
|
+
// Refused before `toI32`, which saturates a longer length under
|
|
78
|
+
// `--number-mode f64` and would end the loop at a prefix.
|
|
79
|
+
if (data.length > 2147483647) {
|
|
80
|
+
panic("base64urlEncode: more than 2^31 - 1 bytes")
|
|
81
|
+
}
|
|
75
82
|
const parts: string[] = []
|
|
76
83
|
let acc: i32 = 0
|
|
77
84
|
let bits: i32 = 0
|
|
@@ -96,12 +103,18 @@ export const base64urlEncode = (data: u8[]): string => {
|
|
|
96
103
|
/**
|
|
97
104
|
* The bytes `text` spells in unpadded base64url, or `null` when it is not the
|
|
98
105
|
* one canonical spelling of any byte string — see the module comment for the
|
|
99
|
-
*
|
|
106
|
+
* five refusals.
|
|
100
107
|
*
|
|
101
108
|
* The same accumulator as `base64urlEncode`, run the other way: six bits in per
|
|
102
109
|
* character, eight out per byte.
|
|
103
110
|
*/
|
|
104
111
|
export const base64urlDecode = (text: string): u8[] | null => {
|
|
112
|
+
// A text past 2^31 - 1 bytes is refused before `toI32`, which would
|
|
113
|
+
// saturate it under `--number-mode f64` and decode only a prefix: a second
|
|
114
|
+
// spelling of that prefix's bytes, which strictness exists to refuse.
|
|
115
|
+
if (text.length > 2147483647) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
105
118
|
const n: i32 = toI32(text.length)
|
|
106
119
|
const tail: i32 = n & 3
|
|
107
120
|
// Six bits cannot finish a byte, so a single character after the last full
|
|
@@ -274,7 +274,10 @@ const chacha20Poly1305Setup = (state: u32[]): u8[] => {
|
|
|
274
274
|
* Whether the blocks `len` bytes need, from `counter` on, fit before the
|
|
275
275
|
* 32-bit counter wraps.
|
|
276
276
|
* 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.
|
|
277
|
+
* keystream, so a request that would need one answers `null` instead. The
|
|
278
|
+
* AEAD asks it too, from counter 1: an array length is below 2^31, so that
|
|
279
|
+
* never refuses today, but the bound is then stated where the counter is
|
|
280
|
+
* rather than left to the width of an array length.
|
|
278
281
|
*/
|
|
279
282
|
const chacha20CounterFits = (counter: u32, len: i32): boolean => {
|
|
280
283
|
const blocks: i64 = (toI64(len) + toI64(63)) >> toI64(6)
|
|
@@ -634,7 +637,8 @@ export const chacha20Poly1305Seal = (key: u8[], nonce: u8[], aad: u8[], plaintex
|
|
|
634
637
|
if (
|
|
635
638
|
toI32(key.length) !== CHACHA20_KEY_SIZE ||
|
|
636
639
|
toI32(nonce.length) !== CHACHA20POLY1305_NONCE_SIZE ||
|
|
637
|
-
len > longest
|
|
640
|
+
len > longest ||
|
|
641
|
+
!chacha20CounterFits(1, len)
|
|
638
642
|
) {
|
|
639
643
|
return null
|
|
640
644
|
}
|
|
@@ -668,6 +672,9 @@ export const chacha20Poly1305Open = (key: u8[], nonce: u8[], aad: u8[], sealed:
|
|
|
668
672
|
return null
|
|
669
673
|
}
|
|
670
674
|
const len: i32 = total - POLY1305_TAG_SIZE
|
|
675
|
+
if (!chacha20CounterFits(1, len)) {
|
|
676
|
+
return null
|
|
677
|
+
}
|
|
671
678
|
const state: u32[] = chacha20State(key, 0, nonce, 0)
|
|
672
679
|
const polyKey: u8[] = chacha20Poly1305Setup(state)
|
|
673
680
|
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.
|
package/std/crypto/hkdf.ts
CHANGED
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
* The counter `i` is one byte, so `L` is at most 255 × HashLen (§2.3): 8160
|
|
17
17
|
* bytes for SHA-256 and 12240 for SHA-384. A longer `L`, or a negative one,
|
|
18
18
|
* answers `null` rather than panicking, because `L` usually comes from a
|
|
19
|
-
* protocol field; `L = 0` answers an empty array.
|
|
19
|
+
* protocol field; `L = 0` answers an empty array. So does an `info` longer
|
|
20
|
+
* than 2^31 - 1 bytes, which `toI32` would cut to a prefix under
|
|
21
|
+
* `--number-mode f64`, so that two `info`s sharing it gave one key, and a PRK
|
|
22
|
+
* shorter than HashLen, which §2.3 does not allow: an empty one is a key
|
|
23
|
+
* anybody can compute with.
|
|
20
24
|
*
|
|
21
25
|
* HKDF-Expand-Label, TLS 1.3's wrapper around `expand`, belongs with TLS and is
|
|
22
26
|
* not here. Written from RFC 5869, not ported from another implementation.
|
|
@@ -64,10 +68,16 @@ export const hkdfExtractSha384 = (salt: u8[], ikm: u8[]): u8[] => hmacSha384(hkd
|
|
|
64
68
|
/**
|
|
65
69
|
* HKDF-Expand with HMAC-SHA-256 (RFC 5869 §2.3): `length` bytes of output
|
|
66
70
|
* keying material from `prk` and `info`, or `null` when `length` is negative
|
|
67
|
-
* or past 255 × 32
|
|
71
|
+
* or past 255 × 32, when `info` is longer than 2^31 - 1 bytes, or when `prk`
|
|
72
|
+
* is shorter than 32 bytes.
|
|
68
73
|
*/
|
|
69
74
|
export const hkdfExpandSha256 = (prk: u8[], info: u8[], length: i32): u8[] | null => {
|
|
70
|
-
if (
|
|
75
|
+
if (
|
|
76
|
+
length < 0 ||
|
|
77
|
+
length > HKDF_MAX_BLOCKS * SHA256_SIZE ||
|
|
78
|
+
info.length > 2147483647 ||
|
|
79
|
+
toI32(prk.length) < SHA256_SIZE
|
|
80
|
+
) {
|
|
71
81
|
return null
|
|
72
82
|
}
|
|
73
83
|
const out: u8[] = new Array<u8>(length)
|
|
@@ -92,10 +102,16 @@ export const hkdfExpandSha256 = (prk: u8[], info: u8[], length: i32): u8[] | nul
|
|
|
92
102
|
/**
|
|
93
103
|
* HKDF-Expand with HMAC-SHA-384 (RFC 5869 §2.3): `length` bytes of output
|
|
94
104
|
* keying material from `prk` and `info`, or `null` when `length` is negative
|
|
95
|
-
* or past 255 × 48
|
|
105
|
+
* or past 255 × 48, when `info` is longer than 2^31 - 1 bytes, or when `prk`
|
|
106
|
+
* is shorter than 48 bytes.
|
|
96
107
|
*/
|
|
97
108
|
export const hkdfExpandSha384 = (prk: u8[], info: u8[], length: i32): u8[] | null => {
|
|
98
|
-
if (
|
|
109
|
+
if (
|
|
110
|
+
length < 0 ||
|
|
111
|
+
length > HKDF_MAX_BLOCKS * SHA384_SIZE ||
|
|
112
|
+
info.length > 2147483647 ||
|
|
113
|
+
toI32(prk.length) < SHA384_SIZE
|
|
114
|
+
) {
|
|
99
115
|
return null
|
|
100
116
|
}
|
|
101
117
|
const out: u8[] = new Array<u8>(length)
|
package/std/crypto/hmac.ts
CHANGED
|
@@ -127,15 +127,31 @@ export class HmacSha384 {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* The HMAC-SHA-256 of all of `data` under `key`, as a fresh 32-byte array.
|
|
132
|
+
*
|
|
133
|
+
* `data` longer than 2^31 - 1 bytes panics, as `sha256` does and for its
|
|
134
|
+
* reason: under `--number-mode f64` the tag would be of a prefix, and a tag
|
|
135
|
+
* that verifies every message sharing that prefix is a forgery. A key that
|
|
136
|
+
* long reaches `sha256` in the constructor and panics there.
|
|
137
|
+
*/
|
|
131
138
|
export const hmacSha256 = (key: u8[], data: u8[]): u8[] => {
|
|
139
|
+
if (data.length > 2147483647) {
|
|
140
|
+
panic("hmacSha256: a message longer than 2^31 - 1 bytes")
|
|
141
|
+
}
|
|
132
142
|
const mac = new HmacSha256(key)
|
|
133
143
|
mac.update(data, HMAC_FROM, toI32(data.length))
|
|
134
144
|
return mac.digest()
|
|
135
145
|
}
|
|
136
146
|
|
|
137
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* The HMAC-SHA-384 of all of `data` under `key`, as a fresh 48-byte array.
|
|
149
|
+
* `data` longer than 2^31 - 1 bytes panics, as in `hmacSha256`.
|
|
150
|
+
*/
|
|
138
151
|
export const hmacSha384 = (key: u8[], data: u8[]): u8[] => {
|
|
152
|
+
if (data.length > 2147483647) {
|
|
153
|
+
panic("hmacSha384: a message longer than 2^31 - 1 bytes")
|
|
154
|
+
}
|
|
139
155
|
const mac = new HmacSha384(key)
|
|
140
156
|
mac.update(data, HMAC_FROM, toI32(data.length))
|
|
141
157
|
return mac.digest()
|
package/std/crypto/p256.ts
CHANGED
|
@@ -4273,6 +4273,11 @@ export const p256Sign = (priv: u8[], digest: u8[]): u8[] | null => {
|
|
|
4273
4273
|
* that is not a point on the curve, and a sum that is the identity all answer
|
|
4274
4274
|
* `false`. Every value here is public, so the answer may be reached by
|
|
4275
4275
|
* branches; the multiplications are the constant-time ones all the same.
|
|
4276
|
+
*
|
|
4277
|
+
* ECDSA is malleable, and so is this: when `(r, s)` verifies, `(r, n - s)`
|
|
4278
|
+
* verifies too, as FIPS 186-4 and SEC 1 have it. Nothing here asks for the
|
|
4279
|
+
* lower `s`, so a protocol that needs one signature per message and key (a
|
|
4280
|
+
* signature used as an identifier, say) has to refuse the upper half itself.
|
|
4276
4281
|
*/
|
|
4277
4282
|
export const p256Verify = (pub: u8[], digest: u8[], sig: u8[]): boolean => {
|
|
4278
4283
|
if (toI32(sig.length) !== 64) {
|
package/std/crypto/sha256.ts
CHANGED
|
@@ -435,8 +435,19 @@ export class Sha256 {
|
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
-
/**
|
|
438
|
+
/**
|
|
439
|
+
* The SHA-256 digest of all of `data`, as a fresh 32-byte array.
|
|
440
|
+
*
|
|
441
|
+
* An array longer than 2^31 - 1 bytes panics. A window's length is an `i32`,
|
|
442
|
+
* and under `--number-mode f64` `toI32` of a longer length saturates, so
|
|
443
|
+
* hashing `toI32(data.length)` bytes would answer the digest of a prefix: one
|
|
444
|
+
* digest for every message that shares its first 2^31 - 1 bytes. The length
|
|
445
|
+
* is compared as the `number` it is, before any `toI32`.
|
|
446
|
+
*/
|
|
439
447
|
export const sha256 = (data: u8[]): u8[] => {
|
|
448
|
+
if (data.length > 2147483647) {
|
|
449
|
+
panic("sha256: a message longer than 2^31 - 1 bytes")
|
|
450
|
+
}
|
|
440
451
|
const hasher: Sha256 = new Sha256()
|
|
441
452
|
const from: i32 = 0
|
|
442
453
|
hasher.update(data, from, toI32(data.length))
|
package/std/crypto/sha512.ts
CHANGED
|
@@ -495,15 +495,29 @@ export class Sha384 {
|
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
/**
|
|
498
|
+
/**
|
|
499
|
+
* The SHA-512 digest of all of `data`, as a fresh 64-byte array. An array
|
|
500
|
+
* longer than 2^31 - 1 bytes panics, for the reason `sha256` gives: a longer
|
|
501
|
+
* length does not survive `toI32` under `--number-mode f64`, and the digest
|
|
502
|
+
* would be of a prefix.
|
|
503
|
+
*/
|
|
499
504
|
export const sha512 = (data: u8[]): u8[] => {
|
|
505
|
+
if (data.length > 2147483647) {
|
|
506
|
+
panic("sha512: a message longer than 2^31 - 1 bytes")
|
|
507
|
+
}
|
|
500
508
|
const hasher = new Sha512()
|
|
501
509
|
hasher.update(data, OFFSET_ZERO, toI32(data.length))
|
|
502
510
|
return hasher.digest()
|
|
503
511
|
}
|
|
504
512
|
|
|
505
|
-
/**
|
|
513
|
+
/**
|
|
514
|
+
* The SHA-384 digest of all of `data`, as a fresh 48-byte array. An array
|
|
515
|
+
* longer than 2^31 - 1 bytes panics, as in `sha512`.
|
|
516
|
+
*/
|
|
506
517
|
export const sha384 = (data: u8[]): u8[] => {
|
|
518
|
+
if (data.length > 2147483647) {
|
|
519
|
+
panic("sha384: a message longer than 2^31 - 1 bytes")
|
|
520
|
+
}
|
|
507
521
|
const hasher = new Sha384()
|
|
508
522
|
hasher.update(data, OFFSET_ZERO, toI32(data.length))
|
|
509
523
|
return hasher.digest()
|
package/std/crypto/x25519.ts
CHANGED
|
@@ -480,9 +480,10 @@ const x25519Ladder = (k: u8[], u: i64[]): u8[] => {
|
|
|
480
480
|
*
|
|
481
481
|
* Answers `null` unless both arguments are exactly `X25519_SIZE` bytes. An
|
|
482
482
|
* all-zero answer is **returned, not refused**: it is what a low-order `u`
|
|
483
|
-
* gives, and RFC 7748 §6.1 leaves checking for it to the protocol
|
|
484
|
-
*
|
|
485
|
-
*
|
|
483
|
+
* gives, and RFC 7748 §6.1 leaves checking for it to the protocol. Nothing in
|
|
484
|
+
* this tree makes that check for you yet (TLS 1.3, WP34 T1, is planned to), so
|
|
485
|
+
* every caller doing a key exchange must refuse an all-zero answer itself —
|
|
486
|
+
* with `timingSafeEqual` against 32 zero bytes, since the answer is a secret.
|
|
486
487
|
*/
|
|
487
488
|
export const x25519 = (scalar: u8[], u: u8[]): u8[] | null => {
|
|
488
489
|
if (toI32(scalar.length) !== 32 || toI32(u.length) !== 32) {
|