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'); 24 25if (!common.opensslCli) 26 common.skip('node compiled without OpenSSL CLI.'); 27 28if (!common.hasCrypto) 29 common.skip('missing crypto'); 30 31const tmpdir = require('../common/tmpdir'); 32tmpdir.refresh(); 33 34doTest(); 35 36// This test consists of three TLS requests -- 37// * The first one should result in a new connection because we don't have 38// a valid session ticket. 39// * The second one should result in connection resumption because we used 40// the session ticket we saved from the first connection. 41// * The third one should result in a new connection because the ticket 42// that we used has expired by now. 43 44function doTest() { 45 const assert = require('assert'); 46 const tls = require('tls'); 47 const fs = require('fs'); 48 const join = require('path').join; 49 const fixtures = require('../common/fixtures'); 50 const spawn = require('child_process').spawn; 51 52 const SESSION_TIMEOUT = 1; 53 54 const key = fixtures.readKey('rsa_private.pem'); 55 const cert = fixtures.readKey('rsa_cert.crt'); 56 const options = { 57 key: key, 58 cert: cert, 59 ca: [cert], 60 sessionTimeout: SESSION_TIMEOUT, 61 maxVersion: 'TLSv1.2', 62 }; 63 64 // We need to store a sample session ticket in the fixtures directory because 65 // `s_client` behaves incorrectly if we do not pass in both the `-sess_in` 66 // and the `-sess_out` flags, and the `-sess_in` argument must point to a 67 // file containing a proper serialization of a session ticket. 68 // To avoid a source control diff, we copy the ticket to a temporary file. 69 70 const sessionFileName = (function() { 71 const ticketFileName = 'tls-session-ticket.txt'; 72 const tmpPath = join(tmpdir.path, ticketFileName); 73 fs.writeFileSync(tmpPath, fixtures.readSync(ticketFileName)); 74 return tmpPath; 75 }()); 76 77 // Expects a callback -- cb(connectionType : enum ['New'|'Reused']) 78 79 function Client(cb) { 80 const flags = [ 81 's_client', 82 '-connect', `localhost:${common.PORT}`, 83 '-sess_in', sessionFileName, 84 '-sess_out', sessionFileName, 85 ]; 86 const client = spawn(common.opensslCli, flags, { 87 stdio: ['ignore', 'pipe', 'ignore'] 88 }); 89 90 let clientOutput = ''; 91 client.stdout.on('data', (data) => { 92 clientOutput += data.toString(); 93 }); 94 client.on('exit', (code) => { 95 let connectionType; 96 const grepConnectionType = (line) => { 97 const matches = line.match(/(New|Reused), /); 98 if (matches) { 99 connectionType = matches[1]; 100 return true; 101 } 102 }; 103 const lines = clientOutput.split('\n'); 104 if (!lines.some(grepConnectionType)) { 105 throw new Error('unexpected output from openssl client'); 106 } 107 assert.strictEqual(code, 0); 108 cb(connectionType); 109 }); 110 } 111 112 const server = tls.createServer(options, (cleartext) => { 113 cleartext.on('error', (er) => { 114 if (er.code !== 'ECONNRESET') 115 throw er; 116 }); 117 cleartext.end(); 118 }); 119 120 server.listen(common.PORT, () => { 121 Client((connectionType) => { 122 assert.strictEqual(connectionType, 'New'); 123 Client((connectionType) => { 124 assert.strictEqual(connectionType, 'Reused'); 125 setTimeout(() => { 126 Client((connectionType) => { 127 assert.strictEqual(connectionType, 'New'); 128 server.close(); 129 }); 130 }, (SESSION_TIMEOUT + 1) * 1000); 131 }); 132 }); 133 }); 134} 135