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.
18 "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys"
22 if c
in ' \*?%~' or ord(c
)<ord(' ') or (c
== '.' and not candot
):
23 r
= r
+ ("%%%02X" % ord
(c
))
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
)
35 """Convert long int to lower-case hex.
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."""
42 plain
=string
.lower(re
.match(r
"0x([0-9A-Fa-f]*)l?$", hex(n
), re
.I
).group(1))
45 # Output REG file header.
46 sys
.stdout
.write("""REGEDIT4
48 [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
51 # Now process all known_hosts input.
52 for line
in fileinput
.input():
55 # Remove leading/trailing whitespace (should zap CR and LF)
56 line
= string
.strip (line
)
58 # Skip blanks and comments
59 if line
== '' or line
[0] == '#':
60 raise "Skipping input line"
62 # Split line on spaces.
63 fields
= string
.split (line
, ' ')
67 magicnumbers
= [] # placeholder
68 keytype
= "" # placeholder
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]):
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])
82 # Treat as SSH2-type host key.
83 # Format: hostpat keytype keyblob64 comment...
84 sshkeytype
, blob
= fields
[1], base64
.decodestring (fields
[2])
86 # 'blob' consists of a number of
87 # uint32 N (big-endian)
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
: ]
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:])
103 # Translate key type into something PuTTY can use.
104 if sshkeytype
== "ssh-rsa": keytype
= "rsa2"
105 elif sshkeytype
== "ssh-dss": keytype
= "dss"
107 raise "Unknown SSH key type", sshkeytype
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"
116 # Slightly bizarre registry key format: 'type@port:hostname'
117 # As far as I know, the input never specifies a port.
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
))
125 except "Unknown SSH key type", k
:
126 sys
.stderr
.write("Unknown SSH key type '%s', skipping\n" % k
)
127 except "Skipping input line":