lemon-tls 0.9.0 → 0.11.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/README.md +168 -41
- package/index.cjs +9 -7
- package/index.d.ts +626 -30
- package/package.json +45 -7
- package/src/ca_store.js +31 -0
- package/src/compat.js +76 -116
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1284 -390
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +355 -191
- package/src/secure_context.js +97 -39
- package/src/session/ecdh.js +140 -122
- package/src/session/ech.js +92 -32
- package/src/session/hpke.js +12 -44
- package/src/session/message.js +29 -9
- package/src/session/signing.js +80 -22
- package/src/session/ticket.js +5 -3
- package/src/session/x509.js +385 -0
- package/src/tls_session.js +2221 -902
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// X.509 for the handshake: certificates read, issuers matched,
|
|
3
|
+
// signatures checked, and the path checks (RFC 5280 §4.2, §6.1)
|
|
4
|
+
// ============================================================
|
|
5
|
+
//
|
|
6
|
+
// The certificate FORMAT is cryptographic-formats': the DER, the extensions, names
|
|
7
|
+
// and their comparison (RFC 5280 §7.1, as OpenSSL canonicalizes them), name
|
|
8
|
+
// constraints, host names. This module holds what a TLS verifier decides on
|
|
9
|
+
// top of it, and the signature check, which is node:crypto's:
|
|
10
|
+
//
|
|
11
|
+
// parse_cert a certificate, read once per DER object
|
|
12
|
+
// issued_by the issuer's name, and its key identifier where the
|
|
13
|
+
// certificate names one (OpenSSL's X509_check_issued,
|
|
14
|
+
// less the keyUsage test — check_path reports that as
|
|
15
|
+
// INVALID_CA, which is OpenSSL's answer too)
|
|
16
|
+
// signed_by the signature, by the issuer's key
|
|
17
|
+
// check_path what each certificate of a built path may do:
|
|
18
|
+
//
|
|
19
|
+
// basicConstraints every issuer a CA (an anchor too: one that is not —
|
|
20
|
+
// an end-entity certificate given as `ca` — vouched for
|
|
21
|
+
// any name); pathLenConstraint; a leaf that is a CA
|
|
22
|
+
// (CA/B Forum BR §7.1.2.7.8: cA MUST be false) refused
|
|
23
|
+
// keyUsage an issuer may sign certificates (keyCertSign); a leaf
|
|
24
|
+
// may sign or agree a key (OpenSSL's TLS purpose rule)
|
|
25
|
+
// extKeyUsage when present, on the leaf and the intermediates, the
|
|
26
|
+
// purpose asked for (serverAuth, clientAuth) or any
|
|
27
|
+
// nameConstraints each CA's, on every certificate below it — the
|
|
28
|
+
// subject, its emailAddress attributes, every
|
|
29
|
+
// subjectAltName, and the leaf's common name when it has
|
|
30
|
+
// no DNS name (OpenSSL's NAME_CONSTRAINTS_check_CN)
|
|
31
|
+
// critical an extension marked critical that is not one this
|
|
32
|
+
// verifier enforces refuses the certificate (§4.2)
|
|
33
|
+
// signature a certificate below the anchor signed with MD2, MD4,
|
|
34
|
+
// MD5 or SHA-1 is refused (CA_MD_TOO_WEAK, OpenSSL's
|
|
35
|
+
// security level 1, node:tls's default)
|
|
36
|
+
//
|
|
37
|
+
// The codes are OpenSSL's names (as node:tls reports them).
|
|
38
|
+
//
|
|
39
|
+
// This module had its own DER reader and its own name-constraint matching.
|
|
40
|
+
// Moving them to cryptographic-formats fixed what they had wrong against OpenSSL: a
|
|
41
|
+
// subject's emailAddress was never checked against email constraints, a
|
|
42
|
+
// leaf's common name never against DNS ones, directory names were compared
|
|
43
|
+
// byte for byte (O=Acme did not match O=ACME), a non-prefix IP mask and a
|
|
44
|
+
// subtree minimum were reported as UNSUPPORTED_CONSTRAINT_SYNTAX where
|
|
45
|
+
// OpenSSL matches the first and says SUBTREE_MINMAX for the second.
|
|
46
|
+
|
|
47
|
+
import crypto from 'node:crypto';
|
|
48
|
+
import * as X509 from 'cryptographic-formats/x509';
|
|
49
|
+
|
|
50
|
+
const ANY_EKU = '2.5.29.37.0', SERVER_AUTH = '1.3.6.1.5.5.7.3.1', CLIENT_AUTH = '1.3.6.1.5.5.7.3.2';
|
|
51
|
+
|
|
52
|
+
// The extensions this verifier enforces, so the only ones that may be marked
|
|
53
|
+
// critical — any other critical: UNHANDLED_CRITICAL_EXTENSION, OpenSSL's
|
|
54
|
+
// error 34. keyUsage, subjectAltName, basicConstraints, nameConstraints and
|
|
55
|
+
// extKeyUsage are checked here; the policy ones restrict nothing this
|
|
56
|
+
// verifier relies on (it requires no policy), and CRL distribution points
|
|
57
|
+
// say where to look, not what to refuse. OpenSSL takes a few more critical
|
|
58
|
+
// (nsCertType, RFC 3779's, proxyCertInfo) because it enforces them by rules
|
|
59
|
+
// of its own; they are not read here, so refused here.
|
|
60
|
+
//
|
|
61
|
+
// The key identifiers, freshest CRL, AIA, SIA, issuer alt name, subject
|
|
62
|
+
// directory attributes and TLS feature are refused critical as well — RFC
|
|
63
|
+
// 5280 has the key identifiers and AIA non-critical ("MUST", §4.2.1.1, .2,
|
|
64
|
+
// §4.2.2.1), and a certificate that marks AIA critical says a verifier that
|
|
65
|
+
// does not fetch from it must not use it (x509-limbo; BoringSSL refuses
|
|
66
|
+
// them too).
|
|
67
|
+
const ENFORCED = X509.enforced_set([
|
|
68
|
+
'2.5.29.15', '2.5.29.17', '2.5.29.19', '2.5.29.30', '2.5.29.37', // KU, SAN, BC, NC, EKU
|
|
69
|
+
'2.5.29.32', '2.5.29.33', '2.5.29.36', '2.5.29.54', // policies, mappings, constraints, inhibitAnyPolicy
|
|
70
|
+
'2.5.29.31', // CRL distribution points
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
// ---- certificates ----
|
|
74
|
+
|
|
75
|
+
// Read once per DER object: the anchors are the same objects on every
|
|
76
|
+
// handshake, and the peer's leaf is read by the chain check, the identity
|
|
77
|
+
// check and the handshake signature. Strings (PEM) are not keys here — the
|
|
78
|
+
// caller turns them into DER first.
|
|
79
|
+
const PARSED = new WeakMap();
|
|
80
|
+
// …and once per DER content, a bounded number of them: a client that
|
|
81
|
+
// connects with a `ca` makes a new secure context — new objects for the
|
|
82
|
+
// same CA — every time, and a server presents the same chain to every
|
|
83
|
+
// client. Reading them again cost a quarter of a millisecond a handshake.
|
|
84
|
+
// A certificate is immutable, so its reading is too. Full: cleared, as the
|
|
85
|
+
// anchor cache in tls_session.
|
|
86
|
+
const PARSED_BY_CONTENT = new Map();
|
|
87
|
+
const PARSED_BY_CONTENT_MAX = 1024;
|
|
88
|
+
|
|
89
|
+
function bytes_equal(a, b) {
|
|
90
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
91
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A certificate from its DER (Buffer or Uint8Array), or null when it is not
|
|
97
|
+
* one cryptographic-formats can read. The result:
|
|
98
|
+
* raw Buffer, the DER
|
|
99
|
+
* cf cryptographic-formats' x509.from_der
|
|
100
|
+
* subjectKey, the names as one string each, equal when the names match
|
|
101
|
+
* issuerKey (RFC 5280 §7.1 as OpenSSL compares them) — for an index
|
|
102
|
+
* notBefore, ms
|
|
103
|
+
* notAfter
|
|
104
|
+
* selfIssued subject and issuer the same name
|
|
105
|
+
* bc, ku, eku, nc, ski, aki the extensions' parsed values, or null
|
|
106
|
+
* publicKey() a crypto.KeyObject, made on first use; null when node:crypto
|
|
107
|
+
* cannot use the key
|
|
108
|
+
*/
|
|
109
|
+
function parse_cert(der) {
|
|
110
|
+
if (der == null || typeof der !== 'object') return null;
|
|
111
|
+
let hit = PARSED.get(der);
|
|
112
|
+
if (hit !== undefined) return hit;
|
|
113
|
+
let content = Buffer.from(der.buffer, der.byteOffset, der.byteLength).toString('latin1');
|
|
114
|
+
let out = PARSED_BY_CONTENT.get(content);
|
|
115
|
+
if (out === undefined) {
|
|
116
|
+
try { out = make_cert(X509.from_der(der)); } catch (e) { out = null; }
|
|
117
|
+
if (PARSED_BY_CONTENT.size >= PARSED_BY_CONTENT_MAX) PARSED_BY_CONTENT.clear();
|
|
118
|
+
PARSED_BY_CONTENT.set(content, out);
|
|
119
|
+
}
|
|
120
|
+
PARSED.set(der, out);
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function make_cert(cf) {
|
|
125
|
+
let ext = function (name) { let e = X509.extension(cf, name); return e ? e.parsed : null; };
|
|
126
|
+
let subjectKey = X509.name_key(cf.subject), issuerKey = X509.name_key(cf.issuer);
|
|
127
|
+
let key;
|
|
128
|
+
return {
|
|
129
|
+
raw: Buffer.from(cf.der.buffer, cf.der.byteOffset, cf.der.byteLength),
|
|
130
|
+
cf: cf,
|
|
131
|
+
subjectKey: subjectKey,
|
|
132
|
+
issuerKey: issuerKey,
|
|
133
|
+
notBefore: cf.validity.not_before.date.getTime(),
|
|
134
|
+
notAfter: cf.validity.not_after.date.getTime(),
|
|
135
|
+
selfIssued: subjectKey === issuerKey,
|
|
136
|
+
bc: ext('basicConstraints'),
|
|
137
|
+
ku: ext('keyUsage'),
|
|
138
|
+
eku: ext('extKeyUsage'),
|
|
139
|
+
nc: ext('nameConstraints'),
|
|
140
|
+
ski: ext('subjectKeyId'),
|
|
141
|
+
aki: ext('authorityKeyId'),
|
|
142
|
+
publicKey: function () {
|
|
143
|
+
if (key === undefined) {
|
|
144
|
+
try { key = crypto.createPublicKey({ key: Buffer.from(cf.public_key.der), format: 'der', type: 'spki' }); }
|
|
145
|
+
catch (e) { key = null; }
|
|
146
|
+
}
|
|
147
|
+
return key;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Inside its validity window at `now` (a Date): both ends inclusive (§4.1.2.5). */
|
|
153
|
+
function valid_at(cert, now) {
|
|
154
|
+
let t = now.getTime();
|
|
155
|
+
return t >= cert.notBefore && t <= cert.notAfter;
|
|
156
|
+
}
|
|
157
|
+
/** The node:tls (OpenSSL) code for a certificate outside its window. */
|
|
158
|
+
function validity_error(cert, now) {
|
|
159
|
+
return now.getTime() < cert.notBefore ? 'CERT_NOT_YET_VALID' : 'CERT_HAS_EXPIRED';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Could `issuer` have issued `cert`? Its subject is the certificate's issuer
|
|
164
|
+
* name, and where the certificate's authorityKeyIdentifier names a key
|
|
165
|
+
* identifier, a serial or an issuer, the candidate is that one — OpenSSL's
|
|
166
|
+
* X509_check_akid. That is what picks the right CA of two with one name.
|
|
167
|
+
* Not the signature (signed_by), and not the issuer's keyUsage (check_path).
|
|
168
|
+
*/
|
|
169
|
+
function issued_by(cert, issuer) {
|
|
170
|
+
if (cert.issuerKey !== issuer.subjectKey) return false;
|
|
171
|
+
let aki = cert.aki;
|
|
172
|
+
if (!aki) return true;
|
|
173
|
+
if (aki.key_id && issuer.ski && !bytes_equal(aki.key_id, issuer.ski)) return false;
|
|
174
|
+
if (aki.serial && !bytes_equal(aki.serial, issuer.cf.serial)) return false;
|
|
175
|
+
if (aki.issuer) {
|
|
176
|
+
let dns = aki.issuer.filter(function (g) { return g.type === 'directory'; });
|
|
177
|
+
if (dns.length > 0 && !dns.some(function (g) { return X509.name_key(g.value) === issuer.issuerKey; })) return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Is `cert`'s signature `issuer`'s? The algorithm as cryptographic-formats
|
|
184
|
+
* describes it, the check node:crypto's. An algorithm node:crypto cannot
|
|
185
|
+
* check — RSASSA-PSS whose mask hash is not its message hash (node:crypto
|
|
186
|
+
* takes one hash for both), MD2 — is a signature that does not verify.
|
|
187
|
+
*/
|
|
188
|
+
function signed_by(cert, issuer) {
|
|
189
|
+
let key = issuer.publicKey();
|
|
190
|
+
if (!key) return false;
|
|
191
|
+
let p = cert.cf.signature_params, tbs = cert.cf.tbs_bytes, sig = cert.cf.signature;
|
|
192
|
+
try {
|
|
193
|
+
switch (p.scheme) {
|
|
194
|
+
case 'pkcs1':
|
|
195
|
+
return crypto.verify(p.hash, tbs, { key: key, padding: crypto.constants.RSA_PKCS1_PADDING }, sig);
|
|
196
|
+
case 'pss':
|
|
197
|
+
if (p.pss.mgf !== 'mgf1' || p.pss.mgf_hash !== p.pss.hash) return false;
|
|
198
|
+
return crypto.verify(p.pss.hash, tbs, { key: key, padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
199
|
+
saltLength: p.pss.salt_length }, sig);
|
|
200
|
+
case 'ecdsa':
|
|
201
|
+
case 'dsa':
|
|
202
|
+
return crypto.verify(p.hash, tbs, key, sig);
|
|
203
|
+
case 'eddsa':
|
|
204
|
+
case 'ml-dsa':
|
|
205
|
+
case 'slh-dsa':
|
|
206
|
+
return crypto.verify(null, tbs, key, sig);
|
|
207
|
+
default:
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
} catch (e) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Issued by itself, and signed by its own key. */
|
|
216
|
+
function self_signed(cert) {
|
|
217
|
+
return issued_by(cert, cert) && signed_by(cert, cert);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- name constraints (§4.2.1.10) ----
|
|
221
|
+
|
|
222
|
+
// The cost of name constraints: names × constraints, both chosen by the
|
|
223
|
+
// certificates. Bounded as OpenSSL bounds it — NAME_CHECK_MAX (2^20)
|
|
224
|
+
// comparisons of one certificate's names against one CA's constraints
|
|
225
|
+
// (cryptographic-formats counts the names as OpenSSL does) — and over the whole
|
|
226
|
+
// path by NAME_CHECK_PATH_MAX: five times one pair's. OpenSSL bounds each
|
|
227
|
+
// pair alone, so CAs stacked under one another multiply the cost: four of
|
|
228
|
+
// 1,024 subtrees, a leaf of 1,023 names under each, kept every pair under
|
|
229
|
+
// 2^20 and cost them all. Five pairs' worth takes that path still — four
|
|
230
|
+
// maximal pairs and the CAs' own names under one another — and is the most
|
|
231
|
+
// the constraints of any path can cost (~100 ms); a path past it is refused
|
|
232
|
+
// (x509-limbo nc-dos). Past either bound: X509_V_ERR_UNSPECIFIED, node:tls's
|
|
233
|
+
// 'UNSPECIFIED'.
|
|
234
|
+
const NAME_CHECK_MAX = 1 << 20;
|
|
235
|
+
const NAME_CHECK_PATH_MAX = 5 * NAME_CHECK_MAX;
|
|
236
|
+
|
|
237
|
+
// `cert`'s names within `nc` (a CA's): null, or the error code. `isLeaf`:
|
|
238
|
+
// the common name counts as a DNS name when there is none other (OpenSSL
|
|
239
|
+
// does this for the leaf). `budget` ({ left }): the path's comparisons.
|
|
240
|
+
function name_constraints_error(cert, nc, isLeaf, budget) {
|
|
241
|
+
let r;
|
|
242
|
+
try {
|
|
243
|
+
r = X509.name_constraints_check(cert.cf, nc, {
|
|
244
|
+
common_name: isLeaf, max_checks: Math.min(NAME_CHECK_MAX, budget.left),
|
|
245
|
+
});
|
|
246
|
+
} catch (e) {
|
|
247
|
+
// A subtree with a minimum or maximum: OpenSSL's error 49, whatever
|
|
248
|
+
// the names. Over the bound: UNSPECIFIED, as there.
|
|
249
|
+
return e && e.code === 'subtree-minmax' ? 'SUBTREE_MINMAX' : 'UNSPECIFIED';
|
|
250
|
+
}
|
|
251
|
+
budget.left -= r.checks;
|
|
252
|
+
if (r.ok === false) {
|
|
253
|
+
// Which rule refused it: OpenSSL says EXCLUDED_VIOLATION (48) for a name
|
|
254
|
+
// inside an excluded subtree, PERMITTED_VIOLATION (47) for one outside
|
|
255
|
+
// every permitted subtree of its kind.
|
|
256
|
+
for (const n of r.refused) if (n.reason === 'excluded') return 'EXCLUDED_VIOLATION';
|
|
257
|
+
return 'PERMITTED_VIOLATION';
|
|
258
|
+
}
|
|
259
|
+
// A name of a kind a constraint covers and nothing here can compare — a
|
|
260
|
+
// URI without a host, an otherName: refused, as OpenSSL's error 53.
|
|
261
|
+
if (r.ok === null) return 'UNSUPPORTED_NAME_SYNTAX';
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ---- the path ----
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The path checks: `path` is [leaf, …, anchor] as parse_cert returns them,
|
|
269
|
+
* `purpose` 'server' (we are the client, checking a server) or 'client'.
|
|
270
|
+
* Returns null, or the error code.
|
|
271
|
+
*/
|
|
272
|
+
function check_path(path, purpose) {
|
|
273
|
+
let want = purpose === 'client' ? CLIENT_AUTH : SERVER_AUTH;
|
|
274
|
+
let last = path.length - 1;
|
|
275
|
+
let budget = { left: NAME_CHECK_PATH_MAX }; // the path's name-constraint comparisons
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i <= last; i++) {
|
|
278
|
+
let p = path[i];
|
|
279
|
+
// Once per certificate (it is read once, and kept): the answer is the
|
|
280
|
+
// certificate's.
|
|
281
|
+
if (p.unenforced === undefined) p.unenforced = X509.unenforced_critical(p.cf, ENFORCED).length > 0;
|
|
282
|
+
if (p.unenforced) return 'UNHANDLED_CRITICAL_EXTENSION';
|
|
283
|
+
// Its signature, unless it is the anchor (whose own signature nothing
|
|
284
|
+
// checks — the caller trusts it as it is): not by a weak digest.
|
|
285
|
+
if (i < last && p.cf.signature_params.weak) return 'CA_MD_TOO_WEAK';
|
|
286
|
+
// extendedKeyUsage, where present, on the leaf and the intermediates
|
|
287
|
+
// (the anchor's is the caller's to trust).
|
|
288
|
+
if (i < last && p.eku && !p.eku.some(function (u) { return u.id === want || u.id === ANY_EKU; })) return 'INVALID_PURPOSE';
|
|
289
|
+
if (i === 0) {
|
|
290
|
+
// The leaf: not a CA (a path of one — the leaf is the anchor itself —
|
|
291
|
+
// is the caller's own trust in it, and never reaches here).
|
|
292
|
+
if (p.bc && p.bc.ca) return 'INVALID_PURPOSE';
|
|
293
|
+
if (p.ku && !(p.ku.digitalSignature || p.ku.keyEncipherment || p.ku.keyAgreement)) return 'INVALID_PURPOSE';
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
// An issuer: a CA (a version 1 self-signed root is one by convention,
|
|
297
|
+
// as OpenSSL takes it). An intermediate that is not one fails OpenSSL's
|
|
298
|
+
// purpose check (a TLS CA) first — INVALID_PURPOSE, as node:tls reports
|
|
299
|
+
// it; an anchor that is not one, INVALID_CA.
|
|
300
|
+
if (!(p.bc && p.bc.ca) && !(p.cf.version === 1 && i === last && p.selfIssued)) {
|
|
301
|
+
return i < last ? 'INVALID_PURPOSE' : 'INVALID_CA';
|
|
302
|
+
}
|
|
303
|
+
// …allowed to sign certificates.
|
|
304
|
+
if (p.ku && !p.ku.keyCertSign) return 'INVALID_CA';
|
|
305
|
+
// pathLenConstraint: the intermediates below it, self-issued ones aside.
|
|
306
|
+
if (p.bc && p.bc.path_len !== null && p.bc.path_len !== undefined) {
|
|
307
|
+
let below = 0;
|
|
308
|
+
for (let j = 1; j < i; j++) if (!path[j].selfIssued) below++;
|
|
309
|
+
if (below > p.bc.path_len) return 'PATH_LENGTH_EXCEEDED';
|
|
310
|
+
}
|
|
311
|
+
// Its name constraints, on every certificate below it — a self-issued
|
|
312
|
+
// intermediate's own names aside (§6.1.3 b).
|
|
313
|
+
if (p.nc) {
|
|
314
|
+
for (let j = 0; j < i; j++) {
|
|
315
|
+
if (j > 0 && path[j].selfIssued) continue;
|
|
316
|
+
let err = name_constraints_error(path[j], p.nc, j === 0, budget);
|
|
317
|
+
if (err) return err;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ---- the host ----
|
|
325
|
+
|
|
326
|
+
// An IPv4 or IPv6 address literal (IPv6 with or without brackets) — what
|
|
327
|
+
// SNI may never carry, and what a certificate is checked against by IP SAN.
|
|
328
|
+
function is_ip_literal(host) {
|
|
329
|
+
host = String(host);
|
|
330
|
+
return /^(\d{1,3}\.){3}\d{1,3}$/.test(host) ||
|
|
331
|
+
(host.indexOf(':') >= 0 && /^[0-9a-fA-F:.\[\]]+$/.test(host));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The certificate for `host`? The one identity check — the handshake's
|
|
336
|
+
* (tls_session) and tls.checkServerIdentity's (compat) both: cryptographic-formats'
|
|
337
|
+
* RFC 9525 rules. A host name against the subjectAltName's DNS names only —
|
|
338
|
+
* the subject's common name is not one (RFC 9525 §6.3: it was, when the
|
|
339
|
+
* certificate had no DNS SAN) — and a wildcard a whole leftmost label
|
|
340
|
+
* (*.example.com, not ba*.example.com, not *.com); an address against its
|
|
341
|
+
* iPAddress names. `cert` is parse_cert's, or DER. True or false: a host
|
|
342
|
+
* that is not ASCII (not in A-labels) matches nothing.
|
|
343
|
+
*/
|
|
344
|
+
function matches_host(cert, host) {
|
|
345
|
+
if (cert && !cert.cf) cert = parse_cert(cert);
|
|
346
|
+
if (!cert) return false;
|
|
347
|
+
host = String(host);
|
|
348
|
+
if (is_ip_literal(host)) host = host.replace(/^\[|\]$/g, '');
|
|
349
|
+
try { return X509.hostname_matches(cert.cf, host); } catch (e) { return false; }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// The same rules over node:tls's subjectaltname string ('DNS:a.example,
|
|
353
|
+
// IP Address:192.0.2.1'), for a certificate object without its DER — as
|
|
354
|
+
// tls.checkServerIdentity is given by callers that build one themselves.
|
|
355
|
+
// A wildcard only as the whole leftmost label, and not alone over a public
|
|
356
|
+
// suffix of one label ('*.com').
|
|
357
|
+
function matches_altnames(san, host) {
|
|
358
|
+
host = String(host).toLowerCase().replace(/\.$/, '');
|
|
359
|
+
let ip = is_ip_literal(host) ? canonical_ip(host.replace(/^\[|\]$/g, '')) : null;
|
|
360
|
+
let entries = String(san || '').split(/,\s*/);
|
|
361
|
+
for (let i = 0; i < entries.length; i++) {
|
|
362
|
+
let e = entries[i], c = e.indexOf(':');
|
|
363
|
+
if (c < 0) continue;
|
|
364
|
+
let kind = e.slice(0, c), value = e.slice(c + 1).trim().toLowerCase().replace(/\.$/, '');
|
|
365
|
+
if (ip !== null) {
|
|
366
|
+
if (kind === 'IP Address' && canonical_ip(value) === ip) return true;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (kind !== 'DNS') continue;
|
|
370
|
+
if (value === host) return true;
|
|
371
|
+
if (value.startsWith('*.') && value.indexOf('.', 2) > 0) {
|
|
372
|
+
let dot = host.indexOf('.');
|
|
373
|
+
if (dot > 0 && host.slice(dot + 1) === value.slice(2)) return true;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
function canonical_ip(s) {
|
|
379
|
+
try { return new URL('http://' + (s.indexOf(':') >= 0 ? '[' + s + ']' : s)).hostname; } catch (e) { return null; }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export {
|
|
383
|
+
parse_cert, issued_by, signed_by, self_signed, valid_at, validity_error,
|
|
384
|
+
check_path, matches_host, matches_altnames, is_ip_literal,
|
|
385
|
+
};
|