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