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