From 314264784a46d22255c00e41b84ce1b12adf3c2f Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Tue, 8 Oct 2019 16:14:32 +0200 Subject: [PATCH] dnsdist: Check that tickets have been written when needed But they might not have been, especially when a session has been resumed and it was encrypted with a Session Ticket Encryption Key still active. --- .../test_TLSSessionResumption.py | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/regression-tests.dnsdist/test_TLSSessionResumption.py b/regression-tests.dnsdist/test_TLSSessionResumption.py index 2c5b615fb..b6c40790b 100644 --- a/regression-tests.dnsdist/test_TLSSessionResumption.py +++ b/regression-tests.dnsdist/test_TLSSessionResumption.py @@ -2,7 +2,9 @@ import base64 import dns import os +import shutil import subprocess +import tempfile import time from dnsdisttests import DNSDistTest try: @@ -16,10 +18,12 @@ class DNSDistTLSSessionResumptionTest(DNSDistTest): _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') @classmethod - def checkSessionResumed(cls, addr, port, serverName, caFile, ticketFileOut, ticketFileIn): + def checkSessionResumed(cls, addr, port, serverName, caFile, ticketFileOut, ticketFileIn, allowNoTicket=False): + outFile = tempfile.NamedTemporaryFile() + # 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 # whereas in TLS 1.2 the existing ticket is written instead.. - testcmd = ['openssl', 's_client', '-tls1_3', '-CAfile', caFile, '-connect', '%s:%d' % (addr, port), '-servername', serverName, '-sess_out', ticketFileOut] + testcmd = ['openssl', 's_client', '-tls1_3', '-CAfile', caFile, '-connect', '%s:%d' % (addr, port), '-servername', serverName, '-sess_out', outFile.name] if ticketFileIn and os.path.exists(ticketFileIn): testcmd = testcmd + ['-sess_in', ticketFileIn] @@ -30,7 +34,17 @@ class DNSDistTLSSessionResumptionTest(DNSDistTest): time.sleep(0.1) output = process.communicate(input=b'') except subprocess.CalledProcessError as exc: - raise AssertionError('dnsdist --check-config failed (%d): %s' % (exc.returncode, exc.output)) + raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, process.output)) + + if process.returncode != 0: + raise AssertionError('%s failed (%d): %s' % (testcmd, process.returncode, output)) + + if os.stat(outFile.name).st_size == 0: + # if tickets have been disabled, or if the session ticket encryption key is exactly the same, we might not get a new ticket + if not allowNoTicket: + raise AssertionError('%s failed (%d) to write a session to the output file: %s' % (testcmd, process.returncode, output)) + else: + shutil.copyfile(outFile.name, ticketFileOut) for line in output[0].decode().splitlines(): if line.startswith('Reused, TLSv1.'): @@ -62,8 +76,8 @@ class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): """ Session Resumption: DoH (disabled) """ - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.out', None)) - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.out', '/tmp/session.out')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/no-session.out.doh', None, allowNoTicket=True)) + 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)) class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): @@ -86,28 +100,28 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): """ Session Resumption: DoH """ - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', None)) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', None)) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True)) # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume for _ in range(self._numberOfKeys - 1): self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()") # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh')) # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume for _ in range(self._numberOfKeys - 1): self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()") - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh')) # rotate the TLS session ticket keys several times, not keeping any key around this time! for _ in range(self._numberOfKeys): self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()") # we should not be able to resume - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh')) # generate a file containing _numberOfKeys ticket keys self.generateTicketKeysFile(self._numberOfKeys, '/tmp/ticketKeys.1') @@ -116,48 +130,48 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')") # create a new session, resume it - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', None)) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', None)) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True)) # reload the same keys self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')") # should still be able to resume - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True)) # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume for _ in range(self._numberOfKeys - 1): self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()") # should still be able to resume - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh')) # reload the same keys self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')") # since the last key was only present in memory, we should not be able to resume - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh')) # but now we can - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True)) # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2') self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')") # 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!!) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.2', '/tmp/session')) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.2', '/tmp/session.2')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True)) # rotate all keys, we should not be able to resume for _ in range(self._numberOfKeys): self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()") - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.3', '/tmp/session.2')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.3', '/tmp/session.doh.2')) # reload from file 1, the old session should resume self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')") - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True)) # reload from file 2, the latest session should resume self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')") - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.2', '/tmp/session.2')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True)) class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest): @@ -178,8 +192,8 @@ class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest): """ Session Resumption: DoT (disabled) """ - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.out', None)) - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.out', '/tmp/session.out')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/no-session.out.dot', None, allowNoTicket=True)) + 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)) class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest): @@ -202,28 +216,28 @@ class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest): """ Session Resumption: DoT """ - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', None)) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', None)) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket=True)) # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume for _ in range(self._numberOfKeys - 1): self.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()") # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot')) # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume for _ in range(self._numberOfKeys - 1): self.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()") - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot')) # rotate the TLS session ticket keys several times, not keeping any key around this time! for _ in range(self._numberOfKeys): self.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()") # we should not be able to resume - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot')) # generate a file containing _numberOfKeys ticket keys self.generateTicketKeysFile(self._numberOfKeys, '/tmp/ticketKeys.1') @@ -232,45 +246,45 @@ class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest): self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')") # create a new session, resume it - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', None)) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', None)) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket=True)) # reload the same keys self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')") # should still be able to resume - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket=True)) # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume for _ in range(self._numberOfKeys - 1): self.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()") # should still be able to resume - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot')) # reload the same keys self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')") # since the last key was only present in memory, we should not be able to resume - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot')) # but now we can - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket=True)) # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2') self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')") # 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!!) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.2', '/tmp/session')) - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.2', '/tmp/session.2')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot.2', '/tmp/session.dot')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket=True)) # rotate all keys, we should not be able to resume for _ in range(self._numberOfKeys): self.sendConsoleCommand("getTLSContext(0):rotateTicketsKey()") - self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.3', '/tmp/session.2')) + self.assertFalse(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot.3', '/tmp/session.dot.2')) # reload from file 1, the old session should resume self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.1')") - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session', '/tmp/session')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot', '/tmp/session.dot', allowNoTicket=True)) # reload from file 2, the latest session should resume self.sendConsoleCommand("getTLSContext(0):loadTicketsKeys('/tmp/ticketKeys.2')") - self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.2', '/tmp/session.2')) + self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket=True)) -- 2.50.1