Coverage for jbank/csr_helpers.py : 76%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import logging
2from typing import Optional
4import cryptography
5from cryptography import x509
6from cryptography.hazmat.primitives import hashes, serialization
7from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
8from cryptography.hazmat.primitives.serialization import load_pem_private_key
9from django.core.exceptions import ValidationError
12logger = logging.getLogger(__name__)
15def create_private_key(public_exponent: int = 65537, key_size: int = 2048) -> RSAPrivateKey:
16 """
17 Creates RSA private key.
18 :param public_exponent: int, exponent
19 :param key_size: int, bits
20 :return: RSAPrivateKey
21 """
22 backend = cryptography.hazmat.backends.default_backend()
23 return cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
24 public_exponent=public_exponent,
25 key_size=key_size,
26 backend=backend
27 )
30def get_private_key_pem(private_key: RSAPrivateKey) -> bytes:
31 """
32 Returns private key PEM file bytes.
33 :param private_key: RSPrivateKey
34 :return: bytes
35 """
36 return private_key.private_bytes( # type: ignore
37 encoding=serialization.Encoding.PEM,
38 format=serialization.PrivateFormat.PKCS8,
39 encryption_algorithm=serialization.NoEncryption()
40 )
43def load_private_key_from_pem_data(pem_data: bytes, password: Optional[bytes] = None) -> RSAPrivateKey:
44 backend = cryptography.hazmat.backends.default_backend()
45 return load_pem_private_key(pem_data, password=password, backend=backend)
48def load_private_key_from_pem_file(filename: str, password: Optional[bytes] = None) -> RSAPrivateKey:
49 with open(filename, 'rb') as fp:
50 return load_private_key_from_pem_data(fp.read(), password)
53def write_private_key_pem_file(filename: str, key_base64: bytes):
54 """
55 Writes PEM data to file.
56 :param filename: PEM filename
57 :param cert_base64: Base64 encoded certificate data without BEGIN CERTIFICATE / END CERTIFICATE
58 """
59 if b'BEGIN' not in key_base64 or b'END' not in key_base64:
60 raise ValidationError('write_private_key_pem_file() assumes PEM data does contains BEGIN / END header and footer')
61 with open(filename, 'wb') as fp:
62 fp.write(key_base64)
63 logger.info('%s written', filename)
66def strip_pem_header_and_footer(pem: bytes) -> bytes:
67 """
68 Strips -----BEGIN and -----END parts of the CSR PEM.
69 :param pem: bytes
70 :return: bytes
71 """
72 if not pem.startswith(b'-----BEGIN '):
73 raise Exception('PEM does not appear to have header: {}...'.format(pem[:32].decode() + '...'))
74 return b'\n'.join(pem.split(b'\n')[1:-2])
77def create_csr_pem(private_key: RSAPrivateKey, common_name: str, country_name: str, # pylint: disable=too-many-arguments,too-many-locals
78 dn_qualifier: str = '', business_category: str = '', domain_component: str = '', email_address: str = '',
79 generation_qualifier: str = '', given_name: str = '', jurisdiction_country_name: str = '',
80 jurisdiction_locality_name: str = '', jurisdiction_state_or_province_name: str = '',
81 locality_name: str = '', organizational_unit_name: str = '', organization_name: str = '',
82 postal_address: str = '', postal_code: str = '', pseudonym: str = '', serial_number: str = '',
83 state_or_province_name: str = '', street_address: str = '', surname: str = '',
84 title: str = '', user_id: str = '', x500_unique_identifier: str = '') -> bytes:
85 """
86 See http://fileformats.archiveteam.org/wiki/PKCS10
87 :return: CSR PEM as bytes
88 """
89 pairs = [
90 (common_name, 'COMMON_NAME'),
91 (country_name, 'COUNTRY_NAME'),
92 (dn_qualifier, 'DN_QUALIFIER'),
93 (business_category, 'BUSINESS_CATEGORY'),
94 (domain_component, 'DOMAIN_COMPONENT'),
95 (email_address, 'EMAIL_ADDRESS'),
96 (generation_qualifier, 'GENERATION_QUALIFIER'),
97 (given_name, 'GIVEN_NAME'),
98 (jurisdiction_country_name, 'JURISDICTION_COUNTRY_NAME'),
99 (jurisdiction_locality_name, 'JURISDICTION_LOCALITY_NAME'),
100 (jurisdiction_state_or_province_name, 'JURISDICTION_STATE_OR_PROVINCE_NAME'),
101 (locality_name, 'LOCALITY_NAME'),
102 (organizational_unit_name, 'ORGANIZATIONAL_UNIT_NAME'),
103 (organization_name, 'ORGANIZATION_NAME'),
104 (postal_address, 'POSTAL_ADDRESS'),
105 (postal_code, 'POSTAL_CODE'),
106 (pseudonym, 'PSEUDONYM'),
107 (serial_number, 'SERIAL_NUMBER'),
108 (state_or_province_name, 'STATE_OR_PROVINCE_NAME'),
109 (street_address, 'STREET_ADDRESS'),
110 (surname, 'SURNAME'),
111 (title, 'TITLE'),
112 (user_id, 'USER_ID'),
113 (x500_unique_identifier, 'X500_UNIQUE_IDENTIFIER'),
114 ]
115 name_parts = []
116 for val, k in pairs:
117 if val:
118 name_parts.append(x509.NameAttribute(getattr(x509.oid.NameOID, k), val))
120 builder = x509.CertificateSigningRequestBuilder()
121 builder = builder.subject_name(x509.Name(name_parts))
122 backend = cryptography.hazmat.backends.default_backend()
123 request = builder.sign(private_key, hashes.SHA256(), backend)
124 assert isinstance(request, x509.CertificateSigningRequest)
125 return request.public_bytes(serialization.Encoding.PEM)