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

Source Code for Module paramiko.pkey

  1  # Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com> 
  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  Common API for all public keys. 
 21  """ 
 22   
 23  import base64 
 24  from binascii import hexlify, unhexlify 
 25  import os 
 26   
 27  from Crypto.Hash import MD5 
 28  from Crypto.Cipher import DES3 
 29   
 30  from paramiko.common import * 
 31  from paramiko import util 
 32  from paramiko.message import Message 
 33  from paramiko.ssh_exception import SSHException, PasswordRequiredException 
 34   
 35   
36 -class PKey (object):
37 """ 38 Base class for public keys. 39 """ 40 41 # known encryption types for private key files: 42 _CIPHER_TABLE = { 43 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC } 44 } 45 46
47 - def __init__(self, msg=None, data=None):
48 """ 49 Create a new instance of this public key type. If C{msg} is given, 50 the key's public part(s) will be filled in from the message. If 51 C{data} is given, the key's public part(s) will be filled in from 52 the string. 53 54 @param msg: an optional SSH L{Message} containing a public key of this 55 type. 56 @type msg: L{Message} 57 @param data: an optional string containing a public key of this type 58 @type data: str 59 60 @raise SSHException: if a key cannot be created from the C{data} or 61 C{msg} given, or no key was passed in. 62 """ 63 pass
64
65 - def __str__(self):
66 """ 67 Return a string of an SSH L{Message} made up of the public part(s) of 68 this key. This string is suitable for passing to L{__init__} to 69 re-create the key object later. 70 71 @return: string representation of an SSH key message. 72 @rtype: str 73 """ 74 return ''
75
76 - def __cmp__(self, other):
77 """ 78 Compare this key to another. Returns 0 if this key is equivalent to 79 the given key, or non-0 if they are different. Only the public parts 80 of the key are compared, so a public key will compare equal to its 81 corresponding private key. 82 83 @param other: key to compare to. 84 @type other: L{PKey} 85 @return: 0 if the two keys are equivalent, non-0 otherwise. 86 @rtype: int 87 """ 88 hs = hash(self) 89 ho = hash(other) 90 if hs != ho: 91 return cmp(hs, ho) 92 return cmp(str(self), str(other))
93
94 - def get_name(self):
95 """ 96 Return the name of this private key implementation. 97 98 @return: name of this private key type, in SSH terminology (for 99 example, C{"ssh-rsa"}). 100 @rtype: str 101 """ 102 return ''
103
104 - def get_bits(self):
105 """ 106 Return the number of significant bits in this key. This is useful 107 for judging the relative security of a key. 108 109 @return: bits in the key. 110 @rtype: int 111 """ 112 return 0
113
114 - def can_sign(self):
115 """ 116 Return C{True} if this key has the private part necessary for signing 117 data. 118 119 @return: C{True} if this is a private key. 120 @rtype: bool 121 """ 122 return False
123
124 - def get_fingerprint(self):
125 """ 126 Return an MD5 fingerprint of the public part of this key. Nothing 127 secret is revealed. 128 129 @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH 130 format. 131 @rtype: str 132 """ 133 return MD5.new(str(self)).digest()
134
135 - def get_base64(self):
136 """ 137 Return a base64 string containing the public part of this key. Nothing 138 secret is revealed. This format is compatible with that used to store 139 public key files or recognized host keys. 140 141 @return: a base64 string containing the public part of the key. 142 @rtype: str 143 """ 144 return base64.encodestring(str(self)).replace('\n', '')
145
146 - def sign_ssh_data(self, randpool, data):
147 """ 148 Sign a blob of data with this private key, and return a L{Message} 149 representing an SSH signature message. 150 151 @param randpool: a secure random number generator. 152 @type randpool: L{Crypto.Util.randpool.RandomPool} 153 @param data: the data to sign. 154 @type data: str 155 @return: an SSH signature message. 156 @rtype: L{Message} 157 """ 158 return ''
159
160 - def verify_ssh_sig(self, data, msg):
161 """ 162 Given a blob of data, and an SSH message representing a signature of 163 that data, verify that it was signed with this key. 164 165 @param data: the data that was signed. 166 @type data: str 167 @param msg: an SSH signature message 168 @type msg: L{Message} 169 @return: C{True} if the signature verifies correctly; C{False} 170 otherwise. 171 @rtype: boolean 172 """ 173 return False
174
175 - def from_private_key_file(cls, filename, password=None):
176 """ 177 Create a key object by reading a private key file. If the private 178 key is encrypted and C{password} is not C{None}, the given password 179 will be used to decrypt the key (otherwise L{PasswordRequiredException} 180 is thrown). Through the magic of python, this factory method will 181 exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but 182 is useless on the abstract PKey class. 183 184 @param filename: name of the file to read 185 @type filename: str 186 @param password: an optional password to use to decrypt the key file, 187 if it's encrypted 188 @type password: str 189 @return: a new key object based on the given private key 190 @rtype: L{PKey} 191 192 @raise IOError: if there was an error reading the file 193 @raise PasswordRequiredException: if the private key file is 194 encrypted, and C{password} is C{None} 195 @raise SSHException: if the key file is invalid 196 """ 197 key = cls(filename=filename, password=password) 198 return key
199 from_private_key_file = classmethod(from_private_key_file) 200
201 - def from_private_key(cls, file_obj, password=None):
202 """ 203 Create a key object by reading a private key from a file (or file-like) 204 object. If the private key is encrypted and C{password} is not C{None}, 205 the given password will be used to decrypt the key (otherwise 206 L{PasswordRequiredException} is thrown). 207 208 @param file_obj: the file to read from 209 @type file_obj: file 210 @param password: an optional password to use to decrypt the key, if it's 211 encrypted 212 @type password: str 213 @return: a new key object based on the given private key 214 @rtype: L{PKey} 215 216 @raise IOError: if there was an error reading the key 217 @raise PasswordRequiredException: if the private key file is encrypted, 218 and C{password} is C{None} 219 @raise SSHException: if the key file is invalid 220 """ 221 key = cls(file_obj=file_obj, password=password) 222 return key
223 from_private_key = classmethod(from_private_key) 224
225 - def write_private_key_file(self, filename, password=None):
226 """ 227 Write private key contents into a file. If the password is not 228 C{None}, the key is encrypted before writing. 229 230 @param filename: name of the file to write 231 @type filename: str 232 @param password: an optional password to use to encrypt the key file 233 @type password: str 234 235 @raise IOError: if there was an error writing the file 236 @raise SSHException: if the key is invalid 237 """ 238 raise Exception('Not implemented in PKey')
239
240 - def write_private_key(self, file_obj, password=None):
241 """ 242 Write private key contents into a file (or file-like) object. If the 243 password is not C{None}, the key is encrypted before writing. 244 245 @param file_obj: the file object to write into 246 @type file_obj: file 247 @param password: an optional password to use to encrypt the key 248 @type password: str 249 250 @raise IOError: if there was an error writing to the file 251 @raise SSHException: if the key is invalid 252 """ 253 raise Exception('Not implemented in PKey')
254
255 - def _read_private_key_file(self, tag, filename, password=None):
256 """ 257 Read an SSH2-format private key file, looking for a string of the type 258 C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we 259 find, and return it as a string. If the private key is encrypted and 260 C{password} is not C{None}, the given password will be used to decrypt 261 the key (otherwise L{PasswordRequiredException} is thrown). 262 263 @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block. 264 @type tag: str 265 @param filename: name of the file to read. 266 @type filename: str 267 @param password: an optional password to use to decrypt the key file, 268 if it's encrypted. 269 @type password: str 270 @return: data blob that makes up the private key. 271 @rtype: str 272 273 @raise IOError: if there was an error reading the file. 274 @raise PasswordRequiredException: if the private key file is 275 encrypted, and C{password} is C{None}. 276 @raise SSHException: if the key file is invalid. 277 """ 278 f = open(filename, 'r') 279 data = self._read_private_key(tag, f, password) 280 f.close() 281 return data
282
283 - def _read_private_key(self, tag, f, password=None):
284 lines = f.readlines() 285 start = 0 286 while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'): 287 start += 1 288 if start >= len(lines): 289 raise SSHException('not a valid ' + tag + ' private key file') 290 # parse any headers first 291 headers = {} 292 start += 1 293 while start < len(lines): 294 l = lines[start].split(': ') 295 if len(l) == 1: 296 break 297 headers[l[0].lower()] = l[1].strip() 298 start += 1 299 # find end 300 end = start 301 while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): 302 end += 1 303 # if we trudged to the end of the file, just try to cope. 304 try: 305 data = base64.decodestring(''.join(lines[start:end])) 306 except base64.binascii.Error, e: 307 raise SSHException('base64 decoding error: ' + str(e)) 308 if 'proc-type' not in headers: 309 # unencryped: done 310 return data 311 # encrypted keyfile: will need a password 312 if headers['proc-type'] != '4,ENCRYPTED': 313 raise SSHException('Unknown private key structure "%s"' % headers['proc-type']) 314 try: 315 encryption_type, saltstr = headers['dek-info'].split(',') 316 except: 317 raise SSHException('Can\'t parse DEK-info in private key file') 318 if encryption_type not in self._CIPHER_TABLE: 319 raise SSHException('Unknown private key cipher "%s"' % encryption_type) 320 # if no password was passed in, raise an exception pointing out that we need one 321 if password is None: 322 raise PasswordRequiredException('Private key file is encrypted') 323 cipher = self._CIPHER_TABLE[encryption_type]['cipher'] 324 keysize = self._CIPHER_TABLE[encryption_type]['keysize'] 325 mode = self._CIPHER_TABLE[encryption_type]['mode'] 326 salt = unhexlify(saltstr) 327 key = util.generate_key_bytes(MD5, salt, password, keysize) 328 return cipher.new(key, mode, salt).decrypt(data)
329
330 - def _write_private_key_file(self, tag, filename, data, password=None):
331 """ 332 Write an SSH2-format private key file in a form that can be read by 333 paramiko or openssh. If no password is given, the key is written in 334 a trivially-encoded format (base64) which is completely insecure. If 335 a password is given, DES-EDE3-CBC is used. 336 337 @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block. 338 @type tag: str 339 @param filename: name of the file to write. 340 @type filename: str 341 @param data: data blob that makes up the private key. 342 @type data: str 343 @param password: an optional password to use to encrypt the file. 344 @type password: str 345 346 @raise IOError: if there was an error writing the file. 347 """ 348 f = open(filename, 'w', 0600) 349 # grrr... the mode doesn't always take hold 350 os.chmod(filename, 0600) 351 self._write_private_key(tag, f, data, password) 352 f.close()
353
354 - def _write_private_key(self, tag, f, data, password=None):
355 f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag) 356 if password is not None: 357 # since we only support one cipher here, use it 358 cipher_name = self._CIPHER_TABLE.keys()[0] 359 cipher = self._CIPHER_TABLE[cipher_name]['cipher'] 360 keysize = self._CIPHER_TABLE[cipher_name]['keysize'] 361 blocksize = self._CIPHER_TABLE[cipher_name]['blocksize'] 362 mode = self._CIPHER_TABLE[cipher_name]['mode'] 363 salt = randpool.get_bytes(8) 364 key = util.generate_key_bytes(MD5, salt, password, keysize) 365 if len(data) % blocksize != 0: 366 n = blocksize - len(data) % blocksize 367 #data += randpool.get_bytes(n) 368 # that would make more sense ^, but it confuses openssh. 369 data += '\0' * n 370 data = cipher.new(key, mode, salt).encrypt(data) 371 f.write('Proc-Type: 4,ENCRYPTED\n') 372 f.write('DEK-Info: %s,%s\n' % (cipher_name, hexlify(salt).upper())) 373 f.write('\n') 374 s = base64.encodestring(data) 375 # re-wrap to 64-char lines 376 s = ''.join(s.split('\n')) 377 s = '\n'.join([s[i : i+64] for i in range(0, len(s), 64)]) 378 f.write(s) 379 f.write('\n') 380 f.write('-----END %s PRIVATE KEY-----\n' % tag)
381