@gjsify/tls 0.3.20 → 0.4.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/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +2 -3
- package/lib/types/cert.spec.d.ts +2 -0
- package/lib/types/index.d.ts +82 -27
- package/lib/types/tls.gjs.spec.d.ts +2 -0
- package/package.json +7 -7
- package/src/cert.spec.ts +230 -0
- package/src/index.spec.ts +126 -103
- package/src/index.ts +580 -181
- package/src/test.mts +3 -1
- package/src/tls.gjs.spec.ts +165 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/index.spec.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
1
2
|
// Ported from refs/node-test/parallel/test-tls-check-server-identity.js,
|
|
2
|
-
// test-tls-basic-validations.js, refs/bun/test/js/node/tls
|
|
3
|
-
// Original:
|
|
3
|
+
// test-tls-basic-validations.js, refs/bun/test/js/node/tls/.
|
|
4
|
+
// Original: Copyright (c) Node.js contributors. MIT.
|
|
5
|
+
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
4
6
|
|
|
5
7
|
import { describe, it, expect } from '@gjsify/unit';
|
|
6
8
|
import tls, {
|
|
@@ -15,9 +17,30 @@ import tls, {
|
|
|
15
17
|
DEFAULT_MAX_VERSION,
|
|
16
18
|
DEFAULT_CIPHERS,
|
|
17
19
|
} from 'node:tls';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
import type { PeerCertificate } from 'node:tls';
|
|
21
|
+
|
|
22
|
+
// Build a fake PeerCertificate from minimal fields. Lets us drive
|
|
23
|
+
// `checkServerIdentity` from tests without crafting a real DER cert.
|
|
24
|
+
// `@types/node`'s PeerCertificate has many more fields than we expose; the
|
|
25
|
+
// `as unknown as PeerCertificate` keeps the test free of `as any`.
|
|
26
|
+
function fakeCert(parts: Record<string, unknown>): PeerCertificate {
|
|
27
|
+
return parts as unknown as PeerCertificate;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Our implementation also exports TLSServer; Node only exports Server.
|
|
31
|
+
const tlsRecord = tls as unknown as Record<string, unknown>;
|
|
32
|
+
const TLSServer = (tlsRecord.TLSServer ?? tlsRecord.Server) as new (
|
|
33
|
+
options?: unknown,
|
|
34
|
+
secureConnectionListener?: unknown,
|
|
35
|
+
) => {
|
|
36
|
+
on: (ev: string, cb: (...a: unknown[]) => void) => unknown;
|
|
37
|
+
emit: (ev: string, ...a: unknown[]) => boolean;
|
|
38
|
+
once: (ev: string, cb: (...a: unknown[]) => void) => unknown;
|
|
39
|
+
addContext: (host: string, ctx: unknown) => void;
|
|
40
|
+
listen: (...a: unknown[]) => unknown;
|
|
41
|
+
close: (cb?: () => void) => unknown;
|
|
42
|
+
address: () => unknown;
|
|
43
|
+
};
|
|
21
44
|
|
|
22
45
|
export default async () => {
|
|
23
46
|
await describe('tls', async () => {
|
|
@@ -93,77 +116,77 @@ export default async () => {
|
|
|
93
116
|
// ===================== TLSSocket =====================
|
|
94
117
|
await describe('TLSSocket', async () => {
|
|
95
118
|
await it('should be constructable', async () => {
|
|
96
|
-
const socket = new TLSSocket(undefined as
|
|
119
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
97
120
|
expect(socket).toBeDefined();
|
|
98
121
|
});
|
|
99
122
|
|
|
100
123
|
await it('should have encrypted property set to true', async () => {
|
|
101
|
-
const socket = new TLSSocket(undefined as
|
|
124
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
102
125
|
expect(socket.encrypted).toBe(true);
|
|
103
126
|
});
|
|
104
127
|
|
|
105
128
|
await it('should have authorized property as boolean', async () => {
|
|
106
|
-
const socket = new TLSSocket(undefined as
|
|
129
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
107
130
|
expect(typeof socket.authorized).toBe('boolean');
|
|
108
131
|
});
|
|
109
132
|
|
|
110
133
|
await it('authorized should default to false', async () => {
|
|
111
|
-
const socket = new TLSSocket(undefined as
|
|
134
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
112
135
|
expect(socket.authorized).toBe(false);
|
|
113
136
|
});
|
|
114
137
|
|
|
115
138
|
await it('should have getPeerCertificate method', async () => {
|
|
116
|
-
const socket = new TLSSocket(undefined as
|
|
139
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
117
140
|
expect(typeof socket.getPeerCertificate).toBe('function');
|
|
118
141
|
});
|
|
119
142
|
|
|
120
143
|
await it('should have getProtocol method', async () => {
|
|
121
|
-
const socket = new TLSSocket(undefined as
|
|
144
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
122
145
|
expect(typeof socket.getProtocol).toBe('function');
|
|
123
146
|
});
|
|
124
147
|
|
|
125
148
|
await it('should have getCipher method', async () => {
|
|
126
|
-
const socket = new TLSSocket(undefined as
|
|
149
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
127
150
|
expect(typeof socket.getCipher).toBe('function');
|
|
128
151
|
});
|
|
129
152
|
|
|
130
153
|
await it('should have alpnProtocol property', async () => {
|
|
131
|
-
const socket = new TLSSocket(undefined as
|
|
154
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
132
155
|
// Node.js: alpnProtocol is a property (null or string), our impl: false or string
|
|
133
|
-
const val =
|
|
156
|
+
const val = socket.alpnProtocol;
|
|
134
157
|
expect(val === false || val === null || typeof val === 'string').toBe(true);
|
|
135
158
|
});
|
|
136
159
|
|
|
137
160
|
await it('getPeerCertificate should return object when not connected', async () => {
|
|
138
|
-
const socket = new TLSSocket(undefined as
|
|
161
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
139
162
|
const cert = socket.getPeerCertificate();
|
|
140
163
|
expect(typeof cert).toBe('object');
|
|
141
164
|
});
|
|
142
165
|
|
|
143
166
|
await it('getProtocol should return null when not connected', async () => {
|
|
144
|
-
const socket = new TLSSocket(undefined as
|
|
167
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
145
168
|
const proto = socket.getProtocol();
|
|
146
169
|
expect(proto === null || typeof proto === 'string').toBe(true);
|
|
147
170
|
});
|
|
148
171
|
|
|
149
172
|
await it('getCipher should return null when not connected', async () => {
|
|
150
|
-
const socket = new TLSSocket(undefined as
|
|
173
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
151
174
|
const cipher = socket.getCipher();
|
|
152
175
|
expect(cipher === null || cipher === undefined || typeof cipher === 'object').toBe(true);
|
|
153
176
|
});
|
|
154
177
|
|
|
155
178
|
await it('alpnProtocol should default to false', async () => {
|
|
156
|
-
const socket = new TLSSocket(undefined as
|
|
157
|
-
expect(
|
|
179
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
180
|
+
expect(socket.alpnProtocol === false || socket.alpnProtocol === null).toBe(true);
|
|
158
181
|
});
|
|
159
182
|
|
|
160
183
|
await it('authorizationError should be undefined initially', async () => {
|
|
161
|
-
const socket = new TLSSocket(undefined as
|
|
184
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
162
185
|
expect(socket.authorizationError === undefined || socket.authorizationError === null).toBe(true);
|
|
163
186
|
});
|
|
164
187
|
|
|
165
188
|
await it('should extend Socket (EventEmitter)', async () => {
|
|
166
|
-
const socket = new TLSSocket(undefined as
|
|
189
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
167
190
|
expect(typeof socket.on).toBe('function');
|
|
168
191
|
expect(typeof socket.emit).toBe('function');
|
|
169
192
|
expect(typeof socket.once).toBe('function');
|
|
@@ -171,17 +194,17 @@ export default async () => {
|
|
|
171
194
|
});
|
|
172
195
|
|
|
173
196
|
await it('should have destroy method', async () => {
|
|
174
|
-
const socket = new TLSSocket(undefined as
|
|
197
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
175
198
|
expect(typeof socket.destroy).toBe('function');
|
|
176
199
|
});
|
|
177
200
|
|
|
178
201
|
await it('should have write method', async () => {
|
|
179
|
-
const socket = new TLSSocket(undefined as
|
|
202
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
180
203
|
expect(typeof socket.write).toBe('function');
|
|
181
204
|
});
|
|
182
205
|
|
|
183
206
|
await it('should have end method', async () => {
|
|
184
|
-
const socket = new TLSSocket(undefined as
|
|
207
|
+
const socket = new TLSSocket(undefined as unknown as import("net").Socket);
|
|
185
208
|
expect(typeof socket.end).toBe('function');
|
|
186
209
|
});
|
|
187
210
|
});
|
|
@@ -259,7 +282,7 @@ export default async () => {
|
|
|
259
282
|
|
|
260
283
|
await it('should have context property', async () => {
|
|
261
284
|
const ctx = createSecureContext();
|
|
262
|
-
expect(ctx.context !== undefined).toBe(true);
|
|
285
|
+
expect((ctx as { context?: unknown }).context !== undefined).toBe(true);
|
|
263
286
|
});
|
|
264
287
|
|
|
265
288
|
await it('should return same type for repeated calls', async () => {
|
|
@@ -330,53 +353,53 @@ export default async () => {
|
|
|
330
353
|
// --- CN matching ---
|
|
331
354
|
await describe('CN matching', async () => {
|
|
332
355
|
await it('should return undefined for exact CN match', async () => {
|
|
333
|
-
const result = checkServerIdentity('a.com', { subject: { CN: 'a.com' } }
|
|
356
|
+
const result = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'a.com' } }));
|
|
334
357
|
expect(result).toBeUndefined();
|
|
335
358
|
});
|
|
336
359
|
|
|
337
360
|
await it('should match CN case-insensitively', async () => {
|
|
338
|
-
const result = checkServerIdentity('a.com', { subject: { CN: 'A.COM' } }
|
|
361
|
+
const result = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'A.COM' } }));
|
|
339
362
|
expect(result).toBeUndefined();
|
|
340
363
|
});
|
|
341
364
|
|
|
342
365
|
await it('should return error for non-matching CN', async () => {
|
|
343
|
-
const err = checkServerIdentity('a.com', { subject: { CN: 'b.com' } }
|
|
366
|
+
const err = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'b.com' } }));
|
|
344
367
|
expect(err instanceof Error).toBe(true);
|
|
345
368
|
});
|
|
346
369
|
|
|
347
370
|
await it('should handle trailing-dot FQDN in hostname', async () => {
|
|
348
|
-
const result = checkServerIdentity('a.com.', { subject: { CN: 'a.com' } }
|
|
371
|
+
const result = checkServerIdentity('a.com.', fakeCert({ subject: { CN: 'a.com' } }));
|
|
349
372
|
expect(result).toBeUndefined();
|
|
350
373
|
});
|
|
351
374
|
|
|
352
375
|
await it('should handle trailing-dot FQDN in CN', async () => {
|
|
353
|
-
const result = checkServerIdentity('a.com', { subject: { CN: 'a.com.' } }
|
|
376
|
+
const result = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'a.com.' } }));
|
|
354
377
|
expect(result).toBeUndefined();
|
|
355
378
|
});
|
|
356
379
|
|
|
357
380
|
await it('should handle trailing dots on both sides', async () => {
|
|
358
|
-
const result = checkServerIdentity('a.com.', { subject: { CN: 'a.com.' } }
|
|
381
|
+
const result = checkServerIdentity('a.com.', fakeCert({ subject: { CN: 'a.com.' } }));
|
|
359
382
|
expect(result).toBeUndefined();
|
|
360
383
|
});
|
|
361
384
|
|
|
362
385
|
await it('should match array CN values', async () => {
|
|
363
|
-
const result = checkServerIdentity('b.com', {
|
|
386
|
+
const result = checkServerIdentity('b.com', fakeCert({
|
|
364
387
|
subject: { CN: ['a.com', 'b.com'] },
|
|
365
|
-
}
|
|
388
|
+
}));
|
|
366
389
|
expect(result).toBeUndefined();
|
|
367
390
|
});
|
|
368
391
|
|
|
369
392
|
await it('should fail when hostname not in CN array', async () => {
|
|
370
|
-
const err = checkServerIdentity('c.com', {
|
|
393
|
+
const err = checkServerIdentity('c.com', fakeCert({
|
|
371
394
|
subject: { CN: ['a.com', 'b.com'] },
|
|
372
|
-
}
|
|
395
|
+
}));
|
|
373
396
|
expect(err instanceof Error).toBe(true);
|
|
374
397
|
});
|
|
375
398
|
|
|
376
399
|
await it('should handle single-label hostname with single-label CN', async () => {
|
|
377
|
-
const result = checkServerIdentity('localhost', {
|
|
400
|
+
const result = checkServerIdentity('localhost', fakeCert({
|
|
378
401
|
subject: { CN: 'localhost' },
|
|
379
|
-
}
|
|
402
|
+
}));
|
|
380
403
|
expect(result).toBeUndefined();
|
|
381
404
|
});
|
|
382
405
|
});
|
|
@@ -384,54 +407,54 @@ export default async () => {
|
|
|
384
407
|
// --- Wildcard matching ---
|
|
385
408
|
await describe('wildcard matching', async () => {
|
|
386
409
|
await it('should match *.example.com against sub.example.com', async () => {
|
|
387
|
-
const result = checkServerIdentity('sub.example.com', {
|
|
410
|
+
const result = checkServerIdentity('sub.example.com', fakeCert({
|
|
388
411
|
subject: {},
|
|
389
412
|
subjectaltname: 'DNS:*.example.com',
|
|
390
|
-
}
|
|
413
|
+
}));
|
|
391
414
|
expect(result).toBeUndefined();
|
|
392
415
|
});
|
|
393
416
|
|
|
394
417
|
await it('should NOT match *.example.com against nested.sub.example.com', async () => {
|
|
395
|
-
const err = checkServerIdentity('nested.sub.example.com', {
|
|
418
|
+
const err = checkServerIdentity('nested.sub.example.com', fakeCert({
|
|
396
419
|
subject: {},
|
|
397
420
|
subjectaltname: 'DNS:*.example.com',
|
|
398
|
-
}
|
|
421
|
+
}));
|
|
399
422
|
expect(err instanceof Error).toBe(true);
|
|
400
423
|
});
|
|
401
424
|
|
|
402
425
|
await it('should NOT match *.example.com against example.com itself', async () => {
|
|
403
|
-
const err = checkServerIdentity('example.com', {
|
|
426
|
+
const err = checkServerIdentity('example.com', fakeCert({
|
|
404
427
|
subject: {},
|
|
405
428
|
subjectaltname: 'DNS:*.example.com',
|
|
406
|
-
}
|
|
429
|
+
}));
|
|
407
430
|
expect(err instanceof Error).toBe(true);
|
|
408
431
|
});
|
|
409
432
|
|
|
410
433
|
await it('should match wildcard CN when no SANs present', async () => {
|
|
411
|
-
const result = checkServerIdentity('foo.example.com', {
|
|
434
|
+
const result = checkServerIdentity('foo.example.com', fakeCert({
|
|
412
435
|
subject: { CN: '*.example.com' },
|
|
413
|
-
}
|
|
436
|
+
}));
|
|
414
437
|
expect(result).toBeUndefined();
|
|
415
438
|
});
|
|
416
439
|
|
|
417
440
|
await it('wildcard CN should NOT match two-level deep', async () => {
|
|
418
|
-
const err = checkServerIdentity('a.b.example.com', {
|
|
441
|
+
const err = checkServerIdentity('a.b.example.com', fakeCert({
|
|
419
442
|
subject: { CN: '*.example.com' },
|
|
420
|
-
}
|
|
443
|
+
}));
|
|
421
444
|
expect(err instanceof Error).toBe(true);
|
|
422
445
|
});
|
|
423
446
|
|
|
424
447
|
await it('should match wildcard with different subdomains', async () => {
|
|
425
|
-
const result1 = checkServerIdentity('foo.example.com', {
|
|
448
|
+
const result1 = checkServerIdentity('foo.example.com', fakeCert({
|
|
426
449
|
subject: {},
|
|
427
450
|
subjectaltname: 'DNS:*.example.com',
|
|
428
|
-
}
|
|
451
|
+
}));
|
|
429
452
|
expect(result1).toBeUndefined();
|
|
430
453
|
|
|
431
|
-
const result2 = checkServerIdentity('bar.example.com', {
|
|
454
|
+
const result2 = checkServerIdentity('bar.example.com', fakeCert({
|
|
432
455
|
subject: {},
|
|
433
456
|
subjectaltname: 'DNS:*.example.com',
|
|
434
|
-
}
|
|
457
|
+
}));
|
|
435
458
|
expect(result2).toBeUndefined();
|
|
436
459
|
});
|
|
437
460
|
});
|
|
@@ -439,59 +462,59 @@ export default async () => {
|
|
|
439
462
|
// --- DNS SAN matching ---
|
|
440
463
|
await describe('DNS SAN matching', async () => {
|
|
441
464
|
await it('should match hostname in DNS SAN', async () => {
|
|
442
|
-
const result = checkServerIdentity('foo.example.com', {
|
|
465
|
+
const result = checkServerIdentity('foo.example.com', fakeCert({
|
|
443
466
|
subject: { CN: 'wrong.com' },
|
|
444
467
|
subjectaltname: 'DNS:foo.example.com',
|
|
445
|
-
}
|
|
468
|
+
}));
|
|
446
469
|
expect(result).toBeUndefined();
|
|
447
470
|
});
|
|
448
471
|
|
|
449
472
|
await it('SAN takes precedence over CN', async () => {
|
|
450
473
|
// When SANs are present, CN should be ignored
|
|
451
|
-
const err = checkServerIdentity('wrong.com', {
|
|
474
|
+
const err = checkServerIdentity('wrong.com', fakeCert({
|
|
452
475
|
subject: { CN: 'wrong.com' },
|
|
453
476
|
subjectaltname: 'DNS:right.com',
|
|
454
|
-
}
|
|
477
|
+
}));
|
|
455
478
|
expect(err instanceof Error).toBe(true);
|
|
456
479
|
});
|
|
457
480
|
|
|
458
481
|
await it('should handle multiple DNS SANs', async () => {
|
|
459
|
-
const result = checkServerIdentity('b.com', {
|
|
482
|
+
const result = checkServerIdentity('b.com', fakeCert({
|
|
460
483
|
subject: { CN: 'a.com' },
|
|
461
484
|
subjectaltname: 'DNS:a.com, DNS:b.com',
|
|
462
|
-
}
|
|
485
|
+
}));
|
|
463
486
|
expect(result).toBeUndefined();
|
|
464
487
|
});
|
|
465
488
|
|
|
466
489
|
await it('should fail when hostname not in any DNS SAN', async () => {
|
|
467
|
-
const err = checkServerIdentity('c.com', {
|
|
490
|
+
const err = checkServerIdentity('c.com', fakeCert({
|
|
468
491
|
subject: { CN: 'c.com' },
|
|
469
492
|
subjectaltname: 'DNS:a.com, DNS:b.com',
|
|
470
|
-
}
|
|
493
|
+
}));
|
|
471
494
|
expect(err instanceof Error).toBe(true);
|
|
472
495
|
});
|
|
473
496
|
|
|
474
497
|
await it('should match wildcard DNS SAN', async () => {
|
|
475
|
-
const result = checkServerIdentity('bar.example.com', {
|
|
498
|
+
const result = checkServerIdentity('bar.example.com', fakeCert({
|
|
476
499
|
subject: { CN: 'wrong.com' },
|
|
477
500
|
subjectaltname: 'DNS:*.example.com',
|
|
478
|
-
}
|
|
501
|
+
}));
|
|
479
502
|
expect(result).toBeUndefined();
|
|
480
503
|
});
|
|
481
504
|
|
|
482
505
|
await it('should handle mixed DNS and IP SANs', async () => {
|
|
483
|
-
const result = checkServerIdentity('example.com', {
|
|
506
|
+
const result = checkServerIdentity('example.com', fakeCert({
|
|
484
507
|
subject: {},
|
|
485
508
|
subjectaltname: 'DNS:example.com, IP Address:1.2.3.4',
|
|
486
|
-
}
|
|
509
|
+
}));
|
|
487
510
|
expect(result).toBeUndefined();
|
|
488
511
|
});
|
|
489
512
|
|
|
490
513
|
await it('should handle DNS SAN with trailing whitespace', async () => {
|
|
491
|
-
const result = checkServerIdentity('example.com', {
|
|
514
|
+
const result = checkServerIdentity('example.com', fakeCert({
|
|
492
515
|
subject: {},
|
|
493
516
|
subjectaltname: 'DNS:example.com ',
|
|
494
|
-
}
|
|
517
|
+
}));
|
|
495
518
|
// May or may not match depending on trimming
|
|
496
519
|
expect(result === undefined || result instanceof Error).toBe(true);
|
|
497
520
|
});
|
|
@@ -500,57 +523,57 @@ export default async () => {
|
|
|
500
523
|
// --- IP address matching ---
|
|
501
524
|
await describe('IP address matching', async () => {
|
|
502
525
|
await it('should match IPv4 in IP Address SAN', async () => {
|
|
503
|
-
const result = checkServerIdentity('8.8.8.8', {
|
|
526
|
+
const result = checkServerIdentity('8.8.8.8', fakeCert({
|
|
504
527
|
subject: { CN: '8.8.8.8' },
|
|
505
528
|
subjectaltname: 'IP Address:8.8.8.8',
|
|
506
|
-
}
|
|
529
|
+
}));
|
|
507
530
|
expect(result).toBeUndefined();
|
|
508
531
|
});
|
|
509
532
|
|
|
510
533
|
await it('should fail IPv4 not in IP Address SAN', async () => {
|
|
511
|
-
const err = checkServerIdentity('8.8.4.4', {
|
|
534
|
+
const err = checkServerIdentity('8.8.4.4', fakeCert({
|
|
512
535
|
subject: { CN: '8.8.4.4' },
|
|
513
536
|
subjectaltname: 'IP Address:8.8.8.8',
|
|
514
|
-
}
|
|
537
|
+
}));
|
|
515
538
|
expect(err instanceof Error).toBe(true);
|
|
516
539
|
});
|
|
517
540
|
|
|
518
541
|
await it('should fail IPv4 with only CN (no SAN IP entry)', async () => {
|
|
519
|
-
const err = checkServerIdentity('8.8.8.8', {
|
|
542
|
+
const err = checkServerIdentity('8.8.8.8', fakeCert({
|
|
520
543
|
subject: { CN: '8.8.8.8' },
|
|
521
|
-
}
|
|
544
|
+
}));
|
|
522
545
|
expect(err instanceof Error).toBe(true);
|
|
523
546
|
});
|
|
524
547
|
|
|
525
548
|
await it('should match multiple IP addresses in SAN', async () => {
|
|
526
|
-
const result = checkServerIdentity('1.2.3.4', {
|
|
549
|
+
const result = checkServerIdentity('1.2.3.4', fakeCert({
|
|
527
550
|
subject: {},
|
|
528
551
|
subjectaltname: 'IP Address:1.2.3.4, IP Address:5.6.7.8',
|
|
529
|
-
}
|
|
552
|
+
}));
|
|
530
553
|
expect(result).toBeUndefined();
|
|
531
554
|
});
|
|
532
555
|
|
|
533
556
|
await it('should match second IP in SAN', async () => {
|
|
534
|
-
const result = checkServerIdentity('5.6.7.8', {
|
|
557
|
+
const result = checkServerIdentity('5.6.7.8', fakeCert({
|
|
535
558
|
subject: {},
|
|
536
559
|
subjectaltname: 'IP Address:1.2.3.4, IP Address:5.6.7.8',
|
|
537
|
-
}
|
|
560
|
+
}));
|
|
538
561
|
expect(result).toBeUndefined();
|
|
539
562
|
});
|
|
540
563
|
|
|
541
564
|
await it('should handle IPv6 in IP Address SAN', async () => {
|
|
542
|
-
const result = checkServerIdentity('::1', {
|
|
565
|
+
const result = checkServerIdentity('::1', fakeCert({
|
|
543
566
|
subject: {},
|
|
544
567
|
subjectaltname: 'IP Address:::1',
|
|
545
|
-
}
|
|
568
|
+
}));
|
|
546
569
|
expect(result).toBeUndefined();
|
|
547
570
|
});
|
|
548
571
|
|
|
549
572
|
await it('should fail IPv4 in DNS SAN (IP should use IP Address SAN)', async () => {
|
|
550
|
-
const err = checkServerIdentity('1.2.3.4', {
|
|
573
|
+
const err = checkServerIdentity('1.2.3.4', fakeCert({
|
|
551
574
|
subject: {},
|
|
552
575
|
subjectaltname: 'DNS:1.2.3.4',
|
|
553
|
-
}
|
|
576
|
+
}));
|
|
554
577
|
expect(err instanceof Error).toBe(true);
|
|
555
578
|
});
|
|
556
579
|
});
|
|
@@ -558,33 +581,33 @@ export default async () => {
|
|
|
558
581
|
// --- Error object properties ---
|
|
559
582
|
await describe('error properties', async () => {
|
|
560
583
|
await it('should have reason property on error', async () => {
|
|
561
|
-
const err = checkServerIdentity('a.com', { subject: { CN: 'b.com' } }
|
|
584
|
+
const err = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'b.com' } }));
|
|
562
585
|
expect(err instanceof Error).toBe(true);
|
|
563
|
-
expect(typeof err.reason).toBe('string');
|
|
586
|
+
expect(typeof (err as { reason?: string }).reason).toBe('string');
|
|
564
587
|
});
|
|
565
588
|
|
|
566
589
|
await it('should have host property on error', async () => {
|
|
567
|
-
const err = checkServerIdentity('a.com', { subject: { CN: 'b.com' } }
|
|
568
|
-
expect(err.host).toBe('a.com');
|
|
590
|
+
const err = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'b.com' } }));
|
|
591
|
+
expect((err as { host?: string }).host).toBe('a.com');
|
|
569
592
|
});
|
|
570
593
|
|
|
571
594
|
await it('should have cert property on error', async () => {
|
|
572
|
-
const cert = { subject: { CN: 'b.com' } }
|
|
573
|
-
const err = checkServerIdentity('a.com', cert)
|
|
574
|
-
expect(err
|
|
595
|
+
const cert = fakeCert({ subject: { CN: 'b.com' } });
|
|
596
|
+
const err = checkServerIdentity('a.com', cert);
|
|
597
|
+
expect((err as { cert?: unknown } | undefined)?.cert).toBe(cert);
|
|
575
598
|
});
|
|
576
599
|
|
|
577
600
|
await it('error message should contain hostname for CN mismatch', async () => {
|
|
578
|
-
const err = checkServerIdentity('a.com', { subject: { CN: 'b.com' } }
|
|
601
|
+
const err = checkServerIdentity('a.com', fakeCert({ subject: { CN: 'b.com' } }));
|
|
579
602
|
expect(err instanceof Error).toBe(true);
|
|
580
603
|
expect((err as Error).message).toContain('a.com');
|
|
581
604
|
});
|
|
582
605
|
|
|
583
606
|
await it('error message should contain IP for IP mismatch', async () => {
|
|
584
|
-
const err = checkServerIdentity('8.8.8.8', {
|
|
607
|
+
const err = checkServerIdentity('8.8.8.8', fakeCert({
|
|
585
608
|
subject: {},
|
|
586
609
|
subjectaltname: 'IP Address:1.2.3.4',
|
|
587
|
-
}
|
|
610
|
+
}));
|
|
588
611
|
expect(err instanceof Error).toBe(true);
|
|
589
612
|
expect((err as Error).message).toContain('8.8.8.8');
|
|
590
613
|
});
|
|
@@ -593,64 +616,64 @@ export default async () => {
|
|
|
593
616
|
// --- Edge cases ---
|
|
594
617
|
await describe('edge cases', async () => {
|
|
595
618
|
await it('should return error when cert has no subject and no altnames', async () => {
|
|
596
|
-
const err = checkServerIdentity('a.com', {}
|
|
619
|
+
const err = checkServerIdentity('a.com', fakeCert({}));
|
|
597
620
|
expect(err instanceof Error).toBe(true);
|
|
598
621
|
expect((err as Error).message).toContain('DNS');
|
|
599
622
|
});
|
|
600
623
|
|
|
601
624
|
await it('should handle false-y host values', async () => {
|
|
602
|
-
const err = checkServerIdentity(false as unknown as string, {
|
|
625
|
+
const err = checkServerIdentity(false as unknown as string, fakeCert({
|
|
603
626
|
subject: { CN: 'a.com' },
|
|
604
|
-
}
|
|
627
|
+
}));
|
|
605
628
|
expect(err instanceof Error).toBe(true);
|
|
606
629
|
});
|
|
607
630
|
|
|
608
631
|
await it('should handle empty string hostname', async () => {
|
|
609
|
-
const err = checkServerIdentity('', { subject: { CN: 'a.com' } }
|
|
632
|
+
const err = checkServerIdentity('', fakeCert({ subject: { CN: 'a.com' } }));
|
|
610
633
|
expect(err instanceof Error).toBe(true);
|
|
611
634
|
});
|
|
612
635
|
|
|
613
636
|
await it('should handle empty CN', async () => {
|
|
614
|
-
const err = checkServerIdentity('a.com', { subject: { CN: '' } }
|
|
637
|
+
const err = checkServerIdentity('a.com', fakeCert({ subject: { CN: '' } }));
|
|
615
638
|
expect(err instanceof Error).toBe(true);
|
|
616
639
|
});
|
|
617
640
|
|
|
618
641
|
await it('should handle undefined CN', async () => {
|
|
619
|
-
const err = checkServerIdentity('a.com', { subject: {} }
|
|
642
|
+
const err = checkServerIdentity('a.com', fakeCert({ subject: {} }));
|
|
620
643
|
expect(err instanceof Error).toBe(true);
|
|
621
644
|
});
|
|
622
645
|
|
|
623
646
|
await it('should handle empty subjectaltname string', async () => {
|
|
624
|
-
const err = checkServerIdentity('a.com', {
|
|
647
|
+
const err = checkServerIdentity('a.com', fakeCert({
|
|
625
648
|
subject: { CN: 'a.com' },
|
|
626
649
|
subjectaltname: '',
|
|
627
|
-
}
|
|
650
|
+
}));
|
|
628
651
|
// Empty altname means CN fallback
|
|
629
652
|
expect(err === undefined || err instanceof Error).toBe(true);
|
|
630
653
|
});
|
|
631
654
|
|
|
632
655
|
await it('should handle numeric hostname as string', async () => {
|
|
633
|
-
const result = checkServerIdentity('127.0.0.1', {
|
|
656
|
+
const result = checkServerIdentity('127.0.0.1', fakeCert({
|
|
634
657
|
subject: {},
|
|
635
658
|
subjectaltname: 'IP Address:127.0.0.1',
|
|
636
|
-
}
|
|
659
|
+
}));
|
|
637
660
|
expect(result).toBeUndefined();
|
|
638
661
|
});
|
|
639
662
|
|
|
640
663
|
await it('should handle cert with only URI SAN (no DNS, no IP)', async () => {
|
|
641
|
-
const err = checkServerIdentity('a.com', {
|
|
664
|
+
const err = checkServerIdentity('a.com', fakeCert({
|
|
642
665
|
subject: {},
|
|
643
666
|
subjectaltname: 'URI:https://a.com',
|
|
644
|
-
}
|
|
667
|
+
}));
|
|
645
668
|
// URI SANs don't count for hostname verification
|
|
646
669
|
expect(err instanceof Error).toBe(true);
|
|
647
670
|
});
|
|
648
671
|
|
|
649
672
|
await it('should handle cert with email SAN only', async () => {
|
|
650
|
-
const err = checkServerIdentity('a.com', {
|
|
673
|
+
const err = checkServerIdentity('a.com', fakeCert({
|
|
651
674
|
subject: {},
|
|
652
675
|
subjectaltname: 'email:admin@a.com',
|
|
653
|
-
}
|
|
676
|
+
}));
|
|
654
677
|
expect(err instanceof Error).toBe(true);
|
|
655
678
|
});
|
|
656
679
|
});
|