forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathboringssl.js
More file actions
346 lines (319 loc) · 11 KB
/
Copy pathboringssl.js
File metadata and controls
346 lines (319 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/* eslint-disable node-core/crypto-check */
'use strict';
const common = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
// This module is for BoringSSL-specific branches in tests whose original
// OpenSSL coverage cannot run unchanged. Each helper should assert the
// observable BoringSSL behavior that explains why the OpenSSL-specific
// assertions are bypassed.
/**
* BoringSSL exposes many removed or disabled TLS cipher suites as "no match"
* at secure-context creation time. This is used for suites such as
* finite-field DHE and anonymous ECDH that OpenSSL builds may still negotiate
* in tests.
* @param {Function} fn
*/
function assertNoCipherMatch(fn) {
assert.throws(fn, {
code: 'ERR_SSL_NO_CIPHER_MATCH',
library: 'SSL routines',
function: 'OPENSSL_internal',
reason: 'NO_CIPHER_MATCH',
});
}
/**
* BoringSSL does not parse OpenSSL cipher-string commands such as `@SECLEVEL`.
* Those are OpenSSL policy directives, not cipher names.
* @param {Function} fn
*/
function assertInvalidCommand(fn) {
assert.throws(fn, {
code: 'ERR_SSL_INVALID_COMMAND',
library: 'SSL routines',
function: 'OPENSSL_internal',
reason: 'INVALID_COMMAND',
});
}
/**
* Node's DHE tests exercise OpenSSL's finite-field DHE cipher support and DH
* parameter-size policy. BoringSSL does not offer these DHE cipher suites on
* this surface, so creating a server context with a DHE-only cipher list fails
* before a handshake can test DH parameter behavior.
*/
function assertFiniteFieldDheUnsupported() {
assertNoCipherMatch(() => {
tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
ciphers: 'DHE-RSA-AES128-GCM-SHA256',
});
});
}
/**
* OpenSSL security levels reject small keys by policy and can be adjusted with
* `@SECLEVEL` in the cipher string. BoringSSL does not implement those security
* levels: the small-key server context is accepted, while the OpenSSL-specific
* `@SECLEVEL` command is rejected as invalid cipher-string syntax.
*/
function assertOpenSSLSecurityLevelsUnsupported() {
const options = {
key: fixtures.readKey('agent11-key.pem'),
cert: fixtures.readKey('agent11-cert.pem'),
ciphers: 'DEFAULT',
};
tls.createServer(options).close();
options.ciphers = 'DEFAULT:@SECLEVEL=0';
assertInvalidCommand(() => tls.createServer(options));
}
/**
* Node's multi-key tests rely on OpenSSL accepting an array of private keys and
* matching them with an array of certificates. BoringSSL rejects this mixed
* EC/RSA identity configuration while configuring the certificate chain, before
* a client can negotiate either identity.
*/
function assertMultiKeyUnsupported() {
assert.throws(() => {
tls.createServer({
key: [
fixtures.readKey('ec10-key.pem'),
fixtures.readKey('agent1-key.pem'),
],
cert: [
fixtures.readKey('agent1-cert.pem'),
fixtures.readKey('ec10-cert.pem'),
],
});
}, {
code: 'ERR_OSSL_X509_KEY_TYPE_MISMATCH',
library: 'X.509 certificate routines',
function: 'OPENSSL_internal',
reason: 'KEY_TYPE_MISMATCH',
});
}
/**
* BoringSSL does not support caller-initiated renegotiation. Even on a TLS 1.2
* connection, TLSSocket#renegotiate() returns false and the callback receives
* Node's BoringSSL-specific unsupported-renegotiation error instead of
* entering the native binding or exercising Node's renegotiation-limit logic.
*/
function testRenegotiationUnsupported() {
const server = tls.createServer({
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'),
maxVersion: 'TLSv1.2',
}, (socket) => socket.resume());
server.listen(0, common.mustCall(() => {
const client = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
maxVersion: 'TLSv1.2',
}, common.mustCall(() => {
const ok = client.renegotiate({}, common.mustCall((err) => {
assert.throws(() => { throw err; }, {
code: 'ERR_TLS_RENEGOTIATION_UNSUPPORTED',
message: 'TLS session renegotiation is unsupported by this TLS ' +
'implementation',
});
client.destroy();
server.close();
}));
assert.strictEqual(ok, false);
}));
client.on('error', common.mustNotCall());
}));
}
/**
* OpenSSL exposes the negotiated ephemeral key type, name, and size for TLS
* clients. With BoringSSL the same ECDHE TLS 1.2 handshake succeeds, but
* getEphemeralKeyInfo() returns null on the server side and an object whose
* fields are undefined on the client side.
*/
function testEphemeralKeyInfoUnsupported() {
const server = tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
ecdhCurve: 'prime256v1',
maxVersion: 'TLSv1.2',
}, common.mustCall((socket) => {
assert.strictEqual(socket.getEphemeralKeyInfo(), null);
socket.end();
}));
server.listen(0, common.mustCall(() => {
const client = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
maxVersion: 'TLSv1.2',
}, common.mustCall(() => {
assert.deepStrictEqual(client.getEphemeralKeyInfo(), {
type: undefined,
name: undefined,
size: undefined,
});
server.close();
}));
}));
}
/**
* The protocol matrix tests cover OpenSSL behavior for legacy TLS protocols.
* For BoringSSL we only need to exhibit that a TLSv1-only client cannot connect
* to a server whose minimum protocol is TLS 1.2; the client receives the
* protocol-version alert instead of the OpenSSL version-specific matrix.
*/
function testLegacyProtocolUnsupported() {
const server = tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
minVersion: 'TLSv1.2',
}, common.mustNotCall());
server.on('tlsClientError', common.mustCall());
server.listen(0, common.mustCall(() => {
const client = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
secureProtocol: 'TLSv1_method',
}, common.mustNotCall());
client.on('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION');
server.close();
}));
}));
}
/**
* BoringSSL can load a multi-PFX option well enough to serve the ECDSA
* identity, but it does not provide the same OpenSSL multi-identity selection
* behavior. After the ECDSA handshake succeeds, an RSA-only client fails with
* no shared cipher instead of selecting the RSA identity from the same PFX list.
*/
function testMultiPfxSelectionDifference() {
const server = tls.createServer({
pfx: [
{
buf: fixtures.readKey('agent1.pfx'),
passphrase: 'sample',
},
fixtures.readKey('ec.pfx'),
],
}, common.mustCallAtLeast((socket) => socket.end(), 1));
server.listen(0, common.mustCall(() => {
const ecdsa = tls.connect(server.address().port, {
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
rejectUnauthorized: false,
}, common.mustCall(() => {
assert.strictEqual(ecdsa.getCipher().name,
'ECDHE-ECDSA-AES256-GCM-SHA384');
ecdsa.end();
server.once('tlsClientError', common.mustCall((err) => {
assert.strictEqual(err.code, 'ERR_SSL_NO_SHARED_CIPHER');
}));
const rsa = tls.connect(server.address().port, {
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
rejectUnauthorized: false,
}, common.mustNotCall());
rsa.on('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE');
server.close();
}));
}));
}));
}
/**
* PSK works for TLS 1.2 in BoringSSL, but Node's PSK tests also cover the
* default TLS 1.3 path. In that path BoringSSL does not complete a certificate-
* less PSK-only handshake through Node's current server setup: the server
* reports NO_CERTIFICATE_SET and the client receives an internal-error alert.
*/
function testPskTls13Unsupported() {
const key = Buffer.from('d731ef57be09e5204f0b205b60627028', 'hex');
let gotClientError = false;
let gotServerError = false;
function maybeClose(server) {
if (gotClientError && gotServerError)
server.close();
}
const server = tls.createServer({
ciphers: 'PSK+HIGH',
pskCallback() { return key; },
}, common.mustNotCall());
server.once('tlsClientError', common.mustCall((err) => {
assert.strictEqual(err.code, 'ERR_SSL_NO_CERTIFICATE_SET');
gotServerError = true;
maybeClose(server);
}));
server.listen(0, common.mustCall(() => {
const client = tls.connect({
port: server.address().port,
ciphers: 'PSK+HIGH',
checkServerIdentity() {},
pskCallback() {
return { psk: key, identity: 'TestUser' };
},
}, common.mustNotCall());
client.on('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR');
gotClientError = true;
maybeClose(server);
}));
}));
}
/**
* The OpenSSL ticket tests assume that once a TLS 1.3 session is reused, the
* client will not necessarily receive a replacement session event before close.
* BoringSSL emits new session tickets on both the initial and resumed TLS 1.3
* connections, so the resumed connection still emits at least one 'session'
* event while isSessionReused() is true.
*/
function testTls13SessionTicketSemanticsDiffer() {
const server = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
}, (socket) => socket.end());
let session;
let secondSessionEvents = 0;
server.listen(0, common.mustCall(() => {
const first = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
}, common.mustCall(() => {
assert.strictEqual(first.isSessionReused(), false);
}));
first.on('session', common.mustCallAtLeast((sess) => {
session = sess;
}, 1));
first.on('close', common.mustCall(() => {
assert(Buffer.isBuffer(session));
const second = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
session,
}, common.mustCall(() => {
assert.strictEqual(second.isSessionReused(), true);
}));
second.on('session', common.mustCallAtLeast(() => {
secondSessionEvents++;
}, 1));
second.on('close', common.mustCall(() => {
assert(secondSessionEvents > 0);
server.close();
}));
second.resume();
}));
first.resume();
}));
}
module.exports = {
assertFiniteFieldDheUnsupported,
assertMultiKeyUnsupported,
assertNoCipherMatch,
assertOpenSSLSecurityLevelsUnsupported,
testEphemeralKeyInfoUnsupported,
testLegacyProtocolUnsupported,
testMultiPfxSelectionDifference,
testPskTls13Unsupported,
testRenegotiationUnsupported,
testTls13SessionTicketSemanticsDiffer,
};