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