Last active
October 16, 2017 21:52
-
-
Save akgood/99dc8b3c42a62bc943fcbe4a780b7a2c to your computer and use it in GitHub Desktop.
Some quick hacks to test your SSH pubkeys for the "ROCA" vuln (https://crocs.fi.muni.cz/public/papers/rsa_ccs17) without having to install any third-party python modules...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# run me as e.g. "ssh-add -L | python2.7 detect_roca_sshkeys.py" | |
import argparse | |
import base64 | |
import csv | |
import re | |
import struct | |
import sys | |
def parse_mpint(data): | |
""" parse OpenSSH "mpint" from https://tools.ietf.org/html/rfc4251, | |
that is, an arbitrary-precision 2s complement integer""" | |
# interpret the first byte as a signed char | |
msbyte = struct.unpack('b', data[0])[0] | |
# interpret the rest of the bytes as an arbitrary-length unsigned | |
# value. If data were unsigned, we could've just done this | |
data = data[1:] | |
if data: | |
val = int(data.encode('hex'), 16) | |
else: | |
val = 0 | |
# add the most significant byte back on | |
val |= msbyte << (8 * len(data)) | |
return val | |
def parse_ssh_key_line(line): | |
"""return tuple of (key algorithm, key data, comment)""" | |
fields = line.strip().split() | |
# skip "command=" and whatever else | |
for i, field in enumerate(fields): | |
if field in ('ssh-rsa', 'ssh-dss'): | |
fields = fields[i:] | |
break | |
return (fields[0], fields[1].decode('base64'), ' '.join(fields[2:])) | |
def parse_ssh_rsa_key(keydata): | |
"""return tuple of (public exponent, modulus) as integers""" | |
keydata_fields = [] | |
while keydata: | |
length = struct.unpack("!L", keydata[:4])[0] | |
keydata = keydata[4:] | |
keydata_fields.append(keydata[:length]) | |
keydata = keydata[length:] | |
assert len(keydata_fields) == 3 | |
assert(keydata_fields[0] == 'ssh-rsa') | |
return [parse_mpint(x) for x in keydata_fields[1:]] | |
class SSHKeyLine(object): | |
roca_primes = [ | |
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, | |
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, | |
151, 157, 163, 167] | |
roca_prints = [ | |
6, 30, 126, 1026, 5658, 107286, 199410, 8388606, 536870910, 2147483646, | |
67109890, 2199023255550, 8796093022206, 140737488355326, | |
5310023542746834, 576460752303423486, 1455791217086302986, | |
147573952589676412926, 20052041432995567486, 6041388139249378920330, | |
207530445072488465666, | |
9671406556917033397649406, | |
618970019642690137449562110, | |
79228162521181866724264247298, | |
2535301200456458802993406410750, | |
1760368345969468176824550810518, | |
50079290986288516948354744811034, | |
473022961816146413042658758988474, | |
10384593717069655257060992658440190, | |
144390480366845522447407333004847678774, | |
2722258935367507707706996859454145691646, | |
174224571863520493293247799005065324265470, | |
696898287454081973172991196020261297061886, | |
713623846352979940529142984724747568191373310, | |
1800793591454480341970779146165214289059119882, | |
126304807362733370595828809000324029340048915994, | |
11692013098647223345629478661730264157247460343806, | |
187072209578355573530071658587684226515959365500926] | |
def is_roca_key(self): | |
if self.m is None: | |
# not ssh-rsa | |
return False | |
for i in range(0, len(self.roca_primes)): | |
if (1 << (self.m % self.roca_primes[i])) & self.roca_prints[i] == 0: | |
return False | |
return True | |
def __init__(self, line): | |
self.line = line | |
(self.algorithm, self.keydata, self.comment) = parse_ssh_key_line(line) | |
self.e, self.m = None, None | |
if self.algorithm == 'ssh-rsa': | |
self.e, self.m = parse_ssh_rsa_key(self.keydata) | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
'--file', type=argparse.FileType('r'), default=sys.stdin) | |
args = parser.parse_args() | |
roca_count = 0 | |
for count, line in enumerate(args.file): | |
line = line.strip() | |
k = SSHKeyLine(line) | |
if k.is_roca_key(): | |
roca_count += 1 | |
attrs = { | |
'line': line, | |
'public_exponent': k.e, | |
'modulus': k.m, | |
'ROCA': True} | |
print attrs | |
print "Detected {} ROCA-impacted key(s) out of {} total".format(roca_count, count+1) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment