Compare commits

...

57 Commits

Author SHA1 Message Date
pfg
d787626f18 four left to categorize, then impl 2025-06-09 21:16:47 -07:00
pfg
ea31ae1d2e wip 2025-06-09 21:01:19 -07:00
pfg
a02d6d48b9 Merge branch 'main' into pfg/tls 2025-06-09 17:59:54 -07:00
pfg
ebbbba0fa6 notes 2025-06-09 15:43:07 -07:00
pfg
7e97cd447e easy one 2025-06-09 14:04:51 -07:00
pfg
10616499a7 wip 2025-06-06 21:25:11 -07:00
pfg
cc08966171 wip 2025-06-06 21:08:26 -07:00
pfg
4b7b5dbdca wip 2025-06-06 20:48:10 -07:00
pfg
3d9534414e list of to check 2025-06-06 20:23:46 -07:00
pfg
47abb06811 indicators 2025-06-06 20:22:30 -07:00
pfg
d43cffbed3 Merge branch 'main' into pfg/tls 2025-06-06 18:51:03 -07:00
pfg
46e087ee0c . 2025-06-05 14:32:53 -07:00
pfg
ba12a3dfd4 problm 2025-06-03 15:34:29 -07:00
pfg
483afffccc maybe cause? 2025-06-03 15:01:20 -07:00
pfg
979cfd96f0 completado 2025-06-03 14:56:24 -07:00
pfg
645fa28843 . 2025-06-03 14:36:24 -07:00
pfg
9162bf45d0 . 2025-06-02 20:38:50 -07:00
pfg
80e9242a0d . 2025-06-02 20:32:44 -07:00
pfg
82cf920a51 . 2025-06-02 20:30:37 -07:00
pfg
ee07032fd9 . 2025-06-02 20:16:11 -07:00
pfg
347d350405 es list 2025-06-02 20:12:58 -07:00
pfg
8c85826c7b . 2025-06-02 20:08:27 -07:00
pfg
1bfd479aad interesting 2025-06-02 19:40:40 -07:00
pfg
a28708ea54 . 2025-06-02 18:25:43 -07:00
pfg
80498e2418 no luck on localhost:3333localhost:3334 problem 2025-06-02 18:21:51 -07:00
pfg
361a3592e5 repro desc 2025-06-02 16:58:23 -07:00
pfg
4a8828d1aa . 2025-06-02 16:57:06 -07:00
pfg
a286134233 repro 2025-06-02 16:38:57 -07:00
pfg
3ee7649f95 repro 2025-06-02 16:37:28 -07:00
pfg
4848a2d0b7 ? 2025-06-02 16:31:07 -07:00
pfg
e3ebd4177a . 2025-06-02 16:30:21 -07:00
pfg
818f3bb83f .. 2025-06-02 16:30:02 -07:00
pfg
b3cf2df234 . 2025-06-02 16:01:43 -07:00
pfg
9d40c62901 . 2025-06-02 15:52:37 -07:00
pfg
e331f00c7e . 2025-06-02 15:34:53 -07:00
pfg
271ee072ef . 2025-06-02 15:23:21 -07:00
pfg
57cff5520b . 2025-06-02 15:17:27 -07:00
pfg
3e938e0fe6 . 2025-06-02 15:16:04 -07:00
pfg
c074fbaa8d . 2025-06-02 15:14:50 -07:00
pfg
0191f65e36 . 2025-06-02 14:56:43 -07:00
pfg
c585ac3a92 . 2025-06-02 14:53:20 -07:00
pfg
eed575da7e . 2025-06-02 13:45:46 -07:00
pfg
bf3086abf8 . 2025-06-02 13:43:34 -07:00
pfg
8ecdabdcd2 . 2025-05-30 21:04:25 -07:00
pfg
4deec54426 . 2025-05-30 21:03:18 -07:00
pfg
0deefe33f2 . 2025-05-30 21:01:46 -07:00
pfg
b949e2062a tls-junk-server 2025-05-30 20:58:38 -07:00
pfg
a99f2e5071 identify cause of test-tls-net-socket-keepalive 2025-05-30 20:56:18 -07:00
pfg
b3d623c091 . 2025-05-30 18:03:53 -07:00
pfg
c12a7ef612 . 2025-05-30 17:16:51 -07:00
pfg
0b1ada3d12 strange 2025-05-30 16:49:43 -07:00
pfg
2fadf4cd03 . 2025-05-30 14:46:19 -07:00
pfg
9496469617 . 2025-05-30 13:50:18 -07:00
pfg
24bc337b0a lookup passing 2025-05-29 18:03:03 -07:00
pfg
15c7cc6008 . 2025-05-29 16:02:41 -07:00
pfg
132f0e75d3 . 2025-05-29 15:03:37 -07:00
pfg
c69c7ab2a8 WIP 2025-05-28 20:22:54 -07:00
90 changed files with 5404 additions and 0 deletions

231
categories.txt Normal file
View File

@@ -0,0 +1,231 @@
```js
doc = (top list)
doc2 = (electron list)
{
[...document.querySelectorAll(".test-link")].forEach(t => {
const name = t.lastChild.nodeValue.trim().slice(0, -3);
if(doc2.includes(name)) {
t.style.color = "red";
}else if(doc.includes(name)) {
t.style.color = "black";
}
})
}
```
# TODO CHECK:
# max version env vars, not investigated.
parallel/test-tls-client-reject-12.js
parallel/test-tls-ticket-12.js
parallel/test-tls-client-resume-12.js
parallel/test-tls-destroy-stream-12.js
parallel/test-tls-net-socket-keepalive-12.js
parallel/test-tls-keylog-tlsv13.js
parallel/test-tls-cli-min-max-conflict.js
# trivial
parallel/test-tls-external-accessor.js
- we don't support `_external`, but we pass the spirit of the test. trivial 'fix'
parallel/test-tls-destroy-stream.js
parallel/test-tls-socket-close.js
parallel/test-double-tls-client.js (this one maybe not as easy, but it is somewhat related)
parallel/test-tls-server-capture-rejection.js (maybe related? async createServer callback does not work with events.captureRejections)
- the test calls `.emit("connection")` on the tlsServer. In node, this calls the connection
handler but in bun it does not. Switch tls to use emit("connection") rather than
calling the connection listener directly, and add .on('connection') in the constructor
like node does.
```diff
if (typeof options === "function") {
connectionListener = options;
options = {};
+this.on("connection", connectionListener);
} else if (options == null || typeof options === "object") {
options = { ...options };
+this.on("connection", connectionListener);
} else {
throw $ERR_INVALID_ARG_TYPE("options", ["Object", "Function"], options);
}
- if (typeof connectionListener === "function") {
- this.pauseOnConnect = pauseOnConnect;
- if (!isTLS) {
- connectionListener.$call(self, _socket);
- }
- }
also check what to do here
const connectionListener = server[bunSocketServerOptions]?.connectionListener;
if (typeof connectionListener === "function") {
connectionListener.$call(server, self);
}
server.emit("secureConnection", self);
```
parallel/test-tls-wrap-event-emmiter.js
- missing error for passing plain eventEmitter to TlsSocket constructor
# probably easy
parallel/test-tls-exportkeyingmaterial.js
- exportKeyingMaterial emits as uncaughtException rather than thrown error
parallel/test-tls-cert-chains-in-ca.js
parallel/test-tls-env-extra-ca.js
- checkServerIdentity callback is not implemented
parallel/test-tls-basic-validations.js
- 12 mismatched errors
parallel/test-tls-error-servername.js
- 2 mismatched errors
parallel/test-tls-client-allow-partial-trust-chain.js
- support allowPartialTrustChain
parallel/test-tls-env-bad-extra-ca.js
- emit a warning when the value in NODE_EXTRA_CA_CERTS is ignored because it fails to load
- this is a little bit hard to do because currently we handle that in us_internal_init_root_certs with getenv,
but we can't emit a warning from there. we would have to have it call out to bun code to emit the warning
# categorize
parallel/test-tls-connect-allow-half-open-option.js
- with allowHalfOpen, if the server ends its socket, it should still be able to receive a message
parallel/test-tls-server-parent-constructor-options.js
- with pauseOnConnect, the client is never connecting
parallel/test-tls-get-ca-certificates-system.js
parallel/test-tls-get-ca-certificates-default.js
parallel/test-tls-get-ca-certificates-error.js
parallel/test-tls-get-ca-certificates-extra-empty.js
parallel/test-tls-get-ca-certificates-bundled.js
parallel/test-tls-get-ca-certificates-extra-subset.js
parallel/test-tls-get-ca-certificates-system-without-flag.js
parallel/test-tls-get-ca-certificates-bundled-subset.js
parallel/test-tls-get-ca-certificates-extra.js
- tls.getCACertificates is not implemented
parallel/test-tls-server-setkeycert.js
- some kind of problem with getPeerX509Certificate?
parallel/test-tls-enable-keylog-cli.js
- support --tls-keylog cli flag
parallel/test-tls-cert-chains-concat.js
- issuerCertificate is not defined?
parallel/test-tls-streamwrap-buffersize.js
- bufferSize is wrong. also it is deprecated.
parallel/test-tls-clientcertengine-invalid-arg-type.js
parallel/test-tls-socket-default-options.js
parallel/test-tls-set-secure-context.js
- createSecureContext (kai)
parallel/test-tls-timeout-server.js
parallel/test-tls-socket-destroy.js
- handshakeTimeout is not implemented
parallel/test-tls-retain-handle-no-abort.js
- unable to verify the first certificate? UNABLE_TO_VERIFY_LEAF_SIGNATURE
parallel/test-tls-client-resume.js
- done https://github.com/oven-sh/bun/pull/20197
parallel/test-tls-connect-timeout-option.js
- support tls connect timeout option
parallel/test-tls-getcertificate-x509.js
- error: error:0900006e:PEM routines:OPENSSL_internal:NO_START_LINE
parallel/test-tls-connect-given-socket.js
- "invalid socket" when passing 'socket: socket' in tls.connect?
parallel/test-double-tls-server.js
- ALPNCallback
parallel/test-tls-writewrap-leak.js
- the write callback is supposed to get an error ECANCELED "Canceled because of SSL destruction"
parallel/test-tls-client-default-ciphers.js
parallel/test-tls-socket-constructor-alpn-options-parsing.js
sequential/test-tls-session-timeout.js
parallel/test-tls-env-extra-ca-with-options.js
# skipped (wontfix)
parallel/test-tls-no-sslv23.js
- skipped
# wontfix
test-tls-disable-renegotiation.js
- boringssl doesn't support client-initiated renegotiation
# passes already
parallel/test-tls-pause.js
parallel/test-tls-connect-memleak.js
- passes already
# Attempted
http.request errors because it passes port but no host and path as `localhost:${port}`. _http_client concatenates
that to `localhost:${port}localhost:${port}` which is an invalid url
- test-tls-over-http-tunnel.js
- the problem is that sending an http request with path `abcd` will do `GET abcd HTTP/1.1`, but
fetch `url.com/abcd` is `GET /abcd HTTP/1.1`. skipping the slash is invalid anyway and node's http server doesn't
accept it. but net does.
- http_client in bun is implemented with fetch which doesn't let you send a malformed http request
- this might be unintentional? unclear
[ES] test-tls-disable-renegotiation.js:
- missing ERR_INVALID_ARG_TYPE for client.renegotiate()
- OpenSSL function should not have been called via OPENSSL_internal SSL routines
- The error is emitted at: <https://github.com/google/boringssl/blob/035e720641f385e82c72b7b0a9e1d89e58cb5ed5/ssl/ssl_lib.cc#L1629>
- marked as 'caller-initiated renegotiation is not supported'. you can't renegotiate unless a renegotiation is already pending.
test-tls-secure-session.js
- client.on("session") never called?
- probably because it sets secureProtocol: 'TLSv1_2_method'
- we probably don't support setting secureProtocol
- we don't emit a session event at all ever except in http2. node emits a session at the end of onConnectSecure and in onnewesssionclient
- search terms: 'emit('session)'
# Skipped by electron
these tests are disabled by electron: (https://github.com/electron/electron/blob/main/script/node-disabled-tests.json)
"parallel/test-tls-alpn-server-client",
"parallel/test-tls-cli-min-version-1.0",
"parallel/test-tls-cli-max-version-1.2",
"parallel/test-tls-cli-max-version-1.3",
"parallel/test-tls-cli-min-version-1.1",
"parallel/test-tls-cli-min-version-1.2",
"parallel/test-tls-cli-min-version-1.3",
"parallel/test-tls-client-auth",
"parallel/test-tls-client-getephemeralkeyinfo",
"parallel/test-tls-client-mindhsize",
"parallel/test-tls-client-reject",
"parallel/test-tls-client-renegotiation-13",
"parallel/test-tls-cnnic-whitelist",
"parallel/test-tls-disable-renegotiation",
"parallel/test-tls-empty-sni-context",
"parallel/test-tls-error-stack",
"parallel/test-tls-finished",
"parallel/test-tls-generic-stream",
"parallel/test-tls-getcipher",
"parallel/test-tls-getprotocol",
"parallel/test-tls-handshake-error",
"parallel/test-tls-handshake-exception",
"parallel/test-tls-hello-parser-failure",
"parallel/test-tls-honorcipherorder",
"parallel/test-tls-junk-closes-server",
"parallel/test-tls-junk-server",
"parallel/test-tls-key-mismatch",
"parallel/test-tls-max-send-fragment",
"parallel/test-tls-min-max-version",
"parallel/test-tls-multi-key",
"parallel/test-tls-multi-pfx",
"parallel/test-tls-no-cert-required",
"parallel/test-tls-options-boolean-check",
"parallel/test-tls-passphrase",
"parallel/test-tls-peer-certificate",
"parallel/test-tls-pfx-authorizationerror",
"parallel/test-tls-psk-circuit",
"parallel/test-tls-reduced-SECLEVEL-in-cipher",
"parallel/test-tls-root-certificates",
"parallel/test-tls-server-failed-handshake-emits-clienterror",
"parallel/test-tls-set-ciphers",
"parallel/test-tls-set-ciphers-error",
"parallel/test-tls-set-sigalgs",
"parallel/test-tls-socket-allow-half-open-option",
"parallel/test-tls-socket-failed-handshake-emits-error",
"parallel/test-tls-ticket",
"parallel/test-tls-ticket-cluster",

View File

@@ -0,0 +1,17 @@
'use strict';
const tls = require('tls');
const assert = require('assert');
const defaultSet = new Set(tls.getCACertificates('default'));
const extraSet = new Set(tls.getCACertificates('extra'));
console.log(defaultSet.size, 'default certificates');
console.log(extraSet.size, 'extra certificates')
// Parent process is supposed to call this with
// NODE_EXTRA_CA_CERTS set to test/fixtures/keys/ca1-cert.pem.
assert.strictEqual(extraSet.size, 1);
// Check that default set is a super set of extra set.
assert.deepStrictEqual(defaultSet.intersection(extraSet),
extraSet);

View File

@@ -0,0 +1,58 @@
'use strict';
const common = require('../common');
const assert = require('assert');
if (!common.hasCrypto) common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const tls = require('tls');
// In reality, this can be a HTTP CONNECT message, signaling the incoming
// data is TLS encrypted
const HEAD = 'XXXX';
const subserver = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
})
.on('secureConnection', common.mustCall(() => {
process.exit(0);
}));
const server = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
})
.listen(client)
.on('secureConnection', (serverTlsSock) => {
serverTlsSock.on('data', (chunk) => {
assert.strictEqual(chunk.toString(), HEAD);
subserver.emit('connection', serverTlsSock);
});
});
function client() {
const down = tls.connect({
host: '127.0.0.1',
port: server.address().port,
rejectUnauthorized: false
}).on('secureConnect', () => {
down.write(HEAD, common.mustSucceed());
// Sending tls data on a client TLSSocket with an active write led to a crash:
//
// node[16862]: ../src/crypto/crypto_tls.cc:963:virtual int node::crypto::TLSWrap::DoWrite(node::WriteWrap*,
// uv_buf_t*, size_t, uv_stream_t*): Assertion `!current_write_' failed.
// 1: 0xb090e0 node::Abort() [node]
// 2: 0xb0915e [node]
// 3: 0xca8413 node::crypto::TLSWrap::DoWrite(node::WriteWrap*, uv_buf_t*, unsigned long, uv_stream_s*) [node]
// 4: 0xcaa549 node::StreamBase::Write(uv_buf_t*, unsigned long, uv_stream_s*, v8::Local<v8::Object>) [node]
// 5: 0xca88d7 node::crypto::TLSWrap::EncOut() [node]
// 6: 0xd3df3e [node]
// 7: 0xd3f35f v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) [node]
// 8: 0x15d9ef9 [node]
// Aborted
tls.connect({
socket: down,
rejectUnauthorized: false
});
});
}

View File

@@ -0,0 +1,119 @@
'use strict';
const common = require('../common');
const assert = require('assert');
if (!common.hasCrypto) common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const net = require('net');
// Sending tls data on a server TLSSocket with an active write led to a crash:
//
// node[1296]: ../src/crypto/crypto_tls.cc:963:virtual int node::crypto::TLSWrap::DoWrite(node::WriteWrap*,
// uv_buf_t*, size_t, uv_stream_t*): Assertion `!current_write_' failed.
// 1: 0xb090e0 node::Abort() [node]
// 2: 0xb0915e [node]
// 3: 0xca8413 node::crypto::TLSWrap::DoWrite(node::WriteWrap*, uv_buf_t*, unsigned long, uv_stream_s*) [node]
// 4: 0xcaa549 node::StreamBase::Write(uv_buf_t*, unsigned long, uv_stream_s*, v8::Local<v8::Object>) [node]
// 5: 0xca88d7 node::crypto::TLSWrap::EncOut() [node]
// 6: 0xca9ba8 node::crypto::TLSWrap::OnStreamRead(long, uv_buf_t const&) [node]
// 7: 0xca8eb0 node::crypto::TLSWrap::ClearOut() [node]
// 8: 0xca9ba0 node::crypto::TLSWrap::OnStreamRead(long, uv_buf_t const&) [node]
// 9: 0xbe50dd node::LibuvStreamWrap::OnUvRead(long, uv_buf_t const*) [node]
// 10: 0xbe54c4 [node]
// 11: 0x15583d7 [node]
// 12: 0x1558c00 [node]
// 13: 0x155ede4 [node]
// 14: 0x154d008 uv_run [node]
const serverReplaySize = 2 * 1024 * 1024;
(async function() {
console.log('Starting test...');
const tlsClientHello = await getClientHello();
console.log('Got client hello, length:', tlsClientHello.length);
const subserver = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
ALPNCallback: common.mustCall(({ sn, protocols }) => {
console.log('ALPN callback called with protocols:', protocols);
// Once `subserver` receives `tlsClientHello` from the underlying net.Socket,
// in this test, a TLSSocket actually, it should be able to proceed to the handshake
// and emit this event
assert.strictEqual(protocols[0], 'h2');
return 'h2';
}),
});
const server = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
})
.listen(startClient)
.on('secureConnection', (serverTlsSock) => {
console.log('Server received secure connection');
// Craft writes that are large enough to stuck in sending
// In reality this can be a 200 response to the incoming HTTP CONNECT
const half = Buffer.alloc(serverReplaySize / 2, 0);
console.log('Writing first half of data...');
serverTlsSock.write(half, common.mustSucceed());
console.log('Writing second half of data...');
serverTlsSock.write(half, common.mustSucceed());
console.log('Emitting connection to subserver...');
subserver.emit('connection', serverTlsSock);
});
function startClient() {
console.log('Starting client connection...');
const clientTlsSock = tls.connect({
host: '127.0.0.1',
port: server.address().port,
rejectUnauthorized: false,
});
const recv = [];
let revcLen = 0;
clientTlsSock.on('data', (chunk) => {
console.log('Client received chunk of size:', chunk.length);
revcLen += chunk.length;
recv.push(chunk);
if (revcLen > serverReplaySize) {
console.log('Received enough data, checking server hello...');
// Check the server's replay is followed by the subserver's TLS ServerHello
const serverHelloFstByte = Buffer.concat(recv).subarray(serverReplaySize, serverReplaySize + 1);
console.log('Server hello first byte:', serverHelloFstByte.toString('hex'));
assert.strictEqual(serverHelloFstByte.toString('hex'), '16');
process.exit(0);
}
});
// In reality, one may want to send a HTTP CONNECT before starting this double TLS
console.log('Writing client hello...');
clientTlsSock.write(tlsClientHello);
}
})().then(common.mustCall());
function getClientHello() {
return new Promise((resolve) => {
console.log('Creating temporary server to get client hello...');
const server = net.createServer((sock) => {
console.log('Temporary server received connection');
sock.on('data', (chunk) => {
console.log('Temporary server received client hello, length:', chunk.length);
resolve(chunk);
});
})
.listen(() => {
console.log('Temporary server listening, connecting client...');
tls.connect({
port: server.address().port,
host: '127.0.0.1',
ALPNProtocols: ['h2'],
}).on('error', () => {
console.log('Client connection error (expected)');
});
});
});
}

View File

@@ -0,0 +1,49 @@
'use strict';
// Flags: --expose-gc
// Tests that memoryUsage().external doesn't go negative
// when a lot tls connections are opened and closed
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { duplexPair } = require('stream');
const { onGC } = require('../common/gc');
const assert = require('assert');
const tls = require('tls');
// Payload doesn't matter. We just need to have the tls
// connection try and connect somewhere.
const dummyPayload = Buffer.alloc(10000, 'yolo');
let runs = 0;
// Count garbage-collected TLS sockets.
let gced = 0;
function ongc() { gced++; }
connect();
function connect() {
if (runs % 64 === 0)
globalThis.gc();
const externalMemoryUsage = process.memoryUsage().external;
assert(externalMemoryUsage >= 0, `${externalMemoryUsage} < 0`);
if (runs++ === 512) {
// Make sure at least half the TLS sockets have been garbage collected
// (so that this test can actually check what it's testing):
assert(gced >= 256, `${gced} < 256`);
return;
}
const [ clientSide, serverSide ] = duplexPair();
const tlsSocket = tls.connect({ socket: clientSide });
tlsSocket.on('error', common.mustCall(connect));
onGC(tlsSocket, { ongc });
// Use setImmediate so that we don't trigger the error within the same
// event loop tick.
setImmediate(() => serverSide.write(dummyPayload));
}

View File

@@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
// Adding a CA certificate to contextWithCert should not also add it to
// contextWithoutCert. This is tested by trying to connect to a server that
// depends on that CA using contextWithoutCert.
const {
assert, connect, keys, tls
} = require(fixtures.path('tls-connect'));
const contextWithoutCert = tls.createSecureContext({});
const contextWithCert = tls.createSecureContext({});
contextWithCert.context.addCACert(keys.agent1.ca);
const serverOptions = {
key: keys.agent1.key,
cert: keys.agent1.cert,
};
const clientOptions = {
ca: [keys.agent1.ca],
servername: 'agent1',
rejectUnauthorized: true,
};
// This client should fail to connect because it doesn't trust the CA
// certificate.
clientOptions.secureContext = contextWithoutCert;
connect({
client: clientOptions,
server: serverOptions,
}, common.mustCall((err, pair, cleanup) => {
assert(err);
assert.strictEqual(err.message, 'unable to verify the first certificate');
cleanup();
// This time it should connect because contextWithCert includes the needed CA
// certificate.
clientOptions.secureContext = contextWithCert;
connect({
client: clientOptions,
server: serverOptions,
}, common.mustSucceed((pair, cleanup) => {
cleanup();
}));
}));

View File

@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
// TODO:
// assert.throws(
// () => tls.createSecureContext({ ciphers: 1 }),
// {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// message: 'The "options.ciphers" property must be of type string.' +
// ' Received type number (1)'
// });
assert.throws(
() => tls.createServer({ ciphers: 1 }),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.ciphers" property must be of type string.' +
' Received type number (1)'
});
// TODO:
// assert.throws(
// () => tls.createSecureContext({ key: 'dummykey', passphrase: 1 }),
// {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// message: /The "options\.passphrase" property must be of type string/
// });
assert.throws(
() => tls.createServer({ key: 'dummykey', passphrase: 1 }),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /The "options\.passphrase" property must be of type string/
});
// TODO:
// assert.throws(
// () => tls.createServer({ ecdhCurve: 1 }),
// {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// message: /The "options\.ecdhCurve" property must be of type string/
// });
// TODO:
// assert.throws(
// () => tls.createServer({ handshakeTimeout: 'abcd' }),
// {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// message: 'The "options.handshakeTimeout" property must be of type number.' +
// " Received type string ('abcd')"
// }
// );
// TODO:
// assert.throws(
// () => tls.createServer({ sessionTimeout: 'abcd' }),
// {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// message: /The "options\.sessionTimeout" property must be of type number/
// });
// TODO:
// assert.throws(
// () => tls.createServer({ ticketKeys: 'abcd' }),
// {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// message: /The "options\.ticketKeys" property must be an instance of/
// });
// TODO:
// assert.throws(() => tls.createServer({ ticketKeys: Buffer.alloc(0) }), {
// code: 'ERR_INVALID_ARG_VALUE',
// message: /The property 'options\.ticketKeys' must be exactly 48 bytes/
// });
{
const buffer = Buffer.from('abcd');
const out = {};
tls.convertALPNProtocols(buffer, out);
out.ALPNProtocols.write('efgh');
assert(buffer.equals(Buffer.from('abcd')));
assert(out.ALPNProtocols.equals(Buffer.from('efgh')));
}
// TODO:
// {
// const arrayBufferViewStr = 'abcd';
// const inputBuffer = Buffer.from(arrayBufferViewStr.repeat(8), 'utf8');
// for (const expectView of common.getArrayBufferViews(inputBuffer)) {
// const out = {};
// const expected = Buffer.from(expectView.buffer.slice(),
// expectView.byteOffset,
// expectView.byteLength);
// tls.convertALPNProtocols(expectView, out);
// assert(out.ALPNProtocols.equals(expected));
// }
// }
// TODO:
// {
// const protocols = [(new String('a')).repeat(500)];
// const out = {};
// assert.throws(
// () => tls.convertALPNProtocols(protocols, out),
// {
// code: 'ERR_OUT_OF_RANGE',
// message: 'The byte length of the protocol at index 0 exceeds the ' +
// 'maximum length. It must be <= 255. Received 500'
// }
// );
// }
// TODO:
// assert.throws(() => { tls.createSecureContext({ minVersion: 'fhqwhgads' }); },
// {
// code: 'ERR_TLS_INVALID_PROTOCOL_VERSION',
// name: 'TypeError'
// });
// TODO:
// assert.throws(() => { tls.createSecureContext({ maxVersion: 'fhqwhgads' }); },
// {
// code: 'ERR_TLS_INVALID_PROTOCOL_VERSION',
// name: 'TypeError'
// });
// TODO:
// for (const checkServerIdentity of [undefined, null, 1, true]) {
// assert.throws(() => {
// tls.connect({ checkServerIdentity });
// }, {
// code: 'ERR_INVALID_ARG_TYPE',
// name: 'TypeError',
// });
// }

View File

@@ -0,0 +1,43 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const iter = 10;
const server = tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem')
}, common.mustCall((socket) => {
let str = '';
socket.setEncoding('utf-8');
socket.on('data', (chunk) => { str += chunk; });
socket.on('end', common.mustCall(() => {
assert.strictEqual(str, 'a'.repeat(iter - 1));
server.close();
}));
}));
server.listen(0, common.mustCall(() => {
const client = tls.connect({
port: server.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
assert.strictEqual(client.bufferSize, 0);
for (let i = 1; i < iter; i++) {
client.write('a');
assert.strictEqual(client.bufferSize, i);
}
client.on('finish', common.mustCall(() => {
assert.strictEqual(client.bufferSize, 0);
}));
client.end();
}));
}));

View File

@@ -0,0 +1,59 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
// Check cert chain is received by client, and is completed with the ca cert
// known to the client.
const {
assert, connect, debug, keys
} = require(fixtures.path('tls-connect'));
console.log('Starting TLS cert chain test...');
// agent6-cert.pem includes cert for agent6 and ca3
connect({
client: {
checkServerIdentity: (servername, cert) => {
console.log('checkServerIdentity called with:', { servername, cert });
},
ca: keys.agent6.ca,
},
server: {
cert: keys.agent6.cert,
key: keys.agent6.key,
},
}, common.mustSucceed((pair, cleanup) => {
console.log('TLS connection established');
const peer = pair.client.conn.getPeerCertificate();
console.log('Peer certificate:', peer);
debug('peer:\n', peer);
assert.strictEqual(peer.subject.emailAddress, 'adam.lippai@tresorit.com');
assert.strictEqual(peer.subject.CN, 'Ádám Lippai');
assert.strictEqual(peer.issuer.CN, 'ca3');
assert.match(peer.serialNumber, /5B75D77EDC7FB5B7FA9F1424DA4C64FB815DCBDE/i);
const next = pair.client.conn.getPeerCertificate(true).issuerCertificate;
console.log('Next certificate in chain:', next);
const root = next.issuerCertificate;
delete next.issuerCertificate;
debug('next:\n', next);
assert.strictEqual(next.subject.CN, 'ca3');
assert.strictEqual(next.issuer.CN, 'ca1');
assert.match(next.serialNumber, /147D36C1C2F74206DE9FAB5F2226D78ADB00A425/i);
console.log('Root certificate:', root);
debug('root:\n', root);
assert.strictEqual(root.subject.CN, 'ca1');
assert.strictEqual(root.issuer.CN, 'ca1');
assert.match(root.serialNumber, /4AB16C8DFD6A7D0D2DFCABDF9C4B0E92C6AD0229/i);
// No client cert, so empty object returned.
console.log('Server peer certificate:', pair.server.conn.getPeerCertificate());
assert.deepStrictEqual(pair.server.conn.getPeerCertificate(), {});
assert.deepStrictEqual(pair.server.conn.getPeerCertificate(true), {});
console.log('Test completed successfully');
return cleanup();
}));

View File

@@ -0,0 +1,14 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
// Check that conflicting TLS protocol versions are not allowed
const assert = require('assert');
const child_process = require('child_process');
const args = ['--tls-min-v1.3', '--tls-max-v1.2', '-p', 'process.version'];
child_process.execFile(process.argv[0], args, (err) => {
assert(err);
assert.match(err.message, /not both/);
});

View File

@@ -0,0 +1,62 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) { common.skip('missing crypto'); };
const assert = require('assert');
const { once } = require('events');
const fixtures = require('../common/fixtures');
// agent6-cert.pem is signed by intermediate cert of ca3.
// The server has a cert chain of agent6->ca3->ca1(root).
const { it, beforeEach, afterEach, describe } = require('node:test');
describe('allowPartialTrustChain', { skip: !common.hasCrypto }, function() {
const tls = require('tls');
let server;
let client;
let opts;
beforeEach(async function() {
console.log('Setting up server and options...');
server = tls.createServer({
ca: fixtures.readKey('ca3-cert.pem'),
key: fixtures.readKey('agent6-key.pem'),
cert: fixtures.readKey('agent6-cert.pem'),
}, (socket) => socket.resume());
server.listen(0);
await once(server, 'listening');
console.log('Server listening on port:', server.address().port);
opts = {
port: server.address().port,
ca: fixtures.readKey('ca3-cert.pem'),
checkServerIdentity() {}
};
console.log('Options configured:', opts);
});
afterEach(async function() {
console.log('Cleaning up client and server...');
client?.destroy();
server?.close();
});
it('can connect successfully with allowPartialTrustChain: true', async function() {
console.log('Testing connection with allowPartialTrustChain: true');
client = tls.connect({ ...opts, allowPartialTrustChain: true });
await once(client, 'secureConnect'); // Should not throw
console.log('Successfully connected with allowPartialTrustChain: true');
});
it('fails without with allowPartialTrustChain: true for an intermediate cert in the CA', async function() {
console.log('Testing connection without allowPartialTrustChain');
// Consistency check: Connecting fails without allowPartialTrustChain: true
await assert.rejects(async () => {
console.log('Attempting connection without allowPartialTrustChain...');
const client = tls.connect(opts);
await once(client, 'secureConnect');
}, { code: 'UNABLE_TO_GET_ISSUER_CERT' });
console.log('Connection failed as expected without allowPartialTrustChain');
});
});

View File

@@ -0,0 +1,44 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
function Done() {}
function test1() {
let ciphers = '';
tls.createSecureContext = function(options) {
ciphers = options.ciphers;
throw new Done();
};
assert.throws(tls.connect, Done);
assert.strictEqual(ciphers, tls.DEFAULT_CIPHERS);
}
test1();

View File

@@ -0,0 +1,132 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
// Check that the ticket from the first connection causes session resumption
// when used to make a second connection.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
console.log('Starting TLS client resume test');
const options = {
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem')
};
console.log('Server options:', options);
// create server
const server = tls.Server(options, common.mustCall((socket) => {
console.log('Server received connection');
socket.end('Goodbye');
}, 2));
// start listening
server.listen(0, common.mustCall(function() {
console.log('Server listening on port:', this.address().port);
let sessionx = null; // From right after connect, invalid for TLS1.3
let session1 = null; // Delivered by the session event, always valid.
let sessions = 0;
let tls13;
const client1 = tls.connect({
port: this.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
console.log('Client1 connected');
tls13 = client1.getProtocol() === 'TLSv1.3';
console.log('TLS version:', client1.getProtocol());
assert.strictEqual(client1.isSessionReused(), false);
sessionx = client1.getSession();
console.log('SessionX obtained:', !!sessionx);
assert(sessionx);
if (session1)
reconnect();
}));
client1.on('data', common.mustCall((data) => {
console.log('Client1 received data:', data.toString());
}));
client1.once('session', common.mustCall((session) => {
console.log('Session1 event received');
session1 = session;
console.log('Session1 obtained:', !!session1);
assert(session1);
if (sessionx)
reconnect();
}));
client1.on('session', () => {
console.log('Client1 session event #', ++sessions);
});
client1.on('close', () => {
console.log('Client1 closed');
assert.strictEqual(sessions, tls13 ? 2 : 1);
});
function reconnect() {
console.log('Starting reconnect');
assert(sessionx);
assert(session1);
if (tls13) {
console.log('TLS1.3 session comparison');
// For TLS1.3, the session immediately after handshake is a dummy,
// unresumable session. The one delivered later in session event is
// resumable.
assert.notStrictEqual(sessionx.compare(session1), 0);
} else {
console.log('TLS1.2 session comparison');
// For TLS1.2, they are identical.
assert.strictEqual(sessionx.compare(session1), 0);
}
const opts = {
port: server.address().port,
rejectUnauthorized: false,
session: session1,
};
console.log('Connecting client2 with session');
const client2 = tls.connect(opts, common.mustCall(() => {
console.log('Client2 connected');
assert.strictEqual(client2.isSessionReused(), true);
}));
client2.on('close', common.mustCall(() => {
console.log('Client2 closed');
server.close();
}));
client2.resume();
}
client1.resume();
}));

View File

@@ -0,0 +1,15 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
{
assert.throws(
() => { tls.createSecureContext({ clientCertEngine: 0 }); },
{ code: 'ERR_INVALID_ARG_TYPE',
message: / Received type number \(0\)/ });
}

View File

@@ -0,0 +1,56 @@
// Flags: --use-bundled-ca
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
function loadPEM(n) {
return fixtures.readKey(`${n}.pem`);
}
const testCases = [
// Test 1: for the fix of node#2061
// agent6-cert.pem is signed by intermediate cert of ca3.
// The server has a cert chain of agent6->ca3->ca1(root) but
// tls.connect should be failed with an error of
// UNABLE_TO_GET_ISSUER_CERT_LOCALLY since the root CA of ca1 is not
// installed locally.
{
serverOpts: {
ca: loadPEM('ca3-key'),
key: loadPEM('agent6-key'),
cert: loadPEM('agent6-cert')
},
clientOpts: {
port: undefined,
rejectUnauthorized: true
},
errorCode: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
},
];
function runTest(tindex) {
const tcase = testCases[tindex];
if (!tcase) return;
const server = tls.createServer(tcase.serverOpts, (s) => {
s.resume();
}).listen(0, common.mustCall(function() {
tcase.clientOpts.port = this.address().port;
const client = tls.connect(tcase.clientOpts);
client.on('error', common.mustCall((e) => {
assert.strictEqual(e.code, tcase.errorCode);
server.close(common.mustCall(() => {
runTest(tindex + 1);
}));
}));
}));
}
runTest(0);

View File

@@ -0,0 +1,85 @@
'use strict';
const common = require('../common');
// This test verifies that `tls.connect()` honors the `allowHalfOpen` option.
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
{
const socket = tls.connect({ port: 42, lookup() {} });
console.log('Default allowHalfOpen:', socket.allowHalfOpen);
assert.strictEqual(socket.allowHalfOpen, false);
}
{
const socket = tls.connect({ port: 42, allowHalfOpen: false, lookup() {} });
console.log('Explicit allowHalfOpen=false:', socket.allowHalfOpen);
assert.strictEqual(socket.allowHalfOpen, false);
}
const server = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
}, common.mustCall((socket) => {
console.log('Server: New connection received');
server.close();
let message = '';
socket.setEncoding('utf8');
socket.on('data', (chunk) => {
console.log('Server received:', chunk);
message += chunk;
if (message === 'Hello') {
console.log('Server sending response');
socket.end(message);
message = '';
}
});
socket.on('end', common.mustCall(() => {
console.log('Server received end, final message:', message);
assert.strictEqual(message, 'Bye');
}));
}));
server.listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
const socket = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
allowHalfOpen: true,
}, common.mustCall(() => {
console.log('Client connected');
let message = '';
socket.on('data', (chunk) => {
console.log('Client received:', chunk);
message += chunk;
});
socket.on('end', common.mustCall(() => {
console.log('Client received end, message:', message);
assert.strictEqual(message, 'Hello');
setTimeout(() => {
console.log('Client writing final message');
assert(socket.writable);
assert(socket.write('Bye'));
// socket.end();
}, 50);
}));
console.log('Client writing initial message');
socket.write('Hello');
}));
socket.setEncoding('utf8');
}));

View File

@@ -0,0 +1,103 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const tls = require('tls');
const net = require('net');
console.log('Loading certificates...');
const options = {
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt')
};
console.log('Certificates loaded successfully');
const server = tls.createServer(options, common.mustCall((socket) => {
console.log('Server received connection');
socket.end('Hello');
}, 2)).listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
let waiting = 2;
function establish(socket, calls) {
console.log('Establishing TLS connection with socket');
const client = tls.connect({
rejectUnauthorized: false,
socket: socket
}, common.mustCall(() => {
console.log('TLS connection established');
let data = '';
client.on('data', common.mustCall((chunk) => {
console.log('Client received data chunk');
data += chunk.toString();
}));
client.on('end', common.mustCall(() => {
console.log('Client connection ended');
assert.strictEqual(data, 'Hello');
if (--waiting === 0) {
console.log('All connections completed, closing server');
server.close();
}
}));
}, calls));
assert(client.readable);
assert(client.writable);
console.log('Client socket is readable and writable');
return client;
}
const { port } = server.address();
// Immediate death socket
console.log('Creating immediate death socket');
const immediateDeath = net.connect(port);
establish(immediateDeath, 0).destroy();
console.log('Immediate death socket destroyed');
// Outliving
console.log('Creating outliving TCP connection');
const outlivingTCP = net.connect(port, common.mustCall(() => {
console.log('Outliving TCP connected, destroying TLS');
outlivingTLS.destroy();
next();
}));
const outlivingTLS = establish(outlivingTCP, 0);
function next() {
console.log('Starting next connection phase');
// Already connected socket
const connected = net.connect(port, common.mustCall(() => {
console.log('Connected socket ready, establishing TLS');
establish(connected);
}));
// Connecting socket
console.log('Creating connecting socket');
const connecting = net.connect(port);
establish(connecting);
}
}));

View File

@@ -0,0 +1,66 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
// Flags: --expose-gc
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { onGC } = require('../common/gc');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
// Test that the implicit listener for an 'connect' event on tls.Sockets is
// added using `once()`, i.e. can be gc'ed once that event has occurred.
const server = tls.createServer({
cert: fixtures.readKey('rsa_cert.crt'),
key: fixtures.readKey('rsa_private.pem')
}).listen(0);
let collected = false;
const gcListener = { ongc() { collected = true; } };
{
const gcObject = {};
onGC(gcObject, gcListener);
const sock = tls.connect(
server.address().port,
{ rejectUnauthorized: false },
common.mustCall(() => {
assert.strictEqual(gcObject, gcObject); // Keep reference alive
assert.strictEqual(collected, false);
setImmediate(done, sock);
}));
}
function done(sock) {
globalThis.gc();
setImmediate(() => {
assert.strictEqual(collected, true);
sock.end();
server.close();
});
}

View File

@@ -0,0 +1,65 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const stream = require('stream');
const net = require('net');
const fixtures = require('../common/fixtures');
const options = { key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'),
ca: [ fixtures.readKey('rsa_ca.crt') ],
ciphers: 'AES256-GCM-SHA384' };
const content = 'hello world';
const recv_bufs = [];
let send_data = '';
const server = tls.createServer(options, function(s) {
s.on('data', function(c) {
recv_bufs.push(c);
});
});
server.listen(0, function() {
const raw = net.connect(this.address().port);
let pending = false;
raw.on('readable', function() {
if (pending)
p._read();
});
const p = new stream.Duplex({
read: function read() {
pending = false;
const chunk = raw.read();
if (chunk) {
this.push(chunk);
} else {
pending = true;
}
},
write: function write(data, enc, cb) {
raw.write(data, enc, cb);
}
});
const socket = tls.connect({
socket: p,
rejectUnauthorized: false
}, function() {
for (let i = 0; i < 50; ++i) {
socket.write(content);
send_data += content;
}
socket.end();
server.close();
});
});
process.on('exit', function() {
const recv_data = (Buffer.concat(recv_bufs)).toString();
assert.strictEqual(send_data, recv_data);
});

View File

@@ -0,0 +1,23 @@
'use strict';
const common = require('../common');
// This test verifies that `tls.connect()` honors the `timeout` option when the
// socket is internally created.
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
console.log('Creating TLS socket with timeout option...');
const socket = tls.connect({
port: 42,
lookup: () => {},
timeout: 1000
});
console.log('Socket timeout value:', socket.timeout);
assert.strictEqual(socket.timeout, 1000);
console.log('Timeout assertion passed');

View File

@@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
const net = require('net');
const fixtures = require('../common/fixtures');
const bonkers = Buffer.alloc(1024, 42);
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem')
};
const server = net.createServer(common.mustCall(function(c) {
setTimeout(common.mustCall(function() {
const s = new tls.TLSSocket(c, {
isServer: true,
secureContext: tls.createSecureContext(options)
});
s.on('_tlsError', common.mustCall());
s.on('close', function() {
server.close();
s.destroy();
});
}), 200);
})).listen(0, function() {
const c = net.connect({ port: this.address().port }, function() {
c.write(bonkers);
});
});

View File

@@ -0,0 +1,13 @@
'use strict';
// test-tls-destroy-stream specifically for TLS1.2.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-destroy-stream.js');

View File

@@ -0,0 +1,104 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const { duplexPair } = require('stream');
const net = require('net');
const assert = require('assert');
const tls = require('tls');
console.log('Starting TLS destroy stream test...');
tls.DEFAULT_MAX_VERSION = 'TLSv1.3';
// This test ensures that an instance of StreamWrap should emit "end" and
// "close" when the socket on the other side call `destroy()` instead of
// `end()`.
// Refs: https://github.com/nodejs/node/issues/14605
const CONTENT = 'Hello World';
const tlsServer = tls.createServer(
{
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt'),
ca: [fixtures.readKey('rsa_ca.crt')],
},
(socket) => {
console.log('TLS server received connection');
socket.on('close', common.mustCall(() => {
console.log('TLS server socket closed');
}));
console.log('Writing content to socket:', CONTENT);
socket.write(CONTENT);
console.log('Destroying socket');
socket.destroy();
socket.on('error', (err) => {
console.log('Socket error:', err.code);
// destroy() is sync, write() is async, whether write completes depends
// on the protocol, it is not guaranteed by stream API.
if (err.code === 'ERR_STREAM_DESTROYED')
return;
assert.ifError(err);
});
},
);
tlsServer.on('connection', () => {
console.log('TLS server connection event');
})
const server = net.createServer((conn) => {
console.log('Net server received connection');
conn.on('error', common.mustNotCall());
// Assume that we want to use data to determine what to do with connections.
conn.once('data', common.mustCall((chunk) => {
console.log('Received initial data chunk');
const [ clientSide, serverSide ] = duplexPair();
serverSide.on('close', common.mustCall(() => {
console.log('Server side closed, destroying connection');
conn.destroy();
}));
clientSide.pipe(conn);
conn.pipe(clientSide);
conn.on('close', common.mustCall(() => {
console.log('Connection closed, destroying client side');
clientSide.destroy();
}));
clientSide.on('close', common.mustCall(() => {
console.log('Client side closed, destroying connection');
conn.destroy();
}));
process.nextTick(() => {
console.log('Unshifting chunk');
conn.unshift(chunk);
});
console.log('Emitting connection to TLS server');
tlsServer.emit('connection', serverSide);
}));
});
server.on('connection', () => {
console.log('Net server connection event');
})
server.listen(0, () => {
const port = server.address().port;
console.log('Server listening on port:', port);
const conn = tls.connect({ port, rejectUnauthorized: false }, () => {
console.log('TLS client connected');
// Whether the server's write() completed before its destroy() is
// indeterminate, but if data was written, we should receive it correctly.
conn.on('data', (data) => {
console.log('Client received data:', data.toString('utf8'));
assert.strictEqual(data.toString('utf8'), CONTENT);
});
conn.on('error', common.mustNotCall());
conn.on('close', common.mustCall(() => {
console.log('Client closed, closing server');
server.close();
}));
});
});

View File

@@ -0,0 +1,108 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
// Tests that calling disableRenegotiation on a TLSSocket stops renegotiation.
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
// Renegotiation as a protocol feature was dropped after TLS1.2.
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
};
const server = tls.Server(options, common.mustCall((socket) => {
console.log('Server: New connection received');
socket.on('error', common.mustCall((err) => {
console.log('Server: Error received:', err.message);
common.expectsError({
name: 'Error',
code: 'ERR_TLS_RENEGOTIATION_DISABLED',
message: 'TLS session renegotiation disabled for this socket'
})(err);
socket.destroy();
server.close();
}));
// Disable renegotiation after the first chunk of data received.
// Demonstrates that renegotiation works successfully up until
// disableRenegotiation is called.
socket.on('data', common.mustCall((chunk) => {
console.log('Server: Data received:', chunk.toString());
socket.write(chunk);
console.log('Server: Disabling renegotiation');
socket.disableRenegotiation();
}));
socket.on('secure', common.mustCall(() => {
console.log('Server: Secure event, handshakes:', socket._handle.handshakes);
assert(socket._handle.handshakes < 2,
`Too many handshakes [${socket._handle.handshakes}]`);
}));
}));
server.listen(0, common.mustCall(() => {
const port = server.address().port;
console.log('Server: Listening on port', port);
const options = {
rejectUnauthorized: false,
port
};
const client = tls.connect(options, common.mustCall(() => {
console.log('Client: Connected');
assert.throws(() => client.renegotiate(), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(() => client.renegotiate(common.mustNotCall()), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(() => client.renegotiate({}, false), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(() => client.renegotiate({}, null), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
// Negotiation is still permitted for this first
// attempt. This should succeed.
let ok = client.renegotiate(options, common.mustSucceed(() => {
console.log('Client: First renegotiation successful');
// Once renegotiation completes, we write some
// data to the socket, which triggers the on
// data event on the server. After that data
// is received, disableRenegotiation is called.
client.write('data', common.mustCall(() => {
console.log('Client: Data written');
// This second renegotiation attempt should fail
// and the callback should never be invoked. The
// server will simply drop the connection after
// emitting the error.
ok = client.renegotiate(options, common.mustNotCall());
console.log('Client: Second renegotiation attempt, ok:', ok);
assert.strictEqual(ok, true);
}));
}));
assert.strictEqual(ok, true);
client.on('secureConnect', common.mustCall(() => {
console.log('Client: secureConnect event');
}));
client.on('secure', common.mustCall(() => {
console.log('Client: secure event');
}));
}));
}));

View File

@@ -0,0 +1,33 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const options = {
SNICallback: (name, callback) => {
callback(null, tls.createSecureContext());
}
};
const server = tls.createServer(options, (c) => {
assert.fail('Should not be called');
}).on('tlsClientError', common.mustCall((err, c) => {
assert.match(err.message, /no suitable signature algorithm/i);
server.close();
})).listen(0, common.mustCall(() => {
const c = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
servername: 'any.name'
}, common.mustNotCall());
c.on('error', common.mustCall((err) => {
const expectedErr = common.hasOpenSSL(3, 2) ?
'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE';
assert.strictEqual(err.code, expectedErr);
}));
}));

View File

@@ -0,0 +1,61 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const fixtures = require('../common/fixtures');
// Test --tls-keylog CLI flag.
const assert = require('assert');
const fs = require('fs');
const { fork } = require('child_process');
if (process.argv[2] === 'test')
return test();
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const file = tmpdir.resolve('keylog.log');
const child = fork(__filename, ['test'], {
execArgv: ['--tls-keylog=' + file]
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
const log = fs.readFileSync(file, 'utf8').trim().split('\n');
// Both client and server should log their secrets,
// so we should have two identical lines in the log
assert.strictEqual(log.length, 2);
assert.strictEqual(log[0], log[1]);
}));
function test() {
const {
connect, keys
} = require(fixtures.path('tls-connect'));
connect({
client: {
checkServerIdentity: (servername, cert) => { },
ca: `${keys.agent1.cert}\n${keys.agent6.ca}`,
},
server: {
cert: keys.agent6.cert,
key: keys.agent6.key,
// Number of keylog events is dependent on protocol version
maxVersion: 'TLSv1.2',
},
}, common.mustCall((err, pair, cleanup) => {
if (pair.server.err) {
console.trace('server', pair.server.err);
}
if (pair.client.err) {
console.trace('client', pair.client.err);
}
assert.ifError(pair.server.err);
assert.ifError(pair.client.err);
return cleanup();
}));
}

View File

@@ -0,0 +1,46 @@
// Setting NODE_EXTRA_CA_CERTS to non-existent file emits a warning
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const fork = require('child_process').fork;
const tls = require('tls');
if (process.env.CHILD) {
// This will try to load the extra CA certs, and emit a warning when it fails.
return tls.createServer({});
}
const env = {
...process.env,
CHILD: 'yes',
NODE_EXTRA_CA_CERTS: `${fixtures.fixturesDir}/no-such-file-exists-🐢`,
};
const opts = {
env: env,
silent: true,
};
let stderr = '';
fork(__filename, opts)
.on('exit', common.mustCall(function(status) {
// Check that client succeeded in connecting.
assert.strictEqual(status, 0);
}))
.on('close', common.mustCall(function() {
// TODO(addaleax): Make `SafeGetenv` work like `process.env`
// encoding-wise
if (!common.isWindows) {
const re = /Warning: Ignoring extra certs from.*no-such-file-exists-🐢.* load failed:.*No such file or directory/;
assert.match(stderr, re);
}
}))
.stderr.setEncoding('utf8').on('data', function(str) {
stderr += str;
});

View File

@@ -0,0 +1,82 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('node:assert');
const tls = require('node:tls');
const { fork } = require('node:child_process');
const fixtures = require('../common/fixtures');
const tests = [
{
get clientOptions() {
const secureContext = tls.createSecureContext();
secureContext.context.addCACert(
fixtures.readKey('ca1-cert.pem')
);
return {
secureContext
};
}
},
{
clientOptions: {
crl: fixtures.readKey('ca2-crl.pem')
}
},
{
clientOptions: {
pfx: fixtures.readKey('agent1.pfx'),
passphrase: 'sample'
}
},
];
if (process.argv[2]) {
const testNumber = parseInt(process.argv[2], 10);
assert(testNumber >= 0 && testNumber < tests.length);
const test = tests[testNumber];
const clientOptions = {
...test.clientOptions,
port: process.argv[3],
checkServerIdentity: common.mustCall()
};
const client = tls.connect(clientOptions, common.mustCall(() => {
client.end('hi');
}));
} else {
const serverOptions = {
key: fixtures.readKey('agent3-key.pem'),
cert: fixtures.readKey('agent3-cert.pem')
};
for (const testNumber in tests) {
const server = tls.createServer(serverOptions, common.mustCall((socket) => {
socket.end('bye');
server.close();
}));
server.listen(0, common.mustCall(() => {
const env = {
...process.env,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
};
const args = [
testNumber,
server.address().port,
];
fork(__filename, args, { env }).on('exit', common.mustCall((status) => {
assert.strictEqual(status, 0);
}));
}));
}
}

View File

@@ -0,0 +1,53 @@
// Certs in NODE_EXTRA_CA_CERTS are used for TLS peer validation
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const { fork } = require('child_process');
if (process.env.CHILD) {
console.log('Child process started');
const copts = {
port: process.env.PORT,
checkServerIdentity: common.mustCall(),
};
console.log('Client options:', copts);
const client = tls.connect(copts, common.mustCall(function() {
console.log('Client connected successfully');
client.end('hi');
}));
return;
}
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
};
console.log('Server options:', options);
const server = tls.createServer(options, common.mustCall(function(s) {
console.log('Server received connection');
s.end('bye');
server.close();
})).listen(0, common.mustCall(function() {
console.log('Server listening on port:', this.address().port);
const env = {
...process.env,
CHILD: 'yes',
PORT: this.address().port,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem')
};
fork(__filename, { env }).on('exit', common.mustCall(function(status) {
console.log('Child process exited with status:', status);
// Client did not succeed in connecting
assert.strictEqual(status, 0);
}));
}));

View File

@@ -0,0 +1,48 @@
'use strict';
// This tests the errors thrown from TLSSocket.prototype.setServername
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { connect, TLSSocket } = require('tls');
const { duplexPair } = require('stream');
const [ clientSide, serverSide ] = duplexPair();
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const ca = fixtures.readKey('ca1-cert.pem');
const client = connect({
socket: clientSide,
ca,
host: 'agent1' // Hostname from certificate
});
[undefined, null, 1, true, {}].forEach((value) => {
assert.throws(() => {
client.setServername(value);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "name" argument must be of type string.' +
common.invalidArgTypeHelper(value)
});
});
const server = new TLSSocket(serverSide, {
isServer: true,
key,
cert,
ca
});
assert.throws(() => {
server.setServername('localhost');
}, {
code: 'ERR_TLS_SNI_FROM_SERVER',
message: 'Cannot issue SNI from a TLS server-side socket'
});

View File

@@ -0,0 +1,122 @@
'use strict';
// Test return value of tlsSocket.exportKeyingMaterial
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const net = require('net');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
console.log('Creating server...');
const server = net.createServer(common.mustCall((s) => {
console.log('Server received connection');
const tlsSocket = new tls.TLSSocket(s, {
isServer: true,
server: server,
secureContext: tls.createSecureContext({ key, cert })
});
console.log('Testing exportKeyingMaterial before secure connection...');
assert.throws(() => {
tlsSocket.exportKeyingMaterial(128, 'label');
}, {
name: 'Error',
message: 'TLS socket connection must be securely established',
code: 'ERR_TLS_INVALID_STATE'
});
console.log('Assert complete.');
tlsSocket.on('secure', common.mustCall(() => {
console.log('TLS connection secured');
const label = 'client finished';
console.log('Testing valid keying material export...');
const validKeyingMaterial = tlsSocket.exportKeyingMaterial(128, label);
assert.strictEqual(validKeyingMaterial.length, 128);
console.log('Valid keying material length:', validKeyingMaterial.length);
console.log('Testing keying material with context...');
const validKeyingMaterialWithContext = tlsSocket
.exportKeyingMaterial(128, label, Buffer.from([0, 1, 2, 3]));
assert.strictEqual(validKeyingMaterialWithContext.length, 128);
console.log('Keying material with context length:', validKeyingMaterialWithContext.length);
// Ensure providing a context results in a different key than without
assert.notStrictEqual(validKeyingMaterial, validKeyingMaterialWithContext);
console.log('Verified different keys for with/without context');
console.log('Testing keying material with empty context...');
const validKeyingMaterialWithEmptyContext = tlsSocket
.exportKeyingMaterial(128, label, Buffer.from([]));
assert.strictEqual(validKeyingMaterialWithEmptyContext.length, 128);
console.log('Empty context keying material length:', validKeyingMaterialWithEmptyContext.length);
console.log('Testing invalid argument types...');
assert.throws(() => {
tlsSocket.exportKeyingMaterial(128, label, 'stringAsContextNotSupported');
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
tlsSocket.exportKeyingMaterial(128, label, 1234);
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
tlsSocket.exportKeyingMaterial(10, null);
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
});
assert.throws(() => {
tlsSocket.exportKeyingMaterial('length', 1234);
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
});
console.log('Testing invalid range values...');
assert.throws(() => {
tlsSocket.exportKeyingMaterial(-3, 'a');
}, {
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE'
});
assert.throws(() => {
tlsSocket.exportKeyingMaterial(0, 'a');
}, {
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE'
});
console.log('Closing TLS socket and server...');
tlsSocket.end();
server.close();
}));
})).listen(0, () => {
console.log('Server listening on port:', server.address().port);
const opts = {
port: server.address().port,
rejectUnauthorized: false
};
console.log('Connecting client...');
tls.connect(opts, common.mustCall(function() {
console.log('Client connected');
this.end();
}));
});

View File

@@ -0,0 +1,16 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
// Ensure accessing ._external doesn't hit an assert in the accessor method.
{
const pctx = tls.createSecureContext().context;
const cctx = { __proto__: pctx };
assert.throws(() => cctx._external, TypeError);
pctx._external; // eslint-disable-line no-unused-expressions
}

View File

@@ -0,0 +1,66 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
// This test ensures that tlsSocket.getFinished() and
// tlsSocket.getPeerFinished() return undefined before
// secure connection is established, and return non-empty
// Buffer objects with Finished messages afterwards, also
// verifying alice.getFinished() == bob.getPeerFinished()
// and alice.getPeerFinished() == bob.getFinished().
const assert = require('assert');
const tls = require('tls');
const msg = {};
const pem = (n) => fixtures.readKey(`${n}.pem`);
const server = tls.createServer({
key: pem('agent1-key'),
cert: pem('agent1-cert')
}, common.mustCall((alice) => {
msg.server = {
alice: alice.getFinished(),
bob: alice.getPeerFinished()
};
server.close();
}));
server.listen(0, common.mustCall(() => {
const bob = tls.connect({
port: server.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
msg.client = {
alice: bob.getPeerFinished(),
bob: bob.getFinished()
};
bob.end();
}));
msg.before = {
alice: bob.getPeerFinished(),
bob: bob.getFinished()
};
}));
process.on('exit', () => {
assert.strictEqual(undefined, msg.before.alice);
assert.strictEqual(undefined, msg.before.bob);
assert(Buffer.isBuffer(msg.server.alice));
assert(Buffer.isBuffer(msg.server.bob));
assert(Buffer.isBuffer(msg.client.alice));
assert(Buffer.isBuffer(msg.client.bob));
assert(msg.server.alice.length > 0);
assert(msg.server.bob.length > 0);
assert(msg.client.alice.length > 0);
assert(msg.client.bob.length > 0);
assert(msg.server.alice.equals(msg.client.alice));
assert(msg.server.bob.equals(msg.client.bob));
});

View File

@@ -0,0 +1,38 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const { duplexPair } = require('stream');
const assert = require('assert');
const { TLSSocket, connect } = require('tls');
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const ca = fixtures.readKey('ca1-cert.pem');
const [ clientSide, serverSide ] = duplexPair();
const clientTLS = connect({
socket: clientSide,
ca,
host: 'agent1' // Hostname from certificate
});
const serverTLS = new TLSSocket(serverSide, {
isServer: true,
key,
cert,
ca
});
assert.strictEqual(clientTLS.connecting, false);
assert.strictEqual(serverTLS.connecting, false);
clientTLS.on('secureConnect', common.mustCall(() => {
clientTLS.write('foobar', common.mustCall(() => {
assert.strictEqual(serverTLS.read().toString(), 'foobar');
assert.strictEqual(clientTLS._handle.writeQueueSize, 0);
}));
assert.ok(clientTLS._handle.writeQueueSize > 0);
}));

View File

@@ -0,0 +1,17 @@
'use strict';
// Flags: --no-use-openssl-ca
// This tests that tls.getCACertificates() returns the bundled
// certificates correctly.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const defaultSet = new Set(tls.getCACertificates('default'));
const bundledSet = new Set(tls.getCACertificates('bundled'));
// When --use-openssl-ca is false (i.e. bundled CA is sued),
// default is a superset of bundled certificates.
assert.deepStrictEqual(defaultSet.intersection(bundledSet), bundledSet);

View File

@@ -0,0 +1,20 @@
'use strict';
// This tests that tls.getCACertificates() returns the bundled
// certificates correctly.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const { assertIsCAArray } = require('../common/tls');
const certs = tls.getCACertificates('bundled');
assertIsCAArray(certs);
// It's the same as tls.rootCertificates - both are
// Mozilla CA stores across platform.
assert.strictEqual(certs, tls.rootCertificates);
// It's cached on subsequent accesses.
assert.strictEqual(certs, tls.getCACertificates('bundled'));

View File

@@ -0,0 +1,20 @@
'use strict';
// This tests that tls.getCACertificates() returns the default
// certificates correctly.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const { assertIsCAArray } = require('../common/tls');
const certs = tls.getCACertificates();
assertIsCAArray(certs);
const certs2 = tls.getCACertificates('default');
assert.strictEqual(certs, certs2);
// It's cached on subsequent accesses.
assert.strictEqual(certs, tls.getCACertificates('default'));

View File

@@ -0,0 +1,20 @@
'use strict';
// This tests that tls.getCACertificates() throws error when being
// passed an invalid argument.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
for (const invalid of [1, null, () => {}, true]) {
assert.throws(() => tls.getCACertificates(invalid), {
code: 'ERR_INVALID_ARG_TYPE'
});
}
assert.throws(() => tls.getCACertificates('test'), {
code: 'ERR_INVALID_ARG_VALUE'
});

View File

@@ -0,0 +1,29 @@
'use strict';
// This tests that tls.getCACertificates('extra') returns an empty
// array if NODE_EXTRA_CA_CERTS is empty.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const tmpdir = require('../common/tmpdir');
const fs = require('fs');
const assert = require('assert');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
const fixtures = require('../common/fixtures');
tmpdir.refresh();
const certsJSON = tmpdir.resolve('certs.json');
// If NODE_EXTRA_CA_CERTS is not set, it should be an empty array.
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-get-ca-certificates.js')], {
env: {
...process.env,
NODE_EXTRA_CA_CERTS: undefined,
CA_TYPE: 'extra',
CA_OUT: certsJSON,
}
});
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
assert.deepStrictEqual(parsed, []);

View File

@@ -0,0 +1,16 @@
'use strict';
// This tests that tls.getCACertificates('defulat') returns a superset
// of tls.getCACertificates('extra') when NODE_EXTRA_CA_CERTS is used.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
const fixtures = require('../common/fixtures');
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-check-extra-ca-certificates.js')], {
env: {
...process.env,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem'),
}
});

View File

@@ -0,0 +1,29 @@
'use strict';
// This tests that tls.getCACertificates('extra') returns the extra
// certificates from NODE_EXTRA_CA_CERTS correctly.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const tmpdir = require('../common/tmpdir');
const fs = require('fs');
const assert = require('assert');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
const fixtures = require('../common/fixtures');
tmpdir.refresh();
const certsJSON = tmpdir.resolve('certs.json');
// If NODE_EXTRA_CA_CERTS is set, it should contain a list of certificates.
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-get-ca-certificates.js')], {
env: {
...process.env,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem'),
CA_TYPE: 'extra',
CA_OUT: certsJSON,
}
});
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
assert.deepStrictEqual(parsed, [fixtures.readKey('ca1-cert.pem', 'utf8')]);

View File

@@ -0,0 +1,36 @@
'use strict';
// This tests that tls.getCACertificates() returns the system
// certificates correctly when --use-system-ca is disabled.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const tmpdir = require('../common/tmpdir');
const fs = require('fs');
const assert = require('assert');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const certs = tls.getCACertificates('system');
if (certs.length === 0) {
common.skip('No trusted system certificates installed. Skip.');
}
tmpdir.refresh();
const certsJSON = tmpdir.resolve('certs.json');
spawnSyncAndExitWithoutError(process.execPath, [
'--no-use-system-ca',
fixtures.path('tls-get-ca-certificates.js'),
], {
env: {
...process.env,
CA_TYPE: 'system',
CA_OUT: certsJSON,
}
});
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
assert.deepStrictEqual(parsed, certs);

View File

@@ -0,0 +1,32 @@
'use strict';
// Flags: --use-system-ca
// This tests that tls.getCACertificates() returns the system
// certificates correctly.
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const { assertIsCAArray } = require('../common/tls');
const systemCerts = tls.getCACertificates('system');
// Usually Windows come with some certificates installed by default.
// This can't be said about other systems, in that case check that
// at least systemCerts is an array (which may be empty).
if (common.isWindows) {
assertIsCAArray(systemCerts);
} else {
assert(Array.isArray(systemCerts));
}
// When --use-system-ca is true, default is a superset of system
// certificates.
const defaultCerts = tls.getCACertificates('default');
assert(defaultCerts.length >= systemCerts.length);
const defaultSet = new Set(defaultCerts);
const systemSet = new Set(systemCerts);
assert.deepStrictEqual(defaultSet.intersection(systemSet), systemSet);
// It's cached on subsequent accesses.
assert.strictEqual(systemCerts, tls.getCACertificates('system'));

View File

@@ -0,0 +1,53 @@
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
// Check cert chain is received by client, and is completed with the ca cert
// known to the client.
const {
assert, connect, debug, keys
} = require(fixtures.path('tls-connect'));
// agent6-cert.pem includes cert for agent6 and ca3, split it apart and
// provide ca3 in the .ca property.
const agent6Chain = keys.agent6.cert.split(/(?=-----BEGIN CERTIFICATE-----)/);
const agent6End = agent6Chain[0];
const agent6Middle = agent6Chain[1];
console.log('Agent6 chain split:', { agent6End, agent6Middle });
connect({
client: {
checkServerIdentity: (servername, cert) => {
console.log('checkServerIdentity', servername, cert);
},
ca: keys.agent6.ca,
},
server: {
cert: agent6End,
key: keys.agent6.key,
ca: agent6Middle,
},
}, function(err, pair, cleanup) {
console.log('Connection error:', err);
assert.ifError(err);
const peer = pair.client.conn.getPeerCertificate();
console.log('Peer certificate:', peer);
debug('peer:\n', peer);
assert.match(peer.serialNumber, /5B75D77EDC7FB5B7FA9F1424DA4C64FB815DCBDE/i);
const next = pair.client.conn.getPeerCertificate(true).issuerCertificate;
console.log('Next certificate:', next);
const root = next.issuerCertificate;
delete next.issuerCertificate;
debug('next:\n', next);
assert.match(next.serialNumber, /147D36C1C2F74206DE9FAB5F2226D78ADB00A425/i);
console.log('Root certificate:', root);
debug('root:\n', root);
assert.match(root.serialNumber, /4AB16C8DFD6A7D0D2DFCABDF9C4B0E92C6AD0229/i);
return cleanup();
});

View File

@@ -0,0 +1,39 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const { X509Certificate } = require('crypto');
console.log(fixtures.readKey('agent6-key.pem'));
const options = {
key: fixtures.readKey('agent6-key.pem'),
cert: fixtures.readKey('agent6-cert.pem')
};
const server = tls.createServer(options, function(cleartext) {
cleartext.end('World');
});
server.once('secureConnection', common.mustCall(function(socket) {
const cert = socket.getX509Certificate();
assert(cert instanceof X509Certificate);
assert.match(cert.serialNumber, /5B75D77EDC7FB5B7FA9F1424DA4C64FB815DCBDE/i);
}));
server.listen(0, common.mustCall(function() {
const socket = tls.connect({
port: this.address().port,
rejectUnauthorized: false
}, common.mustCall(function() {
const peerCert = socket.getPeerX509Certificate();
assert(peerCert.issuerCertificate instanceof X509Certificate);
assert.strictEqual(peerCert.issuerCertificate.issuerCertificate, undefined);
assert.match(peerCert.issuerCertificate.serialNumber, /147D36C1C2F74206DE9FAB5F2226D78ADB00A425/i);
server.close();
}));
socket.end('Hello');
}));

View File

@@ -0,0 +1,57 @@
'use strict';
// Verify that exceptions from a callback don't result in
// failed CHECKs when trying to print the exception message.
// This test is convoluted because it needs to trigger a callback
// into JS land at just the right time when an exception is pending,
// and does so by exploiting a weakness in the streams infrastructure.
// I won't shed any tears if this test ever becomes invalidated.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
if (process.argv[2] === 'child') {
const fixtures = require('../common/fixtures');
const https = require('https');
const net = require('net');
const tls = require('tls');
const { Duplex } = require('stream');
const { mustCall } = common;
const cert = fixtures.readKey('rsa_cert.crt');
const key = fixtures.readKey('rsa_private.pem');
net.createServer(mustCall(onplaintext)).listen(0, mustCall(onlisten));
function onlisten() {
const { port } = this.address();
https.get({ port, rejectUnauthorized: false });
}
function onplaintext(c) {
const d = new class extends Duplex {
_read(n) {
const data = c.read(n);
if (data) d.push(data);
}
_write(...xs) {
c.write(...xs);
}
}();
c.on('data', d.push.bind(d));
const options = { key, cert };
const fail = () => { throw new Error('eyecatcher'); };
tls.createServer(options, mustCall(fail)).emit('connection', d);
}
} else {
const assert = require('assert');
const { spawnSync } = require('child_process');
const result = spawnSync(process.execPath, [__filename, 'child']);
const stderr = result.stderr.toString();
const ok = stderr.includes('Error: eyecatcher');
assert(ok, stderr);
}

View File

@@ -0,0 +1,66 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
// This test ensures that the tls parser causes a client error if the client
// sends invalid data.
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const net = require('net');
const options = {
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt')
};
const bonkers = Buffer.alloc(1024 * 1024, 42);
const server = tls.createServer(options, function(c) {
}).listen(0, common.mustCall(function() {
const client = net.connect(this.address().port, common.mustCall(function() {
client.write(bonkers);
}));
const writeAgain = setImmediate(function() {
client.write(bonkers);
});
client.once('error', common.mustCall(function(err) {
clearImmediate(writeAgain);
client.destroy();
server.close();
}));
client.on('close', common.mustCall(function(hadError) {
// Confirm that client errored
assert.strictEqual(hadError, true);
}));
}));

View File

@@ -0,0 +1,23 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const {
assert, connect, keys
} = require(fixtures.path('tls-connect'));
const invalidPfx = fixtures.readKey('cert-without-key.pfx');
connect({
client: {
pfx: invalidPfx,
passphrase: 'test',
rejectUnauthorized: false
},
server: keys.agent1
}, common.mustCall((e, pair, cleanup) => {
assert.strictEqual(e.message, 'Unable to load private key from PFX data');
cleanup();
}));

View File

@@ -0,0 +1,41 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
// This test expects `tls.connect()` to emit a warning when
// `servername` of options is an IP address.
common.expectWarning(
'DeprecationWarning',
'Setting the TLS ServerName to an IP address is not permitted by ' +
'RFC 6066. This will be ignored in a future version.',
'DEP0123'
);
{
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem')
};
const server = tls.createServer(options, function(s) {
s.end('hello');
}).listen(0, function() {
const client = tls.connect({
port: this.address().port,
rejectUnauthorized: false,
servername: '127.0.0.1',
}, function() {
client.end();
});
});
server.on('connection', common.mustCall(function(socket) {
server.close();
}));
}

View File

@@ -0,0 +1,66 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const net = require('net');
const stream = require('stream');
const tls = require('tls');
const server = tls.createServer({
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem')
}, common.mustCall(function(c) {
console.log('new client');
c.resume();
c.end('ohai');
})).listen(0, common.mustCall(function() {
const raw = net.connect(this.address().port);
let pending = false;
raw.on('readable', function() {
if (pending)
p._read();
});
raw.on('end', function() {
p.push(null);
});
const p = new stream.Duplex({
read: function read() {
pending = false;
const chunk = raw.read();
if (chunk) {
console.log('read', chunk);
this.push(chunk);
} else {
pending = true;
}
},
write: function write(data, enc, cb) {
console.log('write', data, enc);
raw.write(data, enc, cb);
}
});
const socket = tls.connect({
socket: p,
rejectUnauthorized: false
}, common.mustCall(function() {
console.log('client secure');
socket.resume();
socket.end('hello');
}));
socket.once('close', function() {
console.log('client close');
server.close();
});
}));

View File

@@ -0,0 +1,41 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const https = require('https');
const net = require('net');
console.log('Starting TLS junk server test');
const server = net.createServer(function(s) {
console.log('Server received connection');
s.once('data', function() {
console.log('Server received data');
s.end('I was waiting for you, hello!', function() {
console.log('Server sent response');
s.destroy();
});
});
});
server.listen(0, function() {
console.log('Server listening on port:', this.address().port);
const req = https.request({ port: this.address().port });
req.end();
console.log('HTTPS request sent');
let expectedErrorMessage = new RegExp('wrong version number');
if (common.hasOpenSSL(3, 2)) {
console.log('Using OpenSSL 3.2+ error message pattern');
expectedErrorMessage = new RegExp('packet length too long');
}
req.once('error', common.mustCall(function(err) {
console.log('Received error:', err.message);
assert(expectedErrorMessage.test(err.message));
server.close();
console.log('Test completed');
}));
});

View File

@@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const server = tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
// Amount of keylog events depends on negotiated protocol
// version, so force a specific one:
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3',
}).listen(() => {
const client = tls.connect({
port: server.address().port,
rejectUnauthorized: false,
});
server.on('keylog', common.mustCall((line, tlsSocket) => {
assert(Buffer.isBuffer(line));
assert.strictEqual(tlsSocket.encrypted, true);
}, 5));
client.on('keylog', common.mustCall((line) => {
assert(Buffer.isBuffer(line));
}, 5));
client.once('secureConnect', () => {
server.close();
client.end();
});
});

View File

@@ -0,0 +1,15 @@
// Flags: --no-warnings
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
common.expectWarning(
'DeprecationWarning',
'tls.createSecurePair() is deprecated. Please use tls.TLSSocket instead.',
'DEP0064'
);
tls.createSecurePair();

View File

@@ -0,0 +1,33 @@
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
['foobar', 1, {}, []].forEach(function connectThrows(input) {
const opts = {
host: 'localhost',
port: common.PORT,
lookup: input,
};
assert.throws(() => {
tls.connect(opts);
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
});
connectDoesNotThrow(common.mustCall());
function connectDoesNotThrow(input) {
const opts = {
host: 'localhost',
port: common.PORT,
lookup: input,
};
tls.connect(opts);
}

View File

@@ -0,0 +1,54 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const options = {
pfx: [
{
buf: fixtures.readKey('agent1.pfx'),
passphrase: 'sample'
},
fixtures.readKey('ec.pfx'),
]
};
const ciphers = [];
const server = tls.createServer(options, function(conn) {
conn.end('ok');
}).listen(0, function() {
const ecdsa = tls.connect(this.address().port, {
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
rejectUnauthorized: false,
}, common.mustCall(function() {
ciphers.push(ecdsa.getCipher());
const rsa = tls.connect(server.address().port, {
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384',
maxVersion: 'TLSv1.2',
rejectUnauthorized: false,
}, common.mustCall(function() {
ciphers.push(rsa.getCipher());
ecdsa.end();
rsa.end();
server.close();
}));
}));
});
process.on('exit', function() {
assert.deepStrictEqual(ciphers, [{
name: 'ECDHE-ECDSA-AES256-GCM-SHA384',
standardName: 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
version: 'TLSv1.2'
}, {
name: 'ECDHE-RSA-AES256-GCM-SHA384',
standardName: 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
version: 'TLSv1.2'
}]);
});

View File

@@ -0,0 +1,13 @@
'use strict';
// test-tls-net-socket-keepalive specifically for TLS1.2.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-net-socket-keepalive.js');

View File

@@ -0,0 +1,57 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const net = require('net');
// This test ensures that when tls sockets are created with `allowHalfOpen`,
// they won't hang.
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const ca = fixtures.readKey('ca1-cert.pem');
const options = {
key,
cert,
ca: [ca],
};
const server = tls.createServer(options, common.mustCall((conn) => {
conn.write('hello', common.mustCall());
conn.on('data', common.mustCall());
conn.on('end', common.mustCall());
conn.on('data', common.mustCall());
conn.on('close', common.mustCall());
conn.end();
})).listen(0, common.mustCall(() => {
const netSocket = new net.Socket({
allowHalfOpen: true,
});
const socket = tls.connect({
socket: netSocket,
rejectUnauthorized: false,
});
const { port, address } = server.address();
// Doing `net.Socket.connect()` after `tls.connect()` will make tls module
// wrap the socket in StreamWrap.
netSocket.connect({
port,
address,
});
socket.on('secureConnect', common.mustCall());
socket.on('end', common.mustCall());
socket.on('data', common.mustCall());
socket.on('close', common.mustCall(() => {
server.close();
}));
socket.write('hello');
socket.end();
}));

View File

@@ -0,0 +1,66 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
console.log('Testing invalid protocol method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'blargh' });
}, {
code: 'ERR_TLS_INVALID_PROTOCOL_METHOD',
message: 'Unknown method: blargh',
});
const errMessageSSLv2 = /SSLv2 methods disabled/;
console.log('Testing SSLv2 method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'SSLv2_method' });
}, errMessageSSLv2);
console.log('Testing SSLv2 client method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'SSLv2_client_method' });
}, errMessageSSLv2);
console.log('Testing SSLv2 server method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'SSLv2_server_method' });
}, errMessageSSLv2);
const errMessageSSLv3 = /SSLv3 methods disabled/;
console.log('Testing SSLv3 method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'SSLv3_method' });
}, errMessageSSLv3);
console.log('Testing SSLv3 client method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'SSLv3_client_method' });
}, errMessageSSLv3);
console.log('Testing SSLv3 server method');
assert.throws(function() {
tls.createSecureContext({ secureProtocol: 'SSLv3_server_method' });
}, errMessageSSLv3);
// Note that SSLv2 and SSLv3 are disallowed but SSLv2_method and friends are
// still accepted. They are OpenSSL's way of saying that all known protocols
// are supported unless explicitly disabled (which we do for SSLv2 and SSLv3.)
console.log('Testing allowed protocol methods');
tls.createSecureContext({ secureProtocol: 'SSLv23_method' });
tls.createSecureContext({ secureProtocol: 'SSLv23_client_method' });
tls.createSecureContext({ secureProtocol: 'SSLv23_server_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_client_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_server_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_1_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_1_client_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_1_server_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_2_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_2_client_method' });
tls.createSecureContext({ secureProtocol: 'TLSv1_2_server_method' });

View File

@@ -0,0 +1,206 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const options = {
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem')
};
const smallMessage = Buffer.from('hello world');
// Used to test .pause(), so needs to be larger than the internal buffer
const largeMessage = Buffer.alloc(64 * 1024).fill('hello world');
// Test typical usage
tls.createServer(options, common.mustCall(function(socket) {
this.close();
socket.end(smallMessage);
})).listen(0, function() {
let received = 0;
const buffers = [];
const sockBuf = Buffer.alloc(8);
tls.connect({
port: this.address().port,
rejectUnauthorized: false,
onread: {
buffer: sockBuf,
callback: function(nread, buf) {
assert.strictEqual(buf, sockBuf);
received += nread;
buffers.push(Buffer.from(buf.slice(0, nread)));
}
}
}).on('data', common.mustNotCall()).on('end', common.mustCall(() => {
assert.strictEqual(received, smallMessage.length);
assert.deepStrictEqual(Buffer.concat(buffers), smallMessage);
}));
});
// Test Uint8Array support
tls.createServer(options, common.mustCall(function(socket) {
this.close();
socket.end(smallMessage);
})).listen(0, function() {
let received = 0;
let incoming = new Uint8Array(0);
const sockBuf = new Uint8Array(8);
tls.connect({
port: this.address().port,
rejectUnauthorized: false,
onread: {
buffer: sockBuf,
callback: function(nread, buf) {
assert.strictEqual(buf, sockBuf);
received += nread;
const newIncoming = new Uint8Array(incoming.length + nread);
newIncoming.set(incoming);
newIncoming.set(buf.slice(0, nread), incoming.length);
incoming = newIncoming;
}
}
}).on('data', common.mustNotCall()).on('end', common.mustCall(() => {
assert.strictEqual(received, smallMessage.length);
assert.deepStrictEqual(incoming, new Uint8Array(smallMessage));
}));
});
// Test Buffer callback usage
tls.createServer(options, common.mustCall(function(socket) {
this.close();
socket.end(smallMessage);
})).listen(0, function() {
let received = 0;
const incoming = [];
const bufPool = [ Buffer.alloc(2), Buffer.alloc(2), Buffer.alloc(2) ];
let bufPoolIdx = -1;
let bufPoolUsage = 0;
tls.connect({
port: this.address().port,
rejectUnauthorized: false,
onread: {
buffer: () => {
++bufPoolUsage;
bufPoolIdx = (bufPoolIdx + 1) % bufPool.length;
return bufPool[bufPoolIdx];
},
callback: function(nread, buf) {
assert.strictEqual(buf, bufPool[bufPoolIdx]);
received += nread;
incoming.push(Buffer.from(buf.slice(0, nread)));
}
}
}).on('data', common.mustNotCall()).on('end', common.mustCall(() => {
assert.strictEqual(received, smallMessage.length);
assert.deepStrictEqual(Buffer.concat(incoming), smallMessage);
assert.strictEqual(bufPoolUsage, 7);
}));
});
// Test Uint8Array callback support
tls.createServer(options, common.mustCall(function(socket) {
this.close();
socket.end(smallMessage);
})).listen(0, function() {
let received = 0;
let incoming = new Uint8Array(0);
const bufPool = [ new Uint8Array(2), new Uint8Array(2), new Uint8Array(2) ];
let bufPoolIdx = -1;
let bufPoolUsage = 0;
tls.connect({
port: this.address().port,
rejectUnauthorized: false,
onread: {
buffer: () => {
++bufPoolUsage;
bufPoolIdx = (bufPoolIdx + 1) % bufPool.length;
return bufPool[bufPoolIdx];
},
callback: function(nread, buf) {
assert.strictEqual(buf, bufPool[bufPoolIdx]);
received += nread;
const newIncoming = new Uint8Array(incoming.length + nread);
newIncoming.set(incoming);
newIncoming.set(buf.slice(0, nread), incoming.length);
incoming = newIncoming;
}
}
}).on('data', common.mustNotCall()).on('end', common.mustCall(() => {
assert.strictEqual(received, smallMessage.length);
assert.deepStrictEqual(incoming, new Uint8Array(smallMessage));
assert.strictEqual(bufPoolUsage, 7);
}));
});
// Test explicit socket pause
tls.createServer(options, common.mustCall(function(socket) {
this.close();
// Need larger message here to observe the pause
socket.end(largeMessage);
})).listen(0, function() {
let received = 0;
const buffers = [];
const sockBuf = Buffer.alloc(64);
let pauseScheduled = false;
const client = tls.connect({
port: this.address().port,
rejectUnauthorized: false,
onread: {
buffer: sockBuf,
callback: function(nread, buf) {
assert.strictEqual(buf, sockBuf);
received += nread;
buffers.push(Buffer.from(buf.slice(0, nread)));
if (!pauseScheduled) {
pauseScheduled = true;
client.pause();
setTimeout(() => {
client.resume();
}, 100);
}
}
}
}).on('data', common.mustNotCall()).on('end', common.mustCall(() => {
assert.strictEqual(received, largeMessage.length);
assert.deepStrictEqual(Buffer.concat(buffers), largeMessage);
}));
});
// Test implicit socket pause
tls.createServer(options, common.mustCall(function(socket) {
this.close();
// Need larger message here to observe the pause
socket.end(largeMessage);
})).listen(0, function() {
let received = 0;
const buffers = [];
const sockBuf = Buffer.alloc(64);
let pauseScheduled = false;
const client = tls.connect({
port: this.address().port,
rejectUnauthorized: false,
onread: {
buffer: sockBuf,
callback: function(nread, buf) {
assert.strictEqual(buf, sockBuf);
received += nread;
buffers.push(Buffer.from(buf.slice(0, nread)));
if (!pauseScheduled) {
pauseScheduled = true;
setTimeout(() => {
client.resume();
}, 100);
return false;
}
return true;
}
}
}).on('data', common.mustNotCall()).on('end', common.mustCall(() => {
assert.strictEqual(received, largeMessage.length);
assert.deepStrictEqual(Buffer.concat(buffers), largeMessage);
}));
});

View File

@@ -0,0 +1,176 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
// This test ensures that the data received through tls over http tunnel
// is same as what is sent.
const assert = require('assert');
const https = require('https');
const net = require('net');
const http = require('http');
const fixtures = require('../common/fixtures');
let gotRequest = false;
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const options = { key, cert };
const server = https.createServer(options, common.mustCall((req, res) => {
console.log('SERVER: got request');
res.writeHead(200, {
'content-type': 'text/plain'
});
console.log('SERVER: sending response');
res.end('hello world\n');
}));
const proxy = net.createServer((clientSocket) => {
console.log('PROXY: got a client connection');
let serverSocket = null;
clientSocket.on('data', (chunk) => {
if (!serverSocket) {
// Verify the CONNECT request
assert.strictEqual(chunk.toString(),
`CONNECT localhost:${server.address().port} ` +
'HTTP/1.1\r\n' +
'Proxy-Connections: keep-alive\r\n' +
`Host: localhost:${proxy.address().port}\r\n` +
'Connection: keep-alive\r\n\r\n');
console.log('PROXY: got CONNECT request');
console.log('PROXY: creating a tunnel');
// create the tunnel
serverSocket = net.connect(server.address().port, common.mustCall(() => {
console.log('PROXY: replying to client CONNECT request');
// Send the response
clientSocket.write('HTTP/1.1 200 OK\r\nProxy-Connections: keep' +
'-alive\r\nConnections: keep-alive\r\nVia: ' +
`localhost:${proxy.address().port}\r\n\r\n`);
}));
serverSocket.on('data', (chunk) => {
clientSocket.write(chunk);
});
serverSocket.on('end', common.mustCall(() => {
clientSocket.destroy();
}));
} else {
serverSocket.write(chunk);
}
});
clientSocket.on('end', () => {
serverSocket.destroy();
});
});
server.listen(0);
proxy.listen(0, common.mustCall(() => {
console.log('CLIENT: Making CONNECT request');
const req = http.request({
port: proxy.address().port,
method: 'CONNECT',
path: `localhost:${server.address().port}`,
headers: {
'Proxy-Connections': 'keep-alive'
}
});
req.useChunkedEncodingByDefault = false; // for v0.6
req.on('response', onResponse); // for v0.6
req.on('upgrade', onUpgrade); // for v0.6
req.on('connect', onConnect); // for v0.7 or later
req.end();
function onResponse(res) {
// Very hacky. This is necessary to avoid http-parser leaks.
res.upgrade = true;
}
function onUpgrade(res, socket, head) {
// Hacky.
process.nextTick(() => {
onConnect(res, socket, head);
});
}
function onConnect(res, socket, header) {
assert.strictEqual(res.statusCode, 200);
console.log('CLIENT: got CONNECT response');
// detach the socket
socket.removeAllListeners('data');
socket.removeAllListeners('close');
socket.removeAllListeners('error');
socket.removeAllListeners('drain');
socket.removeAllListeners('end');
socket.ondata = null;
socket.onend = null;
socket.ondrain = null;
console.log('CLIENT: Making HTTPS request');
https.get({
path: '/foo',
key: key,
cert: cert,
socket: socket, // reuse the socket
agent: false,
rejectUnauthorized: false
}, (res) => {
assert.strictEqual(res.statusCode, 200);
res.on('data', common.mustCall((chunk) => {
assert.strictEqual(chunk.toString(), 'hello world\n');
console.log('CLIENT: got HTTPS response');
gotRequest = true;
}));
res.on('end', common.mustCall(() => {
proxy.close();
server.close();
}));
}).on('error', (er) => {
// We're ok with getting ECONNRESET in this test, but it's
// timing-dependent, and thus unreliable. Any other errors
// are just failures, though.
if (er.code !== 'ECONNRESET')
throw er;
}).end();
}
}));
process.on('exit', () => {
assert.ok(gotRequest);
});

View File

@@ -0,0 +1,92 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
// This test ensures that the data received over tls-server after pause
// is same as what it was sent
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const options = {
key: fixtures.readKey('rsa_private.pem'),
cert: fixtures.readKey('rsa_cert.crt')
};
const bufSize = 1024 * 1024;
let sent = 0;
let received = 0;
const server = tls.Server(options, common.mustCall((socket) => {
socket.pipe(socket);
socket.on('data', (c) => {
console.error('data', c.length);
});
}));
server.listen(0, common.mustCall(() => {
let resumed = false;
const client = tls.connect({
port: server.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
console.error('connected');
client.pause();
console.error('paused');
const send = (() => {
console.error('sending');
const ret = client.write(Buffer.allocUnsafe(bufSize));
console.error(`write => ${ret}`);
if (ret !== false) {
console.error('write again');
sent += bufSize;
assert.ok(sent < 100 * 1024 * 1024); // max 100MB
return process.nextTick(send);
}
sent += bufSize;
console.error(`sent: ${sent}`);
resumed = true;
client.resume();
console.error('resumed', client);
})();
}));
client.on('data', (data) => {
console.error('data');
assert.ok(resumed);
received += data.length;
console.error('received', received);
console.error('sent', sent);
if (received >= sent) {
console.error(`received: ${received}`);
client.end();
server.close();
}
});
}));
process.on('exit', () => {
assert.strictEqual(sent, received);
});

View File

@@ -0,0 +1,51 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('node compiled without crypto.');
const fixtures = require('../common/fixtures');
// This test ensures that TLS does not fail to read a self-signed certificate
// and thus throw an `authorizationError`.
// https://github.com/nodejs/node/issues/5100
const assert = require('assert');
const tls = require('tls');
const pfx = fixtures.readKey('agent1.pfx');
const server = tls
.createServer(
{
pfx: pfx,
passphrase: 'sample',
requestCert: true,
rejectUnauthorized: false
},
common.mustCall(function(c) {
assert.strictEqual(c.getPeerCertificate().serialNumber,
'147D36C1C2F74206DE9FAB5F2226D78ADB00A426');
assert.strictEqual(c.authorizationError, null);
c.end();
})
)
.listen(0, function() {
const client = tls.connect(
{
port: this.address().port,
pfx: pfx,
passphrase: 'sample',
rejectUnauthorized: false
},
function() {
for (let i = 0; i < 10; ++i) {
// Calling this repeatedly is a regression test that verifies
// that .getCertificate() does not accidentally decrease the
// reference count of the X509* certificate on the native side.
assert.strictEqual(client.getCertificate().serialNumber,
'147D36C1C2F74206DE9FAB5F2226D78ADB00A426');
}
client.end();
server.close();
}
);
});

View File

@@ -0,0 +1,32 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
{
// Check tlsClientError on invalid pskIdentityHint.
const server = tls.createServer({
ciphers: 'PSK+HIGH',
pskCallback: () => {},
pskIdentityHint: 'a'.repeat(512), // Too long identity hint.
});
server.on('tlsClientError', (err) => {
assert.ok(err instanceof Error);
assert.strictEqual(err.code, 'ERR_TLS_PSK_SET_IDENTITY_HINT_FAILED');
server.close();
});
server.listen(0, () => {
const client = tls.connect({
port: server.address().port,
ciphers: 'PSK+HIGH',
checkServerIdentity: () => {},
pskCallback: () => {},
}, () => {});
client.on('error', common.expectsError({ code: 'ECONNRESET' }));
});
}

View File

@@ -0,0 +1,26 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
{
const options = {
key: fixtures.readKey('agent11-key.pem'),
cert: fixtures.readKey('agent11-cert.pem'),
ciphers: 'DEFAULT'
};
// Should throw error as key is too small because openssl v3 doesn't allow it
assert.throws(() => tls.createServer(options, common.mustNotCall()),
/key too small/i);
// Reducing SECLEVEL to 0 in ciphers retains compatibility with previous versions of OpenSSL like using a small key.
// As ciphers are getting set before the cert and key get loaded.
options.ciphers = 'DEFAULT:@SECLEVEL=0';
assert.ok(tls.createServer(options, common.mustNotCall()));
}

View File

@@ -0,0 +1,49 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const util = require('util');
const fixtures = require('../common/fixtures');
const sent = 'hello world';
const serverOptions = {
isServer: true,
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem')
};
let ssl = null;
process.on('exit', function() {
console.log('Exit handler called');
assert.ok(ssl !== null);
// If the internal pointer to stream_ isn't cleared properly then this
// will abort.
console.log('About to inspect ssl');
util.inspect(ssl);
});
const server = tls.createServer(serverOptions, function(s) {
console.log('Server connection received');
s.on('data', function() {
console.log('Server received data');
});
s.on('end', function() {
console.log('Server connection ended');
server.close();
s.destroy();
});
}).listen(0, function() {
console.log('Server listening on port:', this.address().port);
const c = new tls.TLSSocket();
ssl = c.ssl;
console.log('Created TLSSocket with ssl');
c.connect(this.address().port, function() {
console.log('Client connected');
c.end(sent);
});
});

View File

@@ -0,0 +1,52 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const tls = require('tls');
const options = {
key: fixtures.readKey('agent1-key.pem'),
// NOTE: Certificate Common Name is 'agent1'
cert: fixtures.readKey('agent1-cert.pem'),
// NOTE: TLS 1.3 creates new session ticket **after** handshake so
// `getSession()` output will be different even if the session was reused
// during the handshake.
secureProtocol: 'TLSv1_2_method'
};
console.log('Creating TLS server with options:', options);
const server = tls.createServer(options, common.mustCall((socket) => {
console.log('Server received connection');
socket.end();
})).listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
let connected = false;
let session = null;
const client = tls.connect({
rejectUnauthorized: false,
port: server.address().port,
}, common.mustCall(() => {
console.log('Client connected');
assert(!connected);
assert(!session);
connected = true;
}));
client.on('session', common.mustCall((newSession) => {
console.log('Client received session');
assert(connected);
assert(!session);
session = newSession;
client.end();
server.close();
}));
}));

View File

@@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const sslcontext = tls.createSecureContext({
cert: fixtures.readKey('rsa_cert.crt'),
key: fixtures.readKey('rsa_private.pem')
});
const pair = tls.createSecurePair(sslcontext, true, false, false, {
SNICallback: common.mustCall((servername, cb) => {
assert.strictEqual(servername, 'www.google.com');
})
});
// Captured traffic from browser's request to https://www.google.com
const sslHello = fixtures.readSync('google_ssl_hello.bin');
pair.encrypted.write(sslHello);

View File

@@ -0,0 +1,28 @@
// Flags: --expose-gc --no-deprecation
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { createSecureContext } = require('tls');
const { createSecurePair } = require('tls');
const before = process.memoryUsage().external;
{
const context = createSecureContext();
const options = {};
for (let i = 0; i < 1e4; i += 1)
createSecurePair(context, false, false, false, options).destroy();
}
setImmediate(() => {
global.gc();
const after = process.memoryUsage().external;
// It's not an exact science but a SecurePair grows .external by about 45 KiB.
// Unless AdjustAmountOfExternalAllocatedMemory() is called on destruction,
// 10,000 instances make it grow by well over 400 MiB. Allow for some slop
// because objects like buffers also affect the external limit.
assert(after - before < 25 << 20);
});

View File

@@ -0,0 +1,47 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const events = require('events');
const fixtures = require('../common/fixtures');
const { createServer, connect } = require('tls');
const cert = fixtures.readKey('rsa_cert.crt');
const key = fixtures.readKey('rsa_private.pem');
console.log('Setting up event capture rejections...');
events.captureRejections = true;
console.log('Creating TLS server...');
const server = createServer({ cert, key }, common.mustCall(async (sock) => {
console.log('Server received connection');
server.close();
console.log('Server closed');
const _err = new Error('kaboom');
console.log('Setting up error handler on socket...');
sock.on('error', common.mustCall((err) => {
console.log('Socket error handler called with:', err.message);
assert.strictEqual(err, _err);
}));
console.log('Throwing error...');
throw _err;
}));
console.log('Starting server...');
server.listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
console.log('Creating client connection...');
const sock = connect({
port: server.address().port,
host: server.address().host,
rejectUnauthorized: false
});
console.log('Setting up close handler on client socket...');
sock.on('close', common.mustCall(() => {
console.log('Client socket closed');
}));
}));

View File

@@ -0,0 +1,88 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
// Test that `tls.Server` constructor options are passed to the parent
// constructor.
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
};
{
console.log('Test case 1: Default options');
const server = tls.createServer(options, common.mustCall((socket) => {
console.log('Server socket created with default options');
console.log('socket.allowHalfOpen:', socket.allowHalfOpen);
console.log('socket.isPaused():', socket.isPaused());
assert.strictEqual(socket.allowHalfOpen, false);
assert.strictEqual(socket.isPaused(), false);
}));
console.log('Server default options:');
console.log('server.allowHalfOpen:', server.allowHalfOpen);
console.log('server.pauseOnConnect:', server.pauseOnConnect);
assert.strictEqual(server.allowHalfOpen, false);
assert.strictEqual(server.pauseOnConnect, false);
server.listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
const socket = tls.connect({
port: server.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
console.log('Client connected');
socket.end();
}));
socket.on('close', () => {
console.log('Client socket closed');
server.close();
});
}));
}
{
console.log('\nTest case 2: Custom options');
const server = tls.createServer({
allowHalfOpen: true,
pauseOnConnect: true,
...options
}, common.mustCall((socket) => {
console.log('Server socket created with custom options');
console.log('socket.allowHalfOpen:', socket.allowHalfOpen);
console.log('socket.isPaused():', socket.isPaused());
assert.strictEqual(socket.allowHalfOpen, true);
assert.strictEqual(socket.isPaused(), true);
socket.on('end', socket.end);
}));
console.log('Server custom options:');
console.log('server.allowHalfOpen:', server.allowHalfOpen);
console.log('server.pauseOnConnect:', server.pauseOnConnect);
assert.strictEqual(server.allowHalfOpen, true);
assert.strictEqual(server.pauseOnConnect, true);
server.listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
const socket = tls.connect({
port: server.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
console.log('Client connected');
socket.end();
}));
socket.on('close', () => {
console.log('Client socket closed');
server.close();
});
}));
}

View File

@@ -0,0 +1,74 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { X509Certificate } = require('crypto');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const altKeyCert = {
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
minVersion: 'TLSv1.2',
};
console.log('altKeyCert:', altKeyCert);
const altKeyCertVals = [
altKeyCert,
tls.createSecureContext(altKeyCert),
];
console.log('altKeyCertVals length:', altKeyCertVals.length);
(function next() {
if (!altKeyCertVals.length) {
console.log('No more altKeyCertVals to process');
return;
}
const altKeyCertVal = altKeyCertVals.shift();
console.log('Processing altKeyCertVal:', altKeyCertVal);
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
minVersion: 'TLSv1.3',
ALPNCallback: common.mustCall(function({ servername, protocols }) {
console.log('ALPNCallback called with:', { servername, protocols });
this.setKeyCert(altKeyCertVal);
assert.deepStrictEqual(protocols, ['acme-tls/1']);
return protocols[0];
}),
};
console.log('Creating server with options:', options);
tls.createServer(options, (s) => s.end()).listen(0, function() {
console.log('Server listening on port:', this.address().port);
this.on('connection', common.mustCall((socket) => {
console.log('Connection received');
this.close();
}));
tls.connect({
port: this.address().port,
rejectUnauthorized: false,
ALPNProtocols: ['acme-tls/1'],
}, common.mustCall(function() {
console.log('Client connected');
assert.strictEqual(this.getProtocol(), 'TLSv1.3');
const altCert = new X509Certificate(altKeyCert.cert);
console.log('Comparing certificates:\n', this.getPeerX509Certificate().raw, '\n', altCert.raw);
assert.strictEqual(
this.getPeerX509Certificate().raw.equals(altCert.raw),
true
);
this.end();
next();
}));
});
})();

View File

@@ -0,0 +1,97 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
// This test verifies the behavior of the tls setSecureContext() method.
// It also verifies that existing connections are not disrupted when the
// secure context is changed.
const assert = require('assert');
const events = require('events');
const https = require('https');
const timers = require('timers/promises');
const fixtures = require('../common/fixtures');
const credentialOptions = [
{
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
ca: fixtures.readKey('ca1-cert.pem')
},
{
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
ca: fixtures.readKey('ca2-cert.pem')
},
];
let firstResponse;
const server = https.createServer(credentialOptions[0], (req, res) => {
const id = +req.headers.id;
if (id === 1) {
firstResponse = res;
firstResponse.write('multi-');
return;
} else if (id === 4) {
firstResponse.write('success-');
}
res.end('success');
});
server.listen(0, common.mustCall(() => {
const { port } = server.address();
const firstRequest = makeRequest(port, 1);
(async function makeRemainingRequests() {
// Wait until the first request is guaranteed to have been handled.
while (!firstResponse) {
await timers.setImmediate();
}
assert.strictEqual(await makeRequest(port, 2), 'success');
server.setSecureContext(credentialOptions[1]);
firstResponse.write('request-');
await assert.rejects(makeRequest(port, 3), {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT',
});
server.setSecureContext(credentialOptions[0]);
assert.strictEqual(await makeRequest(port, 4), 'success');
server.setSecureContext(credentialOptions[1]);
firstResponse.end('fun!');
await assert.rejects(makeRequest(port, 5), {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT',
});
assert.strictEqual(await firstRequest, 'multi-request-success-fun!');
server.close();
})().then(common.mustCall());
}));
async function makeRequest(port, id) {
const options = {
rejectUnauthorized: true,
ca: credentialOptions[0].ca,
servername: 'agent1',
headers: { id },
agent: new https.Agent()
};
const req = https.get(`https://localhost:${port}`, options);
let errored = false;
req.on('error', () => errored = true);
req.on('finish', () => assert.strictEqual(errored, false));
const [res] = await events.once(req, 'response');
res.setEncoding('utf8');
let response = '';
for await (const chunk of res) response += chunk;
return response;
}

View File

@@ -0,0 +1,174 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
function loadPEM(n) {
return fixtures.readKey(`${n}.pem`);
}
const serverOptions = {
key: loadPEM('agent2-key'),
cert: loadPEM('agent2-cert'),
requestCert: true,
rejectUnauthorized: false,
SNICallback: function(servername, callback) {
const context = SNIContexts[servername];
// Just to test asynchronous callback
setTimeout(function() {
if (context) {
if (context.emptyRegression)
callback(null, {});
else
callback(null, tls.createSecureContext(context));
} else {
callback(null, null);
}
}, 100);
}
};
const SNIContexts = {
'a.example.com': {
key: loadPEM('agent1-key'),
cert: loadPEM('agent1-cert'),
ca: [ loadPEM('ca2-cert') ]
},
'b.example.com': {
key: loadPEM('agent3-key'),
cert: loadPEM('agent3-cert')
},
'c.another.com': {
emptyRegression: true
}
};
test({
port: undefined,
key: loadPEM('agent1-key'),
cert: loadPEM('agent1-cert'),
ca: [loadPEM('ca1-cert')],
servername: 'a.example.com',
rejectUnauthorized: false
},
true,
{ sni: 'a.example.com', authorized: false },
null,
null);
test({
port: undefined,
key: loadPEM('agent4-key'),
cert: loadPEM('agent4-cert'),
ca: [loadPEM('ca1-cert')],
servername: 'a.example.com',
rejectUnauthorized: false
},
true,
{ sni: 'a.example.com', authorized: true },
null,
null);
test({
port: undefined,
key: loadPEM('agent2-key'),
cert: loadPEM('agent2-cert'),
ca: [loadPEM('ca2-cert')],
servername: 'b.example.com',
rejectUnauthorized: false
},
true,
{ sni: 'b.example.com', authorized: false },
null,
null);
test({
port: undefined,
key: loadPEM('agent3-key'),
cert: loadPEM('agent3-cert'),
ca: [loadPEM('ca1-cert')],
servername: 'c.wrong.com',
rejectUnauthorized: false
},
false,
{ sni: 'c.wrong.com', authorized: false },
null,
null);
test({
port: undefined,
key: loadPEM('agent3-key'),
cert: loadPEM('agent3-cert'),
ca: [loadPEM('ca1-cert')],
servername: 'c.another.com',
rejectUnauthorized: false
},
false,
null,
'Client network socket disconnected before secure TLS ' +
'connection was established',
'Invalid SNI context');
function test(options, clientResult, serverResult, clientError, serverError) {
const server = tls.createServer(serverOptions, (c) => {
assert.deepStrictEqual(
serverResult,
{ sni: c.servername, authorized: c.authorized }
);
});
if (serverResult) {
assert(!serverError);
server.on('tlsClientError', common.mustNotCall());
} else {
assert(serverError);
server.on('tlsClientError', common.mustCall((err) => {
assert.strictEqual(err.message, serverError);
}));
}
server.listen(0, () => {
options.port = server.address().port;
const client = tls.connect(options, () => {
const result = client.authorizationError &&
(client.authorizationError === 'ERR_TLS_CERT_ALTNAME_INVALID');
assert.strictEqual(result, clientResult);
client.end();
});
client.on('close', common.mustCall(() => server.close()));
if (clientError)
client.on('error', common.mustCall((err) => {
assert.strictEqual(err.message, clientError);
}));
else
client.on('error', common.mustNotCall());
});
}

View File

@@ -0,0 +1,56 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
// We could get the `tlsSocket.servername` even if the event of "tlsClientError"
// is emitted.
const serverOptions = {
requestCert: true,
rejectUnauthorized: false,
SNICallback: function(servername, callback) {
if (servername === 'c.another.com') {
callback(null, {});
} else {
callback(new Error('Invalid SNI context'), null);
}
}
};
function test(options) {
const server = tls.createServer(serverOptions, common.mustNotCall());
server.on('tlsClientError', common.mustCall((err, socket) => {
assert.strictEqual(err.message, 'Invalid SNI context');
// The `servername` should match.
assert.strictEqual(socket.servername, options.servername);
}));
server.listen(0, () => {
options.port = server.address().port;
const client = tls.connect(options, common.mustNotCall());
client.on('error', common.mustCall((err) => {
assert.strictEqual(err.message, 'Client network socket' +
' disconnected before secure TLS connection was established');
}));
client.on('close', common.mustCall(() => server.close()));
});
}
test({
port: undefined,
servername: 'c.another.com',
rejectUnauthorized: false
});
test({
port: undefined,
servername: 'c.wrong.com',
rejectUnauthorized: false
});

View File

@@ -0,0 +1,24 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const net = require('net');
const tls = require('tls');
for (const SNICallback of ['fhqwhgads', 42, {}, []]) {
assert.throws(() => {
tls.createServer({ SNICallback });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
assert.throws(() => {
new tls.TLSSocket(new net.Socket(), { isServer: true, SNICallback });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
});
}

View File

@@ -0,0 +1,91 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const net = require('net');
const Countdown = require('../common/countdown');
const fixtures = require('../common/fixtures');
const key = fixtures.readKey('agent2-key.pem');
const cert = fixtures.readKey('agent2-cert.pem');
let serverTlsSocket;
const tlsServer = tls.createServer({ cert, key }, (socket) => {
console.log('TLS server received connection');
serverTlsSocket = socket;
socket.on('data', (chunk) => {
console.log('TLS server received data:', chunk[0]);
assert.strictEqual(chunk[0], 46);
socket.write('.');
});
socket.on('close', () => {
console.log('TLS server socket closed');
dec();
});
});
// A plain net server, that manually passes connections to the TLS
// server to be upgraded.
let netSocket;
let netSocketCloseEmitted = false;
const netServer = net.createServer((socket) => {
console.log('Net server received connection');
netSocket = socket;
tlsServer.emit('connection', socket);
socket.on('close', common.mustCall(() => {
console.log('Net socket closed');
netSocketCloseEmitted = true;
assert.strictEqual(serverTlsSocket.destroyed, true);
}));
}).listen(0, common.mustCall(() => {
console.log('Net server listening on port:', netServer.address().port);
connectClient(netServer);
}));
const countdown = new Countdown(2, () => {
console.log('Countdown finished, closing net server');
netServer.close();
});
// A client that connects, sends one message, and closes the raw connection:
function connectClient(server) {
console.log('Connecting client');
const clientTlsSocket = tls.connect({
host: 'localhost',
port: server.address().port,
rejectUnauthorized: false
});
clientTlsSocket.write('.');
clientTlsSocket.on('data', (chunk) => {
console.log('Client received data:', chunk[0]);
assert.strictEqual(chunk[0], 46);
console.log('Destroying net socket');
netSocket.destroy();
assert.strictEqual(netSocket.destroyed, true);
setImmediate(() => {
console.log('Checking socket states in setImmediate:');
console.log('netSocketCloseEmitted:', netSocketCloseEmitted);
console.log('serverTlsSocket.destroyed:', serverTlsSocket.destroyed);
// Close callbacks are executed after `setImmediate()` callbacks.
assert.strictEqual(netSocketCloseEmitted, false);
assert.strictEqual(serverTlsSocket.destroyed, false);
});
});
clientTlsSocket.on('close', () => {
console.log('Client socket closed');
dec();
});
}
function dec() {
console.log('Decrementing countdown');
countdown.dec();
}

View File

@@ -0,0 +1,49 @@
'use strict';
// Test that TLSSocket can take arrays of strings for ALPNProtocols.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
new tls.TLSSocket(null, {
ALPNProtocols: ['http/1.1'],
});
const assert = require('assert');
const net = require('net');
const fixtures = require('../common/fixtures');
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const server = net.createServer(common.mustCall((s) => {
const tlsSocket = new tls.TLSSocket(s, {
isServer: true,
server,
key,
cert,
ALPNProtocols: ['http/1.1'],
});
tlsSocket.on('secure', common.mustCall(() => {
assert.strictEqual(tlsSocket.alpnProtocol, 'http/1.1');
tlsSocket.end();
server.close();
}));
}));
server.listen(0, common.mustCall(() => {
const alpnOpts = {
port: server.address().port,
rejectUnauthorized: false,
ALPNProtocols: ['h2', 'http/1.1']
};
tls.connect(alpnOpts, common.mustCall(function() {
this.end();
}));
}));

View File

@@ -0,0 +1,88 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
// Test directly created TLS sockets and options.
const assert = require('assert');
const {
connect, keys, tls
} = require(fixtures.path('tls-connect'));
test(undefined, (err) => {
console.log('Test 1 - Error code:', err.code);
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
});
test({}, (err) => {
console.log('Test 2 - Error code:', err.code);
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
});
test(
{ secureContext: tls.createSecureContext({ ca: keys.agent1.ca }) },
(err) => {
console.log('Test 3 - Error:', err);
assert.ifError(err);
});
test(
{ ca: keys.agent1.ca },
(err) => {
console.log('Test 4 - Error:', err);
assert.ifError(err);
});
// Secure context options, like ca, are ignored if a sec ctx is explicitly
// provided.
test(
{ secureContext: tls.createSecureContext(), ca: keys.agent1.ca },
(err) => {
console.log('Test 5 - Error code:', err.code);
assert.strictEqual(err.code,
'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
});
function test(client, callback) {
console.log('Starting test with client options:', client);
callback = common.mustCall(callback);
connect({
server: {
key: keys.agent1.key,
cert: keys.agent1.cert,
},
}, function(err, pair, cleanup) {
console.log('Connection error:', err);
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
let recv = '';
pair.server.server.once('secureConnection', common.mustCall((conn) => {
console.log('Secure connection established');
conn.on('data', (data) => {
console.log('Received data:', data.toString());
recv += data;
});
conn.on('end', common.mustCall(() => {
console.log('Connection ended, received:', recv);
// Server sees nothing wrong with connection, even though the client's
// authentication of the server cert failed.
assert.strictEqual(recv, 'hello');
cleanup();
}));
}));
// `new TLSSocket` doesn't support the 'secureConnect' event on client side,
// and doesn't error if authentication failed. Caller must explicitly check
// for failure.
const socket = new tls.TLSSocket(null, client);
console.log('Created new TLSSocket');
socket.connect(pair.server.server.address().port)
.on('connect', common.mustCall(function() {
console.log('Socket connected');
this.end('hello');
}))
.on('secure', common.mustCall(function() {
console.log('Socket secure, verify error:', this.ssl.verifyError());
callback(this.ssl.verifyError());
}));
});
}

View File

@@ -0,0 +1,45 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const net = require('net');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
console.log('Creating secure context with key and cert');
const secureContext = tls.createSecureContext({ key, cert });
const server = net.createServer(common.mustCall((conn) => {
console.log('Server received connection');
const options = { isServer: true, secureContext, server };
const socket = new tls.TLSSocket(conn, options);
socket.once('data', common.mustCall(() => {
console.log('Server received data, destroying SSL and socket');
socket._destroySSL(); // Should not crash.
socket.destroy();
server.close();
}));
}));
server.listen(0, function() {
console.log('Server listening on port:', this.address().port);
const options = {
port: this.address().port,
rejectUnauthorized: false,
};
tls.connect(options, function() {
console.log('Client connected successfully');
this.write('*'.repeat(1 << 20)); // Write more data than fits in a frame.
console.log('Client wrote data');
this.on('error', (err) => {
console.log('Client received error:', err.message);
this.destroy();
}); // Server closes connection on us.
});
});

View File

@@ -0,0 +1,44 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
const net = require('net');
const assert = require('assert');
const bonkers = Buffer.alloc(1024, 42);
const server = net.createServer(function(c) {
console.log('Server: New connection received');
setTimeout(function() {
console.log('Server: Creating TLSSocket');
const s = new tls.TLSSocket(c, {
isServer: true,
server: server
});
s.on('error', common.mustCall(function(e) {
console.log('Server: Error received:', e.message);
assert.ok(e instanceof Error,
'Instance of Error should be passed to error handler');
assert.ok(
/SSL routines:[^:]*:wrong version number/.test(
e.message),
'Expecting SSL unknown protocol');
}));
s.on('close', function() {
console.log('Server: Connection closed');
server.close();
s.destroy();
});
}, common.platformTimeout(200));
}).listen(0, function() {
console.log('Server: Listening on port', this.address().port);
const c = net.connect({ port: this.address().port }, function() {
console.log('Client: Connected, writing bonkers data');
c.write(bonkers);
});
});

View File

@@ -0,0 +1,26 @@
'use strict';
// This is based on test-tls-securepair-fiftharg.js
// for the deprecated `tls.createSecurePair()` variant.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const { duplexPair } = require('stream');
const [ clientSide, serverSide ] = duplexPair();
new tls.TLSSocket(serverSide, {
isServer: true,
SNICallback: common.mustCall((servername, cb) => {
assert.strictEqual(servername, 'www.google.com');
})
});
// Captured traffic from browser's request to https://www.google.com
const sslHello = fixtures.readSync('google_ssl_hello.bin');
clientSide.write(sslHello);

View File

@@ -0,0 +1,48 @@
'use strict';
// Test asynchronous SNI+OCSP on TLSSocket created with `server` set to
// `net.Server` instead of `tls.Server`
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const net = require('net');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const key = fixtures.readKey('agent1-key.pem');
const cert = fixtures.readKey('agent1-cert.pem');
const server = net.createServer(common.mustCall((s) => {
const tlsSocket = new tls.TLSSocket(s, {
isServer: true,
server: server,
secureContext: tls.createSecureContext({ key, cert }),
SNICallback: common.mustCall((hostname, callback) => {
assert.strictEqual(hostname, 'test.test');
callback(null, null);
})
});
tlsSocket.on('secure', common.mustCall(() => {
tlsSocket.end();
server.close();
}));
})).listen(0, () => {
const opts = {
servername: 'test.test',
port: server.address().port,
rejectUnauthorized: false,
requestOCSP: true
};
tls.connect(opts, function() {
this.end();
});
});

View File

@@ -0,0 +1,92 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { duplexPair } = require('stream');
const tls = require('tls');
const net = require('net');
// This test ensures that `bufferSize` also works for those tlsSockets
// created from `socket` of `Duplex`, with which, TLSSocket will wrap
// sockets in `StreamWrap`.
{
const iter = 10;
console.log('Starting test with iter:', iter);
function createDuplex(port) {
console.log('Creating duplex pair for port:', port);
const [ clientSide, serverSide ] = duplexPair();
return new Promise((resolve, reject) => {
const socket = net.connect({
port,
}, common.mustCall(() => {
console.log('Socket connected, setting up pipes');
clientSide.pipe(socket);
socket.pipe(clientSide);
clientSide.on('close', common.mustCall(() => {
console.log('Client side closed, destroying socket');
socket.destroy();
}));
socket.on('close', common.mustCall(() => {
console.log('Socket closed, destroying client side');
clientSide.destroy();
}));
resolve(serverSide);
}));
});
}
const server = tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem')
}, common.mustCall((socket) => {
console.log('Server received connection');
let str = '';
socket.setEncoding('utf-8');
socket.on('data', (chunk) => {
console.log('Server received chunk:', chunk);
str += chunk;
});
socket.on('end', common.mustCall(() => {
console.log('Server received end, str length:', str.length);
assert.strictEqual(str, 'a'.repeat(iter - 1));
server.close();
}));
}));
server.listen(0, common.mustCall(() => {
const { port } = server.address();
console.log('Server listening on port:', port);
createDuplex(port).then((socket) => {
console.log('Duplex created, connecting TLS client');
const client = tls.connect({
socket,
rejectUnauthorized: false,
}, common.mustCall(() => {
console.log('TLS client connected, initial bufferSize:', client.bufferSize);
assert.strictEqual(client.bufferSize, 0);
for (let i = 1; i < iter; i++) {
client.write('a');
console.log('Wrote data, bufferSize:', client.bufferSize);
assert.strictEqual(client.bufferSize, i);
}
client.on('end', common.mustCall(() => {
console.log('Client received end event');
}));
client.on('close', common.mustCall(() => {
console.log('Client closed, final bufferSize:', client.bufferSize);
assert.strictEqual(client.bufferSize, undefined);
}));
client.end();
}));
});
}));
}

View File

@@ -0,0 +1,135 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const cluster = require('cluster');
const fixtures = require('../common/fixtures');
const workerCount = 4;
const expectedReqCount = 16;
if (cluster.isPrimary) {
let listeningCount = 0;
let reusedCount = 0;
let reqCount = 0;
let lastSession = null;
let workerPort = null;
function shoot() {
console.error('[primary] connecting',
workerPort, 'session?', !!lastSession);
const c = tls.connect(workerPort, {
session: lastSession,
rejectUnauthorized: false
}, () => {
c.on('end', c.end);
}).on('close', () => {
// Wait for close to shoot off another connection. We don't want to shoot
// until a new session is allocated, if one will be. The new session is
// not guaranteed on secureConnect (it depends on TLS1.2 vs TLS1.3), but
// it is guaranteed to happen before the connection is closed.
if (++reqCount === expectedReqCount) {
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send('die');
});
} else {
shoot();
}
}).once('session', (session) => {
assert(!lastSession);
lastSession = session;
});
c.resume(); // See close_notify comment in server
}
function fork() {
const worker = cluster.fork();
worker.on('message', ({ msg, port }) => {
console.error('[primary] got %j', msg);
if (msg === 'reused') {
++reusedCount;
} else if (msg === 'listening' && ++listeningCount === workerCount) {
workerPort = port;
shoot();
}
});
worker.on('exit', () => {
console.error('[primary] worker died');
});
}
for (let i = 0; i < workerCount; i++) {
fork();
}
process.on('exit', () => {
assert.strictEqual(reqCount, expectedReqCount);
assert.strictEqual(reusedCount + 1, reqCount);
});
return;
}
const key = fixtures.readKey('rsa_private.pem');
const cert = fixtures.readKey('rsa_cert.crt');
const options = { key, cert };
const server = tls.createServer(options, (c) => {
console.error('[worker] connection reused?', c.isSessionReused());
if (c.isSessionReused()) {
process.send({ msg: 'reused' });
} else {
process.send({ msg: 'not-reused' });
}
// Used to just .end(), but that means client gets close_notify before
// NewSessionTicket. Send data until that problem is solved.
c.end('x');
});
server.listen(0, () => {
const { port } = server.address();
process.send({
msg: 'listening',
port,
});
});
process.on('message', function listener(msg) {
console.error('[worker] got %j', msg);
if (msg === 'die') {
server.close(() => {
console.error('[worker] server close');
process.exit();
});
}
});
process.on('exit', () => {
console.error('[worker] exit');
});

View File

@@ -0,0 +1,26 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
const assert = require('assert');
const tls = require('tls');
const server = new tls.Server();
[null, undefined, 0, 1, 1n, Symbol(), {}, [], true, false, '', () => {}]
.forEach((arg) =>
server.setTicketKeys(arg)
// assert.throws(
// () => server.setTicketKeys(arg),
// { code: 'ERR_INVALID_ARG_TYPE' }
// )
);
[new Uint8Array(1), Buffer.from([1]), new DataView(new ArrayBuffer(2))].forEach(
(arg) =>
assert.throws(() => {
server.setTicketKeys(arg);
}, /Session ticket keys must be a 48-byte buffer/)
);

View File

@@ -0,0 +1,51 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
const net = require('net');
const fixtures = require('../common/fixtures');
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
handshakeTimeout: 50
};
console.log('Creating TLS server with options:', options);
const server = tls.createServer(options, common.mustNotCall());
server.on('tlsClientError', common.mustCall(function(err, conn) {
console.log('TLS client error occurred:', err.message);
conn.destroy();
server.close();
}));
server.listen(0, common.mustCall(function() {
console.log('Server listening on port:', this.address().port);
net.connect({ host: '127.0.0.1', port: this.address().port });
}));

View File

@@ -0,0 +1,48 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
const net = require('net');
const { fork } = require('child_process');
const tmpdir = require('../common/tmpdir');
// Run in a child process because the PIPE file descriptor stays open until
// Node.js completes, blocking the tmpdir and preventing cleanup.
if (process.argv[2] !== 'child') {
// Parent
tmpdir.refresh();
// Run test
const child = fork(__filename, ['child'], { stdio: 'inherit' });
child.on('exit', common.mustCall(function(code) {
assert.strictEqual(code, 0);
}));
return;
}
// Child
const server = net.createServer((c) => {
c.end();
}).listen(common.PIPE, common.mustCall(() => {
let errored = false;
tls.connect({ path: common.PIPE })
.once('error', common.mustCall((e) => {
assert.strictEqual(e.code, 'ECONNRESET');
assert.strictEqual(e.path, common.PIPE);
assert.strictEqual(e.port, undefined);
assert.strictEqual(e.host, undefined);
assert.strictEqual(e.localAddress, undefined);
server.close();
errored = true;
}))
.on('close', common.mustCall(() => {
assert.strictEqual(errored, true);
}));
}));

View File

@@ -0,0 +1,28 @@
'use strict';
// Issue: https://github.com/nodejs/node/issues/3655
// Test checks if we get exception instead of runtime error
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const TlsSocket = require('tls').TLSSocket;
const EventEmitter = require('events').EventEmitter;
console.log('Creating new EventEmitter...');
const emitter = new EventEmitter();
console.log('EventEmitter created');
console.log('Attempting to create new TLSSocket with EventEmitter...');
assert.throws(
() => {
console.log('Inside throws callback');
new TlsSocket(emitter);
console.log('TLSSocket created (this should not happen)');
},
TypeError
);
console.log('Successfully caught TypeError as expected');

View File

@@ -0,0 +1,32 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const net = require('net');
const tls = require('tls');
console.log('Creating server...');
const server = net.createServer(common.mustCall((c) => {
console.log('Server received connection, destroying it...');
c.destroy();
})).listen(0, common.mustCall(() => {
console.log('Server listening on port:', server.address().port);
console.log('Creating TLS client connection...');
const c = tls.connect({ port: server.address().port });
c.on('error', (err) => {
console.log('TLS client received error', err);
// Otherwise `.write()` callback won't be invoked.
// c._undestroy();
});
console.log('Attempting to write data...');
c.write('hello', common.mustCall((err) => {
console.log('Write callback called with error:', err);
assert.strictEqual(err.code, 'ECANCELED');
console.log('Closing server...');
server.close();
}));
}));

View File

@@ -0,0 +1,160 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
const { opensslCli } = require('../common/crypto');
const tmpdir = require('../common/tmpdir');
tmpdir.refresh();
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const key = fixtures.readKey('rsa_private.pem');
const cert = fixtures.readKey('rsa_cert.crt');
{
// Node.js should not allow setting negative timeouts since new versions of
// OpenSSL do not handle those as users might expect
for (const sessionTimeout of [-1, -100, -(2 ** 31)]) {
assert.throws(() => {
tls.createServer({
key: key,
cert: cert,
ca: [cert],
sessionTimeout,
maxVersion: 'TLSv1.2',
});
}, {
code: 'ERR_OUT_OF_RANGE',
message: 'The value of "options.sessionTimeout" is out of range. It ' +
`must be >= 0 && <= ${2 ** 31 - 1}. Received ${sessionTimeout}`,
});
}
}
if (!opensslCli) {
common.skip('node compiled without OpenSSL CLI.');
}
doTest();
// This test consists of three TLS requests --
// * The first one should result in a new connection because we don't have
// a valid session ticket.
// * The second one should result in connection resumption because we used
// the session ticket we saved from the first connection.
// * The third one should result in a new connection because the ticket
// that we used has expired by now.
function doTest() {
const fs = require('fs');
const spawn = require('child_process').spawn;
const SESSION_TIMEOUT = 1;
const options = {
key: key,
cert: cert,
ca: [cert],
sessionTimeout: SESSION_TIMEOUT,
maxVersion: 'TLSv1.2',
};
// We need to store a sample session ticket in the fixtures directory because
// `s_client` behaves incorrectly if we do not pass in both the `-sess_in`
// and the `-sess_out` flags, and the `-sess_in` argument must point to a
// file containing a proper serialization of a session ticket.
// To avoid a source control diff, we copy the ticket to a temporary file.
const sessionFileName = (function() {
const ticketFileName = 'tls-session-ticket.txt';
const tmpPath = tmpdir.resolve(ticketFileName);
fs.writeFileSync(tmpPath, fixtures.readSync(ticketFileName));
return tmpPath;
}());
// Expects a callback -- cb(connectionType : enum ['New'|'Reused'])
function Client(cb) {
const flags = [
's_client',
'-connect', `localhost:${common.PORT}`,
'-sess_in', sessionFileName,
'-sess_out', sessionFileName,
];
const client = spawn(opensslCli, flags, {
stdio: ['ignore', 'pipe', 'ignore']
});
let clientOutput = '';
client.stdout.on('data', (data) => {
clientOutput += data.toString();
});
client.on('exit', (code) => {
let connectionType;
const grepConnectionType = (line) => {
const matches = line.match(/(New|Reused), /);
if (matches) {
connectionType = matches[1];
return true;
}
};
const lines = clientOutput.split('\n');
if (!lines.some(grepConnectionType)) {
throw new Error('unexpected output from openssl client');
}
assert.strictEqual(code, 0);
cb(connectionType);
});
}
const server = tls.createServer(options, (cleartext) => {
cleartext.on('error', (er) => {
if (er.code !== 'ECONNRESET')
throw er;
});
cleartext.end();
});
server.listen(common.PORT, () => {
Client((connectionType) => {
assert.strictEqual(connectionType, 'New');
Client((connectionType) => {
assert.strictEqual(connectionType, 'Reused');
setTimeout(() => {
Client((connectionType) => {
assert.strictEqual(connectionType, 'New');
server.close();
});
}, (SESSION_TIMEOUT + 1) * 1000);
});
});
});
}