@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/crypto/x509.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
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 priv: u8[] = new Array<u8>(32);
|
|
11
|
+
* crypto.getRandomValues(priv); // p256PublicKey(priv) === null: draw again (odds 2^-32)
|
|
9
12
|
* const serial: u8[] = new Array<u8>(16);
|
|
10
13
|
* crypto.getRandomValues(serial);
|
|
11
14
|
* serial[0] = serial[0] & toU8(0x7f); // positive, so it stays 16 octets
|
|
@@ -47,9 +50,11 @@
|
|
|
47
50
|
* hash does no path building for basicConstraints or keyUsage to inform, and
|
|
48
51
|
* every extension would be one more field whose absence is part of the
|
|
49
52
|
* contract a hash pins. OpenSSL verifies it as its own trust anchor all the
|
|
50
|
-
* same (`tests/link/crypto_x509`'s header has the commands). The
|
|
51
|
-
* the time are parameters so the golden certificate is
|
|
52
|
-
* passes `Date.now()` and
|
|
53
|
+
* same (`tests/link/crypto_x509`'s header has the commands). The key, the
|
|
54
|
+
* serial and the time are parameters so the golden certificate is
|
|
55
|
+
* deterministic; a caller passes `Date.now()` and, for the key and the serial,
|
|
56
|
+
* bytes from `crypto.getRandomValues`. This module does not draw them itself,
|
|
57
|
+
* because a call to the operating system would stop it compiling for wasm32.
|
|
53
58
|
*
|
|
54
59
|
* **Constant time.** A private key is the one secret that passes through here:
|
|
55
60
|
* it is base64-decoded from PEM, lifted out of its DER, and handed to
|
|
@@ -288,12 +293,29 @@ const x509DerSmallInteger = (der: u8[], e: X509DerElement): i32 => {
|
|
|
288
293
|
return toI32(der[e.start])
|
|
289
294
|
}
|
|
290
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Whether `e`'s contents are a DER BIT STRING's, whatever its tag (an IMPLICIT
|
|
298
|
+
* one keeps the grammar): an unused-bits count of 0 to 7, none without an
|
|
299
|
+
* octet to hold them, and those bits zero (X.690 §8.6.2 and §11.2.1).
|
|
300
|
+
*/
|
|
301
|
+
const x509DerBitStringSound = (der: u8[], e: X509DerElement): boolean => {
|
|
302
|
+
const n: i32 = e.end - e.start
|
|
303
|
+
if (n < 1) {
|
|
304
|
+
return false
|
|
305
|
+
}
|
|
306
|
+
const unused: i32 = toI32(der[e.start])
|
|
307
|
+
if (unused > 7 || (n === 1 && unused !== 0)) {
|
|
308
|
+
return false
|
|
309
|
+
}
|
|
310
|
+
return (toI32(der[e.end - 1]) & ((1 << unused) - 1)) === 0
|
|
311
|
+
}
|
|
312
|
+
|
|
291
313
|
/**
|
|
292
314
|
* The octets of a BIT STRING that has no unused bits, or `null`. Keys and
|
|
293
315
|
* signatures are whole octets, so the leading unused-bits count must be 0.
|
|
294
316
|
*/
|
|
295
317
|
const x509DerBitStringOctets = (der: u8[], e: X509DerElement): u8[] | null => {
|
|
296
|
-
if (e.tag !== X509_TAG_BIT_STRING ||
|
|
318
|
+
if (e.tag !== X509_TAG_BIT_STRING || !x509DerBitStringSound(der, e) || toI32(der[e.start]) !== 0) {
|
|
297
319
|
return null
|
|
298
320
|
}
|
|
299
321
|
return x509DerSlice(der, e.start + 1, e.end)
|
|
@@ -468,9 +490,11 @@ const x509DerUnsignedInto = (der: u8[], e: X509DerElement, out: u8[], off: i32):
|
|
|
468
490
|
/**
|
|
469
491
|
* `r || s`, 32 octets each, out of an ECDSA-Sig-Value `SEQUENCE { INTEGER r,
|
|
470
492
|
* 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.
|
|
493
|
+
* is exactly one with both integers minimal, positive and below 2^256. This is
|
|
494
|
+
* the form a TLS `CertificateVerify` or an X.509 signature carries, and
|
|
495
|
+
* `nish/crypto/p256` takes the `r || s` it answers.
|
|
472
496
|
*/
|
|
473
|
-
const x509DerSignatureRS = (sig: u8[]): u8[] | null => {
|
|
497
|
+
export const x509DerSignatureRS = (sig: u8[]): u8[] | null => {
|
|
474
498
|
const total: i32 = toI32(sig.length)
|
|
475
499
|
const seq: X509DerElement | null = x509DerExpect(sig, 0, total, X509_TAG_SEQUENCE)
|
|
476
500
|
if (seq === null || seq.end !== total) {
|
|
@@ -605,6 +629,48 @@ const x509DerTimeAt = (seconds: i64): u8[] => {
|
|
|
605
629
|
return x509DerTlv(utc ? X509_TAG_UTC_TIME : X509_TAG_GENERALIZED_TIME, text)
|
|
606
630
|
}
|
|
607
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Whether `bytes` is well-formed UTF-8 (RFC 3629 §4) with no NUL: no stray
|
|
634
|
+
* continuation octet, no truncated sequence, no overlong form, no surrogate and
|
|
635
|
+
* nothing past U+10FFFF. A string in the language is bytes and need not be
|
|
636
|
+
* UTF-8, and a UTF8String that is not UTF-8 is a malformed certificate; a NUL
|
|
637
|
+
* is refused because a name that reads differently to C is the old
|
|
638
|
+
* `localhost\0.example` confusion.
|
|
639
|
+
*/
|
|
640
|
+
const x509IsUtf8Name = (bytes: u8[]): boolean => {
|
|
641
|
+
// Continuation octets still owed, and the range the next one must fall in:
|
|
642
|
+
// 80..BF, narrowed after E0, ED, F0 and F4 (Unicode 15 §3.9, Table 3-7).
|
|
643
|
+
let need: i32 = 0
|
|
644
|
+
let lo: i32 = 0x80
|
|
645
|
+
let hi: i32 = 0xbf
|
|
646
|
+
for (let k: i32 = 0; k < toI32(bytes.length); k++) {
|
|
647
|
+
const b: i32 = toI32(bytes[k])
|
|
648
|
+
if (need > 0) {
|
|
649
|
+
if (b < lo || b > hi) {
|
|
650
|
+
return false
|
|
651
|
+
}
|
|
652
|
+
need = need - 1
|
|
653
|
+
lo = 0x80
|
|
654
|
+
hi = 0xbf
|
|
655
|
+
} else if (b === 0) {
|
|
656
|
+
return false
|
|
657
|
+
} else if (b >= 0xc2 && b <= 0xdf) {
|
|
658
|
+
need = 1
|
|
659
|
+
} else if (b >= 0xe0 && b <= 0xef) {
|
|
660
|
+
need = 2
|
|
661
|
+
lo = b === 0xe0 ? 0xa0 : 0x80
|
|
662
|
+
hi = b === 0xed ? 0x9f : 0xbf
|
|
663
|
+
} else if (b >= 0xf0 && b <= 0xf4) {
|
|
664
|
+
need = 3
|
|
665
|
+
lo = b === 0xf0 ? 0x90 : 0x80
|
|
666
|
+
hi = b === 0xf4 ? 0x8f : 0xbf
|
|
667
|
+
} else if (b >= 0x80) {
|
|
668
|
+
return false
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return need === 0
|
|
672
|
+
}
|
|
673
|
+
|
|
608
674
|
/** The bytes of `text`, as the language holds a string. */
|
|
609
675
|
const x509Bytes = (text: string): u8[] => {
|
|
610
676
|
const out: u8[] = []
|
|
@@ -790,11 +856,13 @@ export const pemToDer = (pem: string, label: string): u8[][] | null => {
|
|
|
790
856
|
if (line !== `-----END ${inside}-----`) {
|
|
791
857
|
return null
|
|
792
858
|
}
|
|
859
|
+
// Every block is decoded, whatever its label: a malformed block beside the
|
|
860
|
+
// one asked for is refused, not skipped (docs/security/crypto-x509.md, X509-1).
|
|
861
|
+
const der: u8[] | null = x509Base64Decode(body.join(""))
|
|
862
|
+
if (der === null) {
|
|
863
|
+
return null
|
|
864
|
+
}
|
|
793
865
|
if (inside === label) {
|
|
794
|
-
const der: u8[] | null = x509Base64Decode(body.join(""))
|
|
795
|
-
if (der === null) {
|
|
796
|
-
return null
|
|
797
|
-
}
|
|
798
866
|
out.push(der)
|
|
799
867
|
}
|
|
800
868
|
inside = ""
|
|
@@ -882,8 +950,9 @@ const x509Sec1Key = (der: u8[], start: i32, end: i32, needCurve: boolean): u8[]
|
|
|
882
950
|
* The scalar of a PKCS#8 PrivateKeyInfo (RFC 5208 §5) or OneAsymmetricKey
|
|
883
951
|
* (RFC 5958 §2): version 0 or 1, the algorithm id-ecPublicKey with the
|
|
884
952
|
* prime256v1 curve, and an ECPrivateKey inside the OCTET STRING, followed by
|
|
885
|
-
* the optional `[0]` attributes and — in version 1 —
|
|
886
|
-
*
|
|
953
|
+
* the optional `[0]` attributes, which are skipped, and — in version 1 — the
|
|
954
|
+
* optional `[1]` public key, which must be a BIT STRING of the key's own point
|
|
955
|
+
* (X509-8 in docs/security/crypto-x509.md).
|
|
887
956
|
*/
|
|
888
957
|
const x509Pkcs8Key = (der: u8[]): u8[] | null => {
|
|
889
958
|
const total: i32 = toI32(der.length)
|
|
@@ -915,17 +984,31 @@ const x509Pkcs8Key = (der: u8[]): u8[] | null => {
|
|
|
915
984
|
}
|
|
916
985
|
at = attributes.end
|
|
917
986
|
}
|
|
987
|
+
let stored: u8[] | null = null
|
|
918
988
|
if (v === 1 && x509DerPeek(der, at, seq.end) === 0x81) {
|
|
919
|
-
const
|
|
920
|
-
if (
|
|
989
|
+
const outer: X509DerElement | null = x509DerRead(der, at, seq.end)
|
|
990
|
+
if (outer === null) {
|
|
921
991
|
return null
|
|
922
992
|
}
|
|
923
|
-
|
|
993
|
+
// `[1] IMPLICIT BIT STRING`: the 0x81 replaces the BIT STRING's own tag,
|
|
994
|
+
// so its contents are the unused-bits octet and then the point.
|
|
995
|
+
const bits: X509DerElement = new X509DerElement(X509_TAG_BIT_STRING, outer.at, outer.start, outer.end)
|
|
996
|
+
stored = x509DerBitStringOctets(der, bits)
|
|
997
|
+
if (stored === null) {
|
|
998
|
+
return null
|
|
999
|
+
}
|
|
1000
|
+
at = outer.end
|
|
924
1001
|
}
|
|
925
1002
|
if (at !== seq.end) {
|
|
926
1003
|
return null
|
|
927
1004
|
}
|
|
928
|
-
|
|
1005
|
+
const priv: u8[] | null = x509Sec1Key(der, key.start, key.end, false)
|
|
1006
|
+
if (priv === null || stored === null) {
|
|
1007
|
+
return priv
|
|
1008
|
+
}
|
|
1009
|
+
// The same rule as the SEC1 key's own `[1]`: the stored point is the key's.
|
|
1010
|
+
const pub: u8[] | null = p256PublicKey(priv)
|
|
1011
|
+
return pub !== null && x509BytesEqual(stored, pub) ? priv : null
|
|
929
1012
|
}
|
|
930
1013
|
|
|
931
1014
|
/**
|
|
@@ -953,6 +1036,16 @@ export const x509ParseP256PrivateKey = (pem: string): u8[] | null => {
|
|
|
953
1036
|
// Certificates (RFC 5280 §4.1)
|
|
954
1037
|
// ---------------------------------------------------------------------------
|
|
955
1038
|
|
|
1039
|
+
/**
|
|
1040
|
+
* Whether a `[3]` extensions wrapper holds one non-empty SEQUENCE and nothing
|
|
1041
|
+
* after it (RFC 5280 §4.1's `SIZE (1..MAX)`). What an extension says is not
|
|
1042
|
+
* read.
|
|
1043
|
+
*/
|
|
1044
|
+
const x509DerExtensionsSound = (der: u8[], e: X509DerElement): boolean => {
|
|
1045
|
+
const seq: X509DerElement | null = x509DerExpect(der, e.start, e.end, X509_TAG_SEQUENCE)
|
|
1046
|
+
return seq !== null && seq.end === e.end && seq.end > seq.start
|
|
1047
|
+
}
|
|
1048
|
+
|
|
956
1049
|
/**
|
|
957
1050
|
* `der` read as one Certificate (RFC 5280 §4.1), or `null` unless it is
|
|
958
1051
|
* exactly one in DER: `SEQUENCE { tbsCertificate, signatureAlgorithm,
|
|
@@ -961,8 +1054,16 @@ export const x509ParseP256PrivateKey = (pem: string): u8[] | null => {
|
|
|
961
1054
|
* to the outer one byte for byte (§4.1.1.2), issuer and subject Names, a
|
|
962
1055
|
* Validity of two valid times, a SubjectPublicKeyInfo whose key is whole
|
|
963
1056
|
* 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
|
|
1057
|
+
* v3) and `[3]` extensions (v3), in that order, each well formed on the
|
|
1058
|
+
* outside. Any algorithm and key type is read; `p256` and `ecdsaSha256` say
|
|
1059
|
+
* whether they are this module's.
|
|
1060
|
+
*
|
|
1061
|
+
* **A parser, not a validator.** It checks that the bytes are one certificate
|
|
1062
|
+
* and nothing else. It does not check the signature (`x509VerifySignature`
|
|
1063
|
+
* does), that `notBefore` is not after `notAfter` or that either is near any
|
|
1064
|
+
* clock, and it does not read extensions — basicConstraints, keyUsage,
|
|
1065
|
+
* subjectAltName and the critical flag RFC 5280 §4.2 obliges a verifier to
|
|
1066
|
+
* honour are neither checked nor exposed.
|
|
966
1067
|
*/
|
|
967
1068
|
export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
968
1069
|
const total: i32 = toI32(der.length)
|
|
@@ -1051,6 +1152,7 @@ export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
|
1051
1152
|
at = spki.end
|
|
1052
1153
|
// [1] issuerUniqueID and [2] subjectUniqueID are IMPLICIT BIT STRINGs (so
|
|
1053
1154
|
// primitive, 0x81 and 0x82) from v2; [3] extensions is explicit (0xa3), v3.
|
|
1155
|
+
// Each is held to its outer shape; what it says is not read.
|
|
1054
1156
|
const trailing: i32[] = [0x81, 0x82, 0xa3]
|
|
1055
1157
|
for (let i: i32 = 0; i < 3; i++) {
|
|
1056
1158
|
if (x509DerPeek(der, at, tbs.end) === trailing[i]) {
|
|
@@ -1058,6 +1160,9 @@ export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
|
1058
1160
|
if (e === null || version < (i === 2 ? 2 : 1)) {
|
|
1059
1161
|
return null
|
|
1060
1162
|
}
|
|
1163
|
+
if (i === 2 ? !x509DerExtensionsSound(der, e) : !x509DerBitStringSound(der, e)) {
|
|
1164
|
+
return null
|
|
1165
|
+
}
|
|
1061
1166
|
at = e.end
|
|
1062
1167
|
}
|
|
1063
1168
|
}
|
|
@@ -1086,9 +1191,17 @@ export const x509ParseCertificate = (der: u8[]): X509Certificate | null => {
|
|
|
1086
1191
|
}
|
|
1087
1192
|
|
|
1088
1193
|
/**
|
|
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
|
|
1194
|
+
* Every `CERTIFICATE` block in `pem`, in the order they appear — for a
|
|
1195
|
+
* server's chain file, leaf first as TLS sends it — or `null` when there is
|
|
1196
|
+
* none, or when any block is not PEM `pemToDer` accepts or a certificate
|
|
1197
|
+
* `x509ParseCertificate` accepts.
|
|
1198
|
+
*
|
|
1199
|
+
* **It reads a chain; it does not validate one.** Nothing relates one
|
|
1200
|
+
* certificate to the next: not the order, not that each is issued by the one
|
|
1201
|
+
* after it (issuer against subject, or signature), not validity against a
|
|
1202
|
+
* clock, basicConstraints, keyUsage, name constraints or a trust anchor. RFC
|
|
1203
|
+
* 5280 §6's path validation is not in this module, and a chain it answers is
|
|
1204
|
+
* no evidence that anyone vouches for the leaf.
|
|
1092
1205
|
*/
|
|
1093
1206
|
export const x509ParseChain = (pem: string): X509Certificate[] | null => {
|
|
1094
1207
|
const blocks: u8[][] | null = pemToDer(pem, "CERTIFICATE")
|
|
@@ -1110,8 +1223,11 @@ export const x509ParseChain = (pem: string): X509Certificate[] | null => {
|
|
|
1110
1223
|
* Whether `cert`'s signature is a valid ecdsa-with-SHA256 signature of its TBS
|
|
1111
1224
|
* bytes under `issuer`'s P-256 key — a self-signed certificate passes itself
|
|
1112
1225
|
* 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
|
|
1226
|
+
* not a DER ECDSA-Sig-Value. Only the signature: that `cert.issuer` is
|
|
1227
|
+
* `issuer.subject` and that the times hold are fields the caller compares, and
|
|
1228
|
+
* whether `issuer` may issue at all (basicConstraints, keyUsage) is in
|
|
1229
|
+
* extensions this module does not read, so a `true` here is not path
|
|
1230
|
+
* validation.
|
|
1115
1231
|
*/
|
|
1116
1232
|
export const x509VerifySignature = (cert: X509Certificate, issuer: X509Certificate): boolean => {
|
|
1117
1233
|
if (!cert.ecdsaSha256 || !issuer.p256) {
|
|
@@ -1131,8 +1247,9 @@ export const x509VerifySignature = (cert: X509Certificate, issuer: X509Certifica
|
|
|
1131
1247
|
* `serial`, and signed with ecdsa-with-SHA256 — see the module comment for why
|
|
1132
1248
|
* it has no extensions. `null` when `days` is below 1 or above
|
|
1133
1249
|
* `X509_MAX_DAYS`, `notBeforeMs` is negative or the validity would end after
|
|
1134
|
-
* 9999, `commonName` is empty
|
|
1135
|
-
*
|
|
1250
|
+
* 9999, `commonName` is empty, longer than 64 bytes, not UTF-8 or holds a NUL
|
|
1251
|
+
* (it is written as a UTF8String), `priv` is not a P-256 private key, or
|
|
1252
|
+
* `serial` — big-endian, leading zeros ignored — is zero or
|
|
1136
1253
|
* needs more than `X509_MAX_SERIAL` octets as a positive INTEGER (clear the top
|
|
1137
1254
|
* bit of 20 random bytes, or use fewer).
|
|
1138
1255
|
*/
|
|
@@ -1154,7 +1271,7 @@ export const x509MintSelfSigned = (
|
|
|
1154
1271
|
}
|
|
1155
1272
|
const name: u8[] = x509Bytes(commonName)
|
|
1156
1273
|
const nameLength: i32 = toI32(name.length)
|
|
1157
|
-
if (nameLength < 1 || nameLength > X509_MAX_COMMON_NAME) {
|
|
1274
|
+
if (nameLength < 1 || nameLength > X509_MAX_COMMON_NAME || !x509IsUtf8Name(name)) {
|
|
1158
1275
|
return null
|
|
1159
1276
|
}
|
|
1160
1277
|
const serialDer: u8[] = x509DerUnsignedInteger(serial)
|