]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - contrib/kh2reg.py
Slightly less grotty script to convert OpenSSH known_hosts and known_hosts2
[PuTTY.git] / contrib / kh2reg.py
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