Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/asn1crypto/pem.py: 49%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

88 statements  

1# coding: utf-8 

2 

3""" 

4Encoding DER to PEM and decoding PEM to DER. Exports the following items: 

5 

6 - armor() 

7 - detect() 

8 - unarmor() 

9 

10""" 

11 

12from __future__ import unicode_literals, division, absolute_import, print_function 

13 

14import base64 

15import re 

16import sys 

17 

18from ._errors import unwrap 

19from ._types import type_name as _type_name, str_cls, byte_cls 

20 

21if sys.version_info < (3,): 

22 from cStringIO import StringIO as BytesIO 

23else: 

24 from io import BytesIO 

25 

26 

27def detect(byte_string): 

28 """ 

29 Detect if a byte string seems to contain a PEM-encoded block 

30 

31 :param byte_string: 

32 A byte string to look through 

33 

34 :return: 

35 A boolean, indicating if a PEM-encoded block is contained in the byte 

36 string 

37 """ 

38 

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

46 

47 return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1 

48 

49 

50def armor(type_name, der_bytes, headers=None): 

51 """ 

52 Armors a DER-encoded byte string in PEM 

53 

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-----". 

59 

60 :param der_bytes: 

61 A byte string to be armored 

62 

63 :param headers: 

64 An OrderedDict of the header lines to write after the BEGIN line 

65 

66 :return: 

67 A byte string of the PEM block 

68 """ 

69 

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

76 

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

84 

85 type_name = type_name.upper().encode('ascii') 

86 

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

108 

109 return output.getvalue() 

110 

111 

112def _unarmor(pem_bytes): 

113 """ 

114 Convert a PEM-encoded byte string into one or more DER-encoded byte strings 

115 

116 :param pem_bytes: 

117 A byte string of the PEM-encoded data 

118 

119 :raises: 

120 ValueError - when the pem_bytes do not appear to be PEM-encoded bytes 

121 

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

129 

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

137 

138 # Valid states include: "trash", "headers", "body" 

139 state = 'trash' 

140 headers = {} 

141 base64_data = b'' 

142 object_type = None 

143 

144 found_start = False 

145 found_end = False 

146 

147 for line in pem_bytes.splitlines(False): 

148 if line == b'': 

149 continue 

150 

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

158 

159 found_start = True 

160 state = 'headers' 

161 continue 

162 

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 

171 

172 if state == 'body': 

173 if line[0:5] in (b'-----', b'---- '): 

174 der_bytes = base64.b64decode(base64_data) 

175 

176 yield (object_type, headers, der_bytes) 

177 

178 state = 'trash' 

179 headers = {} 

180 base64_data = b'' 

181 object_type = None 

182 found_end = True 

183 continue 

184 

185 base64_data += line 

186 

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

194 

195 

196def unarmor(pem_bytes, multiple=False): 

197 """ 

198 Convert a PEM-encoded byte string into a DER-encoded byte string 

199 

200 :param pem_bytes: 

201 A byte string of the PEM-encoded data 

202 

203 :param multiple: 

204 If True, function will return a generator 

205 

206 :raises: 

207 ValueError - when the pem_bytes do not appear to be PEM-encoded bytes 

208 

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

216 

217 generator = _unarmor(pem_bytes) 

218 

219 if not multiple: 

220 return next(generator) 

221 

222 return generator