]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - contrib/kh2reg.py
Make kh2reg.py compatible with modern Python.
[PuTTY.git] / contrib / kh2reg.py
1 #! /usr/bin/env python
2
3 # Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
4 # host keys.
5 #   usage:
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
12 # any more.
13
14 import fileinput
15 import base64
16 import struct
17 import string
18 import re
19 import sys
20 import getopt
21
22 def winmungestr(s):
23     "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys"
24     candot = 0
25     r = ""
26     for c in s:
27         if c in ' \*?%~' or ord(c)<ord(' ') or (c == '.' and not candot):
28             r = r + ("%%%02X" % ord(c))
29         else:
30             r = r + c
31         candot = 1
32     return r
33
34 def strtolong(s):
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)
38
39 def longtohex(n):
40     """Convert long int to lower-case hex.
41
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."""
46
47     plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1))
48     return "0x" + plain
49
50 output_type = 'windows'
51
52 try:
53     optlist, args = getopt.getopt(sys.argv[1:], '', [ 'win', 'unix' ])
54     if filter(lambda x: x[0] == '--unix', optlist):
55         output_type = 'unix'
56 except getopt.error, e:
57     sys.stderr.write(str(e) + "\n")
58     sys.exit(1)
59
60 if output_type == 'windows':
61     # Output REG file header.
62     sys.stdout.write("""REGEDIT4
63
64 [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
65 """)
66
67 class BlankInputLine(Exception):
68     pass
69
70 class UnknownKeyType(Exception):
71    def __init__(self, keytype):
72        self.keytype = keytype
73
74 # Now process all known_hosts input.
75 for line in fileinput.input(args):
76
77     try:
78         # Remove leading/trailing whitespace (should zap CR and LF)
79         line = string.strip (line)
80
81         # Skip blanks and comments
82         if line == '' or line[0] == '#':
83             raise BlankInputLine
84
85         # Split line on spaces.
86         fields = string.split (line, ' ')
87
88         # Common fields
89         hostpat = fields[0]
90         magicnumbers = []   # placeholder
91         keytype = ""        # placeholder
92
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]):
96
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])
101             keytype = "rsa"
102
103         else:
104
105             # Treat as SSH-2-type host key.
106             # Format: hostpat keytype keyblob64 comment...
107             sshkeytype, blob = fields[1], base64.decodestring (fields[2])
108
109             # 'blob' consists of a number of
110             #   uint32    N (big-endian)
111             #   uint8[N]  field_data
112             subfields = []
113             while blob:
114                 sizefmt = ">L"
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 : ]
120
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:])
125
126             # Translate key type into something PuTTY can use.
127             if   sshkeytype == "ssh-rsa":   keytype = "rsa2"
128             elif sshkeytype == "ssh-dss":   keytype = "dss"
129             else:
130                 raise UnknownKeyType(sshkeytype)
131
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"
136                                  % host)
137                 continue
138             elif re.match (r"\|", host):
139                 sys.stderr.write("Skipping hashed hostname '%s'\n" % host)
140                 continue
141             else:
142                 m = re.match (r"\[([^]]*)\]:(\d*)$", host)
143                 if m:
144                     (host, port) = m.group(1,2)
145                     port = int(port)
146                 else:
147                     port = 22
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':
153                     # Unix format.
154                     sys.stdout.write('%s %s\n' % (key, value))
155                 else:
156                     # Windows format.
157                     # XXX: worry about double quotes?
158                     sys.stdout.write("\"%s\"=\"%s\"\n"
159                                      % (winmungestr(key), value))
160
161     except UnknownKeyType, k:
162         sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k.keytype)
163     except BlankInputLine:
164         pass