Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/asn1crypto/pem.py: 37%
87 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:25 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:25 +0000
1# coding: utf-8
3"""
4Encoding DER to PEM and decoding PEM to DER. Exports the following items:
6 - armor()
7 - detect()
8 - unarmor()
10"""
12from __future__ import unicode_literals, division, absolute_import, print_function
14import base64
15import re
16import sys
18from ._errors import unwrap
19from ._types import type_name as _type_name, str_cls, byte_cls
21if sys.version_info < (3,):
22 from cStringIO import StringIO as BytesIO
23else:
24 from io import BytesIO
27def detect(byte_string):
28 """
29 Detect if a byte string seems to contain a PEM-encoded block
31 :param byte_string:
32 A byte string to look through
34 :return:
35 A boolean, indicating if a PEM-encoded block is contained in the byte
36 string
37 """
39 if not isinstance(byte_string, byte_cls):
40 raise TypeError(unwrap(
41 '''
42 byte_string must be a byte string, not %s
43 ''',
44 _type_name(byte_string)
45 ))
47 return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1
50def armor(type_name, der_bytes, headers=None):
51 """
52 Armors a DER-encoded byte string in PEM
54 :param type_name:
55 A unicode string that will be capitalized and placed in the header
56 and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This
57 will appear as "-----BEGIN CERTIFICATE-----" and
58 "-----END CERTIFICATE-----".
60 :param der_bytes:
61 A byte string to be armored
63 :param headers:
64 An OrderedDict of the header lines to write after the BEGIN line
66 :return:
67 A byte string of the PEM block
68 """
70 if not isinstance(der_bytes, byte_cls):
71 raise TypeError(unwrap(
72 '''
73 der_bytes must be a byte string, not %s
74 ''' % _type_name(der_bytes)
75 ))
77 if not isinstance(type_name, str_cls):
78 raise TypeError(unwrap(
79 '''
80 type_name must be a unicode string, not %s
81 ''',
82 _type_name(type_name)
83 ))
85 type_name = type_name.upper().encode('ascii')
87 output = BytesIO()
88 output.write(b'-----BEGIN ')
89 output.write(type_name)
90 output.write(b'-----\n')
91 if headers:
92 for key in headers:
93 output.write(key.encode('ascii'))
94 output.write(b': ')
95 output.write(headers[key].encode('ascii'))
96 output.write(b'\n')
97 output.write(b'\n')
98 b64_bytes = base64.b64encode(der_bytes)
99 b64_len = len(b64_bytes)
100 i = 0
101 while i < b64_len:
102 output.write(b64_bytes[i:i + 64])
103 output.write(b'\n')
104 i += 64
105 output.write(b'-----END ')
106 output.write(type_name)
107 output.write(b'-----\n')
109 return output.getvalue()
112def _unarmor(pem_bytes):
113 """
114 Convert a PEM-encoded byte string into one or more DER-encoded byte strings
116 :param pem_bytes:
117 A byte string of the PEM-encoded data
119 :raises:
120 ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
122 :return:
123 A generator of 3-element tuples in the format: (object_type, headers,
124 der_bytes). The object_type is a unicode string of what is between
125 "-----BEGIN " and "-----". Examples include: "CERTIFICATE",
126 "PUBLIC KEY", "PRIVATE KEY". The headers is a dict containing any lines
127 in the form "Name: Value" that are right after the begin line.
128 """
130 if not isinstance(pem_bytes, byte_cls):
131 raise TypeError(unwrap(
132 '''
133 pem_bytes must be a byte string, not %s
134 ''',
135 _type_name(pem_bytes)
136 ))
138 # Valid states include: "trash", "headers", "body"
139 state = 'trash'
140 headers = {}
141 base64_data = b''
142 object_type = None
144 found_start = False
145 found_end = False
147 for line in pem_bytes.splitlines(False):
148 if line == b'':
149 continue
151 if state == "trash":
152 # Look for a starting line since some CA cert bundle show the cert
153 # into in a parsed format above each PEM block
154 type_name_match = re.match(b'^(?:---- |-----)BEGIN ([A-Z0-9 ]+)(?: ----|-----)', line)
155 if not type_name_match:
156 continue
157 object_type = type_name_match.group(1).decode('ascii')
159 found_start = True
160 state = 'headers'
161 continue
163 if state == 'headers':
164 if line.find(b':') == -1:
165 state = 'body'
166 else:
167 decoded_line = line.decode('ascii')
168 name, value = decoded_line.split(':', 1)
169 headers[name] = value.strip()
170 continue
172 if state == 'body':
173 if line[0:5] in (b'-----', b'---- '):
174 der_bytes = base64.b64decode(base64_data)
176 yield (object_type, headers, der_bytes)
178 state = 'trash'
179 headers = {}
180 base64_data = b''
181 object_type = None
182 found_end = True
183 continue
185 base64_data += line
187 if not found_start or not found_end:
188 raise ValueError(unwrap(
189 '''
190 pem_bytes does not appear to contain PEM-encoded data - no
191 BEGIN/END combination found
192 '''
193 ))
196def unarmor(pem_bytes, multiple=False):
197 """
198 Convert a PEM-encoded byte string into a DER-encoded byte string
200 :param pem_bytes:
201 A byte string of the PEM-encoded data
203 :param multiple:
204 If True, function will return a generator
206 :raises:
207 ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
209 :return:
210 A 3-element tuple (object_name, headers, der_bytes). The object_name is
211 a unicode string of what is between "-----BEGIN " and "-----". Examples
212 include: "CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY". The headers is a
213 dict containing any lines in the form "Name: Value" that are right
214 after the begin line.
215 """
217 generator = _unarmor(pem_bytes)
219 if not multiple:
220 return next(generator)
222 return generator