@amritk/nish-x86_64-linux 0.24.0 → 0.25.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/bin/nish +0 -0
- package/docs/AI.md +24 -4
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/nish.d.ts +100 -0
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +21 -0
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +44 -3
- package/runtime/runtime-os.c +115 -12
- package/scripts/build.sh +13 -3
- package/std/README.md +25 -8
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +132 -20
- package/std/compress/deflate.ts +638 -248
- package/std/compress/gzip.ts +20 -6
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +7 -2
- package/std/net/http1.ts +9 -9
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/tls/verify` — the TLS client's one authentication seam: whether
|
|
3
|
+
* the server a `TlsRecordClient` reached is the one its `serverName` says,
|
|
4
|
+
* decided at `TLS_RECORD_AUTH` and handed to the connection as its verdict.
|
|
5
|
+
*
|
|
6
|
+
* const trust: TlsClientTrust = tlsClientSystemTrust();
|
|
7
|
+
* // when conn.interest() says TLS_RECORD_AUTH:
|
|
8
|
+
* const alert: i32 = tlsClientAuthenticate(conn, trust, toI64(Date.now()));
|
|
9
|
+
*
|
|
10
|
+
* **Two checks, both always made.** The CertificateVerify first: its scheme
|
|
11
|
+
* must be one this client offered and fit the leaf's key — ECDSA P-256 with
|
|
12
|
+
* SHA-256 on a P-256 key, ECDSA P-384 with SHA-384 on a P-384 key, RSA-PSS
|
|
13
|
+
* (rsae, salt as long as the hash) on an RSA key of at least 2048 bits — and
|
|
14
|
+
* the signature must verify over `TlsClient.verifyContent()` under that key
|
|
15
|
+
* (RFC 8446 §4.4.3). Then the chain, from the leaf the server sent to a root
|
|
16
|
+
* in `trust`, for the name the caller dialled. A server with no certificate
|
|
17
|
+
* or a leaf that does not parse is `bad_certificate`, as is a leaf whose key
|
|
18
|
+
* is of any other kind (Ed25519, a short RSA modulus); a signature that does
|
|
19
|
+
* not verify, or a scheme that does not fit the key, is `decrypt_error`.
|
|
20
|
+
* The signature goes first so a forged CertificateVerify reads as what it is
|
|
21
|
+
* whatever the chain would have said.
|
|
22
|
+
*
|
|
23
|
+
* **The chain is `nish/crypto/pki`'s.** `pkiVerifyServer` builds a path from
|
|
24
|
+
* the leaf to a root of `trust`'s store and checks every certificate on it —
|
|
25
|
+
* signatures, validity at the instant given, basicConstraints, keyUsage,
|
|
26
|
+
* serverAuth, name constraints — and that the leaf's subjectAltName names
|
|
27
|
+
* the host dialled. Its refusal becomes the alert: `certificate_expired` for
|
|
28
|
+
* a certificate outside its validity, `unknown_ca` when no path reaches a
|
|
29
|
+
* root of the store, and `bad_certificate` for every other reason. There is
|
|
30
|
+
* no switch that skips the step and none is to be added: a client that is to
|
|
31
|
+
* talk to a server it cannot authenticate is given a store holding its
|
|
32
|
+
* issuing root, never a way around the check (`docs/security/tls.md`, "The
|
|
33
|
+
* client carrier and verify").
|
|
34
|
+
*
|
|
35
|
+
* The verdict is the record client's to send: `tlsClientAuthenticate` calls
|
|
36
|
+
* `authenticate(true)` or `reject(alert)` on it, so the refusal goes out as
|
|
37
|
+
* a fatal alert under the handshake keys. Written from RFC 8446 §4.2.3 and
|
|
38
|
+
* §4.4.3, not ported from another implementation.
|
|
39
|
+
*/
|
|
40
|
+
import { p256VerifySha256 } from "nish/crypto/p256"
|
|
41
|
+
import { p384VerifySha384 } from "nish/crypto/p384"
|
|
42
|
+
import {
|
|
43
|
+
PKI_ERR_EXPIRED,
|
|
44
|
+
PKI_ERR_NOT_YET_VALID,
|
|
45
|
+
PKI_ERR_UNTRUSTED,
|
|
46
|
+
PKI_OK,
|
|
47
|
+
PkiResult,
|
|
48
|
+
PkiTrustStore,
|
|
49
|
+
pkiTrustStore,
|
|
50
|
+
pkiVerifyServer,
|
|
51
|
+
} from "nish/crypto/pki"
|
|
52
|
+
import { PkiRoots, pkiLoadSystemRoots } from "nish/crypto/pki-roots"
|
|
53
|
+
import {
|
|
54
|
+
RSA_HASH_SHA256,
|
|
55
|
+
RSA_HASH_SHA384,
|
|
56
|
+
RSA_HASH_SHA512,
|
|
57
|
+
RsaPublicKey,
|
|
58
|
+
rsaParsePublicKey,
|
|
59
|
+
rsaVerifyPss,
|
|
60
|
+
} from "nish/crypto/rsa"
|
|
61
|
+
import { SHA256_SIZE } from "nish/crypto/sha256"
|
|
62
|
+
import { SHA384_SIZE, SHA512_SIZE } from "nish/crypto/sha512"
|
|
63
|
+
import {
|
|
64
|
+
X509Certificate,
|
|
65
|
+
X509_KEY_P256,
|
|
66
|
+
X509_KEY_P384,
|
|
67
|
+
X509_KEY_RSA,
|
|
68
|
+
x509DerSignatureRSWidth,
|
|
69
|
+
x509ParseCertificate,
|
|
70
|
+
} from "nish/crypto/x509"
|
|
71
|
+
import {
|
|
72
|
+
TLS_ALERT_BAD_CERTIFICATE,
|
|
73
|
+
TLS_ALERT_DECRYPT_ERROR,
|
|
74
|
+
TLS_SIGNATURE_ECDSA_SECP256R1_SHA256,
|
|
75
|
+
TLS_SIGNATURE_ECDSA_SECP384R1_SHA384,
|
|
76
|
+
TLS_SIGNATURE_RSA_PSS_RSAE_SHA256,
|
|
77
|
+
TLS_SIGNATURE_RSA_PSS_RSAE_SHA384,
|
|
78
|
+
TLS_SIGNATURE_RSA_PSS_RSAE_SHA512,
|
|
79
|
+
} from "nish/net/tls/codec"
|
|
80
|
+
import { TlsClient } from "nish/net/tls/client"
|
|
81
|
+
import { TlsRecordClient } from "nish/net/tls/record-client"
|
|
82
|
+
|
|
83
|
+
/** `certificate_expired` (RFC 8446 §6.2): the leaf or a certificate above it is outside its validity. */
|
|
84
|
+
export const TLS_ALERT_CERTIFICATE_EXPIRED: i32 = 45
|
|
85
|
+
|
|
86
|
+
/** `unknown_ca` (RFC 8446 §6.2): no path from the chain reaches a root the client trusts. */
|
|
87
|
+
export const TLS_ALERT_UNKNOWN_CA: i32 = 48
|
|
88
|
+
|
|
89
|
+
/** The smallest RSA modulus a CertificateVerify is checked under, in bits: the Web PKI's floor. */
|
|
90
|
+
export const TLS_VERIFY_MIN_RSA_BITS: i32 = 2048
|
|
91
|
+
|
|
92
|
+
/** An ECDSA signature's `r` and `s` width on P-256, in bytes. */
|
|
93
|
+
const TLS_VERIFY_P256_WIDTH: i32 = 32
|
|
94
|
+
|
|
95
|
+
/** An ECDSA signature's `r` and `s` width on P-384, in bytes. */
|
|
96
|
+
const TLS_VERIFY_P384_WIDTH: i32 = 48
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The roots a client trusts, indexed once and shared by every connection
|
|
100
|
+
* made with them.
|
|
101
|
+
*/
|
|
102
|
+
export class TlsClientTrust {
|
|
103
|
+
/** The roots, as `pkiVerifyServer` looks them up. */
|
|
104
|
+
store: PkiTrustStore
|
|
105
|
+
|
|
106
|
+
constructor(roots: X509Certificate[]) {
|
|
107
|
+
this.store = pkiTrustStore(roots)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The trust of `roots`, as `nish/crypto/pki-roots` read them. */
|
|
112
|
+
export const tlsClientTrust = (roots: PkiRoots): TlsClientTrust => new TlsClientTrust(roots.roots)
|
|
113
|
+
|
|
114
|
+
/** The trust of the system's roots (`pkiLoadSystemRoots`): what a client trusts unless it is given a store. */
|
|
115
|
+
export const tlsClientSystemTrust = (): TlsClientTrust => tlsClientTrust(pkiLoadSystemRoots())
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether `signature`, as a CertificateVerify carries it under `scheme`,
|
|
119
|
+
* verifies over `content` under `leaf`'s key. `false` for a scheme that
|
|
120
|
+
* does not fit the key's kind and for every scheme this client does not
|
|
121
|
+
* offer.
|
|
122
|
+
*/
|
|
123
|
+
export const tlsVerifyCertificateVerify = (
|
|
124
|
+
leaf: X509Certificate,
|
|
125
|
+
scheme: i32,
|
|
126
|
+
content: u8[],
|
|
127
|
+
signature: u8[]
|
|
128
|
+
): boolean => {
|
|
129
|
+
if (scheme === TLS_SIGNATURE_ECDSA_SECP256R1_SHA256) {
|
|
130
|
+
if (leaf.keyType !== X509_KEY_P256) {
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
const rs: u8[] | null = x509DerSignatureRSWidth(signature, TLS_VERIFY_P256_WIDTH)
|
|
134
|
+
return rs !== null && p256VerifySha256(leaf.publicKey, content, rs)
|
|
135
|
+
}
|
|
136
|
+
if (scheme === TLS_SIGNATURE_ECDSA_SECP384R1_SHA384) {
|
|
137
|
+
if (leaf.keyType !== X509_KEY_P384) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
const rs: u8[] | null = x509DerSignatureRSWidth(signature, TLS_VERIFY_P384_WIDTH)
|
|
141
|
+
return rs !== null && p384VerifySha384(leaf.publicKey, content, rs)
|
|
142
|
+
}
|
|
143
|
+
if (leaf.keyType !== X509_KEY_RSA) {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
const key: RsaPublicKey | null = rsaParsePublicKey(leaf.publicKey)
|
|
147
|
+
if (key === null) {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
// RFC 8446 §4.2.3: the salt is as long as the digest.
|
|
151
|
+
switch (scheme) {
|
|
152
|
+
case TLS_SIGNATURE_RSA_PSS_RSAE_SHA256:
|
|
153
|
+
return rsaVerifyPss(key, RSA_HASH_SHA256, SHA256_SIZE, content, signature)
|
|
154
|
+
case TLS_SIGNATURE_RSA_PSS_RSAE_SHA384:
|
|
155
|
+
return rsaVerifyPss(key, RSA_HASH_SHA384, SHA384_SIZE, content, signature)
|
|
156
|
+
case TLS_SIGNATURE_RSA_PSS_RSAE_SHA512:
|
|
157
|
+
return rsaVerifyPss(key, RSA_HASH_SHA512, SHA512_SIZE, content, signature)
|
|
158
|
+
default:
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Whether `leaf`'s key is one a CertificateVerify is checked under: P-256, P-384, or RSA of at least `TLS_VERIFY_MIN_RSA_BITS`. */
|
|
164
|
+
const tlsVerifyKeyUsable = (leaf: X509Certificate): boolean => {
|
|
165
|
+
if (leaf.keyType === X509_KEY_P256 || leaf.keyType === X509_KEY_P384) {
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
if (leaf.keyType !== X509_KEY_RSA) {
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
const key: RsaPublicKey | null = rsaParsePublicKey(leaf.publicKey)
|
|
172
|
+
return key !== null && key.bits >= TLS_VERIFY_MIN_RSA_BITS
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The chain step: 0 when `chain` (DER, leaf first) leads to a root in
|
|
177
|
+
* `trust` and names `serverName` at `nowMs`, else the alert —
|
|
178
|
+
* `certificate_expired`, `unknown_ca` or `bad_certificate`.
|
|
179
|
+
*/
|
|
180
|
+
export const tlsClientChain = (chain: u8[][], serverName: string, nowMs: i64, trust: TlsClientTrust): i32 => {
|
|
181
|
+
const result: PkiResult = pkiVerifyServer(chain, serverName, nowMs, trust.store)
|
|
182
|
+
if (result.code === PKI_OK) {
|
|
183
|
+
return 0
|
|
184
|
+
}
|
|
185
|
+
if (result.code === PKI_ERR_EXPIRED || result.code === PKI_ERR_NOT_YET_VALID) {
|
|
186
|
+
return TLS_ALERT_CERTIFICATE_EXPIRED
|
|
187
|
+
}
|
|
188
|
+
return result.code === PKI_ERR_UNTRUSTED ? TLS_ALERT_UNKNOWN_CA : TLS_ALERT_BAD_CERTIFICATE
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The verdict on `tls`'s server at `TLS_CLIENT_STATE_WAIT_AUTH`: 0 when the
|
|
193
|
+
* CertificateVerify verifies under the leaf's key and the chain leads from
|
|
194
|
+
* that leaf to a root in `trust` for the configured server name at `nowMs`
|
|
195
|
+
* (milliseconds since 1970-01-01 UTC), else the alert to refuse with.
|
|
196
|
+
*/
|
|
197
|
+
export const tlsClientVerdict = (tls: TlsClient, trust: TlsClientTrust, nowMs: i64): i32 => {
|
|
198
|
+
const chain: u8[][] = tls.peerCertificates()
|
|
199
|
+
if (toI32(chain.length) === 0) {
|
|
200
|
+
return TLS_ALERT_BAD_CERTIFICATE
|
|
201
|
+
}
|
|
202
|
+
const leaf: X509Certificate | null = x509ParseCertificate(chain[0])
|
|
203
|
+
if (leaf === null || !tlsVerifyKeyUsable(leaf)) {
|
|
204
|
+
return TLS_ALERT_BAD_CERTIFICATE
|
|
205
|
+
}
|
|
206
|
+
if (!tlsVerifyCertificateVerify(leaf, tls.verifyScheme(), tls.verifyContent(), tls.verifySignature())) {
|
|
207
|
+
return TLS_ALERT_DECRYPT_ERROR
|
|
208
|
+
}
|
|
209
|
+
return tlsClientChain(chain, tls.config.serverName, nowMs, trust)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Authenticates `client`'s server, while its interest says
|
|
214
|
+
* `TLS_RECORD_AUTH`: `tlsClientVerdict`, then `client.authenticate(true)` on
|
|
215
|
+
* 0 or `client.reject(alert)` otherwise, which seals the alert. Answers 0
|
|
216
|
+
* or the alert the connection failed with.
|
|
217
|
+
*/
|
|
218
|
+
export const tlsClientAuthenticate = (client: TlsRecordClient, trust: TlsClientTrust, nowMs: i64): i32 => {
|
|
219
|
+
const alert: i32 = tlsClientVerdict(client.tls, trust, nowMs)
|
|
220
|
+
return alert === 0 ? client.authenticate(true) : client.reject(alert)
|
|
221
|
+
}
|