Package paramiko :: Module hostkeys
[frames] | no frames]

Source Code for Module paramiko.hostkeys

  1  # Copyright (C) 2006-2007  Robey Pointer <robey@lag.net> 
  2  # 
  3  # This file is part of paramiko. 
  4  # 
  5  # Paramiko is free software; you can redistribute it and/or modify it under the 
  6  # terms of the GNU Lesser General Public License as published by the Free 
  7  # Software Foundation; either version 2.1 of the License, or (at your option) 
  8  # any later version. 
  9  # 
 10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY 
 11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 13  # details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public License 
 16  # along with Paramiko; if not, write to the Free Software Foundation, Inc., 
 17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. 
 18   
 19  """ 
 20  L{HostKeys} 
 21  """ 
 22   
 23  import base64 
 24  from Crypto.Hash import SHA, HMAC 
 25  import UserDict 
 26   
 27  from paramiko.common import * 
 28  from paramiko.dsskey import DSSKey 
 29  from paramiko.rsakey import RSAKey 
 30   
 31   
32 -class HostKeyEntry:
33 """ 34 Representation of a line in an OpenSSH-style "known hosts" file. 35 """ 36
37 - def __init__(self, hostnames=None, key=None):
38 self.valid = (hostnames is not None) and (key is not None) 39 self.hostnames = hostnames 40 self.key = key
41
42 - def from_line(cls, line):
43 """ 44 Parses the given line of text to find the names for the host, 45 the type of key, and the key data. The line is expected to be in the 46 format used by the openssh known_hosts file. 47 48 Lines are expected to not have leading or trailing whitespace. 49 We don't bother to check for comments or empty lines. All of 50 that should be taken care of before sending the line to us. 51 52 @param line: a line from an OpenSSH known_hosts file 53 @type line: str 54 """ 55 fields = line.split(' ') 56 if len(fields) != 3: 57 # Bad number of fields 58 return None 59 60 names, keytype, key = fields 61 names = names.split(',') 62 63 # Decide what kind of key we're looking at and create an object 64 # to hold it accordingly. 65 if keytype == 'ssh-rsa': 66 key = RSAKey(data=base64.decodestring(key)) 67 elif keytype == 'ssh-dss': 68 key = DSSKey(data=base64.decodestring(key)) 69 else: 70 return None 71 72 return cls(names, key)
73 from_line = classmethod(from_line) 74
75 - def to_line(self):
76 """ 77 Returns a string in OpenSSH known_hosts file format, or None if 78 the object is not in a valid state. A trailing newline is 79 included. 80 """ 81 if self.valid: 82 return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(), 83 self.key.get_base64()) 84 return None
85
86 - def __repr__(self):
87 return '<HostKeyEntry %r: %r>' % (self.hostnames, self.key)
88 89
90 -class HostKeys (UserDict.DictMixin):
91 """ 92 Representation of an openssh-style "known hosts" file. Host keys can be 93 read from one or more files, and then individual hosts can be looked up to 94 verify server keys during SSH negotiation. 95 96 A HostKeys object can be treated like a dict; any dict lookup is equivalent 97 to calling L{lookup}. 98 99 @since: 1.5.3 100 """ 101
102 - def __init__(self, filename=None):
103 """ 104 Create a new HostKeys object, optionally loading keys from an openssh 105 style host-key file. 106 107 @param filename: filename to load host keys from, or C{None} 108 @type filename: str 109 """ 110 # emulate a dict of { hostname: { keytype: PKey } } 111 self._entries = [] 112 if filename is not None: 113 self.load(filename)
114
115 - def add(self, hostname, keytype, key):
116 """ 117 Add a host key entry to the table. Any existing entry for a 118 C{(hostname, keytype)} pair will be replaced. 119 120 @param hostname: the hostname (or IP) to add 121 @type hostname: str 122 @param keytype: key type (C{"ssh-rsa"} or C{"ssh-dss"}) 123 @type keytype: str 124 @param key: the key to add 125 @type key: L{PKey} 126 """ 127 for e in self._entries: 128 if (hostname in e.hostnames) and (e.key.get_name() == keytype): 129 e.key = key 130 return 131 self._entries.append(HostKeyEntry([hostname], key))
132
133 - def load(self, filename):
134 """ 135 Read a file of known SSH host keys, in the format used by openssh. 136 This type of file unfortunately doesn't exist on Windows, but on 137 posix, it will usually be stored in 138 C{os.path.expanduser("~/.ssh/known_hosts")}. 139 140 If this method is called multiple times, the host keys are merged, 141 not cleared. So multiple calls to C{load} will just call L{add}, 142 replacing any existing entries and adding new ones. 143 144 @param filename: name of the file to read host keys from 145 @type filename: str 146 147 @raise IOError: if there was an error reading the file 148 """ 149 f = open(filename, 'r') 150 for line in f: 151 line = line.strip() 152 if (len(line) == 0) or (line[0] == '#'): 153 continue 154 e = HostKeyEntry.from_line(line) 155 if e is not None: 156 self._entries.append(e) 157 f.close()
158
159 - def save(self, filename):
160 """ 161 Save host keys into a file, in the format used by openssh. The order of 162 keys in the file will be preserved when possible (if these keys were 163 loaded from a file originally). The single exception is that combined 164 lines will be split into individual key lines, which is arguably a bug. 165 166 @param filename: name of the file to write 167 @type filename: str 168 169 @raise IOError: if there was an error writing the file 170 171 @since: 1.6.1 172 """ 173 f = open(filename, 'w') 174 for e in self._entries: 175 line = e.to_line() 176 if line: 177 f.write(line) 178 f.close()
179
180 - def lookup(self, hostname):
181 """ 182 Find a hostkey entry for a given hostname or IP. If no entry is found, 183 C{None} is returned. Otherwise a dictionary of keytype to key is 184 returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}. 185 186 @param hostname: the hostname (or IP) to lookup 187 @type hostname: str 188 @return: keys associated with this host (or C{None}) 189 @rtype: dict(str, L{PKey}) 190 """ 191 class SubDict (UserDict.DictMixin): 192 def __init__(self, hostname, entries, hostkeys): 193 self._hostname = hostname 194 self._entries = entries 195 self._hostkeys = hostkeys
196 197 def __getitem__(self, key): 198 for e in self._entries: 199 if e.key.get_name() == key: 200 return e.key 201 raise KeyError(key)
202 203 def __setitem__(self, key, val): 204 for e in self._entries: 205 if e.key is None: 206 continue 207 if e.key.get_name() == key: 208 # replace 209 e.key = val 210 break 211 else: 212 # add a new one 213 e = HostKeyEntry([hostname], val) 214 self._entries.append(e) 215 self._hostkeys._entries.append(e) 216 217 def keys(self): 218 return [e.key.get_name() for e in self._entries if e.key is not None] 219 220 entries = [] 221 for e in self._entries: 222 for h in e.hostnames: 223 if (h.startswith('|1|') and (self.hash_host(hostname, h) == h)) or (h == hostname): 224 entries.append(e) 225 if len(entries) == 0: 226 return None 227 return SubDict(hostname, entries, self) 228
229 - def check(self, hostname, key):
230 """ 231 Return True if the given key is associated with the given hostname 232 in this dictionary. 233 234 @param hostname: hostname (or IP) of the SSH server 235 @type hostname: str 236 @param key: the key to check 237 @type key: L{PKey} 238 @return: C{True} if the key is associated with the hostname; C{False} 239 if not 240 @rtype: bool 241 """ 242 k = self.lookup(hostname) 243 if k is None: 244 return False 245 host_key = k.get(key.get_name(), None) 246 if host_key is None: 247 return False 248 return str(host_key) == str(key)
249
250 - def clear(self):
251 """ 252 Remove all host keys from the dictionary. 253 """ 254 self._entries = []
255
256 - def __getitem__(self, key):
257 ret = self.lookup(key) 258 if ret is None: 259 raise KeyError(key) 260 return ret
261
262 - def __setitem__(self, hostname, entry):
263 # don't use this please. 264 if len(entry) == 0: 265 self._entries.append(HostKeyEntry([hostname], None)) 266 return 267 for key_type in entry.keys(): 268 found = False 269 for e in self._entries: 270 if (hostname in e.hostnames) and (e.key.get_name() == key_type): 271 # replace 272 e.key = entry[key_type] 273 found = True 274 if not found: 275 self._entries.append(HostKeyEntry([hostname], entry[key_type]))
276
277 - def keys(self):
278 # python 2.4 sets would be nice here. 279 ret = [] 280 for e in self._entries: 281 for h in e.hostnames: 282 if h not in ret: 283 ret.append(h) 284 return ret
285
286 - def values(self):
287 ret = [] 288 for k in self.keys(): 289 ret.append(self.lookup(k)) 290 return ret
291
292 - def hash_host(hostname, salt=None):
293 """ 294 Return a "hashed" form of the hostname, as used by openssh when storing 295 hashed hostnames in the known_hosts file. 296 297 @param hostname: the hostname to hash 298 @type hostname: str 299 @param salt: optional salt to use when hashing (must be 20 bytes long) 300 @type salt: str 301 @return: the hashed hostname 302 @rtype: str 303 """ 304 if salt is None: 305 salt = randpool.get_bytes(SHA.digest_size) 306 else: 307 if salt.startswith('|1|'): 308 salt = salt.split('|')[2] 309 salt = base64.decodestring(salt) 310 assert len(salt) == SHA.digest_size 311 hmac = HMAC.HMAC(salt, hostname, SHA).digest() 312 hostkey = '|1|%s|%s' % (base64.encodestring(salt), base64.encodestring(hmac)) 313 return hostkey.replace('\n', '')
314 hash_host = staticmethod(hash_host) 315