@amritk/nish-x86_64-linux 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +45 -4
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/host.d.ts +12 -0
  6. package/runtime/core/host.mjs +74 -2
  7. package/runtime/nish.d.ts +148 -0
  8. package/runtime/nish.h +59 -0
  9. package/runtime/nish.mjs +25 -0
  10. package/runtime/node.mjs +136 -6
  11. package/runtime/runtime-net.c +44 -3
  12. package/runtime/runtime-os.c +351 -18
  13. package/scripts/build.sh +13 -3
  14. package/std/README.md +27 -10
  15. package/std/cli-args.ts +1216 -0
  16. package/std/cli-flags.ts +585 -0
  17. package/std/cli.ts +308 -19
  18. package/std/compress/checksum.ts +132 -20
  19. package/std/compress/deflate.ts +638 -248
  20. package/std/compress/gzip.ts +20 -6
  21. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  22. package/std/crypto/p256.ts +63 -1
  23. package/std/crypto/p384.ts +6044 -0
  24. package/std/crypto/pki-roots.ts +322 -0
  25. package/std/crypto/pki.ts +1207 -0
  26. package/std/crypto/rsa.ts +663 -0
  27. package/std/crypto/x509.ts +1054 -58
  28. package/std/net/dns-config.ts +734 -0
  29. package/std/net/dns-message.ts +517 -0
  30. package/std/net/dns-resolver.ts +1015 -0
  31. package/std/net/dns.ts +478 -0
  32. package/std/net/http1-client.ts +1227 -0
  33. package/std/net/http1-server.ts +7 -2
  34. package/std/net/http1.ts +9 -9
  35. package/std/net/postgres.ts +259 -26
  36. package/std/net/tls/client.ts +1094 -0
  37. package/std/net/tls/codec.ts +585 -3
  38. package/std/net/tls/record-client.ts +1192 -0
  39. package/std/net/tls/verify.ts +221 -0
  40. package/std/net/tls-tcp.ts +642 -11
  41. package/std/net/tls.ts +2 -2
  42. package/std/net/url.ts +559 -4
  43. package/std/yaml-write.ts +415 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * `nish/crypto/pki-roots` — the root certificates a program should trust,
3
+ * read from where OpenSSL and Go look for them: the files `SSL_CERT_FILE` and
4
+ * `SSL_CERT_DIR` name, or else the system's bundle.
5
+ *
6
+ * import { PkiRoots, pkiLoadSystemRoots } from "nish/crypto/pki-roots";
7
+ *
8
+ * const store: PkiRoots = pkiLoadSystemRoots();
9
+ * // store.roots: X509Certificate[], parsed, no two with the same DER
10
+ * // store.sources: the files they were read from, in order
11
+ * // store.skipped: the PEM blocks and files that were not a root
12
+ *
13
+ * The order (docs/security/crypto-pki.md, "Where the roots come from"):
14
+ *
15
+ * 1. When `SSL_CERT_FILE` or `SSL_CERT_DIR` is set and not empty, exactly
16
+ * those: the file, and every directory of the `:`-separated list. There is
17
+ * no fallback, so naming a file that is missing trusts nothing.
18
+ * 2. Otherwise the first file of `pkiSystemBundles()` that exists, and
19
+ * the directory `/etc/ssl/certs` beside it.
20
+ *
21
+ * Only a path that leads to a regular file is read. A directory gives every
22
+ * entry named `*.pem`, `*.crt` or OpenSSL's hash form `xxxxxxxx.N`, not its
23
+ * subdirectories. A file is split into PEM blocks here, not by `pemToDer`,
24
+ * which refuses a whole text for one bad block: a `CERTIFICATE`
25
+ * block `x509ParseCertificate` accepts is a root, and every other block — a
26
+ * malformed one, an unterminated one, a `TRUSTED CERTIFICATE` — is counted in
27
+ * `skipped` and passed over. Finding nothing is an empty `roots`, never an
28
+ * exit: what to do with no roots is the caller's decision.
29
+ *
30
+ * Whoever sets the environment or writes these files decides what is trusted.
31
+ * That is the contract, as it is OpenSSL's, and it is not defended against;
32
+ * what is bounded is the work a hostile file can cause (PKI-R1 to PKI-R3).
33
+ *
34
+ * **Native only.** It asks `lstatOwnerModeSync` what each path is before it
35
+ * reads it, which a wasm32 build has no file system to answer, so a program
36
+ * that imports this module does not compile for wasm32 (PKI-R4).
37
+ * `nish/crypto/x509` imports nothing from here.
38
+ *
39
+ * Private helpers share the importing program's flat symbol namespace
40
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
41
+ */
42
+ import { readdirSync, readFileSyncOrNull, realpathSync } from "nish:fs"
43
+ import { getenv } from "nish:process"
44
+ import { X509Certificate, pemToDer, x509ParseCertificate } from "nish/crypto/x509"
45
+
46
+ /** The largest file read as roots: 16 MiB, some eighty times a full Mozilla bundle. A bigger one counts as one skip. */
47
+ export const PKI_ROOTS_MAX_FILE_BYTES: i32 = 16777216
48
+
49
+ /** The most files one load reads; an entry past it counts as one skip. */
50
+ export const PKI_ROOTS_MAX_FILES: i32 = 4096
51
+
52
+ /** The most roots one load keeps; a certificate past it counts as one skip. */
53
+ export const PKI_ROOTS_MAX_ROOTS: i32 = 4096
54
+
55
+ /** The trust store one load found. */
56
+ export class PkiRoots {
57
+ /** The parsed roots, in the order they were read, no two with the same DER. */
58
+ roots: X509Certificate[]
59
+ /** Every file that was read, in order, whether or not it held a root. */
60
+ sources: string[]
61
+ /** The PEM blocks that were not a root, and the files too large or past the cap to read. */
62
+ skipped: i32 = 0
63
+
64
+ constructor() {
65
+ this.roots = []
66
+ this.sources = []
67
+ }
68
+ }
69
+
70
+ /**
71
+ * The system bundles, in the order they are tried; the first one that reads
72
+ * as a file is used. A function, not a constant, because a module constant
73
+ * is a number, a boolean or a string. The paths and their order are those of Go's
74
+ * `crypto/x509/root_linux.go`, cited as facts, not copied code: Debian, Ubuntu,
75
+ * Arch and Gentoo; Fedora and RHEL; openSUSE; OpenELEC; CentOS and RHEL 7;
76
+ * Alpine, macOS and the BSDs.
77
+ */
78
+ export const pkiSystemBundles = (): string[] => [
79
+ "/etc/ssl/certs/ca-certificates.crt",
80
+ "/etc/pki/tls/certs/ca-bundle.crt",
81
+ "/etc/ssl/ca-bundle.pem",
82
+ "/etc/pki/tls/cacert.pem",
83
+ "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
84
+ "/etc/ssl/cert.pem",
85
+ ]
86
+
87
+ /** The directory read beside the system bundle. */
88
+ export const PKI_SYSTEM_DIR: string = "/etc/ssl/certs"
89
+
90
+ /** What one load has read so far, and the DER already kept. */
91
+ class PkiRootsLoad {
92
+ out: PkiRoots
93
+ seen: Set<string>
94
+ files: i32 = 0
95
+
96
+ constructor() {
97
+ this.out = new PkiRoots()
98
+ this.seen = new Set<string>()
99
+ }
100
+ }
101
+
102
+ /** Keeps `der` as a root when it parses, is new, and the cap has room; counts it skipped otherwise. */
103
+ const pkiRootsKeep = (load: PkiRootsLoad, der: u8[]): void => {
104
+ const cert: X509Certificate | null = x509ParseCertificate(der)
105
+ if (cert === null) {
106
+ load.out.skipped = load.out.skipped + 1
107
+ return
108
+ }
109
+ const key: string = String.fromBytes(der, 0, der.length)
110
+ if (load.seen.has(key)) {
111
+ return
112
+ }
113
+ if (toI32(load.out.roots.length) >= PKI_ROOTS_MAX_ROOTS) {
114
+ load.out.skipped = load.out.skipped + 1
115
+ return
116
+ }
117
+ load.seen.add(key)
118
+ load.out.roots.push(cert)
119
+ }
120
+
121
+ /**
122
+ * One PEM block, `text` whole from its BEGIN line through its END line. A block
123
+ * labelled other than CERTIFICATE gives `pemToDer` no DER, so it is skipped too.
124
+ */
125
+ const pkiRootsBlock = (load: PkiRootsLoad, text: string): void => {
126
+ const ders: u8[][] | null = pemToDer(text, "CERTIFICATE")
127
+ if (ders === null || ders.length !== 1) {
128
+ load.out.skipped = load.out.skipped + 1
129
+ return
130
+ }
131
+ pkiRootsKeep(load, ders[0])
132
+ }
133
+
134
+ /**
135
+ * Every PEM block of `text`. A BEGIN line opens a block and the next END line
136
+ * closes it; a BEGIN before that END counts the open block skipped and starts
137
+ * again, and a block still open at the end is skipped. Text outside the blocks,
138
+ * a stray END among it, is passed over, as bundles carry comments there.
139
+ */
140
+ const pkiRootsSplit = (load: PkiRootsLoad, text: string): void => {
141
+ const n: i32 = toI32(text.length)
142
+ let open: boolean = false
143
+ let blockStart: i32 = 0
144
+ let lineStart: i32 = 0
145
+ while (lineStart < n) {
146
+ let lineEnd: i32 = lineStart
147
+ while (lineEnd >= 0 && lineEnd < toI32(text.length) && toI32(text.charCodeAt(lineEnd)) !== 10) {
148
+ lineEnd = lineEnd + 1
149
+ }
150
+ const next: i32 = lineEnd < n ? lineEnd + 1 : n
151
+ // A carriage return, space or tab before the line feed is no part of the
152
+ // line, so a BEGIN with trailing whitespace still opens a block and is
153
+ // counted when `pemToDer` refuses it, rather than lost unseen.
154
+ let end: i32 = lineEnd
155
+ while (
156
+ end > lineStart &&
157
+ (toI32(text.charCodeAt(end - 1)) === 13 ||
158
+ toI32(text.charCodeAt(end - 1)) === 32 ||
159
+ toI32(text.charCodeAt(end - 1)) === 9)
160
+ ) {
161
+ end = end - 1
162
+ }
163
+ const line: string = text.slice(lineStart, end)
164
+ if (line.startsWith("-----BEGIN ") && line.endsWith("-----") && line.length > 16) {
165
+ if (open) {
166
+ load.out.skipped = load.out.skipped + 1
167
+ }
168
+ open = true
169
+ blockStart = lineStart
170
+ } else if (open && line.startsWith("-----END ")) {
171
+ pkiRootsBlock(load, text.slice(blockStart, next))
172
+ open = false
173
+ }
174
+ lineStart = next
175
+ }
176
+ if (open) {
177
+ load.out.skipped = load.out.skipped + 1
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Where `path` leads when that is a regular file, or `null`: a missing path,
183
+ * a directory, and a FIFO or device, whose `open` could wait forever or whose
184
+ * read never ends, are not a file of roots (PKI-R2).
185
+ */
186
+ const pkiRootsRegular = (path: string): string | null => {
187
+ const real: string | null = realpathSync(path)
188
+ if (real === null) {
189
+ return null
190
+ }
191
+ const om: i64 = lstatOwnerModeSync(real)
192
+ return om >= 0 && (om & 0xf000) === 0x8000 ? real : null
193
+ }
194
+
195
+ /** Reads `path` as roots: `false` when it is not a regular file that reads, `true` when it was (or would have been, past a cap). */
196
+ const pkiRootsFile = (load: PkiRootsLoad, path: string): boolean => {
197
+ if (load.files >= PKI_ROOTS_MAX_FILES) {
198
+ load.out.skipped = load.out.skipped + 1
199
+ return true
200
+ }
201
+ const real: string | null = pkiRootsRegular(path)
202
+ const text: string | null = real === null ? null : readFileSyncOrNull(real)
203
+ if (text === null) {
204
+ return false
205
+ }
206
+ load.files = load.files + 1
207
+ load.out.sources.push(path)
208
+ if (toI32(text.length) > PKI_ROOTS_MAX_FILE_BYTES) {
209
+ load.out.skipped = load.out.skipped + 1
210
+ return true
211
+ }
212
+ pkiRootsSplit(load, text)
213
+ return true
214
+ }
215
+
216
+ const pkiRootsIsDigit = (c: i32): boolean => c >= 48 && c <= 57
217
+
218
+ const pkiRootsIsHex = (c: i32): boolean => pkiRootsIsDigit(c) || (c >= 97 && c <= 102)
219
+
220
+ /** Whether a directory entry is one a trust directory holds: `*.pem`, `*.crt`, or the hash form `xxxxxxxx.N`. */
221
+ export const pkiRootsDirEntry = (name: string): boolean => {
222
+ if (name.endsWith(".pem") || name.endsWith(".crt")) {
223
+ return name.length > 4
224
+ }
225
+ const n: i32 = toI32(name.length)
226
+ if (n < 10 || toI32(name.charCodeAt(8)) !== 46) {
227
+ return false
228
+ }
229
+ for (let i: i32 = 0; i < 8 && i < toI32(name.length); i = i + 1) {
230
+ if (!pkiRootsIsHex(toI32(name.charCodeAt(i)))) {
231
+ return false
232
+ }
233
+ }
234
+ for (let i: i32 = 9; i < n; i = i + 1) {
235
+ if (!pkiRootsIsDigit(toI32(name.charCodeAt(i)))) {
236
+ return false
237
+ }
238
+ }
239
+ return true
240
+ }
241
+
242
+ /** Every trust entry of the directory `dir`, in byte order; a directory that cannot be read gives nothing. */
243
+ const pkiRootsDir = (load: PkiRootsLoad, dir: string): void => {
244
+ const names: string[] | null = readdirSync(dir)
245
+ if (names === null) {
246
+ return
247
+ }
248
+ const base: string = dir.endsWith("/") ? dir : `${dir}/`
249
+ for (let i: i32 = 0; i < toI32(names.length); i = i + 1) {
250
+ const name: string = names[i]
251
+ if (pkiRootsDirEntry(name)) {
252
+ pkiRootsFile(load, `${base}${name}`)
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * The roots of an explicit lookup: `file` when it is not `null`, then the
259
+ * first of `bundles` that reads as a file, then every directory of `dirs`, in
260
+ * that order. A path that cannot be read contributes nothing; a duplicate DER
261
+ * is kept once, where it was first read.
262
+ */
263
+ export const pkiLoadRoots = (file: string | null, dirs: string[], bundles: string[]): PkiRoots => {
264
+ const load: PkiRootsLoad = new PkiRootsLoad()
265
+ if (file !== null) {
266
+ pkiRootsFile(load, file)
267
+ }
268
+ for (let i: i32 = 0; i < toI32(bundles.length); i = i + 1) {
269
+ if (pkiRootsFile(load, bundles[i])) {
270
+ break
271
+ }
272
+ }
273
+ for (let i: i32 = 0; i < toI32(dirs.length); i = i + 1) {
274
+ pkiRootsDir(load, dirs[i])
275
+ }
276
+ return load.out
277
+ }
278
+
279
+ /** The non-empty components of a `:`-separated directory list. */
280
+ const pkiRootsSplitDirs = (list: string): string[] => {
281
+ const out: string[] = []
282
+ let start: i32 = 0
283
+ const n: i32 = toI32(list.length)
284
+ for (let i: i32 = 0; i <= n; i = i + 1) {
285
+ if (i >= n || (i >= 0 && i < toI32(list.length) && toI32(list.charCodeAt(i)) === 58)) {
286
+ if (i > start) {
287
+ out.push(list.slice(start, i))
288
+ }
289
+ start = i + 1
290
+ }
291
+ }
292
+ return out
293
+ }
294
+
295
+ /**
296
+ * `pkiLoadSystemRoots` with the two variables' values given, and the defaults
297
+ * it falls back to: what it does with the environment, without reading it, so
298
+ * the order can be tested against fixture trees rather than the host's `/etc`.
299
+ * `certFile` and `certDir` are `SSL_CERT_FILE` and `SSL_CERT_DIR`, `null`
300
+ * when unset; an empty value counts as unset.
301
+ */
302
+ export const pkiLoadRootsFor = (
303
+ certFile: string | null,
304
+ certDir: string | null,
305
+ dirs: string[],
306
+ bundles: string[]
307
+ ): PkiRoots => {
308
+ const file: string | null = certFile !== null && certFile.length > 0 ? certFile : null
309
+ const dir: string | null = certDir !== null && certDir.length > 0 ? certDir : null
310
+ if (file === null && dir === null) {
311
+ return pkiLoadRoots(null, dirs, bundles)
312
+ }
313
+ return pkiLoadRoots(file, dir === null ? [] : pkiRootsSplitDirs(dir), [])
314
+ }
315
+
316
+ /**
317
+ * The roots this program should trust: those `SSL_CERT_FILE` and
318
+ * `SSL_CERT_DIR` name when either is set and not empty, and otherwise the
319
+ * first of `pkiSystemBundles()` that exists and `/etc/ssl/certs`.
320
+ */
321
+ export const pkiLoadSystemRoots = (): PkiRoots =>
322
+ pkiLoadRootsFor(getenv("SSL_CERT_FILE"), getenv("SSL_CERT_DIR"), [PKI_SYSTEM_DIR], pkiSystemBundles())