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

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

109 statements  

1# coding: utf-8 

2 

3""" 

4Functions for parsing and dumping using the ASN.1 DER encoding. Exports the 

5following items: 

6 

7 - emit() 

8 - parse() 

9 - peek() 

10 

11Other type classes are defined that help compose the types listed above. 

12""" 

13 

14from __future__ import unicode_literals, division, absolute_import, print_function 

15 

16import sys 

17 

18from ._types import byte_cls, chr_cls, type_name 

19from .util import int_from_bytes, int_to_bytes 

20 

21_PY2 = sys.version_info <= (3,) 

22_INSUFFICIENT_DATA_MESSAGE = 'Insufficient data - %s bytes requested but only %s available' 

23_MAX_DEPTH = 10 

24 

25 

26def emit(class_, method, tag, contents): 

27 """ 

28 Constructs a byte string of an ASN.1 DER-encoded value 

29 

30 This is typically not useful. Instead, use one of the standard classes from 

31 asn1crypto.core, or construct a new class with specific fields, and call the 

32 .dump() method. 

33 

34 :param class_: 

35 An integer ASN.1 class value: 0 (universal), 1 (application), 

36 2 (context), 3 (private) 

37 

38 :param method: 

39 An integer ASN.1 method value: 0 (primitive), 1 (constructed) 

40 

41 :param tag: 

42 An integer ASN.1 tag value 

43 

44 :param contents: 

45 A byte string of the encoded byte contents 

46 

47 :return: 

48 A byte string of the ASN.1 DER value (header and contents) 

49 """ 

50 

51 if not isinstance(class_, int): 

52 raise TypeError('class_ must be an integer, not %s' % type_name(class_)) 

53 

54 if class_ < 0 or class_ > 3: 

55 raise ValueError('class_ must be one of 0, 1, 2 or 3, not %s' % class_) 

56 

57 if not isinstance(method, int): 

58 raise TypeError('method must be an integer, not %s' % type_name(method)) 

59 

60 if method < 0 or method > 1: 

61 raise ValueError('method must be 0 or 1, not %s' % method) 

62 

63 if not isinstance(tag, int): 

64 raise TypeError('tag must be an integer, not %s' % type_name(tag)) 

65 

66 if tag < 0: 

67 raise ValueError('tag must be greater than zero, not %s' % tag) 

68 

69 if not isinstance(contents, byte_cls): 

70 raise TypeError('contents must be a byte string, not %s' % type_name(contents)) 

71 

72 return _dump_header(class_, method, tag, contents) + contents 

73 

74 

75def parse(contents, strict=False): 

76 """ 

77 Parses a byte string of ASN.1 BER/DER-encoded data. 

78 

79 This is typically not useful. Instead, use one of the standard classes from 

80 asn1crypto.core, or construct a new class with specific fields, and call the 

81 .load() class method. 

82 

83 :param contents: 

84 A byte string of BER/DER-encoded data 

85 

86 :param strict: 

87 A boolean indicating if trailing data should be forbidden - if so, a 

88 ValueError will be raised when trailing data exists 

89 

90 :raises: 

91 ValueError - when the contents do not contain an ASN.1 header or are truncated in some way 

92 TypeError - when contents is not a byte string 

93 

94 :return: 

95 A 6-element tuple: 

96 - 0: integer class (0 to 3) 

97 - 1: integer method 

98 - 2: integer tag 

99 - 3: byte string header 

100 - 4: byte string content 

101 - 5: byte string trailer 

102 """ 

103 

104 if not isinstance(contents, byte_cls): 

105 raise TypeError('contents must be a byte string, not %s' % type_name(contents)) 

106 

107 contents_len = len(contents) 

108 info, consumed = _parse(contents, contents_len) 

109 if strict and consumed != contents_len: 

110 raise ValueError('Extra data - %d bytes of trailing data were provided' % (contents_len - consumed)) 

111 return info 

112 

113 

114def peek(contents): 

115 """ 

116 Parses a byte string of ASN.1 BER/DER-encoded data to find the length 

117 

118 This is typically used to look into an encoded value to see how long the 

119 next chunk of ASN.1-encoded data is. Primarily it is useful when a 

120 value is a concatenation of multiple values. 

121 

122 :param contents: 

123 A byte string of BER/DER-encoded data 

124 

125 :raises: 

126 ValueError - when the contents do not contain an ASN.1 header or are truncated in some way 

127 TypeError - when contents is not a byte string 

128 

129 :return: 

130 An integer with the number of bytes occupied by the ASN.1 value 

131 """ 

132 

133 if not isinstance(contents, byte_cls): 

134 raise TypeError('contents must be a byte string, not %s' % type_name(contents)) 

135 

136 info, consumed = _parse(contents, len(contents)) 

137 return consumed 

138 

139 

140def _parse(encoded_data, data_len, pointer=0, lengths_only=False, depth=0): 

141 """ 

142 Parses a byte string into component parts 

143 

144 :param encoded_data: 

145 A byte string that contains BER-encoded data 

146 

147 :param data_len: 

148 The integer length of the encoded data 

149 

150 :param pointer: 

151 The index in the byte string to parse from 

152 

153 :param lengths_only: 

154 A boolean to cause the call to return a 2-element tuple of the integer 

155 number of bytes in the header and the integer number of bytes in the 

156 contents. Internal use only. 

157 

158 :param depth: 

159 The recursion depth when evaluating indefinite-length encoding. 

160 

161 :return: 

162 A 2-element tuple: 

163 - 0: A tuple of (class_, method, tag, header, content, trailer) 

164 - 1: An integer indicating how many bytes were consumed 

165 """ 

166 

167 if depth > _MAX_DEPTH: 

168 raise ValueError('Indefinite-length recursion limit exceeded') 

169 

170 start = pointer 

171 

172 if data_len < pointer + 1: 

173 raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer)) 

174 first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer] 

175 

176 pointer += 1 

177 

178 tag = first_octet & 31 

179 constructed = (first_octet >> 5) & 1 

180 # Base 128 length using 8th bit as continuation indicator 

181 if tag == 31: 

182 tag = 0 

183 while True: 

184 if data_len < pointer + 1: 

185 raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer)) 

186 num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer] 

187 pointer += 1 

188 if num == 0x80 and tag == 0: 

189 raise ValueError('Non-minimal tag encoding') 

190 tag *= 128 

191 tag += num & 127 

192 if num >> 7 == 0: 

193 break 

194 if tag < 31: 

195 raise ValueError('Non-minimal tag encoding') 

196 

197 if data_len < pointer + 1: 

198 raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer)) 

199 length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer] 

200 pointer += 1 

201 trailer = b'' 

202 

203 if length_octet >> 7 == 0: 

204 contents_end = pointer + (length_octet & 127) 

205 

206 else: 

207 length_octets = length_octet & 127 

208 if length_octets: 

209 if data_len < pointer + length_octets: 

210 raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (length_octets, data_len - pointer)) 

211 pointer += length_octets 

212 contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False) 

213 

214 else: 

215 # To properly parse indefinite length values, we need to scan forward 

216 # parsing headers until we find a value with a length of zero. If we 

217 # just scanned looking for \x00\x00, nested indefinite length values 

218 # would not work. 

219 if not constructed: 

220 raise ValueError('Indefinite-length element must be constructed') 

221 contents_end = pointer 

222 while data_len < contents_end + 2 or encoded_data[contents_end:contents_end+2] != b'\x00\x00': 

223 _, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True, depth=depth+1) 

224 contents_end += 2 

225 trailer = b'\x00\x00' 

226 

227 if contents_end > data_len: 

228 raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end - pointer, data_len - pointer)) 

229 

230 if lengths_only: 

231 return (pointer, contents_end) 

232 

233 return ( 

234 ( 

235 first_octet >> 6, 

236 constructed, 

237 tag, 

238 encoded_data[start:pointer], 

239 encoded_data[pointer:contents_end-len(trailer)], 

240 trailer 

241 ), 

242 contents_end 

243 ) 

244 

245 

246def _dump_header(class_, method, tag, contents): 

247 """ 

248 Constructs the header bytes for an ASN.1 object 

249 

250 :param class_: 

251 An integer ASN.1 class value: 0 (universal), 1 (application), 

252 2 (context), 3 (private) 

253 

254 :param method: 

255 An integer ASN.1 method value: 0 (primitive), 1 (constructed) 

256 

257 :param tag: 

258 An integer ASN.1 tag value 

259 

260 :param contents: 

261 A byte string of the encoded byte contents 

262 

263 :return: 

264 A byte string of the ASN.1 DER header 

265 """ 

266 

267 header = b'' 

268 

269 id_num = 0 

270 id_num |= class_ << 6 

271 id_num |= method << 5 

272 

273 if tag >= 31: 

274 cont_bit = 0 

275 while tag > 0: 

276 header = chr_cls(cont_bit | (tag & 0x7f)) + header 

277 if not cont_bit: 

278 cont_bit = 0x80 

279 tag = tag >> 7 

280 header = chr_cls(id_num | 31) + header 

281 else: 

282 header += chr_cls(id_num | tag) 

283 

284 length = len(contents) 

285 if length <= 127: 

286 header += chr_cls(length) 

287 else: 

288 length_bytes = int_to_bytes(length) 

289 header += chr_cls(0x80 | len(length_bytes)) 

290 header += length_bytes 

291 

292 return header