Coverage for jbank/csr_helpers.py: 70%

1 import logging

2 from typing import Optional

3 import cryptography

4 from cryptography import x509

5 from cryptography.hazmat.primitives import hashes, serialization

6 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey

7 from cryptography.hazmat.primitives.serialization import load_pem_private_key

8 from django.core.exceptions import ValidationError

9

10

11 logger = logging.getLogger(__name__)

12

13

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

22

23

24 def 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

27

28

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

40

41

42 def 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

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

64

65

66 def 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])

75

76

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

138

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)