2d51c14f |
1 | #! /usr/bin/env python |
2 | |
3 | # $Id: kh2reg.py,v 1.1 2002/03/10 22:00:06 jacob Exp $ |
4 | # Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY |
5 | # host keys in a Windows .REG file (double-click to install). |
6 | # usage: hosts2reg.py known_hosts1 2 3 4 ... > hosts.reg |
7 | # Line endings are someone else's problem as is traditional. |
8 | # Developed for Python 1.5.2. |
9 | |
10 | import fileinput |
11 | import base64 |
12 | import struct |
13 | import string |
14 | import re |
15 | import sys |
16 | |
17 | def mungestr(s): |
18 | "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys" |
19 | candot = 0 |
20 | r = "" |
21 | for c in s: |
22 | if c in ' \*?%~' or ord(c)<ord(' ') or (c == '.' and not candot): |
23 | r = r + ("%%%02X" % ord(c)) |
24 | else: |
25 | r = r + c |
26 | candot = 1 |
27 | return r |
28 | |
29 | def strtolong(s): |
30 | "Convert arbitrary-length big-endian binary data to a Python long" |
31 | bytes = struct.unpack(">%luB" % len(s), s) |
32 | return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes) |
33 | |
34 | def longtohex(n): |
35 | """Convert long int to lower-case hex. |
36 | |
37 | Ick, Python (at least in 1.5.2) doesn't appear to have a way to |
38 | turn a long int into an unadorned hex string -- % gets upset if the |
39 | number is too big, and raw hex() uses uppercase (sometimes), and |
40 | adds unwanted "0x...L" around it.""" |
41 | |
42 | plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1)) |
43 | return "0x" + plain |
44 | |
45 | # Output REG file header. |
46 | sys.stdout.write("""REGEDIT4 |
47 | |
48 | [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys] |
49 | """) |
50 | |
51 | # Now process all known_hosts input. |
52 | for line in fileinput.input(): |
53 | |
54 | try: |
55 | # Remove leading/trailing whitespace (should zap CR and LF) |
56 | line = string.strip (line) |
57 | |
58 | # Skip blanks and comments |
59 | if line == '' or line[0] == '#': |
60 | raise "Skipping input line" |
61 | |
62 | # Split line on spaces. |
63 | fields = string.split (line, ' ') |
64 | |
65 | # Common fields |
66 | hostpat = fields[0] |
67 | magicnumbers = [] # placeholder |
68 | keytype = "" # placeholder |
69 | |
70 | # Grotty heuristic to distinguish known_hosts from known_hosts2: |
71 | # is second field entirely decimal digits? |
72 | if re.match (r"\d*$", fields[1]): |
73 | |
74 | # Treat as SSH1-type host key. |
75 | # Format: hostpat bits10 exp10 mod10 comment... |
76 | # (PuTTY doesn't store the number of bits.) |
77 | magicnumbers = map (long, fields[2:4]) |
78 | keytype = "rsa" |
79 | |
80 | else: |
81 | |
82 | # Treat as SSH2-type host key. |
83 | # Format: hostpat keytype keyblob64 comment... |
84 | sshkeytype, blob = fields[1], base64.decodestring (fields[2]) |
85 | |
86 | # 'blob' consists of a number of |
87 | # uint32 N (big-endian) |
88 | # uint8[N] field_data |
89 | subfields = [] |
90 | while blob: |
91 | sizefmt = ">L" |
92 | (size,) = struct.unpack (sizefmt, blob[0:4]) |
93 | size = int(size) # req'd for slicage |
94 | (data,) = struct.unpack (">%lus" % size, blob[4:size+4]) |
95 | subfields.append(data) |
96 | blob = blob [struct.calcsize(sizefmt) + size : ] |
97 | |
98 | # The first field is keytype again, and the rest we can treat as |
99 | # an opaque list of bignums (same numbers and order as stored |
100 | # by PuTTY). (currently embedded keytype is ignored entirely) |
101 | magicnumbers = map (strtolong, subfields[1:]) |
102 | |
103 | # Translate key type into something PuTTY can use. |
104 | if sshkeytype == "ssh-rsa": keytype = "rsa2" |
105 | elif sshkeytype == "ssh-dss": keytype = "dss" |
106 | else: |
107 | raise "Unknown SSH key type", sshkeytype |
108 | |
109 | # Now print out one line per host pattern, discarding wildcards. |
110 | for host in string.split (hostpat, ','): |
111 | if re.search (r"[*?!]", host): |
112 | sys.stderr.write("Skipping wildcard host pattern '%s'\n" |
113 | % host) |
114 | continue |
115 | else: |
116 | # Slightly bizarre registry key format: 'type@port:hostname' |
117 | # As far as I know, the input never specifies a port. |
118 | port = 22 |
119 | # XXX: does PuTTY do anything useful with literal IP[v4]s? |
120 | key = keytype + ("@%d:%s" % (port, host)) |
121 | value = string.join (map (longtohex, magicnumbers), ',') |
122 | # XXX: worry about double quotes? |
123 | sys.stdout.write("\"%s\"=\"%s\"\n" % (mungestr(key), value)) |
124 | |
125 | except "Unknown SSH key type", k: |
126 | sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k) |
127 | except "Skipping input line": |
128 | pass |