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

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

246 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 body = string[1 + llen : 1 + llen + length] 

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

168 return tag, body, rest 

169 

170 

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

172 """ 

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

174 

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

176 DER elements. 

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

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

179 and "private". 

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

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

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

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

184 """ 

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

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

187 if exp_class == "application": 

188 tag_class = 0b01000000 

189 elif exp_class == "context-specific": 

190 tag_class = 0b10000000 

191 else: 

192 assert exp_class == "private" 

193 tag_class = 0b11000000 

194 tag_mask = 0b11000000 

195 

196 s0 = str_idx_as_int(string, 0) 

197 

198 if (s0 & tag_mask) != tag_class: 

199 raise UnexpectedDER( 

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

201 ) 

202 if s0 & 0b00100000 != 0: 

203 raise UnexpectedDER( 

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

205 ) 

206 

207 tag = s0 & 0x1F 

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

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

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

211 return tag, body, rest 

212 

213 

214def remove_sequence(string): 

215 if not string: 

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

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

218 n = str_idx_as_int(string, 0) 

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

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

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

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

223 endseq = 1 + lengthlength + length 

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

225 

226 

227def remove_octet_string(string): 

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

229 n = str_idx_as_int(string, 0) 

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

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

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

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

234 return body, rest 

235 

236 

237def remove_object(string): 

238 if not string: 

239 raise UnexpectedDER( 

240 "Empty string does not encode an object identifier" 

241 ) 

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

243 n = str_idx_as_int(string, 0) 

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

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

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

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

248 if not body: 

249 raise UnexpectedDER("Empty object identifier") 

250 if len(body) != length: 

251 raise UnexpectedDER( 

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

253 ) 

254 numbers = [] 

255 while body: 

256 n, ll = read_number(body) 

257 numbers.append(n) 

258 body = body[ll:] 

259 n0 = numbers.pop(0) 

260 if n0 < 80: 

261 first = n0 // 40 

262 else: 

263 first = 2 

264 second = n0 - (40 * first) 

265 numbers.insert(0, first) 

266 numbers.insert(1, second) 

267 return tuple(numbers), rest 

268 

269 

270def remove_integer(string): 

271 if not string: 

272 raise UnexpectedDER( 

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

274 ) 

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

276 n = str_idx_as_int(string, 0) 

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

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

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

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

281 if length == 0: 

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

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

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

285 msb = str_idx_as_int(numberbytes, 0) 

286 if not msb < 0x80: 

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

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

289 if length > 1 and not msb: 

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

291 # considered a negative number otherwise 

292 smsb = str_idx_as_int(numberbytes, 1) 

293 if smsb < 0x80: 

294 raise UnexpectedDER( 

295 "Invalid encoding of integer, unnecessary " 

296 "zero padding bytes" 

297 ) 

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

299 

300 

301def read_number(string): 

302 number = 0 

303 llen = 0 

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

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

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

307 # byte 

308 while True: 

309 if llen >= len(string): 

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

311 number = number << 7 

312 d = str_idx_as_int(string, llen) 

313 number += d & 0x7F 

314 llen += 1 

315 if not d & 0x80: 

316 break 

317 return number, llen 

318 

319 

320def encode_length(l): 

321 assert l >= 0 

322 if l < 0x80: 

323 return int2byte(l) 

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

325 if len(s) % 2: 

326 s = b"0" + s 

327 s = binascii.unhexlify(s) 

328 llen = len(s) 

329 return int2byte(0x80 | llen) + s 

330 

331 

332def read_length(string): 

333 if not string: 

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

335 num = str_idx_as_int(string, 0) 

336 if not (num & 0x80): 

337 # short form 

338 return (num & 0x7F), 1 

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

340 # big-endian 

341 llen = num & 0x7F 

342 if not llen: 

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

344 if llen > len(string) - 1: 

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

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

347 msb = str_idx_as_int(string, 1) 

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

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

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

351 

352 

353def remove_bitstring(string, expect_unused=_sentry): 

354 """ 

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

356 

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

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

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

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

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

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

363 

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

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

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

367 

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

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

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

371 correctness. 

372 

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

374 to be specified. 

375 

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

377 :type string: bytes like object 

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

379 STRING, or None, to return it to caller 

380 :type expect_unused: int or None 

381 

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

383 

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

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

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

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

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

389 end of the byte array as an integer 

390 :rtype: tuple 

391 """ 

392 if not string: 

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

394 if expect_unused is _sentry: 

395 warnings.warn( 

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

397 " specified", 

398 DeprecationWarning, 

399 ) 

400 num = str_idx_as_int(string, 0) 

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

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

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

404 if not length: 

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

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

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

408 if expect_unused is not _sentry: 

409 unused = str_idx_as_int(body, 0) 

410 if not 0 <= unused <= 7: 

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

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

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

414 body = body[1:] 

415 if unused: 

416 if not body: 

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

418 last = str_idx_as_int(body, -1) 

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

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

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

422 if expect_unused is None: 

423 body = (body, unused) 

424 return body, rest 

425 

426 

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

428 

429 

430# signatures: (from RFC3279) 

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

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

433# 

434# id-ecSigType OBJECT IDENTIFIER ::= { 

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

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

437# id-ecSigType 1 } 

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

439# so 0x42, .. .. 

440 

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

442# r INTEGER, 

443# s INTEGER } 

444 

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

446# 

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

448 

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

450# secp224r1 OBJECT IDENTIFIER ::= { 

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

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

453# secp384r1 OBJECT IDENTIFIER ::= { 

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

455 

456 

457def unpem(pem): 

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

459 pem = pem.encode() 

460 

461 d = b"".join( 

462 [ 

463 l.strip() 

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

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

466 ] 

467 ) 

468 return base64.b64decode(d) 

469 

470 

471def topem(der, name): 

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

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

474 lines.extend( 

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

476 ) 

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

478 return b"".join(lines)