Coverage for jbank/csr_helpers.py: 73%

# Coverage for jbank/csr_helpers.py : 73%

## 44 statements

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

1 import logging

2 from typing import Optional

3

4 import cryptography

5 from cryptography import x509

6 from cryptography.hazmat.primitives import hashes, serialization

7 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey

8 from cryptography.hazmat.primitives.serialization import load_pem_private_key

9 from django.core.exceptions import ValidationError

10

11

12 logger = logging.getLogger(__name__)

13

14

15 def 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, key_size=key_size, backend=backend

25 )

26

27

28 def get_private_key_pem(private_key: RSAPrivateKey) -> bytes:

29 """

30 Returns private key PEM file bytes.

31 :param private_key: RSPrivateKey

32 :return: bytes

33 """

34 return private_key.private_bytes( # type: ignore

35 encoding=serialization.Encoding.PEM,

36 format=serialization.PrivateFormat.PKCS8,

37 encryption_algorithm=serialization.NoEncryption(),

38 )

39

40

41 def load_private_key_from_pem_data(pem_data: bytes, password: Optional[bytes] = None) -> RSAPrivateKey:

42 backend = cryptography.hazmat.backends.default_backend()

43 res = load_pem_private_key(pem_data, password=password, backend=backend)

44 assert isinstance(res, RSAPrivateKey)

45 return res

46

47

48 def 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)

51

52

53 def 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(

61 "write_private_key_pem_file() assumes PEM data does contains BEGIN / END header and footer"

62 )

63 with open(filename, "wb") as fp:

64 fp.write(key_base64)

65 logger.info("%s written", filename)

66

67

68 def strip_pem_header_and_footer(pem: bytes) -> bytes:

69 """

70 Strips -----BEGIN and -----END parts of the CSR PEM.

71 :param pem: bytes

72 :return: bytes

73 """

74 if not pem.startswith(b"-----BEGIN "):

75 raise Exception("PEM does not appear to have header: {}...".format(pem[:32].decode() + "..."))

76 return b"\n".join(pem.split(b"\n")[1:-2])

77

78

79 def create_csr_pem( # pylint: disable=too-many-arguments,too-many-locals

80 private_key: RSAPrivateKey,

81 common_name: str,

82 country_name: str,

83 dn_qualifier: str = "",

84 business_category: str = "",

85 domain_component: str = "",

86 email_address: str = "",

87 generation_qualifier: str = "",

88 given_name: str = "",

89 jurisdiction_country_name: str = "",

90 jurisdiction_locality_name: str = "",

91 jurisdiction_state_or_province_name: str = "",

92 locality_name: str = "",

93 organizational_unit_name: str = "",

94 organization_name: str = "",

95 postal_address: str = "",

96 postal_code: str = "",

97 pseudonym: str = "",

98 serial_number: str = "",

99 state_or_province_name: str = "",

100 street_address: str = "",

101 surname: str = "",

102 title: str = "",

103 user_id: str = "",

104 x500_unique_identifier: str = "",

105) -> bytes:

106 """

107 See http://fileformats.archiveteam.org/wiki/PKCS10

108 :return: CSR PEM as bytes

109 """

110 pairs = [

111 (common_name, "COMMON_NAME"),

112 (country_name, "COUNTRY_NAME"),

113 (dn_qualifier, "DN_QUALIFIER"),

114 (business_category, "BUSINESS_CATEGORY"),

115 (domain_component, "DOMAIN_COMPONENT"),

116 (email_address, "EMAIL_ADDRESS"),

117 (generation_qualifier, "GENERATION_QUALIFIER"),

118 (given_name, "GIVEN_NAME"),

119 (jurisdiction_country_name, "JURISDICTION_COUNTRY_NAME"),

120 (jurisdiction_locality_name, "JURISDICTION_LOCALITY_NAME"),

121 (jurisdiction_state_or_province_name, "JURISDICTION_STATE_OR_PROVINCE_NAME"),

122 (locality_name, "LOCALITY_NAME"),

123 (organizational_unit_name, "ORGANIZATIONAL_UNIT_NAME"),

124 (organization_name, "ORGANIZATION_NAME"),

125 (postal_address, "POSTAL_ADDRESS"),

126 (postal_code, "POSTAL_CODE"),

127 (pseudonym, "PSEUDONYM"),

128 (serial_number, "SERIAL_NUMBER"),

129 (state_or_province_name, "STATE_OR_PROVINCE_NAME"),

130 (street_address, "STREET_ADDRESS"),

131 (surname, "SURNAME"),

132 (title, "TITLE"),

133 (user_id, "USER_ID"),

134 (x500_unique_identifier, "X500_UNIQUE_IDENTIFIER"),

135 ]

136 name_parts = []

137 for val, k in pairs:

138 if val:

139 name_parts.append(x509.NameAttribute(getattr(x509.oid.NameOID, k), val))

140

141 builder = x509.CertificateSigningRequestBuilder()

142 builder = builder.subject_name(x509.Name(name_parts))

143 backend = cryptography.hazmat.backends.default_backend()

144 request = builder.sign(private_key, hashes.SHA256(), backend)

145 assert isinstance(request, x509.CertificateSigningRequest)

146 return request.public_bytes(serialization.Encoding.PEM)