1# coding: utf-8
2from __future__ import unicode_literals, division, absolute_import, print_function
3
4import hashlib
5import binascii
6
7from . import backend
8from ._asn1 import (
9 armor,
10 Certificate as Asn1Certificate,
11 DHParameters,
12 EncryptedPrivateKeyInfo,
13 Null,
14 OrderedDict,
15 Pbkdf2Salt,
16 PrivateKeyInfo,
17 PublicKeyInfo,
18)
19from ._asymmetric import _unwrap_private_key_info
20from ._errors import pretty_message
21from ._types import type_name, str_cls
22from .kdf import pbkdf2, pbkdf2_iteration_calculator
23from .symmetric import aes_cbc_pkcs7_encrypt
24from .util import rand_bytes
25
26
27_backend = backend()
28
29
30if _backend == 'mac':
31 from ._mac.asymmetric import (
32 Certificate,
33 dsa_sign,
34 dsa_verify,
35 ecdsa_sign,
36 ecdsa_verify,
37 generate_pair,
38 generate_dh_parameters,
39 load_certificate,
40 load_pkcs12,
41 load_private_key,
42 load_public_key,
43 PrivateKey,
44 PublicKey,
45 rsa_pkcs1v15_sign,
46 rsa_pkcs1v15_verify,
47 rsa_pss_sign,
48 rsa_pss_verify,
49 rsa_pkcs1v15_encrypt,
50 rsa_pkcs1v15_decrypt,
51 rsa_oaep_encrypt,
52 rsa_oaep_decrypt,
53 )
54
55elif _backend == 'win' or _backend == 'winlegacy':
56 from ._win.asymmetric import (
57 Certificate,
58 dsa_sign,
59 dsa_verify,
60 ecdsa_sign,
61 ecdsa_verify,
62 generate_pair,
63 generate_dh_parameters,
64 load_certificate,
65 load_pkcs12,
66 load_private_key,
67 load_public_key,
68 PrivateKey,
69 PublicKey,
70 rsa_pkcs1v15_sign,
71 rsa_pkcs1v15_verify,
72 rsa_pss_sign,
73 rsa_pss_verify,
74 rsa_pkcs1v15_encrypt,
75 rsa_pkcs1v15_decrypt,
76 rsa_oaep_encrypt,
77 rsa_oaep_decrypt,
78 )
79
80else:
81 from ._openssl.asymmetric import (
82 Certificate,
83 dsa_sign,
84 dsa_verify,
85 ecdsa_sign,
86 ecdsa_verify,
87 generate_pair,
88 generate_dh_parameters,
89 load_certificate,
90 load_pkcs12,
91 load_private_key,
92 load_public_key,
93 PrivateKey,
94 PublicKey,
95 rsa_pkcs1v15_sign,
96 rsa_pkcs1v15_verify,
97 rsa_pss_sign,
98 rsa_pss_verify,
99 rsa_pkcs1v15_encrypt,
100 rsa_pkcs1v15_decrypt,
101 rsa_oaep_encrypt,
102 rsa_oaep_decrypt,
103 )
104
105
106__all__ = [
107 'Certificate',
108 'dsa_sign',
109 'dsa_verify',
110 'dump_certificate',
111 'dump_dh_parameters',
112 'dump_openssl_private_key',
113 'dump_private_key',
114 'dump_public_key',
115 'ecdsa_sign',
116 'ecdsa_verify',
117 'generate_pair',
118 'generate_dh_parameters',
119 'load_certificate',
120 'load_pkcs12',
121 'load_private_key',
122 'load_public_key',
123 'PrivateKey',
124 'PublicKey',
125 'rsa_oaep_decrypt',
126 'rsa_oaep_encrypt',
127 'rsa_pkcs1v15_decrypt',
128 'rsa_pkcs1v15_encrypt',
129 'rsa_pkcs1v15_sign',
130 'rsa_pkcs1v15_verify',
131 'rsa_pss_sign',
132 'rsa_pss_verify',
133]
134
135
136def dump_dh_parameters(dh_parameters, encoding='pem'):
137 """
138 Serializes an asn1crypto.algos.DHParameters object into a byte string
139
140 :param dh_parameters:
141 An asn1crypto.algos.DHParameters object
142
143 :param encoding:
144 A unicode string of "pem" or "der"
145
146 :return:
147 A byte string of the encoded DH parameters
148 """
149
150 if encoding not in set(['pem', 'der']):
151 raise ValueError(pretty_message(
152 '''
153 encoding must be one of "pem", "der", not %s
154 ''',
155 repr(encoding)
156 ))
157
158 if not isinstance(dh_parameters, DHParameters):
159 raise TypeError(pretty_message(
160 '''
161 dh_parameters must be an instance of asn1crypto.algos.DHParameters,
162 not %s
163 ''',
164 type_name(dh_parameters)
165 ))
166
167 output = dh_parameters.dump()
168 if encoding == 'pem':
169 output = armor('DH PARAMETERS', output)
170 return output
171
172
173def dump_public_key(public_key, encoding='pem'):
174 """
175 Serializes a public key object into a byte string
176
177 :param public_key:
178 An oscrypto.asymmetric.PublicKey or asn1crypto.keys.PublicKeyInfo object
179
180 :param encoding:
181 A unicode string of "pem" or "der"
182
183 :return:
184 A byte string of the encoded public key
185 """
186
187 if encoding not in set(['pem', 'der']):
188 raise ValueError(pretty_message(
189 '''
190 encoding must be one of "pem", "der", not %s
191 ''',
192 repr(encoding)
193 ))
194
195 is_oscrypto = isinstance(public_key, PublicKey)
196 if not isinstance(public_key, PublicKeyInfo) and not is_oscrypto:
197 raise TypeError(pretty_message(
198 '''
199 public_key must be an instance of oscrypto.asymmetric.PublicKey or
200 asn1crypto.keys.PublicKeyInfo, not %s
201 ''',
202 type_name(public_key)
203 ))
204
205 if is_oscrypto:
206 public_key = public_key.asn1
207
208 output = public_key.dump()
209 if encoding == 'pem':
210 output = armor('PUBLIC KEY', output)
211 return output
212
213
214def dump_certificate(certificate, encoding='pem'):
215 """
216 Serializes a certificate object into a byte string
217
218 :param certificate:
219 An oscrypto.asymmetric.Certificate or asn1crypto.x509.Certificate object
220
221 :param encoding:
222 A unicode string of "pem" or "der"
223
224 :return:
225 A byte string of the encoded certificate
226 """
227
228 if encoding not in set(['pem', 'der']):
229 raise ValueError(pretty_message(
230 '''
231 encoding must be one of "pem", "der", not %s
232 ''',
233 repr(encoding)
234 ))
235
236 is_oscrypto = isinstance(certificate, Certificate)
237 if not isinstance(certificate, Asn1Certificate) and not is_oscrypto:
238 raise TypeError(pretty_message(
239 '''
240 certificate must be an instance of oscrypto.asymmetric.Certificate
241 or asn1crypto.x509.Certificate, not %s
242 ''',
243 type_name(certificate)
244 ))
245
246 if is_oscrypto:
247 certificate = certificate.asn1
248
249 output = certificate.dump()
250 if encoding == 'pem':
251 output = armor('CERTIFICATE', output)
252 return output
253
254
255def dump_private_key(private_key, passphrase, encoding='pem', target_ms=200):
256 """
257 Serializes a private key object into a byte string of the PKCS#8 format
258
259 :param private_key:
260 An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo
261 object
262
263 :param passphrase:
264 A unicode string of the passphrase to encrypt the private key with.
265 A passphrase of None will result in no encryption. A blank string will
266 result in a ValueError to help ensure that the lack of passphrase is
267 intentional.
268
269 :param encoding:
270 A unicode string of "pem" or "der"
271
272 :param target_ms:
273 Use PBKDF2 with the number of iterations that takes about this many
274 milliseconds on the current machine.
275
276 :raises:
277 ValueError - when a blank string is provided for the passphrase
278
279 :return:
280 A byte string of the encoded and encrypted public key
281 """
282
283 if encoding not in set(['pem', 'der']):
284 raise ValueError(pretty_message(
285 '''
286 encoding must be one of "pem", "der", not %s
287 ''',
288 repr(encoding)
289 ))
290
291 if passphrase is not None:
292 if not isinstance(passphrase, str_cls):
293 raise TypeError(pretty_message(
294 '''
295 passphrase must be a unicode string, not %s
296 ''',
297 type_name(passphrase)
298 ))
299 if passphrase == '':
300 raise ValueError(pretty_message(
301 '''
302 passphrase may not be a blank string - pass None to disable
303 encryption
304 '''
305 ))
306
307 is_oscrypto = isinstance(private_key, PrivateKey)
308 if not isinstance(private_key, PrivateKeyInfo) and not is_oscrypto:
309 raise TypeError(pretty_message(
310 '''
311 private_key must be an instance of oscrypto.asymmetric.PrivateKey
312 or asn1crypto.keys.PrivateKeyInfo, not %s
313 ''',
314 type_name(private_key)
315 ))
316
317 if is_oscrypto:
318 private_key = private_key.asn1
319
320 output = private_key.dump()
321
322 if passphrase is not None:
323 cipher = 'aes256_cbc'
324 key_length = 32
325 kdf_hmac = 'sha256'
326 kdf_salt = rand_bytes(key_length)
327 iterations = pbkdf2_iteration_calculator(kdf_hmac, key_length, target_ms=target_ms, quiet=True)
328 # Need a bare minimum of 10,000 iterations for PBKDF2 as of 2015
329 if iterations < 10000:
330 iterations = 10000
331
332 passphrase_bytes = passphrase.encode('utf-8')
333 key = pbkdf2(kdf_hmac, passphrase_bytes, kdf_salt, iterations, key_length)
334 iv, ciphertext = aes_cbc_pkcs7_encrypt(key, output, None)
335
336 output = EncryptedPrivateKeyInfo({
337 'encryption_algorithm': {
338 'algorithm': 'pbes2',
339 'parameters': {
340 'key_derivation_func': {
341 'algorithm': 'pbkdf2',
342 'parameters': {
343 'salt': Pbkdf2Salt(
344 name='specified',
345 value=kdf_salt
346 ),
347 'iteration_count': iterations,
348 'prf': {
349 'algorithm': kdf_hmac,
350 'parameters': Null()
351 }
352 }
353 },
354 'encryption_scheme': {
355 'algorithm': cipher,
356 'parameters': iv
357 }
358 }
359 },
360 'encrypted_data': ciphertext
361 }).dump()
362
363 if encoding == 'pem':
364 if passphrase is None:
365 object_type = 'PRIVATE KEY'
366 else:
367 object_type = 'ENCRYPTED PRIVATE KEY'
368 output = armor(object_type, output)
369
370 return output
371
372
373def dump_openssl_private_key(private_key, passphrase):
374 """
375 Serializes a private key object into a byte string of the PEM formats used
376 by OpenSSL. The format chosen will depend on the type of private key - RSA,
377 DSA or EC.
378
379 Do not use this method unless you really must interact with a system that
380 does not support PKCS#8 private keys. The encryption provided by PKCS#8 is
381 far superior to the OpenSSL formats. This is due to the fact that the
382 OpenSSL formats don't stretch the passphrase, making it very easy to
383 brute-force.
384
385 :param private_key:
386 An oscrypto.asymmetric.PrivateKey or asn1crypto.keys.PrivateKeyInfo
387 object
388
389 :param passphrase:
390 A unicode string of the passphrase to encrypt the private key with.
391 A passphrase of None will result in no encryption. A blank string will
392 result in a ValueError to help ensure that the lack of passphrase is
393 intentional.
394
395 :raises:
396 ValueError - when a blank string is provided for the passphrase
397
398 :return:
399 A byte string of the encoded and encrypted public key
400 """
401
402 if passphrase is not None:
403 if not isinstance(passphrase, str_cls):
404 raise TypeError(pretty_message(
405 '''
406 passphrase must be a unicode string, not %s
407 ''',
408 type_name(passphrase)
409 ))
410 if passphrase == '':
411 raise ValueError(pretty_message(
412 '''
413 passphrase may not be a blank string - pass None to disable
414 encryption
415 '''
416 ))
417
418 is_oscrypto = isinstance(private_key, PrivateKey)
419 if not isinstance(private_key, PrivateKeyInfo) and not is_oscrypto:
420 raise TypeError(pretty_message(
421 '''
422 private_key must be an instance of oscrypto.asymmetric.PrivateKey or
423 asn1crypto.keys.PrivateKeyInfo, not %s
424 ''',
425 type_name(private_key)
426 ))
427
428 if is_oscrypto:
429 private_key = private_key.asn1
430
431 output = _unwrap_private_key_info(private_key).dump()
432
433 headers = None
434 if passphrase is not None:
435 iv = rand_bytes(16)
436
437 headers = OrderedDict()
438 headers['Proc-Type'] = '4,ENCRYPTED'
439 headers['DEK-Info'] = 'AES-128-CBC,%s' % binascii.hexlify(iv).decode('ascii')
440
441 key_length = 16
442 passphrase_bytes = passphrase.encode('utf-8')
443
444 key = hashlib.md5(passphrase_bytes + iv[0:8]).digest()
445 while key_length > len(key):
446 key += hashlib.md5(key + passphrase_bytes + iv[0:8]).digest()
447 key = key[0:key_length]
448
449 iv, output = aes_cbc_pkcs7_encrypt(key, output, iv)
450
451 if private_key.algorithm == 'ec':
452 object_type = 'EC PRIVATE KEY'
453 elif private_key.algorithm == 'rsa':
454 object_type = 'RSA PRIVATE KEY'
455 elif private_key.algorithm == 'dsa':
456 object_type = 'DSA PRIVATE KEY'
457
458 return armor(object_type, output, headers=headers)