]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.dnsdist/test_TLSSessionResumption.py
Merge pull request #8795 from omoerbeek/rec-lua-docs-policytag
[thirdparty/pdns.git] / regression-tests.dnsdist / test_TLSSessionResumption.py
1 #!/usr/bin/env python
2 import base64
3 import dns
4 import os
5 import shutil
6 import subprocess
7 import tempfile
8 import time
9 import unittest
10 from dnsdisttests import DNSDistTest
11 try:
12 range = xrange
13 except NameError:
14 pass
15
16 class DNSDistTLSSessionResumptionTest(DNSDistTest):
17
18 _consoleKey = DNSDistTest.generateConsoleKey()
19 _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
20
21 @classmethod
22 def checkSessionResumed(cls, addr, port, serverName, caFile, ticketFileOut, ticketFileIn, allowNoTicket=False):
23 outFile = tempfile.NamedTemporaryFile()
24
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]
30
31 output = None
32 try:
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..
35 time.sleep(0.5)
36 output = process.communicate(input=b'')
37 except subprocess.CalledProcessError as exc:
38 raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, process.output))
39
40 if process.returncode != 0:
41 raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, output))
42
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
45 if not allowNoTicket:
46 raise AssertionError('%s failed (%d) to write a session to the output file: %s' % (testcmd, process.returncode, output))
47 else:
48 shutil.copyfile(outFile.name, ticketFileOut)
49
50 for line in output[0].decode().splitlines():
51 if line.startswith('Reused, TLSv1.'):
52 return True
53
54 return False
55
56 @staticmethod
57 def generateTicketKeysFile(numberOfTickets, outputFile):
58 with open(outputFile, 'wb') as fp:
59 fp.write(os.urandom(numberOfTickets * 80))
60
61 @unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
62 class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest):
63
64 _serverKey = 'server.key'
65 _serverCert = 'server.chain'
66 _serverName = 'tls.tests.dnsdist.org'
67 _caCert = 'ca.pem'
68 _dohServerPort = 8443
69 _numberOfKeys = 0
70 _config_template = """
71 newServer{address="127.0.0.1:%s"}
72
73 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
74 """
75 _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
76
77 def testNoSessionResumption(self):
78 """
79 Session Resumption: DoH (disabled)
80 """
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))
83
84 @unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
85 class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest):
86
87 _serverKey = 'server.key'
88 _serverCert = 'server.chain'
89 _serverName = 'tls.tests.dnsdist.org'
90 _caCert = 'ca.pem'
91 _dohServerPort = 8443
92 _numberOfKeys = 5
93 _config_template = """
94 setKey("%s")
95 controlSocket("127.0.0.1:%s")
96 newServer{address="127.0.0.1:%s"}
97
98 addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d })
99 """
100 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
101
102 def testSessionResumption(self):
103 """
104 Session Resumption: DoH
105 """
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))
108
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()")
112
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'))
115
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()")
119
120 self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
121
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()")
125
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'))
128
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')")
134
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))
138
139 # reload the same keys
140 self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
141
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))
144
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'))
150
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'))
155
156 # but now we can
157 self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
158
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))
165
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'))
170
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))
174
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))
178
179 class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest):
180
181 _serverKey = 'server.key'
182 _serverCert = 'server.chain'
183 _serverName = 'tls.tests.dnsdist.org'
184 _caCert = 'ca.pem'
185 _tlsServerPort = 8443
186 _numberOfKeys = 0
187 _config_template = """
188 newServer{address="127.0.0.1:%s"}
189
190 addTLSLocal("127.0.0.1:%s", "%s", "%s", { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
191 """
192 _config_params = ['_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
193
194 def testNoSessionResumption(self):
195 """
196 Session Resumption: DoT (disabled)
197 """
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))
200
201 class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest):
202
203 _serverKey = 'server.key'
204 _serverCert = 'server.chain'
205 _serverName = 'tls.tests.dnsdist.org'
206 _caCert = 'ca.pem'
207 _tlsServerPort = 8443
208 _numberOfKeys = 5
209 _config_template = """
210 setKey("%s")
211 controlSocket("127.0.0.1:%s")
212 newServer{address="127.0.0.1:%s"}
213
214 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", numberOfTicketsKeys=%d })
215 """
216 _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
217
218 def testSessionResumption(self):
219 """
220 Session Resumption: DoT
221 """
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))
224
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()")
228
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'))
231
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()")
235
236 self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot'))
237
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()")
241
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'))
244
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')")
250
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))
254
255 # reload the same keys
256 self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')")
257
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))
260
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'))
266
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'))
271
272 # but now we can
273 self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket=True))
274
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))
281
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'))
286
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))
290
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))