Skip to content

Commit 016e352

Browse files
sam-githubBethGriggs
authored andcommitted
test: test TLS client authentication
TLS client authentication should be tested, including failure scenarios. PR-URL: #24733 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
1 parent 39af61f commit 016e352

File tree

3 files changed

+339
-3
lines changed

3 files changed

+339
-3
lines changed

doc/api/tls.md

+2
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,8 @@ changes:
10921092
certificate can match or chain to.
10931093
For self-signed certificates, the certificate is its own CA, and must be
10941094
provided.
1095+
For PEM encoded certificates, supported types are "X509 CERTIFICATE", and
1096+
"CERTIFICATE".
10951097
* `cert` {string|string[]|Buffer|Buffer[]} Cert chains in PEM format. One cert
10961098
chain should be provided per private key. Each cert chain should consist of
10971099
the PEM formatted certificate for a provided private `key`, followed by the

test/fixtures/tls-connect.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ const keys = exports.keys = {
2626
agent5: load('agent5', 'ca2'),
2727
agent6: load('agent6', 'ca1'),
2828
agent7: load('agent7', 'fake-cnnic-root'),
29+
agent10: load('agent10', 'ca2'),
30+
ec10: load('ec10', 'ca5'),
2931
ec: load('ec', 'ec'),
3032
};
3133

32-
function load(cert, issuer) {
33-
issuer = issuer || cert; // Assume self-signed if no issuer
34+
// root is the self-signed root of the trust chain, not an intermediate ca.
35+
function load(cert, root) {
36+
root = root || cert; // Assume self-signed if no issuer
3437
const id = {
3538
key: fixtures.readKey(cert + '-key.pem', 'binary'),
3639
cert: fixtures.readKey(cert + '-cert.pem', 'binary'),
37-
ca: fixtures.readKey(issuer + '-cert.pem', 'binary'),
40+
ca: fixtures.readKey(root + '-cert.pem', 'binary'),
3841
};
3942
return id;
4043
}

test/parallel/test-tls-client-auth.js

+331
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
'use strict';
2+
3+
require('../common');
4+
const fixtures = require('../common/fixtures');
5+
6+
const {
7+
assert, connect, keys
8+
} = require(fixtures.path('tls-connect'));
9+
10+
// Use ec10 and agent10, they are the only identities with intermediate CAs.
11+
const client = keys.ec10;
12+
const server = keys.agent10;
13+
14+
// The certificates aren't for "localhost", so override the identity check.
15+
function checkServerIdentity(hostname, cert) {
16+
assert.strictEqual(hostname, 'localhost');
17+
assert.strictEqual(cert.subject.CN, 'agent10.example.com');
18+
}
19+
20+
// Split out the single end-entity cert and the subordinate CA for later use.
21+
split(client.cert, client);
22+
split(server.cert, server);
23+
24+
function split(file, into) {
25+
const certs = /([^]*END CERTIFICATE-----\r?\n)(-----BEGIN[^]*)/.exec(file);
26+
assert.strictEqual(certs.length, 3);
27+
into.single = certs[1];
28+
into.subca = certs[2];
29+
}
30+
31+
// Typical setup, nothing special, complete cert chains sent to peer.
32+
connect({
33+
client: {
34+
key: client.key,
35+
cert: client.cert,
36+
ca: server.ca,
37+
checkServerIdentity,
38+
},
39+
server: {
40+
key: server.key,
41+
cert: server.cert,
42+
ca: client.ca,
43+
requestCert: true,
44+
},
45+
}, function(err, pair, cleanup) {
46+
assert.ifError(err);
47+
return cleanup();
48+
});
49+
50+
// As above, but without requesting client's cert.
51+
connect({
52+
client: {
53+
ca: server.ca,
54+
checkServerIdentity,
55+
},
56+
server: {
57+
key: server.key,
58+
cert: server.cert,
59+
ca: client.ca,
60+
},
61+
}, function(err, pair, cleanup) {
62+
assert.ifError(err);
63+
return cleanup();
64+
});
65+
66+
// Request cert from client that doesn't have one.
67+
connect({
68+
client: {
69+
ca: server.ca,
70+
checkServerIdentity,
71+
},
72+
server: {
73+
key: server.key,
74+
cert: server.cert,
75+
ca: client.ca,
76+
requestCert: true,
77+
},
78+
}, function(err, pair, cleanup) {
79+
assert.strictEqual(err.code, 'ECONNRESET');
80+
return cleanup();
81+
});
82+
83+
// Typical configuration error, incomplete cert chains sent, we have to know the
84+
// peer's subordinate CAs in order to verify the peer.
85+
connect({
86+
client: {
87+
key: client.key,
88+
cert: client.single,
89+
ca: [server.ca, server.subca],
90+
checkServerIdentity,
91+
},
92+
server: {
93+
key: server.key,
94+
cert: server.single,
95+
ca: [client.ca, client.subca],
96+
requestCert: true,
97+
},
98+
}, function(err, pair, cleanup) {
99+
assert.ifError(err);
100+
return cleanup();
101+
});
102+
103+
// Like above, but provide root CA and subordinate CA as multi-PEM.
104+
connect({
105+
client: {
106+
key: client.key,
107+
cert: client.single,
108+
ca: server.ca + '\n' + server.subca,
109+
checkServerIdentity,
110+
},
111+
server: {
112+
key: server.key,
113+
cert: server.single,
114+
ca: client.ca + '\n' + client.subca,
115+
requestCert: true,
116+
},
117+
}, function(err, pair, cleanup) {
118+
assert.ifError(err);
119+
return cleanup();
120+
});
121+
122+
// Like above, but provide multi-PEM in an array.
123+
connect({
124+
client: {
125+
key: client.key,
126+
cert: client.single,
127+
ca: [server.ca + '\n' + server.subca],
128+
checkServerIdentity,
129+
},
130+
server: {
131+
key: server.key,
132+
cert: server.single,
133+
ca: [client.ca + '\n' + client.subca],
134+
requestCert: true,
135+
},
136+
}, function(err, pair, cleanup) {
137+
assert.ifError(err);
138+
return cleanup();
139+
});
140+
141+
// Fail to complete server's chain
142+
connect({
143+
client: {
144+
ca: server.ca,
145+
checkServerIdentity,
146+
},
147+
server: {
148+
key: server.key,
149+
cert: server.single,
150+
},
151+
}, function(err, pair, cleanup) {
152+
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
153+
return cleanup();
154+
});
155+
156+
// Fail to complete client's chain.
157+
connect({
158+
client: {
159+
key: client.key,
160+
cert: client.single,
161+
ca: server.ca,
162+
checkServerIdentity,
163+
},
164+
server: {
165+
key: server.key,
166+
cert: server.cert,
167+
ca: client.ca,
168+
requestCert: true,
169+
},
170+
}, function(err, pair, cleanup) {
171+
assert.ifError(pair.client.error);
172+
assert.ifError(pair.server.error);
173+
assert.strictEqual(err.code, 'ECONNRESET');
174+
return cleanup();
175+
});
176+
177+
// Fail to find CA for server.
178+
connect({
179+
client: {
180+
checkServerIdentity,
181+
},
182+
server: {
183+
key: server.key,
184+
cert: server.cert,
185+
},
186+
}, function(err, pair, cleanup) {
187+
assert.strictEqual(err.code, 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY');
188+
return cleanup();
189+
});
190+
191+
// Server sent their CA, but CA cannot be trusted if it is not locally known.
192+
connect({
193+
client: {
194+
checkServerIdentity,
195+
},
196+
server: {
197+
key: server.key,
198+
cert: server.cert + '\n' + server.ca,
199+
},
200+
}, function(err, pair, cleanup) {
201+
assert.strictEqual(err.code, 'SELF_SIGNED_CERT_IN_CHAIN');
202+
return cleanup();
203+
});
204+
205+
// Server sent their CA, wrongly, but its OK since we know the CA locally.
206+
connect({
207+
client: {
208+
checkServerIdentity,
209+
ca: server.ca,
210+
},
211+
server: {
212+
key: server.key,
213+
cert: server.cert + '\n' + server.ca,
214+
},
215+
}, function(err, pair, cleanup) {
216+
assert.ifError(err);
217+
return cleanup();
218+
});
219+
220+
// Fail to complete client's chain.
221+
connect({
222+
client: {
223+
key: client.key,
224+
cert: client.single,
225+
ca: server.ca,
226+
checkServerIdentity,
227+
},
228+
server: {
229+
key: server.key,
230+
cert: server.cert,
231+
ca: client.ca,
232+
requestCert: true,
233+
},
234+
}, function(err, pair, cleanup) {
235+
assert.strictEqual(err.code, 'ECONNRESET');
236+
return cleanup();
237+
});
238+
239+
// Fail to find CA for client.
240+
connect({
241+
client: {
242+
key: client.key,
243+
cert: client.cert,
244+
ca: server.ca,
245+
checkServerIdentity,
246+
},
247+
server: {
248+
key: server.key,
249+
cert: server.cert,
250+
requestCert: true,
251+
},
252+
}, function(err, pair, cleanup) {
253+
assert.strictEqual(err.code, 'ECONNRESET');
254+
return cleanup();
255+
});
256+
257+
// Confirm lack of support for "BEGIN TRUSTED CERTIFICATE".
258+
connect({
259+
client: {
260+
key: client.key,
261+
cert: client.cert,
262+
ca: server.ca.replace(/CERTIFICATE/g, 'TRUSTED CERTIFICATE'),
263+
checkServerIdentity,
264+
},
265+
server: {
266+
key: server.key,
267+
cert: server.cert,
268+
ca: client.ca,
269+
requestCert: true,
270+
},
271+
}, function(err, pair, cleanup) {
272+
assert.strictEqual(err.code, 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY');
273+
return cleanup();
274+
});
275+
276+
// Confirm lack of support for "BEGIN TRUSTED CERTIFICATE".
277+
connect({
278+
client: {
279+
key: client.key,
280+
cert: client.cert,
281+
ca: server.ca,
282+
checkServerIdentity,
283+
},
284+
server: {
285+
key: server.key,
286+
cert: server.cert,
287+
ca: client.ca.replace(/CERTIFICATE/g, 'TRUSTED CERTIFICATE'),
288+
requestCert: true,
289+
},
290+
}, function(err, pair, cleanup) {
291+
assert.strictEqual(err.code, 'ECONNRESET');
292+
return cleanup();
293+
});
294+
295+
// Confirm support for "BEGIN X509 CERTIFICATE".
296+
connect({
297+
client: {
298+
key: client.key,
299+
cert: client.cert,
300+
ca: server.ca.replace(/CERTIFICATE/g, 'X509 CERTIFICATE'),
301+
checkServerIdentity,
302+
},
303+
server: {
304+
key: server.key,
305+
cert: server.cert,
306+
ca: client.ca,
307+
requestCert: true,
308+
},
309+
}, function(err, pair, cleanup) {
310+
assert.ifError(err);
311+
return cleanup();
312+
});
313+
314+
// Confirm support for "BEGIN X509 CERTIFICATE".
315+
connect({
316+
client: {
317+
key: client.key,
318+
cert: client.cert,
319+
ca: server.ca,
320+
checkServerIdentity,
321+
},
322+
server: {
323+
key: server.key,
324+
cert: server.cert,
325+
ca: client.ca.replace(/CERTIFICATE/g, 'X509 CERTIFICATE'),
326+
requestCert: true,
327+
},
328+
}, function(err, pair, cleanup) {
329+
assert.ifError(err);
330+
return cleanup();
331+
});

0 commit comments

Comments
 (0)