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