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'); 26 27const assert = require('assert'); 28const tls = require('tls'); 29const net = require('net'); 30const crypto = require('crypto'); 31const fixtures = require('../common/fixtures'); 32 33const keys = crypto.randomBytes(48); 34const serverLog = []; 35const ticketLog = []; 36 37let s; 38 39let serverCount = 0; 40function createServer() { 41 const id = serverCount++; 42 43 let counter = 0; 44 let previousKey = null; 45 46 const server = tls.createServer({ 47 key: fixtures.readKey('agent1-key.pem'), 48 cert: fixtures.readKey('agent1-cert.pem'), 49 ticketKeys: keys 50 }, function(c) { 51 serverLog.push(id); 52 // TODO(@sam-github) Triggers close_notify before NewSessionTicket bug. 53 // c.end(); 54 c.end('x'); 55 56 counter++; 57 58 // Rotate ticket keys 59 // 60 // Take especial care to account for TLS1.2 and TLS1.3 differences around 61 // when ticket keys are encrypted. In TLS1.2, they are encrypted before the 62 // handshake complete callback, but in TLS1.3, they are encrypted after. 63 // There is no callback or way for us to know when they were sent, so hook 64 // the client's reception of the keys, and use it as proof that the current 65 // keys were used, and its safe to rotate them. 66 // 67 // Rotation can occur right away if the session was reused, the keys were 68 // already decrypted or we wouldn't have a reused session. 69 function setTicketKeys(keys) { 70 if (c.isSessionReused()) 71 server.setTicketKeys(keys); 72 else 73 s.once('session', () => { 74 server.setTicketKeys(keys); 75 }); 76 } 77 if (counter === 1) { 78 previousKey = server.getTicketKeys(); 79 assert.strictEqual(previousKey.compare(keys), 0); 80 setTicketKeys(crypto.randomBytes(48)); 81 } else if (counter === 2) { 82 setTicketKeys(previousKey); 83 } else if (counter === 3) { 84 // Use keys from counter=2 85 } else { 86 throw new Error('UNREACHABLE'); 87 } 88 }); 89 90 return server; 91} 92 93const naturalServers = [ createServer(), createServer(), createServer() ]; 94 95// 3x servers 96const servers = naturalServers.concat(naturalServers).concat(naturalServers); 97 98// Create one TCP server and balance sockets to multiple TLS server instances 99const shared = net.createServer(function(c) { 100 servers.shift().emit('connection', c); 101}).listen(0, function() { 102 start(function() { 103 shared.close(); 104 }); 105}); 106 107// 'session' events only occur for new sessions. The first connection is new. 108// After, for each set of 3 connections, the middle connection is made when the 109// server has random keys set, so the client's ticket is silently ignored, and a 110// new ticket is sent. 111const onNewSession = common.mustCall((s, session) => { 112 assert(session); 113 assert.strictEqual(session.compare(s.getSession()), 0); 114}, 4); 115 116function start(callback) { 117 let sess = null; 118 let left = servers.length; 119 120 function connect() { 121 s = tls.connect(shared.address().port, { 122 session: sess, 123 rejectUnauthorized: false 124 }, function() { 125 if (s.isSessionReused()) 126 ticketLog.push(s.getTLSTicket().toString('hex')); 127 }); 128 s.on('data', () => { 129 s.end(); 130 }); 131 s.on('close', function() { 132 if (--left === 0) 133 callback(); 134 else 135 connect(); 136 }); 137 s.on('session', (session) => { 138 sess = sess || session; 139 }); 140 s.once('session', (session) => onNewSession(s, session)); 141 s.once('session', () => ticketLog.push(s.getTLSTicket().toString('hex'))); 142 } 143 144 connect(); 145} 146 147process.on('exit', function() { 148 assert.strictEqual(ticketLog.length, serverLog.length); 149 for (let i = 0; i < naturalServers.length - 1; i++) { 150 assert.notStrictEqual(serverLog[i], serverLog[i + 1]); 151 assert.strictEqual(ticketLog[i], ticketLog[i + 1]); 152 153 // 2nd connection should have different ticket 154 assert.notStrictEqual(ticketLog[i], ticketLog[i + naturalServers.length]); 155 156 // 3rd connection should have the same ticket 157 assert.strictEqual(ticketLog[i], ticketLog[i + naturalServers.length * 2]); 158 } 159}); 160