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

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 

9 

10 

11logger = logging.getLogger(__name__) 

12 

13 

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) 

22 

23 

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 

27 

28 

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 ) 

40 

41 

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 

46 

47 

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) 

51 

52 

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) 

64 

65 

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

75 

76 

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

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)