10 from dnsdisttests
import DNSDistTest
, pickAvailablePort
16 class DNSDistTLSSessionResumptionTest(DNSDistTest
):
18 _consoleKey
= DNSDistTest
.generateConsoleKey()
19 _consoleKeyB64
= base64
.b64encode(_consoleKey
).decode('ascii')
22 def checkSessionResumed(cls
, addr
, port
, serverName
, caFile
, ticketFileOut
, ticketFileIn
, allowNoTicket
=False):
23 outFile
= tempfile
.NamedTemporaryFile()
25 # we force TLS 1.3 because the session file gets updated when an existing ticket encrypted with an older key gets re-encrypted with the active key
26 # whereas in TLS 1.2 the existing ticket is written instead..
27 testcmd
= ['openssl', 's_client', '-tls1_3', '-CAfile', caFile
, '-connect', '%s:%d' % (addr
, port
), '-servername', serverName
, '-sess_out', outFile
.name
]
28 if ticketFileIn
and os
.path
.exists(ticketFileIn
):
29 testcmd
= testcmd
+ ['-sess_in', ticketFileIn
]
33 process
= subprocess
.Popen(testcmd
, stdout
=subprocess
.PIPE
, stdin
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, close_fds
=True)
34 # we need to wait just a bit so that the Post-Handshake New Session Ticket has the time to arrive..
36 output
= process
.communicate(input=b
'')
37 except subprocess
.CalledProcessError
as exc
:
38 raise AssertionError('%s failed (%d): %s' % (testcmd
, process
.returncode
, process
.output
))
40 if process
.returncode
!= 0:
41 raise AssertionError('%s failed (%d): %s' % (testcmd
, process
.returncode
, output
))
43 if os
.stat(outFile
.name
).st_size
== 0:
44 # if tickets have been disabled, or if the session ticket encryption key is exactly the same, we might not get a new ticket
46 raise AssertionError('%s failed (%d) to write a session to the output file: %s' % (testcmd
, process
.returncode
, output
))
48 shutil
.copyfile(outFile
.name
, ticketFileOut
)
50 for line
in output
[0].decode().splitlines():
51 if line
.startswith('Reused, TLSv1.'):
57 def generateTicketKeysFile(numberOfTickets
, outputFile
):
58 with
open(outputFile
, 'wb') as fp
:
59 fp
.write(os
.urandom(numberOfTickets
* 80))
61 @unittest.skipIf('SKIP_DOH_TESTS' in os
.environ
, 'DNS over HTTPS tests are disabled')
62 class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest
):
64 _serverKey
= 'server.key'
65 _serverCert
= 'server.chain'
66 _serverName
= 'tls.tests.dnsdist.org'
68 _dohWithNGHTTP2ServerPort
= pickAvailablePort()
69 _dohWithH2OServerPort
= pickAvailablePort()
71 _config_template
= """
72 newServer{address="127.0.0.1:%s"}
74 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='nghttp2' })
75 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='h2o' })
77 _config_params
= ['_testServerPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_numberOfKeys', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
79 def testNoSessionResumption(self
):
81 Session Resumption: DoH (disabled)
83 for port
in [self
._dohWithNGHTTP
2ServerPort
, self
._dohWithH
2OServerPort
]:
84 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.doh', None, allowNoTicket
=True))
85 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.doh', '/tmp/no-session.out.doh', allowNoTicket
=True))
87 @unittest.skipIf('SKIP_DOH_TESTS' in os
.environ
, 'DNS over HTTPS tests are disabled')
88 class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest
):
90 _serverKey
= 'server.key'
91 _serverCert
= 'server.chain'
92 _serverName
= 'tls.tests.dnsdist.org'
94 _dohWithNGHTTP2ServerPort
= pickAvailablePort()
95 _dohWithH2OServerPort
= pickAvailablePort()
97 _config_template
= """
99 controlSocket("127.0.0.1:%s")
100 newServer{address="127.0.0.1:%s"}
102 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='nghttp2' })
103 addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='h2o' })
105 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_numberOfKeys', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
107 def testSessionResumption(self
):
109 Session Resumption: DoH
111 for (port
, bindIdx
) in [(self
._dohWithNGHTTP
2ServerPort
, 0), (self
._dohWithH
2OServerPort
, 1)]:
112 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', None))
113 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
115 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
116 for _
in range(self
._numberOfKeys
- 1):
117 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):rotateTicketsKey()")
119 # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
120 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
122 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
123 for _
in range(self
._numberOfKeys
- 1):
124 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):rotateTicketsKey()")
126 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
128 # rotate the TLS session ticket keys several times, not keeping any key around this time!
129 for _
in range(self
._numberOfKeys
):
130 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):rotateTicketsKey()")
132 # we should not be able to resume
133 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
135 # generate a file containing _numberOfKeys ticket keys
136 self
.generateTicketKeysFile(self
._numberOfKeys
, '/tmp/ticketKeys.1')
137 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
138 # load all ticket keys from the file
139 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
141 # create a new session, resume it
142 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', None))
143 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
145 # reload the same keys
146 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
148 # should still be able to resume
149 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
151 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
152 for _
in range(self
._numberOfKeys
- 1):
153 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):rotateTicketsKey()")
154 # should still be able to resume
155 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
157 # reload the same keys
158 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
159 # since the last key was only present in memory, we should not be able to resume
160 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
163 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
165 # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
166 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
167 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.2')")
168 # we should be able to resume, and the ticket should be re-encrypted with the new key (NOTE THAT we store into a new file!!)
169 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh'))
170 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket
=True))
172 # rotate all keys, we should not be able to resume
173 for _
in range(self
._numberOfKeys
):
174 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):rotateTicketsKey()")
175 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh.3', '/tmp/session.doh.2'))
177 # reload from file 1, the old session should resume
178 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
179 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
181 # reload from file 2, the latest session should resume
182 self
.sendConsoleCommand(f
"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.2')")
183 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', port
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket
=True))
185 class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest
):
187 _serverKey
= 'server.key'
188 _serverCert
= 'server.chain'
189 _serverName
= 'tls.tests.dnsdist.org'
191 _tlsServerPort
= pickAvailablePort()
193 _config_template
= """
194 newServer{address="127.0.0.1:%s"}
196 addTLSLocal("127.0.0.1:%s", "%s", "%s", { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
198 _config_params
= ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
200 def testNoSessionResumption(self
):
202 Session Resumption: DoT (disabled)
204 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.dot', None, allowNoTicket
=True))
205 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.dot', '/tmp/no-session.out.dot', allowNoTicket
=True))
207 class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest
):
209 _serverKey
= 'server.key'
210 _serverCert
= 'server.chain'
211 _serverName
= 'tls.tests.dnsdist.org'
213 _tlsServerPort
= pickAvailablePort()
215 _config_template
= """
217 controlSocket("127.0.0.1:%s")
218 newServer{address="127.0.0.1:%s"}
220 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", numberOfTicketsKeys=%d })
222 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
224 def testSessionResumption(self
):
226 Session Resumption: DoT
228 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', None))
229 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
231 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
232 for _
in range(self
._numberOfKeys
- 1):
233 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
235 # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
236 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
238 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
239 for _
in range(self
._numberOfKeys
- 1):
240 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
242 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
244 # rotate the TLS session ticket keys several times, not keeping any key around this time!
245 for _
in range(self
._numberOfKeys
):
246 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
248 # we should not be able to resume
249 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
251 # generate a file containing _numberOfKeys ticket keys
252 self
.generateTicketKeysFile(self
._numberOfKeys
, '/tmp/ticketKeys.1')
253 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
254 # load all ticket keys from the file
255 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
257 # create a new session, resume it
258 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', None))
259 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
261 # reload the same keys
262 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
264 # should still be able to resume
265 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
267 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
268 for _
in range(self
._numberOfKeys
- 1):
269 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
270 # should still be able to resume
271 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
273 # reload the same keys
274 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
275 # since the last key was only present in memory, we should not be able to resume
276 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
279 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
281 # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
282 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
283 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')")
284 # we should be able to resume, and the ticket should be re-encrypted with the new key (NOTE THAT we store into a new file!!)
285 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot'))
286 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket
=True))
288 # rotate all keys, we should not be able to resume
289 for _
in range(self
._numberOfKeys
):
290 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
291 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.3', '/tmp/session.dot.2'))
293 # reload from file 1, the old session should resume
294 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
295 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
297 # reload from file 2, the latest session should resume
298 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')")
299 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket
=True))