98f4868c395a7d9efdb0d8be2804e2f8cb90783b
[u/mdw/putty] / contrib / kh2reg.py
1 #! /usr/bin/env python
2
3 # $Id: kh2reg.py,v 1.3 2003/10/21 13:26:12 jacob Exp $
4 # Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
5 # host keys.
6 # usage:
7 # kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg
8 # Creates a Windows .REG file (double-click to install).
9 # kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys
10 # Creates data suitable for storing in ~/.putty/sshhostkeys (Unix).
11 # Line endings are someone else's problem as is traditional.
12 # Developed for Python 1.5.2.
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 # Now process all known_hosts input.
68 for line in fileinput.input(args):
69
70 try:
71 # Remove leading/trailing whitespace (should zap CR and LF)
72 line = string.strip (line)
73
74 # Skip blanks and comments
75 if line == '' or line[0] == '#':
76 raise "Skipping input line"
77
78 # Split line on spaces.
79 fields = string.split (line, ' ')
80
81 # Common fields
82 hostpat = fields[0]
83 magicnumbers = [] # placeholder
84 keytype = "" # placeholder
85
86 # Grotty heuristic to distinguish known_hosts from known_hosts2:
87 # is second field entirely decimal digits?
88 if re.match (r"\d*$", fields[1]):
89
90 # Treat as SSH1-type host key.
91 # Format: hostpat bits10 exp10 mod10 comment...
92 # (PuTTY doesn't store the number of bits.)
93 magicnumbers = map (long, fields[2:4])
94 keytype = "rsa"
95
96 else:
97
98 # Treat as SSH2-type host key.
99 # Format: hostpat keytype keyblob64 comment...
100 sshkeytype, blob = fields[1], base64.decodestring (fields[2])
101
102 # 'blob' consists of a number of
103 # uint32 N (big-endian)
104 # uint8[N] field_data
105 subfields = []
106 while blob:
107 sizefmt = ">L"
108 (size,) = struct.unpack (sizefmt, blob[0:4])
109 size = int(size) # req'd for slicage
110 (data,) = struct.unpack (">%lus" % size, blob[4:size+4])
111 subfields.append(data)
112 blob = blob [struct.calcsize(sizefmt) + size : ]
113
114 # The first field is keytype again, and the rest we can treat as
115 # an opaque list of bignums (same numbers and order as stored
116 # by PuTTY). (currently embedded keytype is ignored entirely)
117 magicnumbers = map (strtolong, subfields[1:])
118
119 # Translate key type into something PuTTY can use.
120 if sshkeytype == "ssh-rsa": keytype = "rsa2"
121 elif sshkeytype == "ssh-dss": keytype = "dss"
122 else:
123 raise "Unknown SSH key type", sshkeytype
124
125 # Now print out one line per host pattern, discarding wildcards.
126 for host in string.split (hostpat, ','):
127 if re.search (r"[*?!]", host):
128 sys.stderr.write("Skipping wildcard host pattern '%s'\n"
129 % host)
130 continue
131 else:
132 # Slightly bizarre key format: 'type@port:hostname'
133 # As far as I know, the input never specifies a port.
134 port = 22
135 # XXX: does PuTTY do anything useful with literal IP[v4]s?
136 key = keytype + ("@%d:%s" % (port, host))
137 value = string.join (map (longtohex, magicnumbers), ',')
138 if output_type == 'unix':
139 # Unix format.
140 sys.stdout.write('%s %s\n' % (key, value))
141 else:
142 # Windows format.
143 # XXX: worry about double quotes?
144 sys.stdout.write("\"%s\"=\"%s\"\n"
145 % (winmungestr(key), value))
146
147 except "Unknown SSH key type", k:
148 sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k)
149 except "Skipping input line":
150 pass