10 from dnsdisttests
import DNSDistTest
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'
70 _config_template
= """
71 newServer{address="127.0.0.1:%s"}
73 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
75 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
77 def testNoSessionResumption(self
):
79 Session Resumption: DoH (disabled)
81 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.doh', None, allowNoTicket
=True))
82 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.doh', '/tmp/no-session.out.doh', allowNoTicket
=True))
84 @unittest.skipIf('SKIP_DOH_TESTS' in os
.environ
, 'DNS over HTTPS tests are disabled')
85 class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest
):
87 _serverKey
= 'server.key'
88 _serverCert
= 'server.chain'
89 _serverName
= 'tls.tests.dnsdist.org'
93 _config_template
= """
95 controlSocket("127.0.0.1:%s")
96 newServer{address="127.0.0.1:%s"}
98 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d })
100 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
102 def testSessionResumption(self
):
104 Session Resumption: DoH
106 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', None))
107 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
109 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
110 for _
in range(self
._numberOfKeys
- 1):
111 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
113 # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
114 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
116 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
117 for _
in range(self
._numberOfKeys
- 1):
118 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
120 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
122 # rotate the TLS session ticket keys several times, not keeping any key around this time!
123 for _
in range(self
._numberOfKeys
):
124 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
126 # we should not be able to resume
127 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
129 # generate a file containing _numberOfKeys ticket keys
130 self
.generateTicketKeysFile(self
._numberOfKeys
, '/tmp/ticketKeys.1')
131 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
132 # load all ticket keys from the file
133 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
135 # create a new session, resume it
136 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', None))
137 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
139 # reload the same keys
140 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
142 # should still be able to resume
143 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
145 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
146 for _
in range(self
._numberOfKeys
- 1):
147 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
148 # should still be able to resume
149 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
151 # reload the same keys
152 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
153 # since the last key was only present in memory, we should not be able to resume
154 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
157 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
159 # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
160 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
161 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
162 # 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!!)
163 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh'))
164 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket
=True))
166 # rotate all keys, we should not be able to resume
167 for _
in range(self
._numberOfKeys
):
168 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
169 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.3', '/tmp/session.doh.2'))
171 # reload from file 1, the old session should resume
172 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
173 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
175 # reload from file 2, the latest session should resume
176 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
177 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket
=True))
179 class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest
):
181 _serverKey
= 'server.key'
182 _serverCert
= 'server.chain'
183 _serverName
= 'tls.tests.dnsdist.org'
185 _tlsServerPort
= 8443
187 _config_template
= """
188 newServer{address="127.0.0.1:%s"}
190 addTLSLocal("127.0.0.1:%s", "%s", "%s", { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
192 _config_params
= ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
194 def testNoSessionResumption(self
):
196 Session Resumption: DoT (disabled)
198 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.dot', None, allowNoTicket
=True))
199 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))
201 class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest
):
203 _serverKey
= 'server.key'
204 _serverCert
= 'server.chain'
205 _serverName
= 'tls.tests.dnsdist.org'
207 _tlsServerPort
= 8443
209 _config_template
= """
211 controlSocket("127.0.0.1:%s")
212 newServer{address="127.0.0.1:%s"}
214 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", numberOfTicketsKeys=%d })
216 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
218 def testSessionResumption(self
):
220 Session Resumption: DoT
222 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', None))
223 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
225 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
226 for _
in range(self
._numberOfKeys
- 1):
227 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
229 # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
230 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
232 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
233 for _
in range(self
._numberOfKeys
- 1):
234 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
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, not keeping any key around this time!
239 for _
in range(self
._numberOfKeys
):
240 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
242 # we should not be able to resume
243 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
245 # generate a file containing _numberOfKeys ticket keys
246 self
.generateTicketKeysFile(self
._numberOfKeys
, '/tmp/ticketKeys.1')
247 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
248 # load all ticket keys from the file
249 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
251 # create a new session, resume it
252 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', None))
253 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
255 # reload the same keys
256 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
258 # should still be able to resume
259 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
261 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
262 for _
in range(self
._numberOfKeys
- 1):
263 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
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'))
267 # reload the same keys
268 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
269 # since the last key was only present in memory, we should not be able to resume
270 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
273 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
275 # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
276 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
277 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')")
278 # 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!!)
279 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot'))
280 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket
=True))
282 # rotate all keys, we should not be able to resume
283 for _
in range(self
._numberOfKeys
):
284 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
285 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.3', '/tmp/session.dot.2'))
287 # reload from file 1, the old session should resume
288 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
289 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
291 # reload from file 2, the latest session should resume
292 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')")
293 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket
=True))