• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const common = require('../common');
24if (!common.hasCrypto)
25  common.skip('missing crypto');
26const fixtures = require('../common/fixtures');
27const assert = require('assert');
28const tls = require('tls');
29const { spawn } = require('child_process');
30
31if (!common.opensslCli)
32  common.skip('node compiled without OpenSSL CLI.');
33
34
35doTest({ tickets: false }, function() {
36  doTest({ tickets: true }, function() {
37    doTest({ tickets: false, invalidSession: true }, function() {
38      console.error('all done');
39    });
40  });
41});
42
43function doTest(testOptions, callback) {
44  const key = fixtures.readKey('rsa_private.pem');
45  const cert = fixtures.readKey('rsa_cert.crt');
46  const options = {
47    key,
48    cert,
49    ca: [cert],
50    requestCert: true,
51    rejectUnauthorized: false,
52    secureProtocol: 'TLS_method',
53    ciphers: 'RSA@SECLEVEL=0'
54  };
55  let requestCount = 0;
56  let resumeCount = 0;
57  let newSessionCount = 0;
58  let session;
59
60  const server = tls.createServer(options, function(cleartext) {
61    cleartext.on('error', function(er) {
62      // We're ok with getting ECONNRESET in this test, but it's
63      // timing-dependent, and thus unreliable. Any other errors
64      // are just failures, though.
65      if (er.code !== 'ECONNRESET')
66        throw er;
67    });
68    ++requestCount;
69    cleartext.end('');
70  });
71  server.on('newSession', function(id, data, cb) {
72    ++newSessionCount;
73    // Emulate asynchronous store
74    setImmediate(() => {
75      assert.ok(!session);
76      session = { id, data };
77      cb();
78    });
79  });
80  server.on('resumeSession', function(id, callback) {
81    ++resumeCount;
82    assert.ok(session);
83    assert.strictEqual(session.id.toString('hex'), id.toString('hex'));
84
85    let data = session.data;
86
87    // Return an invalid session to test Node does not crash.
88    if (testOptions.invalidSession) {
89      data = Buffer.from('INVALID SESSION');
90      session = null;
91    }
92
93    // Just to check that async really works there
94    setImmediate(() => {
95      callback(null, data);
96    });
97  });
98
99  server.listen(0, function() {
100    const args = [
101      's_client',
102      '-tls1',
103      '-connect', `localhost:${this.address().port}`,
104      '-servername', 'ohgod',
105      '-key', fixtures.path('keys/rsa_private.pem'),
106      '-cert', fixtures.path('keys/rsa_cert.crt'),
107      '-reconnect',
108    ].concat(testOptions.tickets ? [] : '-no_ticket');
109
110    function spawnClient() {
111      const client = spawn(common.opensslCli, args, {
112        stdio: [ 0, 1, 'pipe' ]
113      });
114      let err = '';
115      client.stderr.setEncoding('utf8');
116      client.stderr.on('data', function(chunk) {
117        err += chunk;
118      });
119
120      client.on('exit', common.mustCall(function(code, signal) {
121        if (code !== 0) {
122          // If SmartOS and connection refused, then retry. See
123          // https://github.com/nodejs/node/issues/2663.
124          if (common.isSunOS && err.includes('Connection refused')) {
125            requestCount = 0;
126            spawnClient();
127            return;
128          }
129          assert.fail(`code: ${code}, signal: ${signal}, output: ${err}`);
130        }
131        assert.strictEqual(code, 0);
132        server.close(common.mustCall(function() {
133          setImmediate(callback);
134        }));
135      }));
136    }
137
138    spawnClient();
139  });
140
141  process.on('exit', function() {
142    // Each test run connects 6 times: an initial request and 5 reconnect
143    // requests.
144    assert.strictEqual(requestCount, 6);
145
146    if (testOptions.tickets) {
147      // No session cache callbacks are called.
148      assert.strictEqual(resumeCount, 0);
149      assert.strictEqual(newSessionCount, 0);
150    } else if (testOptions.invalidSession) {
151      // The resume callback was called, but each connection established a
152      // fresh session.
153      assert.strictEqual(resumeCount, 5);
154      assert.strictEqual(newSessionCount, 6);
155    } else {
156      // The resume callback was called, and only the initial connection
157      // establishes a fresh session.
158      assert.ok(session);
159      assert.strictEqual(resumeCount, 5);
160      assert.strictEqual(newSessionCount, 1);
161    }
162  });
163}
164