9 from dnsdisttests
import DNSDistTest
15 class DNSDistTLSSessionResumptionTest(DNSDistTest
):
17 _consoleKey
= DNSDistTest
.generateConsoleKey()
18 _consoleKeyB64
= base64
.b64encode(_consoleKey
).decode('ascii')
21 def checkSessionResumed(cls
, addr
, port
, serverName
, caFile
, ticketFileOut
, ticketFileIn
, allowNoTicket
=False):
22 outFile
= tempfile
.NamedTemporaryFile()
24 # 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
25 # whereas in TLS 1.2 the existing ticket is written instead..
26 testcmd
= ['openssl', 's_client', '-tls1_3', '-CAfile', caFile
, '-connect', '%s:%d' % (addr
, port
), '-servername', serverName
, '-sess_out', outFile
.name
]
27 if ticketFileIn
and os
.path
.exists(ticketFileIn
):
28 testcmd
= testcmd
+ ['-sess_in', ticketFileIn
]
32 process
= subprocess
.Popen(testcmd
, stdout
=subprocess
.PIPE
, stdin
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, close_fds
=True)
33 # we need to wait just a bit so that the Post-Handshake New Session Ticket has the time to arrive..
35 output
= process
.communicate(input=b
'')
36 except subprocess
.CalledProcessError
as exc
:
37 raise AssertionError('%s failed (%d): %s' % (testcmd
, process
.returncode
, process
.output
))
39 if process
.returncode
!= 0:
40 raise AssertionError('%s failed (%d): %s' % (testcmd
, process
.returncode
, output
))
42 if os
.stat(outFile
.name
).st_size
== 0:
43 # if tickets have been disabled, or if the session ticket encryption key is exactly the same, we might not get a new ticket
45 raise AssertionError('%s failed (%d) to write a session to the output file: %s' % (testcmd
, process
.returncode
, output
))
47 shutil
.copyfile(outFile
.name
, ticketFileOut
)
49 for line
in output
[0].decode().splitlines():
50 if line
.startswith('Reused, TLSv1.'):
56 def generateTicketKeysFile(numberOfTickets
, outputFile
):
57 with
open(outputFile
, 'wb') as fp
:
58 fp
.write(os
.urandom(numberOfTickets
* 80))
60 class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest
):
62 _serverKey
= 'server.key'
63 _serverCert
= 'server.chain'
64 _serverName
= 'tls.tests.dnsdist.org'
68 _config_template
= """
69 newServer{address="127.0.0.1:%s"}
71 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
73 _config_params
= ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
75 def testNoSessionResumption(self
):
77 Session Resumption: DoH (disabled)
79 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.doh', None, allowNoTicket
=True))
80 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))
82 class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest
):
84 _serverKey
= 'server.key'
85 _serverCert
= 'server.chain'
86 _serverName
= 'tls.tests.dnsdist.org'
90 _config_template
= """
92 controlSocket("127.0.0.1:%s")
93 newServer{address="127.0.0.1:%s"}
95 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d })
97 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
99 def testSessionResumption(self
):
101 Session Resumption: DoH
103 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', None))
104 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
106 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
107 for _
in range(self
._numberOfKeys
- 1):
108 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
110 # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
111 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
113 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
114 for _
in range(self
._numberOfKeys
- 1):
115 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
117 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
119 # rotate the TLS session ticket keys several times, not keeping any key around this time!
120 for _
in range(self
._numberOfKeys
):
121 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
123 # we should not be able to resume
124 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
126 # generate a file containing _numberOfKeys ticket keys
127 self
.generateTicketKeysFile(self
._numberOfKeys
, '/tmp/ticketKeys.1')
128 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
129 # load all ticket keys from the file
130 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
132 # create a new session, resume it
133 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', None))
134 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
136 # reload the same keys
137 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
139 # should still be able to resume
140 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
142 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
143 for _
in range(self
._numberOfKeys
- 1):
144 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
145 # should still be able to resume
146 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
148 # reload the same keys
149 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
150 # since the last key was only present in memory, we should not be able to resume
151 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh'))
154 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
156 # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
157 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
158 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
159 # 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!!)
160 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh'))
161 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket
=True))
163 # rotate all keys, we should not be able to resume
164 for _
in range(self
._numberOfKeys
):
165 self
.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
166 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.3', '/tmp/session.doh.2'))
168 # reload from file 1, the old session should resume
169 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
170 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket
=True))
172 # reload from file 2, the latest session should resume
173 self
.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
174 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._dohServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket
=True))
176 class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest
):
178 _serverKey
= 'server.key'
179 _serverCert
= 'server.chain'
180 _serverName
= 'tls.tests.dnsdist.org'
182 _tlsServerPort
= 8443
184 _config_template
= """
185 newServer{address="127.0.0.1:%s"}
187 addTLSLocal("127.0.0.1:%s", "%s", "%s", { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
189 _config_params
= ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
191 def testNoSessionResumption(self
):
193 Session Resumption: DoT (disabled)
195 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/no-session.out.dot', None, allowNoTicket
=True))
196 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))
198 class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest
):
200 _serverKey
= 'server.key'
201 _serverCert
= 'server.chain'
202 _serverName
= 'tls.tests.dnsdist.org'
204 _tlsServerPort
= 8443
206 _config_template
= """
208 controlSocket("127.0.0.1:%s")
209 newServer{address="127.0.0.1:%s"}
211 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", numberOfTicketsKeys=%d })
213 _config_params
= ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
215 def testSessionResumption(self
):
217 Session Resumption: DoT
219 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', None))
220 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
222 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
223 for _
in range(self
._numberOfKeys
- 1):
224 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
226 # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
227 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
229 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
230 for _
in range(self
._numberOfKeys
- 1):
231 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
233 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
235 # rotate the TLS session ticket keys several times, not keeping any key around this time!
236 for _
in range(self
._numberOfKeys
):
237 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
239 # we should not be able to resume
240 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
242 # generate a file containing _numberOfKeys ticket keys
243 self
.generateTicketKeysFile(self
._numberOfKeys
, '/tmp/ticketKeys.1')
244 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
245 # load all ticket keys from the file
246 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
248 # create a new session, resume it
249 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', None))
250 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
252 # reload the same keys
253 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
255 # should still be able to resume
256 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
258 # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
259 for _
in range(self
._numberOfKeys
- 1):
260 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
261 # should still be able to resume
262 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
264 # reload the same keys
265 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
266 # since the last key was only present in memory, we should not be able to resume
267 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot'))
270 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
272 # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
273 self
.generateTicketKeysFile(self
._numberOfKeys
- 1, '/tmp/ticketKeys.2')
274 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')")
275 # 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!!)
276 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot'))
277 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket
=True))
279 # rotate all keys, we should not be able to resume
280 for _
in range(self
._numberOfKeys
):
281 self
.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()")
282 self
.assertFalse(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.3', '/tmp/session.dot.2'))
284 # reload from file 1, the old session should resume
285 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
286 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket
=True))
288 # reload from file 2, the latest session should resume
289 self
.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')")
290 self
.assertTrue(self
.checkSessionResumed('127.0.0.1', self
._tlsServerPort
, self
._serverName
, self
._caCert
, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket
=True))