4 # Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
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.
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 # Now process all known_hosts input.
68 for line
in fileinput
.input(args
):
71 # Remove leading/trailing whitespace (should zap CR and LF)
72 line
= string
.strip (line
)
74 # Skip blanks and comments
75 if line
== '' or line
[0] == '#':
76 raise "Skipping input line"
78 # Split line on spaces.
79 fields
= string
.split (line
, ' ')
83 magicnumbers
= [] # placeholder
84 keytype
= "" # placeholder
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]):
90 # Treat as SSH-1-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])
98 # Treat as SSH-2-type host key.
99 # Format: hostpat keytype keyblob64 comment...
100 sshkeytype
, blob
= fields
[1], base64
.decodestring (fields
[2])
102 # 'blob' consists of a number of
103 # uint32 N (big-endian)
104 # uint8[N] field_data
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
: ]
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:])
119 # Translate key type into something PuTTY can use.
120 if sshkeytype
== "ssh-rsa": keytype
= "rsa2"
121 elif sshkeytype
== "ssh-dss": keytype
= "dss"
123 raise "Unknown SSH key type", sshkeytype
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"
132 # Slightly bizarre key format: 'type@port:hostname'
133 # As far as I know, the input never specifies a port.
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':
140 sys
.stdout
.write('%s %s\n' %
(key
, value
))
143 # XXX: worry about double quotes?
144 sys
.stdout
.write("\"%s\"=\"%s\"\n"
145 %
(winmungestr(key
), value
))
147 except "Unknown SSH key type", k
:
148 sys
.stderr
.write("Unknown SSH key type '%s', skipping\n" % k
)
149 except "Skipping input line":