Skip to content

Commit

Permalink
Merge pull request #695 from tomato42/more-rsa-tests
Browse files Browse the repository at this point in the history
More RSA timing tests
  • Loading branch information
tomato42 committed Sep 8, 2020
2 parents ac1b278 + 5ec77a0 commit fa705db
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 85 deletions.
5 changes: 5 additions & 0 deletions build-requirements-2.7.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage
coveralls
pylint
diff_cover<4.0.0
mock>2.0.0
208 changes: 130 additions & 78 deletions scripts/test-bleichenbacher-timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
from tlslite.constants import CipherSuite, AlertLevel, AlertDescription, \
ExtensionType
from tlslite.utils.dns_utils import is_valid_hostname
from tlslite.extensions import SNIExtension
from tlslite.extensions import SNIExtension, SignatureAlgorithmsCertExtension,\
SignatureAlgorithmsExtension
from tlsfuzzer.utils.lists import natural_sort_keys
from tlsfuzzer.utils.ordered_dict import OrderedDict
from tlsfuzzer.helpers import SIG_ALL, RSA_PKCS1_ALL

version = 7

version = 10


def help_msg():
Expand Down Expand Up @@ -161,6 +164,10 @@ def main():
if is_valid_hostname(host) and not no_sni:
cln_extensions[ExtensionType.server_name] = \
SNIExtension().create(bytearray(host, 'ascii'))
cln_extensions[ExtensionType.signature_algorithms] = \
SignatureAlgorithmsExtension().create(RSA_PKCS1_ALL)
cln_extensions[ExtensionType.signature_algorithms_cert] = \
SignatureAlgorithmsCertExtension().create(SIG_ALL)

# RSA key exchange check
if cipher not in CipherSuite.certSuites:
Expand Down Expand Up @@ -226,51 +233,8 @@ def main():

conversations["sanity - static non-zero byte in random padding"] = conversation

# first put tests that are the benchmark to be compared to
# fuzz MAC in the Finshed message to make decryption fail
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
node = node.add_child(ClientHelloGenerator(ciphers,
extensions=cln_extensions))
node = node.add_child(ExpectServerHello(extensions=srv_extensions))

node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator())
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(fuzz_mac(FinishedGenerator(), xors={0:0xff}))
node = node.add_child(TCPBufferingDisable())
node = node.add_child(TCPBufferingFlush())
node = node.add_child(ExpectAlert(level,
alert))
node.add_child(ExpectClose())

conversations["invalid MAC in Finished on pos 0"] = conversation

conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
node = node.add_child(ClientHelloGenerator(ciphers,
extensions=cln_extensions))
node = node.add_child(ExpectServerHello(extensions=srv_extensions))

node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator())
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(fuzz_mac(FinishedGenerator(), xors={-1:0xff}))
node = node.add_child(TCPBufferingDisable())
node = node.add_child(TCPBufferingFlush())
node = node.add_child(ExpectAlert(level,
alert))
node.add_child(ExpectClose())

conversations["invalid MAC in Finished on pos -1"] = conversation

# and for good measure, add something that sends invalid padding
# create a CKE with PMS the runner doesn't know/use
# (benchmark to measure other tests to)
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
Expand All @@ -281,19 +245,23 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator())
# use too short PMS but then change padding so that the PMS is
# correct length with correct TLS version but the encryption keys
# that tlsfuzzer calculates will be incorrect
node = node.add_child(ClientKeyExchangeGenerator(
padding_subs={-3: 0, -2: 3, -1: 3},
premaster_secret=bytearray([0] * 46)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(fuzz_padding(FinishedGenerator(),
xors={-1:0xff, -2:0x01}))
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
node = node.add_child(TCPBufferingFlush())
node = node.add_child(ExpectAlert(level,
alert))
node.add_child(ExpectClose())

conversations["invalid padding_length in Finished"] = conversation
conversations["fuzzed pre master secret"] = conversation

# create a CKE with PMS the runner doesn't know/use
# set 2nd byte of padding to 3 (invalid value)
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
Expand All @@ -304,12 +272,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
# use too short PMS but then change padding so that the PMS is
# correct length with correct TLS version but the encryption keys
# that tlsfuzzer calculates will be incorrect
node = node.add_child(ClientKeyExchangeGenerator(
padding_subs={-3: 0, -2: 3, -1: 3},
premaster_secret=bytearray([1] * 46)))
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={1: 3}))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -318,9 +281,9 @@ def main():
alert))
node.add_child(ExpectClose())

conversations["fuzzed pre master secret"] = conversation
conversations["set PKCS#1 padding type to 3"] = conversation

# set 2nd byte of padding to 3 (invalid value)
# set 2nd byte of padding to 1 (signing)
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
Expand All @@ -331,7 +294,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={1: 3}))
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={1: 1}))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -340,9 +303,9 @@ def main():
alert))
node.add_child(ExpectClose())

conversations["set PKCS#1 padding type to 3"] = conversation
conversations["set PKCS#1 padding type to 1"] = conversation

# set 2nd byte of padding to 1 (signing)
# use the padding for signing (type 1)
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
Expand All @@ -353,7 +316,8 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={1: 1}))
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={1: 1},
padding_byte=0xff))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -362,7 +326,7 @@ def main():
alert))
node.add_child(ExpectClose())

conversations["set PKCS#1 padding type to 1"] = conversation
conversations["use PKCS#1 padding type 1"] = conversation

# test early zero in random data
conversation = Connect(host, port)
Expand Down Expand Up @@ -487,7 +451,7 @@ def main():
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={-1: 1},
premaster_secret=bytearray([1] * 48)))
premaster_secret=bytearray([3, 3])))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand Down Expand Up @@ -581,7 +545,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([1] * 47)))
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([0] * 47)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -603,7 +567,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([1] * 4)))
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([0] * 4)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -625,7 +589,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([1] * 49)))
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([0] * 49)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -647,7 +611,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([1] * 124)))
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([0] * 124)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand All @@ -668,7 +632,7 @@ def main():
node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([1] * 96)))
node = node.add_child(ClientKeyExchangeGenerator(premaster_secret=bytearray([0] * 96)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
Expand Down Expand Up @@ -800,6 +764,83 @@ def main():

conversations["too long PKCS padding"] = conversation

# test for Hamming weight sensitivity:
# very low Hamming weight:
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
node = node.add_child(ClientHelloGenerator(ciphers,
extensions=cln_extensions))
node = node.add_child(ExpectServerHello(extensions=srv_extensions))

node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(padding_byte=0,
client_version=(0, 0)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
node = node.add_child(TCPBufferingFlush())
node = node.add_child(ExpectAlert(level,
alert))
node.add_child(ExpectClose())

conversations["very low Hamming weight RSA plaintext"] = conversation

# low Hamming weight:
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
node = node.add_child(ClientHelloGenerator(ciphers,
extensions=cln_extensions))
node = node.add_child(ExpectServerHello(extensions=srv_extensions))

node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={-1: 1},
padding_byte=1,
client_version=(1, 1),
premaster_secret=
bytearray([1]*48)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
node = node.add_child(TCPBufferingFlush())
node = node.add_child(ExpectAlert(level,
alert))
node.add_child(ExpectClose())

conversations["low Hamming weight RSA plaintext"] = conversation

# test for Hamming weight sensitivity:
# very high Hamming weight:
conversation = Connect(host, port)
node = conversation
ciphers = [cipher]
node = node.add_child(ClientHelloGenerator(ciphers,
extensions=cln_extensions))
node = node.add_child(ExpectServerHello(extensions=srv_extensions))

node = node.add_child(ExpectCertificate())
node = node.add_child(ExpectServerHelloDone())
node = node.add_child(TCPBufferingEnable())
node = node.add_child(ClientKeyExchangeGenerator(padding_subs={-1: 0xff},
padding_byte=0xff,
client_version=(0xff, 0xff),
premaster_secret=
bytearray([0xff]*48)))
node = node.add_child(ChangeCipherSpecGenerator())
node = node.add_child(FinishedGenerator())
node = node.add_child(TCPBufferingDisable())
node = node.add_child(TCPBufferingFlush())
node = node.add_child(ExpectAlert(level,
alert))
node.add_child(ExpectClose())

conversations["very high Hamming weight RSA plaintext"] = conversation

# run the conversation
good = 0
bad = 0
Expand Down Expand Up @@ -897,15 +938,14 @@ def main():
All tests should exhibit the same kind of timing behaviour, but
if some groups of tests are inconsistent, that points to likely
place where the timing leak happens:
- the control group, lack of consistency here points to Lucky 13:
- 'invalid MAC in Finished on pos 0'
- 'invalid MAC in Finished on pos -1'
- the control test case:
- 'fuzzed pre master secret' - this will end up with random
plaintexts in record with Finished, most resembling a randomly
selected PMS by the server
verification:
- padding type verification:
- 'set PKCS#1 padding type to 3'
- 'set PKCS#1 padding type to 1'
- 'use PKCS#1 padding type 1'
- incorrect size of encrypted value (pre-master secret),
inconsistent results here suggests that the decryption leaks
length of plaintext:
Expand Down Expand Up @@ -945,7 +985,17 @@ def main():
- invalid TLS version in PMS, differences here suggest a leak in
code checking for correctness of this value:
- 'wrong TLS version (2, 2) in pre master secret'
- 'wrong TLS version (0, 0) in pre master secret'""")
- 'wrong TLS version (0, 0) in pre master secret'
- plaintext with specific Hamming weights, start with 0x00 and 0x02 bytes
but then switch to special plaintext, differences here suggest a leak
happening in the maths library:
- 'very low Hamming weight RSA plaintext' - padding, TLS version and PMS
are all zero bytes
- 'very high Hamming weight RSA plaintext' - padding, padding separator, TLS
version and PMS are all 0xff bytes
- 'use PKCS#1 padding type 1' - here the padding will be all 0xff bytes
- 'low Hamming weight RSA plaintext' - padding, padding separator, TLS
version and PMS are all 0x01 bytes""")
print(20 * '=')
print("version: {0}".format(version))
print(20 * '=')
Expand All @@ -968,8 +1018,10 @@ def main():
elif timing:
# if regular tests passed, run timing collection and analysis
if TimingRunner.check_tcpdump():
timing_runner = TimingRunner("{0}_{1}".format(sys.argv[0],
CipherSuite.ietfNames[cipher]),
timing_runner = TimingRunner("{0}_v{1}_{2}".format(
sys.argv[0],
version,
CipherSuite.ietfNames[cipher]),
sampled_tests,
outdir,
host,
Expand Down
8 changes: 5 additions & 3 deletions scripts/test-lucky13.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,11 @@ def main():
elif timing:
# if regular tests passed, run timing collection and analysis
if TimingRunner.check_tcpdump():
timing_runner = TimingRunner("{0}_{1}_{2}".format(sys.argv[0],
group_name,
CipherSuite.ietfNames[cipher]),
timing_runner = TimingRunner("{0}_v{1}_{2}_{3}".format(
sys.argv[0],
version,
group_name,
CipherSuite.ietfNames[cipher]),
sampled_tests,
outdir,
host,
Expand Down

0 comments on commit fa705db

Please sign in to comment.