Coverage for jbank/csr_helpers.py: 70%
44 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-27 13:36 +0700
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-27 13:36 +0700
1import logging
2from typing import Optional
3import cryptography
4from cryptography import x509
5from cryptography.hazmat.primitives import hashes, serialization
6from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
7from cryptography.hazmat.primitives.serialization import load_pem_private_key
8from django.core.exceptions import ValidationError
11logger = logging.getLogger(__name__)
14def create_private_key(public_exponent: int = 65537, key_size: int = 2048) -> RSAPrivateKey:
15 """
16 Creates RSA private key.
17 :param public_exponent: int, exponent
18 :param key_size: int, bits
19 :return: RSAPrivateKey
20 """
21 return cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(public_exponent=public_exponent, key_size=key_size)
24def get_public_key_pem(public_key: RSAPublicKey) -> bytes:
25 pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)
26 return pem
29def get_private_key_pem(private_key: RSAPrivateKey) -> bytes:
30 """
31 Returns private key PEM file bytes.
32 :param private_key: RSPrivateKey
33 :return: bytes
34 """
35 return private_key.private_bytes( # type: ignore
36 encoding=serialization.Encoding.PEM,
37 format=serialization.PrivateFormat.PKCS8,
38 encryption_algorithm=serialization.NoEncryption(),
39 )
42def load_private_key_from_pem_data(pem_data: bytes, password: Optional[bytes] = None) -> RSAPrivateKey:
43 res = load_pem_private_key(pem_data, password=password)
44 assert isinstance(res, RSAPrivateKey)
45 return res
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 key_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( # pylint: disable=too-many-arguments,too-many-locals
78 private_key: RSAPrivateKey,
79 common_name: str,
80 country_name: str,
81 dn_qualifier: str = "",
82 business_category: str = "",
83 domain_component: str = "",
84 email_address: str = "",
85 generation_qualifier: str = "",
86 given_name: str = "",
87 jurisdiction_country_name: str = "",
88 jurisdiction_locality_name: str = "",
89 jurisdiction_state_or_province_name: str = "",
90 locality_name: str = "",
91 organizational_unit_name: str = "",
92 organization_name: str = "",
93 postal_address: str = "",
94 postal_code: str = "",
95 pseudonym: str = "",
96 serial_number: str = "",
97 state_or_province_name: str = "",
98 street_address: str = "",
99 surname: str = "",
100 title: str = "",
101 user_id: str = "",
102 x500_unique_identifier: str = "",
103) -> bytes:
104 """
105 See http://fileformats.archiveteam.org/wiki/PKCS10
106 :return: CSR PEM as bytes
107 """
108 pairs = [
109 (common_name, "COMMON_NAME"),
110 (country_name, "COUNTRY_NAME"),
111 (dn_qualifier, "DN_QUALIFIER"),
112 (business_category, "BUSINESS_CATEGORY"),
113 (domain_component, "DOMAIN_COMPONENT"),
114 (email_address, "EMAIL_ADDRESS"),
115 (generation_qualifier, "GENERATION_QUALIFIER"),
116 (given_name, "GIVEN_NAME"),
117 (jurisdiction_country_name, "JURISDICTION_COUNTRY_NAME"),
118 (jurisdiction_locality_name, "JURISDICTION_LOCALITY_NAME"),
119 (jurisdiction_state_or_province_name, "JURISDICTION_STATE_OR_PROVINCE_NAME"),
120 (locality_name, "LOCALITY_NAME"),
121 (organizational_unit_name, "ORGANIZATIONAL_UNIT_NAME"),
122 (organization_name, "ORGANIZATION_NAME"),
123 (postal_address, "POSTAL_ADDRESS"),
124 (postal_code, "POSTAL_CODE"),
125 (pseudonym, "PSEUDONYM"),
126 (serial_number, "SERIAL_NUMBER"),
127 (state_or_province_name, "STATE_OR_PROVINCE_NAME"),
128 (street_address, "STREET_ADDRESS"),
129 (surname, "SURNAME"),
130 (title, "TITLE"),
131 (user_id, "USER_ID"),
132 (x500_unique_identifier, "X500_UNIQUE_IDENTIFIER"),
133 ]
134 name_parts = []
135 for val, k in pairs:
136 if val:
137 name_parts.append(x509.NameAttribute(getattr(x509.oid.NameOID, k), val))
139 builder = x509.CertificateSigningRequestBuilder()
140 builder = builder.subject_name(x509.Name(name_parts))
141 request = builder.sign(private_key, hashes.SHA256())
142 assert isinstance(request, x509.CertificateSigningRequest)
143 return request.public_bytes(serialization.Encoding.PEM)