1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-25 01:02:24 +00:00

kh2reg.py: handle OpenSSH hashed hostnames.

Obviously we can't do that by inverting the hash function itself, but
if the user provides one or more host names on the command line that
they're expecting to appear in the file, we can at least compare the
stored hashes against those.
This commit is contained in:
Simon Tatham 2019-04-21 14:42:17 +01:00
parent 0842d4627e
commit 5f204d1ef1

View File

@ -19,6 +19,7 @@ import sys
import argparse import argparse
import itertools import itertools
import collections import collections
import hashlib
from functools import reduce from functools import reduce
def winmungestr(s): def winmungestr(s):
@ -52,6 +53,34 @@ def warn(s):
sys.stderr.write("%s:%d: %s\n" sys.stderr.write("%s:%d: %s\n"
% (fileinput.filename(), fileinput.filelineno(), s)) % (fileinput.filename(), fileinput.filelineno(), s))
class HMAC(object):
def __init__(self, hashclass, blocksize):
self.hashclass = hashclass
self.blocksize = blocksize
self.struct = struct.Struct(">{:d}B".format(self.blocksize))
def pad_key(self, key):
return key + b'\0' * (self.blocksize - len(key))
def xor_key(self, key, xor):
return self.struct.pack(*[b ^ xor for b in self.struct.unpack(key)])
def keyed_hash(self, key, padbyte, string):
return self.hashclass(self.xor_key(key, padbyte) + string).digest()
def compute(self, key, string):
if len(key) > self.blocksize:
key = self.hashclass(key).digest()
key = self.pad_key(key)
return self.keyed_hash(key, 0x5C, self.keyed_hash(key, 0x36, string))
def openssh_hashed_host_match(hashed_host, try_host):
if hashed_host.startswith(b'|1|'):
salt, expected = hashed_host[3:].split(b'|')
salt = base64.decodestring(salt)
expected = base64.decodestring(expected)
mac = HMAC(hashlib.sha1, 64)
else:
return False # unrecognised magic number prefix
return mac.compute(salt, try_host) == expected
def invert(n, p): def invert(n, p):
"""Compute inverse mod p.""" """Compute inverse mod p."""
if n % p == 0: if n % p == 0:
@ -172,7 +201,7 @@ class KeyFormatError(Exception):
def __init__(self, msg): def __init__(self, msg):
self.msg = msg self.msg = msg
def handle_line(line, output_formatter): def handle_line(line, output_formatter, try_hosts):
try: try:
# Remove leading/trailing whitespace (should zap CR and LF) # Remove leading/trailing whitespace (should zap CR and LF)
line = line.strip() line = line.strip()
@ -308,10 +337,17 @@ def handle_line(line, output_formatter):
if re.search (r"[*?!]", host): if re.search (r"[*?!]", host):
warn("skipping wildcard host pattern '%s'" % host) warn("skipping wildcard host pattern '%s'" % host)
continue continue
elif re.match (r"\|", host):
warn("skipping hashed hostname '%s'" % host) if re.match (r"\|", host):
continue for try_host in try_hosts:
if openssh_hashed_host_match(host.encode('ASCII'),
try_host.encode('UTF-8')):
host = try_host
break
else: else:
warn("unable to match hashed hostname '%s'" % host)
continue
m = re.match (r"\[([^]]*)\]:(\d*)$", host) m = re.match (r"\[([^]]*)\]:(\d*)$", host)
if m: if m:
(host, port) = m.group(1,2) (host, port) = m.group(1,2)
@ -385,15 +421,19 @@ def main():
parser.add_argument("-o", "--output", type=argparse.FileType("w"), parser.add_argument("-o", "--output", type=argparse.FileType("w"),
default=argparse.FileType("w")("-"), default=argparse.FileType("w")("-"),
help="Output file to write to (default stdout).") help="Output file to write to (default stdout).")
parser.add_argument("--hostname", action="append",
help="Host name(s) to try matching against hashed "
"host entries in input.")
parser.add_argument("infile", nargs="*", parser.add_argument("infile", nargs="*",
help="Input file(s) to read from (default stdin).") help="Input file(s) to read from (default stdin).")
parser.set_defaults(output_formatter_class=WindowsOutputFormatter) parser.set_defaults(output_formatter_class=WindowsOutputFormatter,
hostname=[])
args = parser.parse_args() args = parser.parse_args()
output_formatter = args.output_formatter_class(args.output) output_formatter = args.output_formatter_class(args.output)
output_formatter.header() output_formatter.header()
for line in fileinput.input(args.infile): for line in fileinput.input(args.infile):
handle_line(line, output_formatter) handle_line(line, output_formatter, args.hostname)
output_formatter.trailer() output_formatter.trailer()
if __name__ == "__main__": if __name__ == "__main__":