@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/x509.ts
CHANGED
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* import { x509MintSelfSigned, x509CertificateHash, derToPem } from "nish/crypto/x509";
|
|
8
8
|
*
|
|
9
|
+
* // Both secrets come from the kernel's CSPRNG; a predictable key or serial is the caller's bug.
|
|
10
|
+
* const drawn: u8[] = new Array<u8>(32);
|
|
11
|
+
* crypto.getRandomValues(drawn);
|
|
12
|
+
* const priv: Secret<u8[]> = secret(drawn); // nish:secret; p256PublicKey(priv) === null: draw again (odds 2^-32)
|
|
9
13
|
* const serial: u8[] = new Array<u8>(16);
|
|
10
14
|
* crypto.getRandomValues(serial);
|
|
11
15
|
* serial[0] = serial[0] & toU8(0x7f); // positive, so it stays 16 octets
|
|
12
16
|
* const der: u8[] | null = x509MintSelfSigned(priv, "localhost", toI64(Date.now()), 14, serial);
|
|
17
|
+
* wipe(priv);
|
|
13
18
|
* // x509CertificateHash(der) is what serverCertificateHashes names;
|
|
14
19
|
* // derToPem(der, "CERTIFICATE") is the file a TLS stack loads.
|
|
15
20
|
*
|
|
@@ -28,7 +33,8 @@
|
|
|
28
33
|
* `null`, never a panic. The reader looks at what this module uses — the
|
|
29
34
|
* version, serial, algorithms, names as opaque bytes, validity, the subject
|
|
30
35
|
* public key and the signature — and checks the order of what follows the key
|
|
31
|
-
* (`[1]`, `[2]`, `[3]`) without parsing extensions
|
|
36
|
+
* (`[1]`, `[2]`, `[3]`) without parsing extensions; the mint writes two of its
|
|
37
|
+
* own (below).
|
|
32
38
|
*
|
|
33
39
|
* **PEM is RFC 7468's strict grammar**, with standard base64 (`+` and `/`,
|
|
34
40
|
* padded with `=`), not `base64url`: 64 characters a line and a shorter last
|
|
@@ -42,14 +48,27 @@
|
|
|
42
48
|
* requirements"): X.509 v3, an ECDSA P-256 key signed with ecdsa-with-SHA256,
|
|
43
49
|
* and a validity period of at most fourteen days — `notAfter - notBefore`,
|
|
44
50
|
* which is what Chromium compares, so `days` is 1 to 14. Subject and issuer
|
|
45
|
-
* are the same single common name, as a UTF8String. It carries **
|
|
46
|
-
* extensions
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* are the same single common name, as a UTF8String. It carries **two
|
|
52
|
+
* extensions, both critical and both constant**: basicConstraints with cA
|
|
53
|
+
* FALSE and keyUsage with digitalSignature alone. The W3C text requires no
|
|
54
|
+
* extension, but Chromium does: the verifier behind `serverCertificateHashes`,
|
|
55
|
+
* quiche's `WebTransportFingerprintProofVerifier`, parses the certificate
|
|
56
|
+
* with `CertificateView::ParseSingleCertificate` before it compares the hash,
|
|
57
|
+
* and that answers null when `[3] extensions` is absent
|
|
58
|
+
* (`quiche/quic/core/crypto/certificate_view.cc:395`, at the quiche revision
|
|
59
|
+
* 62826931 Chromium 141 pins), so the handshake fails with
|
|
60
|
+
* CERTIFICATE_VERIFY_FAILED. It also holds every extension to `SEQUENCE {
|
|
61
|
+
* OID, BOOLEAN OPTIONAL, OCTET STRING }` (`:413`–`:432`) and reads
|
|
62
|
+
* subjectAltName only when one is present (`:434`), so no name is needed
|
|
63
|
+
* there. The two chosen say what the certificate is, an end entity whose key
|
|
64
|
+
* only signs handshakes, so a verifier that does build a path need not fall
|
|
65
|
+
* back on its defaults; OpenSSL verifies it as its own trust anchor
|
|
66
|
+
* (`tests/link/crypto_x509`'s header has the commands; X509-9 in
|
|
67
|
+
* docs/security/crypto-x509.md). The key, the serial and the time are
|
|
68
|
+
* parameters so the golden certificate is deterministic; a caller passes
|
|
69
|
+
* `Date.now()` and, for the key and the serial, bytes from
|
|
70
|
+
* `crypto.getRandomValues`. This module does not draw them itself,
|
|
71
|
+
* because a call to the operating system would stop it compiling for wasm32.
|
|
53
72
|
*
|
|
54
73
|
* **Constant time.** A private key is the one secret that passes through here:
|
|
55
74
|
* it is base64-decoded from PEM, lifted out of its DER, and handed to
|
|
@@ -65,10 +84,18 @@
|
|
|
65
84
|
* public key stored beside it compares public values. None of this is under
|
|
66
85
|
* the disassembly check; it is discipline.
|
|
67
86
|
*
|
|
87
|
+
* **The private key is a `Secret`** (`nish:secret`, docs/LANGUAGE.md
|
|
88
|
+
* "Secrets"). `x509ParseP256PrivateKey` answers one and `x509MintSelfSigned`
|
|
89
|
+
* takes one; the scalar is copied out of its DER once, into the `Secret`, and
|
|
90
|
+
* every block decoded from the PEM is wiped before the parse returns (X509-7,
|
|
91
|
+
* docs/security/crypto-x509.md). The PEM text is the caller's string, which
|
|
92
|
+
* nothing in the language can wipe.
|
|
93
|
+
*
|
|
68
94
|
* Private names carry the `x509` prefix because a `std/` module's private
|
|
69
95
|
* functions share the importing program's flat symbol namespace
|
|
70
96
|
* (`docs/wp26-stdlib.md` §3e).
|
|
71
97
|
*/
|
|
98
|
+
import { Secret, secret, wipe } from "nish:secret"
|
|
72
99
|
import { p256PublicKey, p256SignSha256, p256VerifySha256 } from "nish/crypto/p256"
|
|
73
100
|
import { sha256 } from "nish/crypto/sha256"
|
|
74
101
|
|
|
@@ -288,12 +315,29 @@ const x509DerSmallInteger = (der: u8[], e: X509DerElement): i32 => {
|
|
|
288
315
|
return toI32(der[e.start])
|
|
289
316
|
}
|
|
290
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Whether `e`'s contents are a DER BIT STRING's, whatever its tag (an IMPLICIT
|
|
320
|
+
* one keeps the grammar): an unused-bits count of 0 to 7, none without an
|
|
321
|
+
* octet to hold them, and those bits zero (X.690 §8.6.2 and §11.2.1).
|
|
322
|
+
*/
|
|
323
|
+
const x509DerBitStringSound = (der: u8[], e: X509DerElement): boolean => {
|
|
324
|
+
const n: i32 = e.end - e.start
|
|
325
|
+
if (n < 1) {
|
|
326
|
+
return false
|
|
327
|
+
}
|
|
328
|
+
const unused: i32 = toI32(der[e.start])
|
|
329
|
+
if (unused > 7 || (n === 1 && unused !== 0)) {
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
return (toI32(der[e.end - 1]) & ((1 << unused) - 1)) === 0
|
|
333
|
+
}
|
|
334
|
+
|
|
291
335
|
/**
|
|
292
336
|
* The octets of a BIT STRING that has no unused bits, or `null`. Keys and
|
|
293
337
|
* signatures are whole octets, so the leading unused-bits count must be 0.
|
|
294
338
|
*/
|
|
295
339
|
const x509DerBitStringOctets = (der: u8[], e: X509DerElement): u8[] | null => {
|
|
296
|
-
if (e.tag !== X509_TAG_BIT_STRING ||
|
|
340
|
+
if (e.tag !== X509_TAG_BIT_STRING || !x509DerBitStringSound(der, e) || toI32(der[e.start]) !== 0) {
|
|
297
341
|
return null
|
|
298
342
|
}
|
|
299
343
|
return x509DerSlice(der, e.start + 1, e.end)
|
|
@@ -468,9 +512,11 @@ const x509DerUnsignedInto = (der: u8[], e: X509DerElement, out: u8[], off: i32):
|
|
|
468
512
|
/**
|
|
469
513
|
* `r || s`, 32 octets each, out of an ECDSA-Sig-Value `SEQUENCE { INTEGER r,
|
|
470
514
|
* INTEGER s }` (RFC 5480 §2.2 by way of RFC 3279 §2.2.3), or `null` unless it
|
|
471
|
-
* is exactly one with both integers minimal, positive and below 2^256.
|
|
515
|
+
* is exactly one with both integers minimal, positive and below 2^256. This is
|
|
516
|
+
* the form a TLS `CertificateVerify` or an X.509 signature carries, and
|
|
517
|
+
* `nish/crypto/p256` takes the `r || s` it answers.
|
|
472
518
|
*/
|
|
473
|
-
const x509DerSignatureRS = (sig: u8[]): u8[] | null => {
|
|
519
|
+
export const x509DerSignatureRS = (sig: u8[]): u8[] | null => {
|
|
474
520
|
const total: i32 = toI32(sig.length)
|
|
475
521
|
const seq: X509DerElement | null = x509DerExpect(sig, 0, total, X509_TAG_SEQUENCE)
|
|
476
522
|
if (seq === null || seq.end !== total) {
|
|
@@ -605,6 +651,48 @@ const x509DerTimeAt = (seconds: i64): u8[] => {
|
|
|
605
651
|
return x509DerTlv(utc ? X509_TAG_UTC_TIME : X509_TAG_GENERALIZED_TIME, text)
|
|
606
652
|
}
|
|
607
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Whether `bytes` is well-formed UTF-8 (RFC 3629 §4) with no NUL: no stray
|
|
656
|
+
* continuation octet, no truncated sequence, no overlong form, no surrogate and
|
|
657
|
+
* nothing past U+10FFFF. A string in the language is bytes and need not be
|
|
658
|
+
* UTF-8, and a UTF8String that is not UTF-8 is a malformed certificate; a NUL
|
|
659
|
+
* is refused because a name that reads differently to C is the old
|
|
660
|
+
* `localhost\0.example` confusion.
|
|
661
|
+
*/
|
|
662
|
+
const x509IsUtf8Name = (bytes: u8[]): boolean => {
|
|
663
|
+
// Continuation octets still owed, and the range the next one must fall in:
|
|
664
|
+
// 80..BF, narrowed after E0, ED, F0 and F4 (Unicode 15 §3.9, Table 3-7).
|
|
665
|
+
let need: i32 = 0
|
|
666
|
+
let lo: i32 = 0x80
|
|
667
|
+
let hi: i32 = 0xbf
|
|
668
|
+
for (let k: i32 = 0; k < toI32(bytes.length); k++) {
|
|
669
|
+
const b: i32 = toI32(bytes[k])
|
|
670
|
+
if (need > 0) {
|
|
671
|
+
if (b < lo || b > hi) {
|
|
672
|
+
return false
|
|
673
|
+
}
|
|
674
|
+
need = need - 1
|
|
675
|
+
lo = 0x80
|
|
676
|
+
hi = 0xbf
|
|
677
|
+
} else if (b === 0) {
|
|
678
|
+
return false
|
|
679
|
+
} else if (b >= 0xc2 && b <= 0xdf) {
|
|
680
|
+
need = 1
|
|
681
|
+
} else if (b >= 0xe0 && b <= 0xef) {
|
|
682
|
+
need = 2
|
|
683
|
+
lo = b === 0xe0 ? 0xa0 : 0x80
|
|
684
|
+
hi = b === 0xed ? 0x9f : 0xbf
|
|
685
|
+
} else if (b >= 0xf0 && b <= 0xf4) {
|
|
686
|
+
need = 3
|
|
687
|
+
lo = b === 0xf0 ? 0x90 : 0x80
|
|
688
|
+
hi = b === 0xf4 ? 0x8f : 0xbf
|
|
689
|
+
} else if (b >= 0x80) {
|
|
690
|
+
return false
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return need === 0
|
|
694
|
+
}
|
|
695
|
+
|
|
608
696
|
/** The bytes of `text`, as the language holds a string. */
|
|
609
697
|
const x509Bytes = (text: string): u8[] => {
|
|
610
698
|
const out: u8[] = []
|
|
@@ -619,8 +707,15 @@ const x509Bytes = (text: string): u8[] => {
|
|
|
619
707
|
// PEM (RFC 7468) and standard base64 (RFC 4648 §4)
|
|
620
708
|
// ---------------------------------------------------------------------------
|
|
621
709
|
|
|
622
|
-
/**
|
|
623
|
-
|
|
710
|
+
/**
|
|
711
|
+
* All ones when `lo <= c <= hi`, else zero, without a branch; `c` is a byte or
|
|
712
|
+
* a sextet. The subtractions are `u32`'s, which wrap by definition, rather
|
|
713
|
+
* than checked `i32` ones, whose check would be a branch on a byte of what may
|
|
714
|
+
* be a private key's PEM; the sign bit `>> 31` reads is the same.
|
|
715
|
+
* `x509Base64CharOf` and `x509Base64SextetOf` sum in `u32` for the same reason.
|
|
716
|
+
*/
|
|
717
|
+
const x509RangeMask = (c: i32, lo: i32, hi: i32): i32 =>
|
|
718
|
+
toI32((toU32(lo) - 1 - toU32(c)) & (toU32(c) - toU32(hi) - 1)) >> 31
|
|
624
719
|
|
|
625
720
|
/**
|
|
626
721
|
* The base64 character for the sextet `v`: `'A' + v`, stepped by the gap to
|
|
@@ -628,12 +723,14 @@ const x509RangeMask = (c: i32, lo: i32, hi: i32): i32 => ((lo - 1 - c) & (c - hi
|
|
|
628
723
|
* 63 on `/`.
|
|
629
724
|
*/
|
|
630
725
|
const x509Base64CharOf = (v: i32): i32 =>
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
726
|
+
toI32(
|
|
727
|
+
65 +
|
|
728
|
+
toU32(v) +
|
|
729
|
+
toU32(x509RangeMask(v, 26, 63) & 6) -
|
|
730
|
+
toU32(x509RangeMask(v, 52, 63) & 75) -
|
|
731
|
+
toU32(x509RangeMask(v, 62, 63) & 15) +
|
|
732
|
+
toU32(x509RangeMask(v, 63, 63) & 3)
|
|
733
|
+
)
|
|
637
734
|
|
|
638
735
|
/** The sextet the byte `c` stands for in base64, or `-1` when it is not in the alphabet. */
|
|
639
736
|
const x509Base64SextetOf = (c: i32): i32 => {
|
|
@@ -642,7 +739,12 @@ const x509Base64SextetOf = (c: i32): i32 => {
|
|
|
642
739
|
const digit: i32 = x509RangeMask(c, 48, 57)
|
|
643
740
|
const plus: i32 = x509RangeMask(c, 43, 43)
|
|
644
741
|
const slash: i32 = x509RangeMask(c, 47, 47)
|
|
645
|
-
const value: i32 =
|
|
742
|
+
const value: i32 =
|
|
743
|
+
(upper & toI32(toU32(c) - 65)) |
|
|
744
|
+
(lower & toI32(toU32(c) - 71)) |
|
|
745
|
+
(digit & toI32(toU32(c) + 4)) |
|
|
746
|
+
(plus & 62) |
|
|
747
|
+
(slash & 63)
|
|
646
748
|
return value | ~(upper | lower | digit | plus | slash)
|
|
647
749
|
}
|
|
648
750
|
|
|
@@ -790,11 +892,13 @@ export const pemToDer = (pem: string, label: string): u8[][] | null => {
|
|
|
790
892
|
if (line !== `-----END ${inside}-----`) {
|
|
791
893
|
return null
|
|
792
894
|
}
|
|
895
|
+
// Every block is decoded, whatever its label: a malformed block beside the
|
|
896
|
+
// one asked for is refused, not skipped (docs/security/crypto-x509.md, X509-1).
|
|
897
|
+
const der: u8[] | null = x509Base64Decode(body.join(""))
|
|
898
|
+
if (der === null) {
|
|
899
|
+
return null
|
|
900
|
+
}
|
|
793
901
|
if (inside === label) {
|
|
794
|
-
const der: u8[] | null = x509Base64Decode(body.join(""))
|
|
795
|
-
if (der === null) {
|
|
796
|
-
return null
|
|
797
|
-
}
|
|
798
902
|
out.push(der)
|
|
799
903
|
}
|
|
800
904
|
inside = ""
|
|
@@ -824,7 +928,7 @@ export const pemToDer = (pem: string, label: string): u8[][] | null => {
|
|
|
824
928
|
* optional `[1]` public key, which must be the key's own. `null` otherwise,
|
|
825
929
|
* or when the scalar is 0 or not below n.
|
|
826
930
|
*/
|
|
827
|
-
const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): u8[] | null => {
|
|
931
|
+
const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): Secret<u8[]> | null => {
|
|
828
932
|
const seq: X509DerElement | null = x509DerExpect(der, start, end, X509_TAG_SEQUENCE)
|
|
829
933
|
if (seq === null || seq.end !== end) {
|
|
830
934
|
return null
|
|
@@ -837,7 +941,6 @@ const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): u8[]
|
|
|
837
941
|
if (key === null || key.end - key.start !== 32) {
|
|
838
942
|
return null
|
|
839
943
|
}
|
|
840
|
-
const priv: u8[] = x509DerSlice(der, key.start, key.end)
|
|
841
944
|
let at: i32 = key.end
|
|
842
945
|
let curve: boolean = false
|
|
843
946
|
let stored: u8[] | null = null
|
|
@@ -867,12 +970,13 @@ const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): u8[]
|
|
|
867
970
|
if (at !== seq.end || (needCurve && !curve)) {
|
|
868
971
|
return null
|
|
869
972
|
}
|
|
870
|
-
//
|
|
973
|
+
// The scalar is copied out of the DER only once the structure is known to
|
|
974
|
+
// be sound, straight into the `Secret` that owns it (X509-7). The check
|
|
975
|
+
// refuses 0 and n and above, and gives the point to hold `stored` against.
|
|
976
|
+
const priv: Secret<u8[]> = secret(x509DerSlice(der, key.start, key.end))
|
|
871
977
|
const pub: u8[] | null = p256PublicKey(priv)
|
|
872
|
-
if (pub === null) {
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
if (stored !== null && !x509BytesEqual(stored, pub)) {
|
|
978
|
+
if (pub === null || (stored !== null && !x509BytesEqual(stored, pub))) {
|
|
979
|
+
wipe(priv)
|
|
876
980
|
return null
|
|
877
981
|
}
|
|
878
982
|
return priv
|
|
@@ -882,10 +986,11 @@ const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): u8[]
|
|
|
882
986
|
* The scalar of a PKCS#8 PrivateKeyInfo (RFC 5208 §5) or OneAsymmetricKey
|
|
883
987
|
* (RFC 5958 §2): version 0 or 1, the algorithm id-ecPublicKey with the
|
|
884
988
|
* prime256v1 curve, and an ECPrivateKey inside the OCTET STRING, followed by
|
|
885
|
-
* the optional `[0]` attributes and — in version 1 —
|
|
886
|
-
*
|
|
989
|
+
* the optional `[0]` attributes, which are skipped, and — in version 1 — the
|
|
990
|
+
* optional `[1]` public key, which must be a BIT STRING of the key's own point
|
|
991
|
+
* (X509-8 in docs/security/crypto-x509.md).
|
|
887
992
|
*/
|
|
888
|
-
const x509Pkcs8Key = (der: u8[]): u8[] | null => {
|
|
993
|
+
const x509Pkcs8Key = (der: u8[]): Secret<u8[]> | null => {
|
|
889
994
|
const total: i32 = toI32(der.length)
|
|
890
995
|
const seq: X509DerElement | null = x509DerExpect(der, 0, total, X509_TAG_SEQUENCE)
|
|
891
996
|
if (seq === null || seq.end !== total) {
|
|
@@ -915,44 +1020,88 @@ const x509Pkcs8Key = (der: u8[]): u8[] | null => {
|
|
|
915
1020
|
}
|
|
916
1021
|
at = attributes.end
|
|
917
1022
|
}
|
|
1023
|
+
let stored: u8[] | null = null
|
|
918
1024
|
if (v === 1 && x509DerPeek(der, at, seq.end) === 0x81) {
|
|
919
|
-
const
|
|
920
|
-
if (
|
|
1025
|
+
const outer: X509DerElement | null = x509DerRead(der, at, seq.end)
|
|
1026
|
+
if (outer === null) {
|
|
921
1027
|
return null
|
|
922
1028
|
}
|
|
923
|
-
|
|
1029
|
+
// `[1] IMPLICIT BIT STRING`: the 0x81 replaces the BIT STRING's own tag,
|
|
1030
|
+
// so its contents are the unused-bits octet and then the point.
|
|
1031
|
+
const bits: X509DerElement = new X509DerElement(X509_TAG_BIT_STRING, outer.at, outer.start, outer.end)
|
|
1032
|
+
stored = x509DerBitStringOctets(der, bits)
|
|
1033
|
+
if (stored === null) {
|
|
1034
|
+
return null
|
|
1035
|
+
}
|
|
1036
|
+
at = outer.end
|
|
924
1037
|
}
|
|
925
1038
|
if (at !== seq.end) {
|
|
926
1039
|
return null
|
|
927
1040
|
}
|
|
928
|
-
|
|
1041
|
+
const priv: Secret<u8[]> | null = x509Sec1Key(der, key.start, key.end, false)
|
|
1042
|
+
if (priv === null || stored === null) {
|
|
1043
|
+
return priv
|
|
1044
|
+
}
|
|
1045
|
+
// The same rule as the SEC1 key's own `[1]`: the stored point is the key's.
|
|
1046
|
+
const pub: u8[] | null = p256PublicKey(priv)
|
|
1047
|
+
if (pub === null || !x509BytesEqual(stored, pub)) {
|
|
1048
|
+
wipe(priv)
|
|
1049
|
+
return null
|
|
1050
|
+
}
|
|
1051
|
+
return priv
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** Zero every block `pemToDer` decoded: a private key's DER is key material, refused or not. */
|
|
1055
|
+
const x509WipeBlocks = (blocks: u8[][] | null): void => {
|
|
1056
|
+
if (blocks === null) {
|
|
1057
|
+
return
|
|
1058
|
+
}
|
|
1059
|
+
for (const block of blocks) {
|
|
1060
|
+
wipe(block)
|
|
1061
|
+
}
|
|
929
1062
|
}
|
|
930
1063
|
|
|
931
1064
|
/**
|
|
932
|
-
* The 32-byte P-256 private key in `pem
|
|
933
|
-
* RFC 5915, which must name its curve) or one
|
|
934
|
-
* PKCS#8, RFC 5208 or RFC 5958), and not both.
|
|
935
|
-
* more than one, for any curve but P-256, for a
|
|
936
|
-
* a stored public key that is not the key's own,
|
|
937
|
-
* An `ENCRYPTED PRIVATE KEY` is a different label,
|
|
1065
|
+
* The 32-byte P-256 private key in `pem`, as the `Secret` it is: one `EC
|
|
1066
|
+
* PRIVATE KEY` block (SEC1, RFC 5915, which must name its curve) or one
|
|
1067
|
+
* `PRIVATE KEY` block (unencrypted PKCS#8, RFC 5208 or RFC 5958), and not both.
|
|
1068
|
+
* `null` for no such block or more than one, for any curve but P-256, for a
|
|
1069
|
+
* scalar of 0 or n or more, for a stored public key that is not the key's own,
|
|
1070
|
+
* and for anything malformed. An `ENCRYPTED PRIVATE KEY` is a different label,
|
|
1071
|
+
* and answers `null`. Every block decoded from `pem` is wiped before this
|
|
1072
|
+
* returns, whichever answer it gives; `pem` itself is the caller's string,
|
|
1073
|
+
* which nothing can wipe (X509-7 in docs/security/crypto-x509.md).
|
|
938
1074
|
*/
|
|
939
|
-
export const x509ParseP256PrivateKey = (pem: string): u8[] | null => {
|
|
1075
|
+
export const x509ParseP256PrivateKey = (pem: string): Secret<u8[]> | null => {
|
|
940
1076
|
const sec1: u8[][] | null = pemToDer(pem, "EC PRIVATE KEY")
|
|
941
1077
|
const pkcs8: u8[][] | null = pemToDer(pem, "PRIVATE KEY")
|
|
1078
|
+
let key: Secret<u8[]> | null = null
|
|
942
1079
|
if (sec1 !== null && pkcs8 === null && toI32(sec1.length) === 1) {
|
|
943
1080
|
const der: u8[] = sec1[0]
|
|
944
|
-
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1081
|
+
key = x509Sec1Key(der, 0, toI32(der.length), true)
|
|
1082
|
+
} else if (pkcs8 !== null && sec1 === null && toI32(pkcs8.length) === 1) {
|
|
1083
|
+
key = x509Pkcs8Key(pkcs8[0])
|
|
1084
|
+
}
|
|
1085
|
+
// X509-7: the decoded DER holds the scalar too, whichever block it was.
|
|
1086
|
+
x509WipeBlocks(sec1)
|
|
1087
|
+
x509WipeBlocks(pkcs8)
|
|
1088
|
+
return key
|
|
950
1089
|
}
|
|
951
1090
|
|
|
952
1091
|
// ---------------------------------------------------------------------------
|
|
953
1092
|
// Certificates (RFC 5280 §4.1)
|
|
954
1093
|
// ---------------------------------------------------------------------------
|
|
955
1094
|
|
|
1095
|
+
/**
|
|
1096
|
+
* Whether a `[3]` extensions wrapper holds one non-empty SEQUENCE and nothing
|
|
1097
|
+
* after it (RFC 5280 §4.1's `SIZE (1..MAX)`). What an extension says is not
|
|
1098
|
+
* read.
|
|
1099
|
+
*/
|
|
1100
|
+
const x509DerExtensionsSound = (der: u8[], e: X509DerElement): boolean => {
|
|
1101
|
+
const seq: X509DerElement | null = x509DerExpect(der, e.start, e.end, X509_TAG_SEQUENCE)
|
|
1102
|
+
return seq !== null && seq.end === e.end && seq.end > seq.start
|
|
1103
|
+
}
|
|
1104
|
+
|
|
956
1105
|
/**
|
|
957
1106
|
* `der` read as one Certificate (RFC 5280 §4.1), or `null` unless it is
|
|
958
1107
|
* exactly one in DER: `SEQUENCE { tbsCertificate, signatureAlgorithm,
|
|
@@ -961,8 +1110,16 @@ export const x509ParseP256PrivateKey = (pem: string): u8[] | null => {
|
|
|
961
1110
|
* to the outer one byte for byte (§4.1.1.2), issuer and subject Names, a
|
|
962
1111
|
* Validity of two valid times, a SubjectPublicKeyInfo whose key is whole
|
|
963
1112
|
* octets, then only the optional `[1]` and `[2]` unique identifiers (v2 and
|
|
964
|
-
* v3) and `[3]` extensions (v3), in that order
|
|
965
|
-
* read; `p256` and `ecdsaSha256` say
|
|
1113
|
+
* v3) and `[3]` extensions (v3), in that order, each well formed on the
|
|
1114
|
+
* outside. Any algorithm and key type is read; `p256` and `ecdsaSha256` say
|
|
1115
|
+
* whether they are this module's.
|
|
1116
|
+
*
|
|
1117
|
+
* **A parser, not a validator.** It checks that the bytes are one certificate
|
|
1118
|
+
* and nothing else. It does not check the signature (`x509VerifySignature`
|
|
1119
|
+
* does), that `notBefore` is not after `notAfter` or that either is near any
|
|
1120
|
+
* clock, and it does not read extensions — basicConstraints, keyUsage,
|
|
1121
|
+
* subjectAltName and the critical flag RFC 5280 §4.2 obliges a verifier to
|
|
1122
|
+
* honour are neither checked nor exposed.
|
|
966
1123
|
*/
|
|
967
1124
|
export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
968
1125
|
const total: i32 = toI32(der.length)
|
|
@@ -1051,6 +1208,7 @@ export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
|
1051
1208
|
at = spki.end
|
|
1052
1209
|
// [1] issuerUniqueID and [2] subjectUniqueID are IMPLICIT BIT STRINGs (so
|
|
1053
1210
|
// primitive, 0x81 and 0x82) from v2; [3] extensions is explicit (0xa3), v3.
|
|
1211
|
+
// Each is held to its outer shape; what it says is not read.
|
|
1054
1212
|
const trailing: i32[] = [0x81, 0x82, 0xa3]
|
|
1055
1213
|
for (let i: i32 = 0; i < 3; i++) {
|
|
1056
1214
|
if (x509DerPeek(der, at, tbs.end) === trailing[i]) {
|
|
@@ -1058,6 +1216,9 @@ export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
|
1058
1216
|
if (e === null || version < (i === 2 ? 2 : 1)) {
|
|
1059
1217
|
return null
|
|
1060
1218
|
}
|
|
1219
|
+
if (i === 2 ? !x509DerExtensionsSound(der, e) : !x509DerBitStringSound(der, e)) {
|
|
1220
|
+
return null
|
|
1221
|
+
}
|
|
1061
1222
|
at = e.end
|
|
1062
1223
|
}
|
|
1063
1224
|
}
|
|
@@ -1086,9 +1247,17 @@ export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
|
1086
1247
|
}
|
|
1087
1248
|
|
|
1088
1249
|
/**
|
|
1089
|
-
* Every `CERTIFICATE` block in `pem`, in order
|
|
1090
|
-
* as TLS sends it — or `null` when there is
|
|
1091
|
-
* PEM `pemToDer` accepts or a certificate
|
|
1250
|
+
* Every `CERTIFICATE` block in `pem`, in the order they appear — for a
|
|
1251
|
+
* server's chain file, leaf first as TLS sends it — or `null` when there is
|
|
1252
|
+
* none, or when any block is not PEM `pemToDer` accepts or a certificate
|
|
1253
|
+
* `x509ParseCertificate` accepts.
|
|
1254
|
+
*
|
|
1255
|
+
* **It reads a chain; it does not validate one.** Nothing relates one
|
|
1256
|
+
* certificate to the next: not the order, not that each is issued by the one
|
|
1257
|
+
* after it (issuer against subject, or signature), not validity against a
|
|
1258
|
+
* clock, basicConstraints, keyUsage, name constraints or a trust anchor. RFC
|
|
1259
|
+
* 5280 §6's path validation is not in this module, and a chain it answers is
|
|
1260
|
+
* no evidence that anyone vouches for the leaf.
|
|
1092
1261
|
*/
|
|
1093
1262
|
export const x509ParseChain = (pem: string): X509Certificate[] | null => {
|
|
1094
1263
|
const blocks: u8[][] | null = pemToDer(pem, "CERTIFICATE")
|
|
@@ -1110,8 +1279,11 @@ export const x509ParseChain = (pem: string): X509Certificate[] | null => {
|
|
|
1110
1279
|
* Whether `cert`'s signature is a valid ecdsa-with-SHA256 signature of its TBS
|
|
1111
1280
|
* bytes under `issuer`'s P-256 key — a self-signed certificate passes itself
|
|
1112
1281
|
* as both. `false` for any other algorithm or key, and for a signature that is
|
|
1113
|
-
* not a DER ECDSA-Sig-Value. Only the signature:
|
|
1114
|
-
* are the caller
|
|
1282
|
+
* not a DER ECDSA-Sig-Value. Only the signature: that `cert.issuer` is
|
|
1283
|
+
* `issuer.subject` and that the times hold are fields the caller compares, and
|
|
1284
|
+
* whether `issuer` may issue at all (basicConstraints, keyUsage) is in
|
|
1285
|
+
* extensions this module does not read, so a `true` here is not path
|
|
1286
|
+
* validation.
|
|
1115
1287
|
*/
|
|
1116
1288
|
export const x509VerifySignature = (cert: X509Certificate, issuer: X509Certificate): boolean => {
|
|
1117
1289
|
if (!cert.ecdsaSha256 || !issuer.p256) {
|
|
@@ -1124,20 +1296,47 @@ export const x509VerifySignature = (cert: X509Certificate, issuer: X509Certifica
|
|
|
1124
1296
|
return p256VerifySha256(issuer.publicKey, cert.tbs, rs)
|
|
1125
1297
|
}
|
|
1126
1298
|
|
|
1299
|
+
/**
|
|
1300
|
+
* The minted certificate's `[3] extensions`, whole, in DER. They do not depend
|
|
1301
|
+
* on the key, the name or the time, so they are one constant block rather
|
|
1302
|
+
* than a tree of `x509DerWrap` calls (`tests/link/crypto_x509` pins each
|
|
1303
|
+
* octet, and `x509ParseCertificate` reads the result back):
|
|
1304
|
+
*
|
|
1305
|
+
* a3 20 [3] EXPLICIT, 32 octets
|
|
1306
|
+
* 30 1e Extensions SEQUENCE, 30 octets
|
|
1307
|
+
* 30 0c Extension (RFC 5280 §4.2.1.9)
|
|
1308
|
+
* 06 03 55 1d 13 basicConstraints, 2.5.29.19
|
|
1309
|
+
* 01 01 ff critical TRUE
|
|
1310
|
+
* 04 02 30 00 OCTET STRING { SEQUENCE {} }: cA FALSE is the
|
|
1311
|
+
* DEFAULT, so DER leaves it out (X.690 §11.5)
|
|
1312
|
+
* 30 0e Extension (RFC 5280 §4.2.1.3)
|
|
1313
|
+
* 06 03 55 1d 0f keyUsage, 2.5.29.15
|
|
1314
|
+
* 01 01 ff critical TRUE
|
|
1315
|
+
* 04 04 03 02 07 80 OCTET STRING { BIT STRING, 7 unused bits,
|
|
1316
|
+
* bit 0 digitalSignature }: DER drops a named
|
|
1317
|
+
* bit list's trailing zero bits (X.690 §11.2.2)
|
|
1318
|
+
*/
|
|
1319
|
+
const x509MintExtensions = (): u8[] => [
|
|
1320
|
+
0xa3, 0x20, 0x30, 0x1e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00,
|
|
1321
|
+
0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80,
|
|
1322
|
+
]
|
|
1323
|
+
|
|
1127
1324
|
/**
|
|
1128
1325
|
* A self-signed X.509 v3 certificate, in DER, for the P-256 key `priv`: subject
|
|
1129
1326
|
* and issuer `CN=commonName`, valid from `notBeforeMs` (milliseconds since
|
|
1130
1327
|
* 1970, rounded down to the second) for exactly `days` days, serial number
|
|
1131
|
-
* `serial`,
|
|
1132
|
-
*
|
|
1328
|
+
* `serial`, carrying `x509MintExtensions`' basicConstraints and keyUsage, and
|
|
1329
|
+
* signed with ecdsa-with-SHA256 — see the module comment for why Chromium
|
|
1330
|
+
* needs an extension at all. `null` when `days` is below 1 or above
|
|
1133
1331
|
* `X509_MAX_DAYS`, `notBeforeMs` is negative or the validity would end after
|
|
1134
|
-
* 9999, `commonName` is empty
|
|
1135
|
-
*
|
|
1332
|
+
* 9999, `commonName` is empty, longer than 64 bytes, not UTF-8 or holds a NUL
|
|
1333
|
+
* (it is written as a UTF8String), `priv` is not a P-256 private key, or
|
|
1334
|
+
* `serial` — big-endian, leading zeros ignored — is zero or
|
|
1136
1335
|
* needs more than `X509_MAX_SERIAL` octets as a positive INTEGER (clear the top
|
|
1137
1336
|
* bit of 20 random bytes, or use fewer).
|
|
1138
1337
|
*/
|
|
1139
1338
|
export const x509MintSelfSigned = (
|
|
1140
|
-
priv: u8[]
|
|
1339
|
+
priv: Secret<u8[]>,
|
|
1141
1340
|
commonName: string,
|
|
1142
1341
|
notBeforeMs: i64,
|
|
1143
1342
|
days: i32,
|
|
@@ -1154,7 +1353,7 @@ export const x509MintSelfSigned = (
|
|
|
1154
1353
|
}
|
|
1155
1354
|
const name: u8[] = x509Bytes(commonName)
|
|
1156
1355
|
const nameLength: i32 = toI32(name.length)
|
|
1157
|
-
if (nameLength < 1 || nameLength > X509_MAX_COMMON_NAME) {
|
|
1356
|
+
if (nameLength < 1 || nameLength > X509_MAX_COMMON_NAME || !x509IsUtf8Name(name)) {
|
|
1158
1357
|
return null
|
|
1159
1358
|
}
|
|
1160
1359
|
const serialDer: u8[] = x509DerUnsignedInteger(serial)
|
|
@@ -1195,6 +1394,7 @@ export const x509MintSelfSigned = (
|
|
|
1195
1394
|
x509DerWrap(X509_TAG_SEQUENCE, [x509DerTimeAt(start), x509DerTimeAt(end)]),
|
|
1196
1395
|
distinguished,
|
|
1197
1396
|
spki,
|
|
1397
|
+
x509MintExtensions(),
|
|
1198
1398
|
])
|
|
1199
1399
|
const rs: u8[] | null = p256SignSha256(priv, tbs)
|
|
1200
1400
|
if (rs === null) {
|