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 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
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 - 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
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
74
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 .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
92 return hash(self) == hash(other)
93
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
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
114 """
115 Return ``True`` if this key has the private part necessary for signing
116 data.
117 """
118 return False
119
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
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
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
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
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
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
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
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
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
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
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
275 end = start
276 while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)):
277 end += 1
278
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
285 return data
286
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
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
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
321 os.chmod(filename, o600)
322 self._write_private_key(tag, f, data, password)
323
325 f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag)
326 if password is not None:
327
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
338
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
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