3 # Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
6 # kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg
7 # Creates a Windows .REG file (double-click to install).
8 # kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys
9 # Creates data suitable for storing in ~/.putty/sshhostkeys (Unix).
10 # Line endings are someone else's problem as is traditional.
11 # Originally developed for Python 1.5.2, but probably won't run on that
23 "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys"
27 if c in ' \*?%~' or ord(c)<ord(' ') or (c == '.' and not candot):
28 r = r + ("%%%02X" % ord(c))
35 "Convert arbitrary-length big-endian binary data to a Python long"
36 bytes = struct.unpack(">%luB" % len(s), s)
37 return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes)
40 """Convert long int to lower-case hex.
42 Ick, Python (at least in 1.5.2) doesn't appear to have a way to
43 turn a long int into an unadorned hex string -- % gets upset if the
44 number is too big, and raw hex() uses uppercase (sometimes), and
45 adds unwanted "0x...L" around it."""
47 plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1))
50 output_type = 'windows'
53 optlist, args = getopt.getopt(sys.argv[1:], '', [ 'win', 'unix' ])
54 if filter(lambda x: x[0] == '--unix', optlist):
56 except getopt.error, e:
57 sys.stderr.write(str(e) + "\n")
60 if output_type == 'windows':
61 # Output REG file header.
62 sys.stdout.write("""REGEDIT4
64 [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
67 class BlankInputLine(Exception):
70 class UnknownKeyType(Exception):
71 def __init__(self, keytype):
72 self.keytype = keytype
74 # Now process all known_hosts input.
75 for line in fileinput.input(args):
78 # Remove leading/trailing whitespace (should zap CR and LF)
79 line = string.strip (line)
81 # Skip blanks and comments
82 if line == '' or line[0] == '#':
85 # Split line on spaces.
86 fields = string.split (line, ' ')
90 magicnumbers = [] # placeholder
91 keytype = "" # placeholder
93 # Grotty heuristic to distinguish known_hosts from known_hosts2:
94 # is second field entirely decimal digits?
95 if re.match (r"\d*$", fields[1]):
97 # Treat as SSH-1-type host key.
98 # Format: hostpat bits10 exp10 mod10 comment...
99 # (PuTTY doesn't store the number of bits.)
100 magicnumbers = map (long, fields[2:4])
105 # Treat as SSH-2-type host key.
106 # Format: hostpat keytype keyblob64 comment...
107 sshkeytype, blob = fields[1], base64.decodestring (fields[2])
109 # 'blob' consists of a number of
110 # uint32 N (big-endian)
111 # uint8[N] field_data
115 (size,) = struct.unpack (sizefmt, blob[0:4])
116 size = int(size) # req'd for slicage
117 (data,) = struct.unpack (">%lus" % size, blob[4:size+4])
118 subfields.append(data)
119 blob = blob [struct.calcsize(sizefmt) + size : ]
121 # The first field is keytype again, and the rest we can treat as
122 # an opaque list of bignums (same numbers and order as stored
123 # by PuTTY). (currently embedded keytype is ignored entirely)
124 magicnumbers = map (strtolong, subfields[1:])
126 # Translate key type into something PuTTY can use.
127 if sshkeytype == "ssh-rsa": keytype = "rsa2"
128 elif sshkeytype == "ssh-dss": keytype = "dss"
130 raise UnknownKeyType(sshkeytype)
132 # Now print out one line per host pattern, discarding wildcards.
133 for host in string.split (hostpat, ','):
134 if re.search (r"[*?!]", host):
135 sys.stderr.write("Skipping wildcard host pattern '%s'\n"
138 elif re.match (r"\|", host):
139 sys.stderr.write("Skipping hashed hostname '%s'\n" % host)
142 m = re.match (r"\[([^]]*)\]:(\d*)$", host)
144 (host, port) = m.group(1,2)
148 # Slightly bizarre output key format: 'type@port:hostname'
149 # XXX: does PuTTY do anything useful with literal IP[v4]s?
150 key = keytype + ("@%d:%s" % (port, host))
151 value = string.join (map (longtohex, magicnumbers), ',')
152 if output_type == 'unix':
154 sys.stdout.write('%s %s\n' % (key, value))
157 # XXX: worry about double quotes?
158 sys.stdout.write("\"%s\"=\"%s\"\n"
159 % (winmungestr(key), value))
161 except UnknownKeyType, k:
162 sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k.keytype)
163 except BlankInputLine: