@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,1207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/crypto/pki` — whether the certificate chain a TLS server sent leads to
|
|
3
|
+
* a root the program trusts and names the host it dialled: RFC 5280 §6 path
|
|
4
|
+
* validation and RFC 6125 §6 hostname matching, at the instant the caller
|
|
5
|
+
* gives.
|
|
6
|
+
*
|
|
7
|
+
* import { pkiLoadSystemRoots } from "nish/crypto/pki-roots";
|
|
8
|
+
* import { PKI_OK, PkiResult, PkiTrustStore, pkiReason, pkiTrustStore, pkiVerifyServer } from "nish/crypto/pki";
|
|
9
|
+
*
|
|
10
|
+
* const store: PkiTrustStore = pkiTrustStore(pkiLoadSystemRoots().roots);
|
|
11
|
+
* // chain: each certificate's DER as the server sent it, its own first
|
|
12
|
+
* const r: PkiResult = pkiVerifyServer(chain, "example.com", toI64(Date.now()), store);
|
|
13
|
+
* if (r.code !== PKI_OK) {
|
|
14
|
+
* console.log(pkiReason(r.code));
|
|
15
|
+
* }
|
|
16
|
+
* // r.path, when PKI_OK: the leaf first, the trusted root last
|
|
17
|
+
*
|
|
18
|
+
* The network chooses every byte of `chain` (docs/security/crypto-pki.md,
|
|
19
|
+
* "Threat model"), so every refusal has a reason code of its own and every
|
|
20
|
+
* cost has a named bound: the certificates and bytes of a chain, the names
|
|
21
|
+
* and subtrees of a certificate, the certificates of a path, the signatures
|
|
22
|
+
* checked and the name comparisons made by one call.
|
|
23
|
+
*
|
|
24
|
+
* The chain's first certificate is the leaf; the rest are a pool, in any
|
|
25
|
+
* order and possibly with extras, as servers send them. A path is searched
|
|
26
|
+
* for depth first from the leaf: an issuer is a root of the store, tried
|
|
27
|
+
* first, or a pool certificate, whose subject is the child's issuer Name byte
|
|
28
|
+
* for byte, and whose subjectKeyIdentifier, when both it and the child's
|
|
29
|
+
* authorityKeyIdentifier are present, is that identifier. A certificate is
|
|
30
|
+
* never twice in a path. A path ends at a root, whose own signature is not
|
|
31
|
+
* read, but whose validity, critical extensions, basicConstraints,
|
|
32
|
+
* extKeyUsage, name constraints and key identifiers are checked as an
|
|
33
|
+
* intermediate's are, as x509-limbo's cases ask. Every check of an issuer is
|
|
34
|
+
* made as it is added, against the certificates below it, so a refusal ends
|
|
35
|
+
* that branch at once; the leaf's profile (CA/Browser Forum Baseline
|
|
36
|
+
* Requirements §7.1) is checked once a path is found. When no path is
|
|
37
|
+
* accepted, the reason is that of the attempt that got nearest a root: one
|
|
38
|
+
* that reached a root beats any that did not, a longer one a shorter, and the
|
|
39
|
+
* first found a later one of the same length.
|
|
40
|
+
*
|
|
41
|
+
* Revocation (CRLs, OCSP, Certificate Transparency), certificate policies,
|
|
42
|
+
* RFC 5280 §7.1's Name folding and an extension's criticality are not
|
|
43
|
+
* checked; each is a finding of docs/security/crypto-pki.md. Nothing here is
|
|
44
|
+
* secret — certificates, names and public keys — so nothing is wiped.
|
|
45
|
+
*
|
|
46
|
+
* Written for this module from RFC 5280 (§4.2 and §6.1), RFC 6125 (§6.4),
|
|
47
|
+
* RFC 4291 §2.2 and RFC 3986 §3.2.2 (address literals), RFC 5758, RFC 8017
|
|
48
|
+
* and RFC 4055 (the signature algorithms), RFC 8032 (Ed25519) and the
|
|
49
|
+
* Baseline Requirements §7.1, and checked against x509-limbo
|
|
50
|
+
* (tests/link/crypto_pki_limbo).
|
|
51
|
+
*
|
|
52
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
53
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
54
|
+
*/
|
|
55
|
+
import { ed25519Verify, ED25519_PUBLIC_KEY_SIZE } from "nish/crypto/ed25519"
|
|
56
|
+
import { p256Verify, P256_POINT_SIZE } from "nish/crypto/p256"
|
|
57
|
+
import { p384Verify, P384_POINT_SIZE } from "nish/crypto/p384"
|
|
58
|
+
import {
|
|
59
|
+
RSA_HASH_SHA256,
|
|
60
|
+
RSA_HASH_SHA384,
|
|
61
|
+
RSA_HASH_SHA512,
|
|
62
|
+
RsaPublicKey,
|
|
63
|
+
rsaParsePublicKey,
|
|
64
|
+
rsaVerifyPkcs1v15,
|
|
65
|
+
rsaVerifyPss,
|
|
66
|
+
} from "nish/crypto/rsa"
|
|
67
|
+
import { sha256 } from "nish/crypto/sha256"
|
|
68
|
+
import { sha384 } from "nish/crypto/sha512"
|
|
69
|
+
import {
|
|
70
|
+
X509Certificate,
|
|
71
|
+
X509GeneralSubtree,
|
|
72
|
+
X509_GN_DIR,
|
|
73
|
+
X509_GN_DNS,
|
|
74
|
+
X509_GN_IP,
|
|
75
|
+
X509_KEY_ED25519,
|
|
76
|
+
X509_KEY_P256,
|
|
77
|
+
X509_KEY_P384,
|
|
78
|
+
X509_KEY_RSA,
|
|
79
|
+
X509_KU_DIGITAL_SIGNATURE,
|
|
80
|
+
X509_KU_KEY_CERT_SIGN,
|
|
81
|
+
X509_MAX_SERIAL,
|
|
82
|
+
X509_SIG_ECDSA_SHA256,
|
|
83
|
+
X509_SIG_ECDSA_SHA384,
|
|
84
|
+
X509_SIG_ED25519,
|
|
85
|
+
X509_SIG_RSA_PKCS1_SHA256,
|
|
86
|
+
X509_SIG_RSA_PKCS1_SHA384,
|
|
87
|
+
X509_SIG_RSA_PKCS1_SHA512,
|
|
88
|
+
X509_SIG_RSA_PSS,
|
|
89
|
+
x509DerSignatureRSWidth,
|
|
90
|
+
x509NameEqual,
|
|
91
|
+
x509NameIsPrefix,
|
|
92
|
+
x509ParseCertificate,
|
|
93
|
+
} from "nish/crypto/x509"
|
|
94
|
+
|
|
95
|
+
/** The chain leads to a trusted root, every check passed and the leaf names the host. */
|
|
96
|
+
export const PKI_OK: i32 = 0
|
|
97
|
+
/**
|
|
98
|
+
* The chain is empty, a certificate in it is not one `x509ParseCertificate`
|
|
99
|
+
* reads, or a certificate on the path breaks the profile a server's chain
|
|
100
|
+
* keeps: a key identifier, serial number or dNSName.
|
|
101
|
+
*/
|
|
102
|
+
export const PKI_ERR_MALFORMED: i32 = 1
|
|
103
|
+
/** A certificate's notAfter is before the instant given. */
|
|
104
|
+
export const PKI_ERR_EXPIRED: i32 = 2
|
|
105
|
+
/** A certificate's notBefore is after the instant given. */
|
|
106
|
+
export const PKI_ERR_NOT_YET_VALID: i32 = 3
|
|
107
|
+
/** No path leads to a root of the store. */
|
|
108
|
+
export const PKI_ERR_UNTRUSTED: i32 = 4
|
|
109
|
+
/** A signature does not verify under its issuer's key, or the key is not one. */
|
|
110
|
+
export const PKI_ERR_BAD_SIGNATURE: i32 = 5
|
|
111
|
+
/** An issuer is not a v3 certificate with basicConstraints cA, or the leaf is a CA. */
|
|
112
|
+
export const PKI_ERR_NOT_CA: i32 = 6
|
|
113
|
+
/** An issuer's pathLenConstraint is fewer than the intermediates below it. */
|
|
114
|
+
export const PKI_ERR_PATH_LENGTH: i32 = 7
|
|
115
|
+
/** An issuer's keyUsage lacks keyCertSign, or the leaf's lacks digitalSignature or has keyCertSign. */
|
|
116
|
+
export const PKI_ERR_KEY_USAGE: i32 = 8
|
|
117
|
+
/** An extKeyUsage lacks serverAuth, the leaf's allows any, or the root has one. */
|
|
118
|
+
export const PKI_ERR_EXT_KEY_USAGE: i32 = 9
|
|
119
|
+
/** A certificate carries a critical extension `nish/crypto/x509` does not decode. */
|
|
120
|
+
export const PKI_ERR_UNKNOWN_CRITICAL: i32 = 10
|
|
121
|
+
/** A name below an issuer is outside its nameConstraints, they are of a form not checked, or the leaf has some. */
|
|
122
|
+
export const PKI_ERR_NAME_CONSTRAINTS: i32 = 11
|
|
123
|
+
/** The leaf's subjectAltName does not name the host. */
|
|
124
|
+
export const PKI_ERR_HOSTNAME: i32 = 12
|
|
125
|
+
/** An RSA key is shorter than `PKI_MIN_RSA_BITS`, or its modulus is not a whole number of bytes. */
|
|
126
|
+
export const PKI_ERR_WEAK_KEY: i32 = 13
|
|
127
|
+
/** A signature algorithm is SHA-1, unknown, or not one the issuer's key makes, or the leaf's key is of no type offered. */
|
|
128
|
+
export const PKI_ERR_UNSUPPORTED_ALGORITHM: i32 = 14
|
|
129
|
+
/** The chain or a path is longer than its bound. */
|
|
130
|
+
export const PKI_ERR_CHAIN_TOO_LONG: i32 = 15
|
|
131
|
+
/** The chain asks for more work than one call does: signatures, names or subtrees. */
|
|
132
|
+
export const PKI_ERR_BUDGET: i32 = 16
|
|
133
|
+
|
|
134
|
+
/** The most certificates a chain may hold, the leaf among them. */
|
|
135
|
+
export const PKI_MAX_CHAIN_CERTS: i32 = 128
|
|
136
|
+
/** The most bytes a chain's certificates may total: 1 MiB. */
|
|
137
|
+
export const PKI_MAX_CHAIN_BYTES: i32 = 1048576
|
|
138
|
+
/** The most certificates in a path, the leaf and the root among them. */
|
|
139
|
+
export const PKI_MAX_PATH: i32 = 8
|
|
140
|
+
/** The most signatures one call checks. */
|
|
141
|
+
export const PKI_MAX_SIGNATURES: i32 = 64
|
|
142
|
+
/** The most subjectAltName dNSNames and iPAddresses together a certificate may carry. */
|
|
143
|
+
export const PKI_MAX_NAMES: i32 = 1024
|
|
144
|
+
/** The most permitted and excluded subtrees together a certificate may carry. */
|
|
145
|
+
export const PKI_MAX_SUBTREES: i32 = 256
|
|
146
|
+
/** The most name-against-subtree comparisons one call makes. */
|
|
147
|
+
export const PKI_MAX_NAME_CHECKS: i32 = 250000
|
|
148
|
+
/** The shortest RSA modulus accepted, in bits. */
|
|
149
|
+
export const PKI_MIN_RSA_BITS: i32 = 2048
|
|
150
|
+
|
|
151
|
+
/** What an attempt that reached a root adds to its length, so it outranks every one that did not. */
|
|
152
|
+
const PKI_AT_ROOT: i32 = 1000
|
|
153
|
+
|
|
154
|
+
/** The roots a program trusts, indexed by their subject Name. */
|
|
155
|
+
export class PkiTrustStore {
|
|
156
|
+
/** Every root, in the order given, no two with the same DER. */
|
|
157
|
+
roots: X509Certificate[]
|
|
158
|
+
/** The roots under each subject, keyed by the subject's DER in hex. */
|
|
159
|
+
bySubject: Map<string, X509Certificate[]>
|
|
160
|
+
|
|
161
|
+
constructor() {
|
|
162
|
+
this.roots = []
|
|
163
|
+
this.bySubject = new Map<string, X509Certificate[]>()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** What `pkiVerifyServer` found. */
|
|
168
|
+
export class PkiResult {
|
|
169
|
+
/** `PKI_OK` or a `PKI_ERR_` constant. */
|
|
170
|
+
code: i32
|
|
171
|
+
/** When `PKI_OK`, the path: the leaf first, the root last; otherwise empty. */
|
|
172
|
+
path: X509Certificate[]
|
|
173
|
+
|
|
174
|
+
constructor(code: i32, path: X509Certificate[]) {
|
|
175
|
+
this.code = code
|
|
176
|
+
this.path = path
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** One call's search: the pool, the host, the path so far, and what has been spent. */
|
|
181
|
+
class PkiSearch {
|
|
182
|
+
store: PkiTrustStore
|
|
183
|
+
pool: X509Certificate[]
|
|
184
|
+
path: X509Certificate[]
|
|
185
|
+
/** The host as a DNS name, normalised, or `""` when it is an address. */
|
|
186
|
+
host: string
|
|
187
|
+
nowMs: i64 = 0
|
|
188
|
+
signatures: i32 = 0
|
|
189
|
+
nameChecks: i32 = 0
|
|
190
|
+
/** The reason of the attempt nearest a root, and how near it got. */
|
|
191
|
+
bestCode: i32 = 0
|
|
192
|
+
bestScore: i32 = -1
|
|
193
|
+
/** Whether a bound was spent, which ends the search. */
|
|
194
|
+
spent: boolean = false
|
|
195
|
+
|
|
196
|
+
constructor(store: PkiTrustStore, pool: X509Certificate[], host: string, nowMs: i64) {
|
|
197
|
+
this.store = store
|
|
198
|
+
this.pool = pool
|
|
199
|
+
this.path = []
|
|
200
|
+
this.host = host
|
|
201
|
+
this.nowMs = nowMs
|
|
202
|
+
this.bestCode = PKI_ERR_UNTRUSTED
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Bytes and ASCII
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
const pkiBytesEqual = (a: u8[], b: u8[]): boolean => {
|
|
211
|
+
const n: i32 = toI32(a.length)
|
|
212
|
+
if (n !== toI32(b.length)) {
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
for (let i: i32 = 0; i < n && i < toI32(b.length); i++) {
|
|
216
|
+
if (a[i] !== b[i]) {
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const pkiHexDigits: string = "0123456789abcdef"
|
|
224
|
+
|
|
225
|
+
/** `bytes` in lower-case hex, a `Map` key. */
|
|
226
|
+
const pkiHex = (bytes: u8[]): string => {
|
|
227
|
+
const out: u8[] = new Array<u8>(2 * toI32(bytes.length))
|
|
228
|
+
for (let i: i32 = 0; i < toI32(bytes.length) && 2 * i + 1 < toI32(out.length); i++) {
|
|
229
|
+
const b: i32 = toI32(bytes[i])
|
|
230
|
+
out[2 * i] = toU8(toI32(pkiHexDigits.charCodeAt(b >> 4)))
|
|
231
|
+
out[2 * i + 1] = toU8(toI32(pkiHexDigits.charCodeAt(b & 15)))
|
|
232
|
+
}
|
|
233
|
+
return String.fromBytes(out, 0, toI32(out.length))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const pkiLower = (c: i32): i32 => (c >= 65 && c <= 90 ? c + 32 : c)
|
|
237
|
+
|
|
238
|
+
/** Whether `a[aAt ..]` and `b[bAt ..]` agree for `n` bytes, ASCII case folded; both ranges must lie inside. */
|
|
239
|
+
const pkiFoldEqualAt = (a: string, aAt: i32, b: string, bAt: i32, n: i32): boolean => {
|
|
240
|
+
if (aAt < 0 || bAt < 0 || n < 0 || aAt + n > toI32(a.length) || bAt + n > toI32(b.length)) {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
244
|
+
if (pkiLower(toI32(a.charCodeAt(aAt + i))) !== pkiLower(toI32(b.charCodeAt(bAt + i)))) {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** `s` without one trailing dot. */
|
|
252
|
+
const pkiStripDot = (s: string): string =>
|
|
253
|
+
s.length > 0 && toI32(s.charCodeAt(toI32(s.length) - 1)) === 46 ? s.slice(0, toI32(s.length) - 1) : s
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Whether `s` is a DNS name a hostname can be: one to 253 bytes of labels of
|
|
257
|
+
* one to 63 letters, digits, hyphens or underscores, joined by single dots,
|
|
258
|
+
* with no trailing dot (a caller strips it). No `*`, no space, no octet past
|
|
259
|
+
* ASCII.
|
|
260
|
+
*/
|
|
261
|
+
const pkiValidDns = (s: string): boolean => {
|
|
262
|
+
const n: i32 = toI32(s.length)
|
|
263
|
+
if (n === 0 || n > 253) {
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
let label: i32 = 0
|
|
267
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
268
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
269
|
+
if (c === 46) {
|
|
270
|
+
if (label === 0) {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
label = 0
|
|
274
|
+
} else if (
|
|
275
|
+
(c >= 97 && c <= 122) ||
|
|
276
|
+
(c >= 65 && c <= 90) ||
|
|
277
|
+
(c >= 48 && c <= 57) ||
|
|
278
|
+
c === 45 ||
|
|
279
|
+
c === 95
|
|
280
|
+
) {
|
|
281
|
+
label++
|
|
282
|
+
if (label > 63) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return label > 0
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const pkiLabelCount = (s: string): i32 => {
|
|
293
|
+
let count: i32 = 1
|
|
294
|
+
for (let i: i32 = 0; i < toI32(s.length); i++) {
|
|
295
|
+
if (toI32(s.charCodeAt(i)) === 46) {
|
|
296
|
+
count++
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return count
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Address literals (RFC 3986 §3.2.2, RFC 4291 §2.2)
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
const pkiDigit = (c: i32): i32 => (c >= 48 && c <= 57 ? c - 48 : -1)
|
|
307
|
+
|
|
308
|
+
const pkiHexDigit = (c: i32): i32 => {
|
|
309
|
+
if (c >= 48 && c <= 57) {
|
|
310
|
+
return c - 48
|
|
311
|
+
}
|
|
312
|
+
const l: i32 = pkiLower(c)
|
|
313
|
+
return l >= 97 && l <= 102 ? l - 87 : -1
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The four octets of a dotted quad in `s[start .. end)`: four decimal numbers
|
|
318
|
+
* of 0 to 255, with no sign and no leading zero, or `null`.
|
|
319
|
+
*/
|
|
320
|
+
const pkiParseIpv4 = (s: string, start: i32, end: i32): u8[] | null => {
|
|
321
|
+
if (start < 0 || end > toI32(s.length)) {
|
|
322
|
+
return null
|
|
323
|
+
}
|
|
324
|
+
const out: u8[] = new Array<u8>(4)
|
|
325
|
+
let part: i32 = 0
|
|
326
|
+
let value: i32 = 0
|
|
327
|
+
let digits: i32 = 0
|
|
328
|
+
for (let i: i32 = start; i <= end; i++) {
|
|
329
|
+
const c: i32 = i < end ? toI32(s.charCodeAt(i)) : 46
|
|
330
|
+
if (c === 46) {
|
|
331
|
+
if (digits === 0 || part > 3) {
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
out[part] = toU8(value)
|
|
335
|
+
part++
|
|
336
|
+
value = 0
|
|
337
|
+
digits = 0
|
|
338
|
+
} else {
|
|
339
|
+
const d: i32 = pkiDigit(c)
|
|
340
|
+
if (d < 0 || (digits > 0 && value === 0)) {
|
|
341
|
+
return null
|
|
342
|
+
}
|
|
343
|
+
value = value * 10 + d
|
|
344
|
+
digits++
|
|
345
|
+
if (value > 255) {
|
|
346
|
+
return null
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return part === 4 ? out : null
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The sixteen octets of an IPv6 address in `s` (RFC 4291 §2.2): eight groups
|
|
355
|
+
* of one to four hex digits, at most one `::` standing for one or more zero
|
|
356
|
+
* groups, and a dotted quad as the last two groups. A zone (`%eth0`) is not an
|
|
357
|
+
* address. `null` for anything else.
|
|
358
|
+
*/
|
|
359
|
+
const pkiParseIpv6 = (s: string): u8[] | null => {
|
|
360
|
+
const n: i32 = toI32(s.length)
|
|
361
|
+
const words: i32[] = []
|
|
362
|
+
let gap: i32 = -1
|
|
363
|
+
let value: i32 = 0
|
|
364
|
+
let digits: i32 = 0
|
|
365
|
+
let colons: i32 = 0
|
|
366
|
+
let group: i32 = 0
|
|
367
|
+
let quadAt: i32 = -1
|
|
368
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
369
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
370
|
+
if (c === 58) {
|
|
371
|
+
if (digits > 0) {
|
|
372
|
+
if (toI32(words.length) >= 8) {
|
|
373
|
+
return null
|
|
374
|
+
}
|
|
375
|
+
words.push(value)
|
|
376
|
+
value = 0
|
|
377
|
+
digits = 0
|
|
378
|
+
}
|
|
379
|
+
colons++
|
|
380
|
+
if (colons > 2 || (colons === 2 && gap >= 0)) {
|
|
381
|
+
return null
|
|
382
|
+
}
|
|
383
|
+
if (colons === 2) {
|
|
384
|
+
gap = toI32(words.length)
|
|
385
|
+
}
|
|
386
|
+
} else if (c === 46) {
|
|
387
|
+
quadAt = group
|
|
388
|
+
break
|
|
389
|
+
} else {
|
|
390
|
+
const d: i32 = pkiHexDigit(c)
|
|
391
|
+
if (d < 0 || digits >= 4 || (colons === 1 && toI32(words.length) === 0 && gap < 0)) {
|
|
392
|
+
return null
|
|
393
|
+
}
|
|
394
|
+
if (digits === 0) {
|
|
395
|
+
group = i
|
|
396
|
+
}
|
|
397
|
+
colons = 0
|
|
398
|
+
value = value * 16 + d
|
|
399
|
+
digits++
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (quadAt >= 0) {
|
|
403
|
+
const quad: u8[] | null = pkiParseIpv4(s, quadAt, n)
|
|
404
|
+
if (quad === null || toI32(words.length) > 6) {
|
|
405
|
+
return null
|
|
406
|
+
}
|
|
407
|
+
words.push((toI32(quad[0]) << 8) | toI32(quad[1]))
|
|
408
|
+
words.push((toI32(quad[2]) << 8) | toI32(quad[3]))
|
|
409
|
+
} else if (digits > 0) {
|
|
410
|
+
if (toI32(words.length) >= 8) {
|
|
411
|
+
return null
|
|
412
|
+
}
|
|
413
|
+
words.push(value)
|
|
414
|
+
} else if (colons !== 2) {
|
|
415
|
+
return null
|
|
416
|
+
}
|
|
417
|
+
const count: i32 = toI32(words.length)
|
|
418
|
+
if ((gap < 0 && count !== 8) || (gap >= 0 && count > 7)) {
|
|
419
|
+
return null
|
|
420
|
+
}
|
|
421
|
+
const out: u8[] = new Array<u8>(16)
|
|
422
|
+
for (let w: i32 = 0; w < count; w++) {
|
|
423
|
+
const at: i32 = gap >= 0 && w >= gap ? w + 8 - count : w
|
|
424
|
+
if (at >= 0 && 2 * at + 1 < 16) {
|
|
425
|
+
out[2 * at] = toU8(words[w] >> 8)
|
|
426
|
+
out[2 * at + 1] = toU8(words[w] & 255)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return out
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* The octets of `host` when it is an address: a dotted quad, or an IPv6
|
|
434
|
+
* address bare or in brackets. `null` when it is not one — then it is a DNS
|
|
435
|
+
* name or nothing, and a bracket or a colon is never part of a DNS name.
|
|
436
|
+
*/
|
|
437
|
+
const pkiParseIp = (host: string): u8[] | null => {
|
|
438
|
+
const n: i32 = toI32(host.length)
|
|
439
|
+
if (n >= 2 && toI32(host.charCodeAt(0)) === 91 && toI32(host.charCodeAt(n - 1)) === 93) {
|
|
440
|
+
return pkiParseIpv6(host.slice(1, n - 1))
|
|
441
|
+
}
|
|
442
|
+
if (host.indexOf(":") >= 0) {
|
|
443
|
+
return pkiParseIpv6(host)
|
|
444
|
+
}
|
|
445
|
+
return pkiParseIpv4(host, 0, n)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// Hostname (RFC 6125 §6.4)
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Whether the subjectAltName dNSName `san`, IA5 bytes as encoded, names
|
|
454
|
+
* `host`, a valid DNS name without its trailing dot. A wildcard is only the
|
|
455
|
+
* whole leftmost label, `*.` before two labels or more, and stands for
|
|
456
|
+
* exactly one label of `host`.
|
|
457
|
+
*/
|
|
458
|
+
const pkiDnsMatch = (san: string, host: string): boolean => {
|
|
459
|
+
const name: string = pkiStripDot(san)
|
|
460
|
+
const n: i32 = toI32(name.length)
|
|
461
|
+
if (n >= 2 && toI32(name.charCodeAt(0)) === 42 && toI32(name.charCodeAt(1)) === 46) {
|
|
462
|
+
const rest: string = name.slice(2, n)
|
|
463
|
+
const dot: i32 = toI32(host.indexOf("."))
|
|
464
|
+
const restLength: i32 = n - 2
|
|
465
|
+
return (
|
|
466
|
+
pkiValidDns(rest) &&
|
|
467
|
+
pkiLabelCount(rest) >= 2 &&
|
|
468
|
+
dot > 0 &&
|
|
469
|
+
toI32(host.length) - dot - 1 === restLength &&
|
|
470
|
+
pkiFoldEqualAt(host, dot + 1, rest, 0, restLength)
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
return pkiValidDns(name) && n === toI32(host.length) && pkiFoldEqualAt(name, 0, host, 0, n)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** `hostname` without one trailing dot when it is a valid DNS name, else `null`. */
|
|
477
|
+
const pkiHostDns = (hostname: string): string | null => {
|
|
478
|
+
const host: string = pkiStripDot(hostname)
|
|
479
|
+
return pkiValidDns(host) ? host : null
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Whether `leaf` names `hostname` (RFC 6125 §6.4): an address against the
|
|
484
|
+
* subjectAltName iPAddresses, octet for octet; any other name, ASCII and
|
|
485
|
+
* compared without case after one trailing dot is stripped from each side,
|
|
486
|
+
* against the dNSNames, a wildcard only as a whole leftmost label. The
|
|
487
|
+
* subject's common name is never read, so a certificate without a
|
|
488
|
+
* subjectAltName names no host.
|
|
489
|
+
*/
|
|
490
|
+
export const pkiMatchHostname = (leaf: X509Certificate, hostname: string): boolean => {
|
|
491
|
+
const ip: u8[] | null = pkiParseIp(pkiStripDot(hostname))
|
|
492
|
+
if (ip !== null) {
|
|
493
|
+
for (const a of leaf.sanIp) {
|
|
494
|
+
if (pkiBytesEqual(a, ip)) {
|
|
495
|
+
return true
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return false
|
|
499
|
+
}
|
|
500
|
+
const host: string | null = pkiHostDns(hostname)
|
|
501
|
+
if (host === null) {
|
|
502
|
+
return false
|
|
503
|
+
}
|
|
504
|
+
for (const d of leaf.sanDns) {
|
|
505
|
+
if (pkiDnsMatch(d, host)) {
|
|
506
|
+
return true
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return false
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// Signatures
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Whether an RSA key is fit to sign or serve, as a code: one
|
|
518
|
+
* `rsaParsePublicKey` refused is a bad signature, and one shorter than
|
|
519
|
+
* `PKI_MIN_RSA_BITS`, or whose modulus is not a whole number of bytes, a weak
|
|
520
|
+
* key.
|
|
521
|
+
*/
|
|
522
|
+
const pkiRsaKeyCode = (key: RsaPublicKey | null): i32 => {
|
|
523
|
+
if (key === null) {
|
|
524
|
+
return PKI_ERR_BAD_SIGNATURE
|
|
525
|
+
}
|
|
526
|
+
return key.bits < PKI_MIN_RSA_BITS || key.bits % 8 !== 0 ? PKI_ERR_WEAK_KEY : PKI_OK
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* An RSA signature of `cert` under `issuer`'s key, PKCS#1 v1.5 or PSS as
|
|
531
|
+
* `cert.sigAlg` names. The key is checked by `pkiRsaKeyCode` first. `X509_HASH_` and
|
|
532
|
+
* `RSA_HASH_` number SHA-256, SHA-384 and SHA-512 alike, so `pssHash` passes
|
|
533
|
+
* straight through.
|
|
534
|
+
*/
|
|
535
|
+
const pkiRsaCode = (cert: X509Certificate, issuer: X509Certificate): i32 => {
|
|
536
|
+
const key: RsaPublicKey | null = rsaParsePublicKey(issuer.publicKey)
|
|
537
|
+
const fit: i32 = pkiRsaKeyCode(key)
|
|
538
|
+
if (key === null || fit !== PKI_OK) {
|
|
539
|
+
return fit
|
|
540
|
+
}
|
|
541
|
+
const alg: i32 = cert.sigAlg
|
|
542
|
+
if (alg === X509_SIG_RSA_PSS) {
|
|
543
|
+
return rsaVerifyPss(key, cert.pssHash, cert.pssSaltLen, cert.tbs, cert.signature)
|
|
544
|
+
? PKI_OK
|
|
545
|
+
: PKI_ERR_BAD_SIGNATURE
|
|
546
|
+
}
|
|
547
|
+
let hash: i32 = RSA_HASH_SHA512
|
|
548
|
+
if (alg === X509_SIG_RSA_PKCS1_SHA256) {
|
|
549
|
+
hash = RSA_HASH_SHA256
|
|
550
|
+
} else if (alg === X509_SIG_RSA_PKCS1_SHA384) {
|
|
551
|
+
hash = RSA_HASH_SHA384
|
|
552
|
+
}
|
|
553
|
+
return rsaVerifyPkcs1v15(key, hash, cert.tbs, cert.signature) ? PKI_OK : PKI_ERR_BAD_SIGNATURE
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** An ECDSA signature of `cert` under a P-256 or P-384 key, with SHA-256 or SHA-384 as `cert.sigAlg` names. */
|
|
557
|
+
const pkiEcdsaCode = (cert: X509Certificate, issuer: X509Certificate): i32 => {
|
|
558
|
+
const digest: u8[] = cert.sigAlg === X509_SIG_ECDSA_SHA256 ? sha256(cert.tbs) : sha384(cert.tbs)
|
|
559
|
+
if (issuer.keyType === X509_KEY_P256) {
|
|
560
|
+
const rs: u8[] | null = x509DerSignatureRSWidth(cert.signature, 32)
|
|
561
|
+
return toI32(issuer.publicKey.length) === P256_POINT_SIZE &&
|
|
562
|
+
rs !== null &&
|
|
563
|
+
p256Verify(issuer.publicKey, digest, rs)
|
|
564
|
+
? PKI_OK
|
|
565
|
+
: PKI_ERR_BAD_SIGNATURE
|
|
566
|
+
}
|
|
567
|
+
const rs: u8[] | null = x509DerSignatureRSWidth(cert.signature, 48)
|
|
568
|
+
return toI32(issuer.publicKey.length) === P384_POINT_SIZE &&
|
|
569
|
+
rs !== null &&
|
|
570
|
+
p384Verify(issuer.publicKey, digest, rs)
|
|
571
|
+
? PKI_OK
|
|
572
|
+
: PKI_ERR_BAD_SIGNATURE
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Whether `cert`'s signature verifies under `issuer`'s key, as a code: `PKI_OK`,
|
|
577
|
+
* `PKI_ERR_BAD_SIGNATURE`, `PKI_ERR_WEAK_KEY`, or `PKI_ERR_UNSUPPORTED_ALGORITHM`
|
|
578
|
+
* for SHA-1, an unknown algorithm, or one the key does not make.
|
|
579
|
+
*/
|
|
580
|
+
const pkiSignatureCode = (cert: X509Certificate, issuer: X509Certificate): i32 => {
|
|
581
|
+
const alg: i32 = cert.sigAlg
|
|
582
|
+
const key: i32 = issuer.keyType
|
|
583
|
+
if (alg === X509_SIG_ECDSA_SHA256 || alg === X509_SIG_ECDSA_SHA384) {
|
|
584
|
+
return key === X509_KEY_P256 || key === X509_KEY_P384
|
|
585
|
+
? pkiEcdsaCode(cert, issuer)
|
|
586
|
+
: PKI_ERR_UNSUPPORTED_ALGORITHM
|
|
587
|
+
}
|
|
588
|
+
if (alg === X509_SIG_ED25519) {
|
|
589
|
+
if (key !== X509_KEY_ED25519) {
|
|
590
|
+
return PKI_ERR_UNSUPPORTED_ALGORITHM
|
|
591
|
+
}
|
|
592
|
+
return toI32(issuer.publicKey.length) === ED25519_PUBLIC_KEY_SIZE &&
|
|
593
|
+
ed25519Verify(issuer.publicKey, cert.tbs, cert.signature)
|
|
594
|
+
? PKI_OK
|
|
595
|
+
: PKI_ERR_BAD_SIGNATURE
|
|
596
|
+
}
|
|
597
|
+
if (
|
|
598
|
+
alg === X509_SIG_RSA_PKCS1_SHA256 ||
|
|
599
|
+
alg === X509_SIG_RSA_PKCS1_SHA384 ||
|
|
600
|
+
alg === X509_SIG_RSA_PKCS1_SHA512 ||
|
|
601
|
+
alg === X509_SIG_RSA_PSS
|
|
602
|
+
) {
|
|
603
|
+
return key === X509_KEY_RSA ? pkiRsaCode(cert, issuer) : PKI_ERR_UNSUPPORTED_ALGORITHM
|
|
604
|
+
}
|
|
605
|
+
return PKI_ERR_UNSUPPORTED_ALGORITHM
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Whether `cert`'s signature verifies under `issuer`'s key: ECDSA with SHA-256
|
|
610
|
+
* or SHA-384 under P-256 or P-384, RSA PKCS#1 v1.5 with SHA-256, SHA-384 or
|
|
611
|
+
* SHA-512 and RSASSA-PSS under a key of `PKI_MIN_RSA_BITS` or more whose
|
|
612
|
+
* modulus is a whole number of bytes, and Ed25519. Nothing else about either certificate is checked.
|
|
613
|
+
*/
|
|
614
|
+
export const pkiVerifySignature = (cert: X509Certificate, issuer: X509Certificate): boolean =>
|
|
615
|
+
pkiSignatureCode(cert, issuer) === PKI_OK
|
|
616
|
+
|
|
617
|
+
/** `pkiSignatureCode` within the call's budget of signatures. */
|
|
618
|
+
const pkiCheckSignature = (s: PkiSearch, cert: X509Certificate, issuer: X509Certificate): i32 => {
|
|
619
|
+
if (s.signatures >= PKI_MAX_SIGNATURES) {
|
|
620
|
+
s.spent = true
|
|
621
|
+
return PKI_ERR_BUDGET
|
|
622
|
+
}
|
|
623
|
+
s.signatures = s.signatures + 1
|
|
624
|
+
return pkiSignatureCode(cert, issuer)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// ---------------------------------------------------------------------------
|
|
628
|
+
// Name constraints (RFC 5280 §4.2.1.10, §6.1.3 (b) and (c))
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Whether the DNS name `name` lies in the subtree `base`, without case: `""`
|
|
633
|
+
* is every name, `.example.com` every name below example.com, and
|
|
634
|
+
* `example.com` it and every name below it.
|
|
635
|
+
*/
|
|
636
|
+
const pkiDnsWithin = (name: string, base: string): boolean => {
|
|
637
|
+
const b: string = pkiStripDot(base)
|
|
638
|
+
const nb: i32 = toI32(b.length)
|
|
639
|
+
const nn: i32 = toI32(name.length)
|
|
640
|
+
if (nb === 0) {
|
|
641
|
+
return true
|
|
642
|
+
}
|
|
643
|
+
if (toI32(b.charCodeAt(0)) === 46) {
|
|
644
|
+
return nn > nb && pkiFoldEqualAt(name, nn - nb, b, 0, nb)
|
|
645
|
+
}
|
|
646
|
+
if (nn === nb) {
|
|
647
|
+
return pkiFoldEqualAt(name, 0, b, 0, nb)
|
|
648
|
+
}
|
|
649
|
+
return nn > nb && toI32(name.charCodeAt(nn - nb - 1)) === 46 && pkiFoldEqualAt(name, nn - nb, b, 0, nb)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** `base` as the name its subtree is rooted at: without its leading dot. */
|
|
653
|
+
const pkiSubtreeRoot = (base: string): string =>
|
|
654
|
+
base.length > 0 && toI32(base.charCodeAt(0)) === 46 ? base.slice(1, toI32(base.length)) : base
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Whether `issuer`'s dNSName constraints allow the name `dns`: inside one
|
|
658
|
+
* permitted subtree when any is listed, and in no excluded one. A wildcard
|
|
659
|
+
* `*.X` stands for every name one label below X, so it is permitted when X
|
|
660
|
+
* lies in the subtree's root, and excluded when it could reach an excluded
|
|
661
|
+
* subtree — X lies in that root, or the root lies below X.
|
|
662
|
+
*/
|
|
663
|
+
const pkiDnsAllowed = (issuer: X509Certificate, dns: string): boolean => {
|
|
664
|
+
const name: string = pkiStripDot(dns)
|
|
665
|
+
const wildcard: boolean = name.startsWith("*.")
|
|
666
|
+
const base: string = wildcard ? name.slice(2, toI32(name.length)) : name
|
|
667
|
+
let listed: boolean = false
|
|
668
|
+
let inside: boolean = false
|
|
669
|
+
for (const p of issuer.ncPermitted) {
|
|
670
|
+
if (p.kind === X509_GN_DNS) {
|
|
671
|
+
listed = true
|
|
672
|
+
if (wildcard ? pkiDnsWithin(base, pkiSubtreeRoot(p.dns)) : pkiDnsWithin(base, p.dns)) {
|
|
673
|
+
inside = true
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (listed && !inside) {
|
|
678
|
+
return false
|
|
679
|
+
}
|
|
680
|
+
for (const e of issuer.ncExcluded) {
|
|
681
|
+
if (e.kind === X509_GN_DNS) {
|
|
682
|
+
const root: string = pkiSubtreeRoot(e.dns)
|
|
683
|
+
if (wildcard ? pkiDnsWithin(base, root) || pkiDnsWithin(root, base) : pkiDnsWithin(base, e.dns)) {
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return true
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Whether `ip` lies in the iPAddress subtree `t`: the same family, and equal under its mask. */
|
|
692
|
+
const pkiIpWithin = (ip: u8[], t: X509GeneralSubtree): boolean => {
|
|
693
|
+
const n: i32 = toI32(ip.length)
|
|
694
|
+
if (n !== toI32(t.ip.length) || n !== toI32(t.mask.length)) {
|
|
695
|
+
return false
|
|
696
|
+
}
|
|
697
|
+
for (let k: i32 = 0; k < n && k < toI32(t.ip.length) && k < toI32(t.mask.length); k++) {
|
|
698
|
+
if ((toI32(ip[k]) & toI32(t.mask[k])) !== (toI32(t.ip[k]) & toI32(t.mask[k]))) {
|
|
699
|
+
return false
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return true
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Whether `issuer`'s iPAddress constraints allow `ip`; a subtree of the other family never holds it. */
|
|
706
|
+
const pkiIpAllowed = (issuer: X509Certificate, ip: u8[]): boolean => {
|
|
707
|
+
let listed: boolean = false
|
|
708
|
+
let inside: boolean = false
|
|
709
|
+
for (const p of issuer.ncPermitted) {
|
|
710
|
+
if (p.kind === X509_GN_IP) {
|
|
711
|
+
listed = true
|
|
712
|
+
if (pkiIpWithin(ip, p)) {
|
|
713
|
+
inside = true
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (listed && !inside) {
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
720
|
+
for (const e of issuer.ncExcluded) {
|
|
721
|
+
if (e.kind === X509_GN_IP && pkiIpWithin(ip, e)) {
|
|
722
|
+
return false
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return true
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Whether `issuer`'s permitted directoryName constraints allow the subject
|
|
730
|
+
* Name `subject`: one of them is a prefix of it, RDN by RDN. An empty subject
|
|
731
|
+
* names nothing and is allowed. Excluded directoryNames never reach here: a
|
|
732
|
+
* byte-exact prefix test would let a differently spelt Name past one, so a
|
|
733
|
+
* chain that carries any is refused whole.
|
|
734
|
+
*/
|
|
735
|
+
const pkiDirAllowed = (issuer: X509Certificate, subject: u8[]): boolean => {
|
|
736
|
+
if (toI32(subject.length) <= 2) {
|
|
737
|
+
return true
|
|
738
|
+
}
|
|
739
|
+
let listed: boolean = false
|
|
740
|
+
for (const p of issuer.ncPermitted) {
|
|
741
|
+
if (p.kind === X509_GN_DIR) {
|
|
742
|
+
listed = true
|
|
743
|
+
if (x509NameIsPrefix(p.dirName, subject)) {
|
|
744
|
+
return true
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return !listed
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const pkiSelfIssued = (c: X509Certificate): boolean => x509NameEqual(c.subject, c.issuer)
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Whether `issuer`'s nameConstraints allow every name below it in the path
|
|
755
|
+
* and the host, as a code. Each issuer's constraints are checked on their
|
|
756
|
+
* own, which makes permitted subtrees an intersection down the path and
|
|
757
|
+
* excluded ones a union.
|
|
758
|
+
*/
|
|
759
|
+
const pkiConstraintsCode = (s: PkiSearch, issuer: X509Certificate): i32 => {
|
|
760
|
+
if (!issuer.ncPresent) {
|
|
761
|
+
return PKI_OK
|
|
762
|
+
}
|
|
763
|
+
if (issuer.ncUnsupported) {
|
|
764
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
765
|
+
}
|
|
766
|
+
for (const e of issuer.ncExcluded) {
|
|
767
|
+
if (e.kind === X509_GN_DIR) {
|
|
768
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
let names: i32 = 1
|
|
772
|
+
for (const c of s.path) {
|
|
773
|
+
names = names + toI32(c.sanDns.length) + toI32(c.sanIp.length) + 1
|
|
774
|
+
}
|
|
775
|
+
const cost: i32 = names * (toI32(issuer.ncPermitted.length) + toI32(issuer.ncExcluded.length))
|
|
776
|
+
if (cost > PKI_MAX_NAME_CHECKS - s.nameChecks) {
|
|
777
|
+
s.spent = true
|
|
778
|
+
return PKI_ERR_BUDGET
|
|
779
|
+
}
|
|
780
|
+
s.nameChecks = s.nameChecks + cost
|
|
781
|
+
if (s.host.length > 0 && !pkiDnsAllowed(issuer, s.host)) {
|
|
782
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
783
|
+
}
|
|
784
|
+
for (let i: i32 = 0; i < toI32(s.path.length); i++) {
|
|
785
|
+
const c: X509Certificate = s.path[i]
|
|
786
|
+
for (const d of c.sanDns) {
|
|
787
|
+
if (!pkiDnsAllowed(issuer, d)) {
|
|
788
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
for (const ip of c.sanIp) {
|
|
792
|
+
if (!pkiIpAllowed(issuer, ip)) {
|
|
793
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if ((i === 0 || !pkiSelfIssued(c)) && !pkiDirAllowed(issuer, c.subject)) {
|
|
797
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return PKI_OK
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// ---------------------------------------------------------------------------
|
|
804
|
+
// The path (RFC 5280 §6.1)
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
|
|
807
|
+
/** Whether `c` is valid at `nowMs`, as a code. */
|
|
808
|
+
const pkiTimeCode = (c: X509Certificate, nowMs: i64): i32 => {
|
|
809
|
+
if (nowMs < c.notBefore) {
|
|
810
|
+
return PKI_ERR_NOT_YET_VALID
|
|
811
|
+
}
|
|
812
|
+
return nowMs > c.notAfter ? PKI_ERR_EXPIRED : PKI_OK
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* The key identifiers an issuer `cand` must carry (RFC 5280 §4.2.1.1 and
|
|
817
|
+
* §4.2.1.2), as a code: a subjectKeyIdentifier, which a root may lack, as
|
|
818
|
+
* one Mozilla ships does, and an authorityKeyIdentifier unless it is
|
|
819
|
+
* self-issued, even on a root. A self-issued root's authorityKeyIdentifier,
|
|
820
|
+
* when present, is its own subjectKeyIdentifier.
|
|
821
|
+
*/
|
|
822
|
+
const pkiKeyIdsCode = (cand: X509Certificate, root: boolean): i32 => {
|
|
823
|
+
const skid: u8[] | null = cand.skid
|
|
824
|
+
const akid: u8[] | null = cand.akid
|
|
825
|
+
if (skid === null) {
|
|
826
|
+
return root ? PKI_OK : PKI_ERR_MALFORMED
|
|
827
|
+
}
|
|
828
|
+
if (!pkiSelfIssued(cand)) {
|
|
829
|
+
return akid === null ? PKI_ERR_MALFORMED : PKI_OK
|
|
830
|
+
}
|
|
831
|
+
return root && akid !== null && !pkiBytesEqual(akid, skid) ? PKI_ERR_MALFORMED : PKI_OK
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* The checks of the issuer `cand` added above the path, a root of the store
|
|
836
|
+
* when `root` holds and an intermediate otherwise: its validity, its critical
|
|
837
|
+
* extensions, that it is a CA, its extKeyUsage, its name constraints, its
|
|
838
|
+
* signature over `child` — the only check that costs — and, once that
|
|
839
|
+
* verifies, its key identifiers, so a wrong key reads as a signature. An
|
|
840
|
+
* intermediate also needs room for a root above it and a keyUsage that
|
|
841
|
+
* allows keyCertSign; every issuer, the root too, needs a pathLenConstraint
|
|
842
|
+
* that covers the intermediates below it. A root's keyUsage is not read, and
|
|
843
|
+
* it may carry no extKeyUsage at all.
|
|
844
|
+
*/
|
|
845
|
+
const pkiIssuerCode = (s: PkiSearch, child: X509Certificate, cand: X509Certificate, root: boolean): i32 => {
|
|
846
|
+
// No intermediate is added past PKI_MAX_PATH - 1, so a root always fits.
|
|
847
|
+
if (!root && toI32(s.path.length) + 2 > PKI_MAX_PATH) {
|
|
848
|
+
return PKI_ERR_CHAIN_TOO_LONG
|
|
849
|
+
}
|
|
850
|
+
const time: i32 = pkiTimeCode(cand, s.nowMs)
|
|
851
|
+
if (time !== PKI_OK) {
|
|
852
|
+
return time
|
|
853
|
+
}
|
|
854
|
+
if (cand.unknownCritical) {
|
|
855
|
+
return PKI_ERR_UNKNOWN_CRITICAL
|
|
856
|
+
}
|
|
857
|
+
if (cand.version !== 2 || !cand.bcPresent || !cand.bcCa) {
|
|
858
|
+
return PKI_ERR_NOT_CA
|
|
859
|
+
}
|
|
860
|
+
if (!root && cand.keyUsage >= 0 && (cand.keyUsage & X509_KU_KEY_CERT_SIGN) === 0) {
|
|
861
|
+
return PKI_ERR_KEY_USAGE
|
|
862
|
+
}
|
|
863
|
+
if (cand.ekuPresent && (root || (!cand.ekuServerAuth && !cand.ekuAny))) {
|
|
864
|
+
return PKI_ERR_EXT_KEY_USAGE
|
|
865
|
+
}
|
|
866
|
+
if (cand.bcPathLen >= 0) {
|
|
867
|
+
let below: i32 = 0
|
|
868
|
+
for (let i: i32 = 1; i < toI32(s.path.length); i++) {
|
|
869
|
+
if (!pkiSelfIssued(s.path[i])) {
|
|
870
|
+
below++
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (below > cand.bcPathLen) {
|
|
874
|
+
return PKI_ERR_PATH_LENGTH
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const names: i32 = pkiConstraintsCode(s, cand)
|
|
878
|
+
if (names !== PKI_OK) {
|
|
879
|
+
return names
|
|
880
|
+
}
|
|
881
|
+
const signature: i32 = pkiCheckSignature(s, child, cand)
|
|
882
|
+
return signature !== PKI_OK ? signature : pkiKeyIdsCode(cand, root)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** Keeps `code` as the answer when its attempt, `score` long, got nearer a root than any before it. */
|
|
886
|
+
const pkiNote = (s: PkiSearch, code: i32, score: i32): void => {
|
|
887
|
+
if (score > s.bestScore) {
|
|
888
|
+
s.bestScore = score
|
|
889
|
+
s.bestCode = code
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Whether `cand` may be tried as `child`'s issuer: its key identifier is not
|
|
895
|
+
* another than the one `child` names, and it is not already in the path.
|
|
896
|
+
*/
|
|
897
|
+
const pkiCandidate = (s: PkiSearch, child: X509Certificate, cand: X509Certificate): boolean => {
|
|
898
|
+
const akid: u8[] | null = child.akid
|
|
899
|
+
const skid: u8[] | null = cand.skid
|
|
900
|
+
if (akid !== null && skid !== null && !pkiBytesEqual(akid, skid)) {
|
|
901
|
+
return false
|
|
902
|
+
}
|
|
903
|
+
for (const c of s.path) {
|
|
904
|
+
if (pkiBytesEqual(c.der, cand.der)) {
|
|
905
|
+
return false
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return true
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Extends the path above `child`, its last certificate, toward a root: the
|
|
913
|
+
* store's roots under `child`'s issuer first, then the pool's certificates in
|
|
914
|
+
* the order sent. Answers whether a path was accepted, then whole in `s.path`.
|
|
915
|
+
*/
|
|
916
|
+
const pkiExtend = (s: PkiSearch, child: X509Certificate): boolean => {
|
|
917
|
+
const depth: i32 = toI32(s.path.length)
|
|
918
|
+
if (toI32(child.issuer.length) > 2) {
|
|
919
|
+
const roots: X509Certificate[] | undefined = s.store.bySubject.get(pkiHex(child.issuer))
|
|
920
|
+
if (roots !== undefined) {
|
|
921
|
+
for (const root of roots) {
|
|
922
|
+
if (pkiCandidate(s, child, root)) {
|
|
923
|
+
const code: i32 = pkiIssuerCode(s, child, root, true)
|
|
924
|
+
if (s.spent) {
|
|
925
|
+
return false
|
|
926
|
+
}
|
|
927
|
+
if (code === PKI_OK) {
|
|
928
|
+
s.path.push(root)
|
|
929
|
+
return true
|
|
930
|
+
}
|
|
931
|
+
pkiNote(s, code, PKI_AT_ROOT + depth + 1)
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
for (const cand of s.pool) {
|
|
936
|
+
if (x509NameEqual(cand.subject, child.issuer) && pkiCandidate(s, child, cand)) {
|
|
937
|
+
const code: i32 = pkiIssuerCode(s, child, cand, false)
|
|
938
|
+
if (s.spent) {
|
|
939
|
+
return false
|
|
940
|
+
}
|
|
941
|
+
if (code !== PKI_OK) {
|
|
942
|
+
pkiNote(s, code, depth + 1)
|
|
943
|
+
} else {
|
|
944
|
+
s.path.push(cand)
|
|
945
|
+
if (pkiExtend(s, cand)) {
|
|
946
|
+
return true
|
|
947
|
+
}
|
|
948
|
+
if (s.spent) {
|
|
949
|
+
return false
|
|
950
|
+
}
|
|
951
|
+
s.path.pop()
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
pkiNote(s, PKI_ERR_UNTRUSTED, depth)
|
|
957
|
+
return false
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ---------------------------------------------------------------------------
|
|
961
|
+
// The leaf's profile (CA/Browser Forum Baseline Requirements §7.1)
|
|
962
|
+
// ---------------------------------------------------------------------------
|
|
963
|
+
|
|
964
|
+
/** Whether `c`'s serial number is 1 to 20 octets and not zero (RFC 5280 §4.1.2.2). */
|
|
965
|
+
const pkiSerialSound = (c: X509Certificate): boolean => {
|
|
966
|
+
const n: i32 = toI32(c.serial.length)
|
|
967
|
+
if (n < 1 || n > X509_MAX_SERIAL) {
|
|
968
|
+
return false
|
|
969
|
+
}
|
|
970
|
+
for (let i: i32 = 0; i < n; i++) {
|
|
971
|
+
if (c.serial[i] !== 0) {
|
|
972
|
+
return true
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return false
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Whether the dNSName `san` is one a server's certificate may carry: a valid
|
|
980
|
+
* DNS name of letters, digits and hyphens, after one trailing dot, or `*.`
|
|
981
|
+
* before one.
|
|
982
|
+
*/
|
|
983
|
+
const pkiSanDnsSound = (san: string): boolean => {
|
|
984
|
+
const name: string = pkiStripDot(san)
|
|
985
|
+
const n: i32 = toI32(name.length)
|
|
986
|
+
const wild: boolean = n >= 2 && toI32(name.charCodeAt(0)) === 42 && toI32(name.charCodeAt(1)) === 46
|
|
987
|
+
return name.indexOf("_") < 0 && pkiValidDns(wild ? name.slice(2, n) : name)
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Whether the leaf's own key is one a server may hold, as a code: RSA of
|
|
992
|
+
* `PKI_MIN_RSA_BITS` or more in whole bytes, P-256, P-384 or Ed25519, the keys
|
|
993
|
+
* a signature is verified under here, so the server can prove it holds it.
|
|
994
|
+
*/
|
|
995
|
+
const pkiSubscriberKeyCode = (leaf: X509Certificate): i32 => {
|
|
996
|
+
const type: i32 = leaf.keyType
|
|
997
|
+
if (type === X509_KEY_RSA) {
|
|
998
|
+
return pkiRsaKeyCode(rsaParsePublicKey(leaf.publicKey))
|
|
999
|
+
}
|
|
1000
|
+
return type === X509_KEY_P256 || type === X509_KEY_P384 || type === X509_KEY_ED25519
|
|
1001
|
+
? PKI_OK
|
|
1002
|
+
: PKI_ERR_UNSUPPORTED_ALGORITHM
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* The checks of the leaf's profile, once a path above it is found: no keyCertSign
|
|
1007
|
+
* and no nameConstraints, no `anyExtendedKeyUsage`, a key a server may
|
|
1008
|
+
* hold, a sound serial number, an authorityKeyIdentifier unless it is
|
|
1009
|
+
* self-issued, and dNSNames of letters, digits and hyphens. The common name
|
|
1010
|
+
* is not read: limbo's own passing cases carry ones their subjectAltName
|
|
1011
|
+
* lacks.
|
|
1012
|
+
*/
|
|
1013
|
+
const pkiLeafProfileCode = (leaf: X509Certificate): i32 => {
|
|
1014
|
+
if (leaf.keyUsage >= 0 && (leaf.keyUsage & X509_KU_KEY_CERT_SIGN) !== 0) {
|
|
1015
|
+
return PKI_ERR_KEY_USAGE
|
|
1016
|
+
}
|
|
1017
|
+
if (leaf.ncPresent) {
|
|
1018
|
+
return PKI_ERR_NAME_CONSTRAINTS
|
|
1019
|
+
}
|
|
1020
|
+
if (leaf.ekuAny) {
|
|
1021
|
+
return PKI_ERR_EXT_KEY_USAGE
|
|
1022
|
+
}
|
|
1023
|
+
const key: i32 = pkiSubscriberKeyCode(leaf)
|
|
1024
|
+
if (key !== PKI_OK) {
|
|
1025
|
+
return key
|
|
1026
|
+
}
|
|
1027
|
+
if (!pkiSerialSound(leaf) || (leaf.akid === null && !pkiSelfIssued(leaf))) {
|
|
1028
|
+
return PKI_ERR_MALFORMED
|
|
1029
|
+
}
|
|
1030
|
+
for (const d of leaf.sanDns) {
|
|
1031
|
+
if (!pkiSanDnsSound(d)) {
|
|
1032
|
+
return PKI_ERR_MALFORMED
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return PKI_OK
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* The checks of the leaf alone: its validity, its critical extensions, that
|
|
1040
|
+
* it is not a CA, that its key may sign a TLS handshake and is meant for a
|
|
1041
|
+
* server, and that it names the host. Its profile is judged after the path
|
|
1042
|
+
* search, so a path's reason comes first.
|
|
1043
|
+
*/
|
|
1044
|
+
const pkiLeafCode = (leaf: X509Certificate, hostname: string, nowMs: i64): i32 => {
|
|
1045
|
+
const time: i32 = pkiTimeCode(leaf, nowMs)
|
|
1046
|
+
if (time !== PKI_OK) {
|
|
1047
|
+
return time
|
|
1048
|
+
}
|
|
1049
|
+
if (leaf.unknownCritical) {
|
|
1050
|
+
return PKI_ERR_UNKNOWN_CRITICAL
|
|
1051
|
+
}
|
|
1052
|
+
if (leaf.bcCa) {
|
|
1053
|
+
return PKI_ERR_NOT_CA
|
|
1054
|
+
}
|
|
1055
|
+
if (leaf.keyUsage >= 0 && (leaf.keyUsage & X509_KU_DIGITAL_SIGNATURE) === 0) {
|
|
1056
|
+
return PKI_ERR_KEY_USAGE
|
|
1057
|
+
}
|
|
1058
|
+
if (leaf.ekuPresent && !leaf.ekuServerAuth) {
|
|
1059
|
+
return PKI_ERR_EXT_KEY_USAGE
|
|
1060
|
+
}
|
|
1061
|
+
return pkiMatchHostname(leaf, hostname) ? PKI_OK : PKI_ERR_HOSTNAME
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/** A store of `roots`, indexed by subject; a root whose DER is one already given is kept once. */
|
|
1065
|
+
export const pkiTrustStore = (roots: X509Certificate[]): PkiTrustStore => {
|
|
1066
|
+
const store = new PkiTrustStore()
|
|
1067
|
+
for (const root of roots) {
|
|
1068
|
+
const key: string = pkiHex(root.subject)
|
|
1069
|
+
const list: X509Certificate[] | undefined = store.bySubject.get(key)
|
|
1070
|
+
if (list === undefined) {
|
|
1071
|
+
const fresh: X509Certificate[] = [root]
|
|
1072
|
+
store.bySubject.set(key, fresh)
|
|
1073
|
+
store.roots.push(root)
|
|
1074
|
+
} else {
|
|
1075
|
+
let seen: boolean = false
|
|
1076
|
+
for (const r of list) {
|
|
1077
|
+
if (pkiBytesEqual(r.der, root.der)) {
|
|
1078
|
+
seen = true
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (!seen) {
|
|
1082
|
+
list.push(root)
|
|
1083
|
+
store.roots.push(root)
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return store
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Verifies a TLS server's chain: `chain` holds each certificate's DER as the
|
|
1092
|
+
* server sent it, the leaf first; `hostname` is the name or address dialled,
|
|
1093
|
+
* ASCII (an internationalised name in its A-label form); `nowMs` is the
|
|
1094
|
+
* instant to judge validity at, milliseconds since 1970-01-01 UTC; `store`
|
|
1095
|
+
* holds the roots trusted. `PKI_OK` with the path, or the reason it was
|
|
1096
|
+
* refused — see the module's header for which reason when several apply.
|
|
1097
|
+
*/
|
|
1098
|
+
export const pkiVerifyServer = (
|
|
1099
|
+
chain: u8[][],
|
|
1100
|
+
hostname: string,
|
|
1101
|
+
nowMs: i64,
|
|
1102
|
+
store: PkiTrustStore
|
|
1103
|
+
): PkiResult => {
|
|
1104
|
+
const none: X509Certificate[] = []
|
|
1105
|
+
const count: i32 = toI32(chain.length)
|
|
1106
|
+
if (count === 0) {
|
|
1107
|
+
return new PkiResult(PKI_ERR_MALFORMED, none)
|
|
1108
|
+
}
|
|
1109
|
+
if (count > PKI_MAX_CHAIN_CERTS) {
|
|
1110
|
+
return new PkiResult(PKI_ERR_CHAIN_TOO_LONG, none)
|
|
1111
|
+
}
|
|
1112
|
+
let bytes: i32 = 0
|
|
1113
|
+
for (const der of chain) {
|
|
1114
|
+
bytes = bytes + toI32(der.length)
|
|
1115
|
+
if (bytes > PKI_MAX_CHAIN_BYTES) {
|
|
1116
|
+
return new PkiResult(PKI_ERR_CHAIN_TOO_LONG, none)
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
const certs: X509Certificate[] = []
|
|
1120
|
+
for (const der of chain) {
|
|
1121
|
+
const c: X509Certificate | null = x509ParseCertificate(der)
|
|
1122
|
+
if (c === null) {
|
|
1123
|
+
return new PkiResult(PKI_ERR_MALFORMED, none)
|
|
1124
|
+
}
|
|
1125
|
+
if (
|
|
1126
|
+
toI32(c.sanDns.length) + toI32(c.sanIp.length) > PKI_MAX_NAMES ||
|
|
1127
|
+
toI32(c.ncPermitted.length) + toI32(c.ncExcluded.length) > PKI_MAX_SUBTREES
|
|
1128
|
+
) {
|
|
1129
|
+
return new PkiResult(PKI_ERR_BUDGET, none)
|
|
1130
|
+
}
|
|
1131
|
+
certs.push(c)
|
|
1132
|
+
}
|
|
1133
|
+
const leaf: X509Certificate = certs[0]
|
|
1134
|
+
const leafCode: i32 = pkiLeafCode(leaf, hostname, nowMs)
|
|
1135
|
+
if (leafCode !== PKI_OK) {
|
|
1136
|
+
return new PkiResult(leafCode, none)
|
|
1137
|
+
}
|
|
1138
|
+
const pool: X509Certificate[] = []
|
|
1139
|
+
for (let i: i32 = 1; i < toI32(certs.length); i++) {
|
|
1140
|
+
pool.push(certs[i])
|
|
1141
|
+
}
|
|
1142
|
+
const ip: u8[] | null = pkiParseIp(pkiStripDot(hostname))
|
|
1143
|
+
const dns: string | null = ip === null ? pkiHostDns(hostname) : null
|
|
1144
|
+
const s = new PkiSearch(store, pool, dns === null ? "" : dns, nowMs)
|
|
1145
|
+
s.path.push(leaf)
|
|
1146
|
+
if (pkiExtend(s, leaf)) {
|
|
1147
|
+
const profile: i32 = pkiLeafProfileCode(leaf)
|
|
1148
|
+
return profile === PKI_OK ? new PkiResult(PKI_OK, s.path) : new PkiResult(profile, none)
|
|
1149
|
+
}
|
|
1150
|
+
return new PkiResult(s.spent ? PKI_ERR_BUDGET : s.bestCode, none)
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/** A sentence for `code`, for a log or an error message. */
|
|
1154
|
+
export const pkiReason = (code: i32): string => {
|
|
1155
|
+
if (code === PKI_OK) {
|
|
1156
|
+
return "the chain is trusted and names the host"
|
|
1157
|
+
}
|
|
1158
|
+
if (code === PKI_ERR_MALFORMED) {
|
|
1159
|
+
return "the chain is empty, or holds a certificate that does not parse or breaks the server profile"
|
|
1160
|
+
}
|
|
1161
|
+
if (code === PKI_ERR_EXPIRED) {
|
|
1162
|
+
return "a certificate has expired"
|
|
1163
|
+
}
|
|
1164
|
+
if (code === PKI_ERR_NOT_YET_VALID) {
|
|
1165
|
+
return "a certificate is not yet valid"
|
|
1166
|
+
}
|
|
1167
|
+
if (code === PKI_ERR_UNTRUSTED) {
|
|
1168
|
+
return "no path leads to a trusted root"
|
|
1169
|
+
}
|
|
1170
|
+
if (code === PKI_ERR_BAD_SIGNATURE) {
|
|
1171
|
+
return "a signature does not verify under its issuer's key"
|
|
1172
|
+
}
|
|
1173
|
+
if (code === PKI_ERR_NOT_CA) {
|
|
1174
|
+
return "an issuer is not a CA certificate, or the leaf is one"
|
|
1175
|
+
}
|
|
1176
|
+
if (code === PKI_ERR_PATH_LENGTH) {
|
|
1177
|
+
return "an issuer's path length constraint is exceeded"
|
|
1178
|
+
}
|
|
1179
|
+
if (code === PKI_ERR_KEY_USAGE) {
|
|
1180
|
+
return "a key usage does not allow its use here"
|
|
1181
|
+
}
|
|
1182
|
+
if (code === PKI_ERR_EXT_KEY_USAGE) {
|
|
1183
|
+
return "an extended key usage does not allow TLS server authentication"
|
|
1184
|
+
}
|
|
1185
|
+
if (code === PKI_ERR_UNKNOWN_CRITICAL) {
|
|
1186
|
+
return "a certificate has a critical extension that is not understood"
|
|
1187
|
+
}
|
|
1188
|
+
if (code === PKI_ERR_NAME_CONSTRAINTS) {
|
|
1189
|
+
return "a name is outside an issuer's name constraints"
|
|
1190
|
+
}
|
|
1191
|
+
if (code === PKI_ERR_HOSTNAME) {
|
|
1192
|
+
return "the certificate does not name the host"
|
|
1193
|
+
}
|
|
1194
|
+
if (code === PKI_ERR_WEAK_KEY) {
|
|
1195
|
+
return "an RSA key is shorter than 2048 bits or not a whole number of bytes"
|
|
1196
|
+
}
|
|
1197
|
+
if (code === PKI_ERR_UNSUPPORTED_ALGORITHM) {
|
|
1198
|
+
return "a signature algorithm is not supported"
|
|
1199
|
+
}
|
|
1200
|
+
if (code === PKI_ERR_CHAIN_TOO_LONG) {
|
|
1201
|
+
return "the chain or its path is too long"
|
|
1202
|
+
}
|
|
1203
|
+
if (code === PKI_ERR_BUDGET) {
|
|
1204
|
+
return "the chain asks for more work than one verification does"
|
|
1205
|
+
}
|
|
1206
|
+
return "unknown reason"
|
|
1207
|
+
}
|