1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 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   
 37      """ 
 38      Base class for public keys. 
 39      """ 
 40   
 41       
 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   
 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   
 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   
 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   
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   
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   
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   
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   
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   
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      
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   
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   
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       
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   
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       
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           
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           
300          end = start 
301          while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): 
302              end += 1 
303           
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               
310              return data 
311           
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           
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   
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           
350          os.chmod(filename, 0600) 
351          self._write_private_key(tag, f, data, password) 
352          f.close() 
 353       
355          f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag) 
356          if password is not None: 
357               
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                   
368                   
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           
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