Coverage for jbank/csr_helpers.py: 76%

# Coverage for jbank/csr_helpers.py : 76%

## 42 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,

25 key_size=key_size,

26 backend=backend

27 )

28

29

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

41

42

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

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

119

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)