Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/ecdsa/der.py: 59%

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

252 statements  

1from __future__ import division 

2 

3import binascii 

4import base64 

5import warnings 

6from itertools import chain 

7from six import int2byte, text_type 

8from ._compat import compat26_str, str_idx_as_int 

9 

10 

11class UnexpectedDER(Exception): 

12 pass 

13 

14 

15def encode_constructed(tag, value): 

16 return int2byte(0xA0 + tag) + encode_length(len(value)) + value 

17 

18 

19def encode_implicit(tag, value, cls="context-specific"): 

20 """ 

21 Encode and IMPLICIT value using :term:`DER`. 

22 

23 :param int tag: the tag value to encode, must be between 0 an 31 inclusive 

24 :param bytes value: the data to encode 

25 :param str cls: the class of the tag to encode: "application", 

26 "context-specific", or "private" 

27 :rtype: bytes 

28 """ 

29 if cls not in ("application", "context-specific", "private"): 

30 raise ValueError("invalid tag class") 

31 if tag > 31: 

32 raise ValueError("Long tags not supported") 

33 

34 if cls == "application": 

35 tag_class = 0b01000000 

36 elif cls == "context-specific": 

37 tag_class = 0b10000000 

38 else: 

39 assert cls == "private" 

40 tag_class = 0b11000000 

41 

42 return int2byte(tag_class + tag) + encode_length(len(value)) + value 

43 

44 

45def encode_integer(r): 

46 assert r >= 0 # can't support negative numbers yet 

47 h = ("%x" % r).encode() 

48 if len(h) % 2: 

49 h = b"0" + h 

50 s = binascii.unhexlify(h) 

51 num = str_idx_as_int(s, 0) 

52 if num <= 0x7F: 

53 return b"\x02" + encode_length(len(s)) + s 

54 else: 

55 # DER integers are two's complement, so if the first byte is 

56 # 0x80-0xff then we need an extra 0x00 byte to prevent it from 

57 # looking negative. 

58 return b"\x02" + encode_length(len(s) + 1) + b"\x00" + s 

59 

60 

61# sentry object to check if an argument was specified (used to detect 

62# deprecated calling convention) 

63_sentry = object() 

64 

65 

66def encode_bitstring(s, unused=_sentry): 

67 """ 

68 Encode a binary string as a BIT STRING using :term:`DER` encoding. 

69 

70 Note, because there is no native Python object that can encode an actual 

71 bit string, this function only accepts byte strings as the `s` argument. 

72 The byte string is the actual bit string that will be encoded, padded 

73 on the right (least significant bits, looking from big endian perspective) 

74 to the first full byte. If the bit string has a bit length that is multiple 

75 of 8, then the padding should not be included. For correct DER encoding 

76 the padding bits MUST be set to 0. 

77 

78 Number of bits of padding need to be provided as the `unused` parameter. 

79 In case they are specified as None, it means the number of unused bits 

80 is already encoded in the string as the first byte. 

81 

82 The deprecated call convention specifies just the `s` parameters and 

83 encodes the number of unused bits as first parameter (same convention 

84 as with None). 

85 

86 Empty string must be encoded with `unused` specified as 0. 

87 

88 Future version of python-ecdsa will make specifying the `unused` argument 

89 mandatory. 

90 

91 :param s: bytes to encode 

92 :type s: bytes like object 

93 :param unused: number of bits at the end of `s` that are unused, must be 

94 between 0 and 7 (inclusive) 

95 :type unused: int or None 

96 

97 :raises ValueError: when `unused` is too large or too small 

98 

99 :return: `s` encoded using DER 

100 :rtype: bytes 

101 """ 

102 encoded_unused = b"" 

103 len_extra = 0 

104 if unused is _sentry: 

105 warnings.warn( 

106 "Legacy call convention used, unused= needs to be specified", 

107 DeprecationWarning, 

108 ) 

109 elif unused is not None: 

110 if not 0 <= unused <= 7: 

111 raise ValueError("unused must be integer between 0 and 7") 

112 if unused: 

113 if not s: 

114 raise ValueError("unused is non-zero but s is empty") 

115 last = str_idx_as_int(s, -1) 

116 if last & (2**unused - 1): 

117 raise ValueError("unused bits must be zeros in DER") 

118 encoded_unused = int2byte(unused) 

119 len_extra = 1 

120 return b"\x03" + encode_length(len(s) + len_extra) + encoded_unused + s 

121 

122 

123def encode_octet_string(s): 

124 return b"\x04" + encode_length(len(s)) + s 

125 

126 

127def encode_oid(first, second, *pieces): 

128 assert 0 <= first < 2 and 0 <= second <= 39 or first == 2 and 0 <= second 

129 body = b"".join( 

130 chain( 

131 [encode_number(40 * first + second)], 

132 (encode_number(p) for p in pieces), 

133 ) 

134 ) 

135 return b"\x06" + encode_length(len(body)) + body 

136 

137 

138def encode_sequence(*encoded_pieces): 

139 total_len = sum([len(p) for p in encoded_pieces]) 

140 return b"\x30" + encode_length(total_len) + b"".join(encoded_pieces) 

141 

142 

143def encode_number(n): 

144 b128_digits = [] 

145 while n: 

146 b128_digits.insert(0, (n & 0x7F) | 0x80) 

147 n = n >> 7 

148 if not b128_digits: 

149 b128_digits.append(0) 

150 b128_digits[-1] &= 0x7F 

151 return b"".join([int2byte(d) for d in b128_digits]) 

152 

153 

154def is_sequence(string): 

155 return string and string[:1] == b"\x30" 

156 

157 

158def remove_constructed(string): 

159 s0 = str_idx_as_int(string, 0) 

160 if (s0 & 0xE0) != 0xA0: 

161 raise UnexpectedDER( 

162 "wanted type 'constructed tag' (0xa0-0xbf), got 0x%02x" % s0 

163 ) 

164 tag = s0 & 0x1F 

165 length, llen = read_length(string[1:]) 

166 if length > len(string) - 1 - llen: 

167 raise UnexpectedDER("Length longer than the provided buffer") 

168 body = string[1 + llen : 1 + llen + length] 

169 rest = string[1 + llen + length :] 

170 return tag, body, rest 

171 

172 

173def remove_implicit(string, exp_class="context-specific"): 

174 """ 

175 Removes an IMPLICIT tagged value from ``string`` following :term:`DER`. 

176 

177 :param bytes string: a byte string that can have one or more 

178 DER elements. 

179 :param str exp_class: the expected tag class of the implicitly 

180 encoded value. Possible values are: "context-specific", "application", 

181 and "private". 

182 :return: a tuple with first value being the tag without indicator bits, 

183 second being the raw bytes of the value and the third one being 

184 remaining bytes (or an empty string if there are none) 

185 :rtype: tuple(int,bytes,bytes) 

186 """ 

187 if exp_class not in ("context-specific", "application", "private"): 

188 raise ValueError("invalid `exp_class` value") 

189 if exp_class == "application": 

190 tag_class = 0b01000000 

191 elif exp_class == "context-specific": 

192 tag_class = 0b10000000 

193 else: 

194 assert exp_class == "private" 

195 tag_class = 0b11000000 

196 tag_mask = 0b11000000 

197 

198 s0 = str_idx_as_int(string, 0) 

199 

200 if (s0 & tag_mask) != tag_class: 

201 raise UnexpectedDER( 

202 "wanted class {0}, got 0x{1:02x} tag".format(exp_class, s0) 

203 ) 

204 if s0 & 0b00100000 != 0: 

205 raise UnexpectedDER( 

206 "wanted type primitive, got 0x{0:02x} tag".format(s0) 

207 ) 

208 

209 tag = s0 & 0x1F 

210 length, llen = read_length(string[1:]) 

211 if length > len(string) - 1 - llen: 

212 raise UnexpectedDER("Length longer than the provided buffer") 

213 body = string[1 + llen : 1 + llen + length] 

214 rest = string[1 + llen + length :] 

215 return tag, body, rest 

216 

217 

218def remove_sequence(string): 

219 if not string: 

220 raise UnexpectedDER("Empty string does not encode a sequence") 

221 if string[:1] != b"\x30": 

222 n = str_idx_as_int(string, 0) 

223 raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n) 

224 length, lengthlength = read_length(string[1:]) 

225 if length > len(string) - 1 - lengthlength: 

226 raise UnexpectedDER("Length longer than the provided buffer") 

227 endseq = 1 + lengthlength + length 

228 return string[1 + lengthlength : endseq], string[endseq:] 

229 

230 

231def remove_octet_string(string): 

232 if string[:1] != b"\x04": 

233 n = str_idx_as_int(string, 0) 

234 raise UnexpectedDER("wanted type 'octetstring' (0x04), got 0x%02x" % n) 

235 length, llen = read_length(string[1:]) 

236 if length > len(string) - 1 - llen: 

237 raise UnexpectedDER("Length longer than the provided buffer") 

238 body = string[1 + llen : 1 + llen + length] 

239 rest = string[1 + llen + length :] 

240 return body, rest 

241 

242 

243def remove_object(string): 

244 if not string: 

245 raise UnexpectedDER( 

246 "Empty string does not encode an object identifier" 

247 ) 

248 if string[:1] != b"\x06": 

249 n = str_idx_as_int(string, 0) 

250 raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n) 

251 length, lengthlength = read_length(string[1:]) 

252 body = string[1 + lengthlength : 1 + lengthlength + length] 

253 rest = string[1 + lengthlength + length :] 

254 if not body: 

255 raise UnexpectedDER("Empty object identifier") 

256 if len(body) != length: 

257 raise UnexpectedDER( 

258 "Length of object identifier longer than the provided buffer" 

259 ) 

260 numbers = [] 

261 while body: 

262 n, ll = read_number(body) 

263 numbers.append(n) 

264 body = body[ll:] 

265 n0 = numbers.pop(0) 

266 if n0 < 80: 

267 first = n0 // 40 

268 else: 

269 first = 2 

270 second = n0 - (40 * first) 

271 numbers.insert(0, first) 

272 numbers.insert(1, second) 

273 return tuple(numbers), rest 

274 

275 

276def remove_integer(string): 

277 if not string: 

278 raise UnexpectedDER( 

279 "Empty string is an invalid encoding of an integer" 

280 ) 

281 if string[:1] != b"\x02": 

282 n = str_idx_as_int(string, 0) 

283 raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n) 

284 length, llen = read_length(string[1:]) 

285 if length > len(string) - 1 - llen: 

286 raise UnexpectedDER("Length longer than provided buffer") 

287 if length == 0: 

288 raise UnexpectedDER("0-byte long encoding of integer") 

289 numberbytes = string[1 + llen : 1 + llen + length] 

290 rest = string[1 + llen + length :] 

291 msb = str_idx_as_int(numberbytes, 0) 

292 if not msb < 0x80: 

293 raise UnexpectedDER("Negative integers are not supported") 

294 # check if the encoding is the minimal one (DER requirement) 

295 if length > 1 and not msb: 

296 # leading zero byte is allowed if the integer would have been 

297 # considered a negative number otherwise 

298 smsb = str_idx_as_int(numberbytes, 1) 

299 if smsb < 0x80: 

300 raise UnexpectedDER( 

301 "Invalid encoding of integer, unnecessary " 

302 "zero padding bytes" 

303 ) 

304 return int(binascii.hexlify(numberbytes), 16), rest 

305 

306 

307def read_number(string): 

308 number = 0 

309 llen = 0 

310 if str_idx_as_int(string, 0) == 0x80: 

311 raise UnexpectedDER("Non minimal encoding of OID subidentifier") 

312 # base-128 big endian, with most significant bit set in all but the last 

313 # byte 

314 while True: 

315 if llen >= len(string): 

316 raise UnexpectedDER("ran out of length bytes") 

317 number = number << 7 

318 d = str_idx_as_int(string, llen) 

319 number += d & 0x7F 

320 llen += 1 

321 if not d & 0x80: 

322 break 

323 return number, llen 

324 

325 

326def encode_length(l): 

327 assert l >= 0 

328 if l < 0x80: 

329 return int2byte(l) 

330 s = ("%x" % l).encode() 

331 if len(s) % 2: 

332 s = b"0" + s 

333 s = binascii.unhexlify(s) 

334 llen = len(s) 

335 return int2byte(0x80 | llen) + s 

336 

337 

338def read_length(string): 

339 if not string: 

340 raise UnexpectedDER("Empty string can't encode valid length value") 

341 num = str_idx_as_int(string, 0) 

342 if not (num & 0x80): 

343 # short form 

344 return (num & 0x7F), 1 

345 # else long-form: b0&0x7f is number of additional base256 length bytes, 

346 # big-endian 

347 llen = num & 0x7F 

348 if not llen: 

349 raise UnexpectedDER("Invalid length encoding, length of length is 0") 

350 if llen > len(string) - 1: 

351 raise UnexpectedDER("Length of length longer than provided buffer") 

352 # verify that the encoding is minimal possible (DER requirement) 

353 msb = str_idx_as_int(string, 1) 

354 if not msb or llen == 1 and msb < 0x80: 

355 raise UnexpectedDER("Not minimal encoding of length") 

356 return int(binascii.hexlify(string[1 : 1 + llen]), 16), 1 + llen 

357 

358 

359def remove_bitstring(string, expect_unused=_sentry): 

360 """ 

361 Remove a BIT STRING object from `string` following :term:`DER`. 

362 

363 The `expect_unused` can be used to specify if the bit string should 

364 have the amount of unused bits decoded or not. If it's an integer, any 

365 read BIT STRING that has number of unused bits different from specified 

366 value will cause UnexpectedDER exception to be raised (this is especially 

367 useful when decoding BIT STRINGS that have DER encoded object in them; 

368 DER encoding is byte oriented, so the unused bits will always equal 0). 

369 

370 If the `expect_unused` is specified as None, the first element returned 

371 will be a tuple, with the first value being the extracted bit string 

372 while the second value will be the decoded number of unused bits. 

373 

374 If the `expect_unused` is unspecified, the decoding of byte with 

375 number of unused bits will not be attempted and the bit string will be 

376 returned as-is, the callee will be required to decode it and verify its 

377 correctness. 

378 

379 Future version of python will require the `expected_unused` parameter 

380 to be specified. 

381 

382 :param string: string of bytes to extract the BIT STRING from 

383 :type string: bytes like object 

384 :param expect_unused: number of bits that should be unused in the BIT 

385 STRING, or None, to return it to caller 

386 :type expect_unused: int or None 

387 

388 :raises UnexpectedDER: when the encoding does not follow DER. 

389 

390 :return: a tuple with first element being the extracted bit string and 

391 the second being the remaining bytes in the string (if any); if the 

392 `expect_unused` is specified as None, the first element of the returned 

393 tuple will be a tuple itself, with first element being the bit string 

394 as bytes and the second element being the number of unused bits at the 

395 end of the byte array as an integer 

396 :rtype: tuple 

397 """ 

398 if not string: 

399 raise UnexpectedDER("Empty string does not encode a bitstring") 

400 if expect_unused is _sentry: 

401 warnings.warn( 

402 "Legacy call convention used, expect_unused= needs to be" 

403 " specified", 

404 DeprecationWarning, 

405 ) 

406 num = str_idx_as_int(string, 0) 

407 if string[:1] != b"\x03": 

408 raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num) 

409 length, llen = read_length(string[1:]) 

410 if not length: 

411 raise UnexpectedDER("Invalid length of bit string, can't be 0") 

412 body = string[1 + llen : 1 + llen + length] 

413 rest = string[1 + llen + length :] 

414 if expect_unused is not _sentry: 

415 unused = str_idx_as_int(body, 0) 

416 if not 0 <= unused <= 7: 

417 raise UnexpectedDER("Invalid encoding of unused bits") 

418 if expect_unused is not None and expect_unused != unused: 

419 raise UnexpectedDER("Unexpected number of unused bits") 

420 body = body[1:] 

421 if unused: 

422 if not body: 

423 raise UnexpectedDER("Invalid encoding of empty bit string") 

424 last = str_idx_as_int(body, -1) 

425 # verify that all the unused bits are set to zero (DER requirement) 

426 if last & (2**unused - 1): 

427 raise UnexpectedDER("Non zero padding bits in bit string") 

428 if expect_unused is None: 

429 body = (body, unused) 

430 return body, rest 

431 

432 

433# SEQUENCE([1, STRING(secexp), cont[0], OBJECT(curvename), cont[1], BINTSTRING) 

434 

435 

436# signatures: (from RFC3279) 

437# ansi-X9-62 OBJECT IDENTIFIER ::= { 

438# iso(1) member-body(2) us(840) 10045 } 

439# 

440# id-ecSigType OBJECT IDENTIFIER ::= { 

441# ansi-X9-62 signatures(4) } 

442# ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { 

443# id-ecSigType 1 } 

444# so 1,2,840,10045,4,1 

445# so 0x42, .. .. 

446 

447# Ecdsa-Sig-Value ::= SEQUENCE { 

448# r INTEGER, 

449# s INTEGER } 

450 

451# id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 } 

452# 

453# id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 } 

454 

455# I think the secp224r1 identifier is (t=06,l=05,v=2b81040021) 

456# secp224r1 OBJECT IDENTIFIER ::= { 

457# iso(1) identified-organization(3) certicom(132) curve(0) 33 } 

458# and the secp384r1 is (t=06,l=05,v=2b81040022) 

459# secp384r1 OBJECT IDENTIFIER ::= { 

460# iso(1) identified-organization(3) certicom(132) curve(0) 34 } 

461 

462 

463def unpem(pem): 

464 if isinstance(pem, text_type): # pragma: no branch 

465 pem = pem.encode() 

466 

467 d = b"".join( 

468 [ 

469 l.strip() 

470 for l in pem.split(b"\n") 

471 if l and not l.startswith(b"-----") 

472 ] 

473 ) 

474 return base64.b64decode(d) 

475 

476 

477def topem(der, name): 

478 b64 = base64.b64encode(compat26_str(der)) 

479 lines = [("-----BEGIN %s-----\n" % name).encode()] 

480 lines.extend( 

481 [b64[start : start + 76] + b"\n" for start in range(0, len(b64), 76)] 

482 ) 

483 lines.append(("-----END %s-----\n" % name).encode()) 

484 return b"".join(lines)