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

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

213 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_integer(r): 

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

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

22 if len(h) % 2: 

23 h = b"0" + h 

24 s = binascii.unhexlify(h) 

25 num = str_idx_as_int(s, 0) 

26 if num <= 0x7F: 

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

28 else: 

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

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

31 # looking negative. 

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

33 

34 

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

36# deprecated calling convention) 

37_sentry = object() 

38 

39 

40def encode_bitstring(s, unused=_sentry): 

41 """ 

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

43 

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

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

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

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

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

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

50 the padding bits MUST be set to 0. 

51 

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

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

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

55 

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

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

58 as with None). 

59 

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

61 

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

63 mandatory. 

64 

65 :param s: bytes to encode 

66 :type s: bytes like object 

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

68 between 0 and 7 (inclusive) 

69 :type unused: int or None 

70 

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

72 

73 :return: `s` encoded using DER 

74 :rtype: bytes 

75 """ 

76 encoded_unused = b"" 

77 len_extra = 0 

78 if unused is _sentry: 

79 warnings.warn( 

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

81 DeprecationWarning, 

82 ) 

83 elif unused is not None: 

84 if not 0 <= unused <= 7: 

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

86 if unused: 

87 if not s: 

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

89 last = str_idx_as_int(s, -1) 

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

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

92 encoded_unused = int2byte(unused) 

93 len_extra = 1 

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

95 

96 

97def encode_octet_string(s): 

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

99 

100 

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

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

103 body = b"".join( 

104 chain( 

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

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

107 ) 

108 ) 

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

110 

111 

112def encode_sequence(*encoded_pieces): 

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

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

115 

116 

117def encode_number(n): 

118 b128_digits = [] 

119 while n: 

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

121 n = n >> 7 

122 if not b128_digits: 

123 b128_digits.append(0) 

124 b128_digits[-1] &= 0x7F 

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

126 

127 

128def is_sequence(string): 

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

130 

131 

132def remove_constructed(string): 

133 s0 = str_idx_as_int(string, 0) 

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

135 raise UnexpectedDER( 

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

137 ) 

138 tag = s0 & 0x1F 

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

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

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

142 return tag, body, rest 

143 

144 

145def remove_sequence(string): 

146 if not string: 

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

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

149 n = str_idx_as_int(string, 0) 

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

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

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

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

154 endseq = 1 + lengthlength + length 

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

156 

157 

158def remove_octet_string(string): 

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

160 n = str_idx_as_int(string, 0) 

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

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

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

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

165 return body, rest 

166 

167 

168def remove_object(string): 

169 if not string: 

170 raise UnexpectedDER( 

171 "Empty string does not encode an object identifier" 

172 ) 

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

174 n = str_idx_as_int(string, 0) 

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

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

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

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

179 if not body: 

180 raise UnexpectedDER("Empty object identifier") 

181 if len(body) != length: 

182 raise UnexpectedDER( 

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

184 ) 

185 numbers = [] 

186 while body: 

187 n, ll = read_number(body) 

188 numbers.append(n) 

189 body = body[ll:] 

190 n0 = numbers.pop(0) 

191 if n0 < 80: 

192 first = n0 // 40 

193 else: 

194 first = 2 

195 second = n0 - (40 * first) 

196 numbers.insert(0, first) 

197 numbers.insert(1, second) 

198 return tuple(numbers), rest 

199 

200 

201def remove_integer(string): 

202 if not string: 

203 raise UnexpectedDER( 

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

205 ) 

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

207 n = str_idx_as_int(string, 0) 

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

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

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

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

212 if length == 0: 

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

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

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

216 msb = str_idx_as_int(numberbytes, 0) 

217 if not msb < 0x80: 

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

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

220 if length > 1 and not msb: 

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

222 # considered a negative number otherwise 

223 smsb = str_idx_as_int(numberbytes, 1) 

224 if smsb < 0x80: 

225 raise UnexpectedDER( 

226 "Invalid encoding of integer, unnecessary " 

227 "zero padding bytes" 

228 ) 

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

230 

231 

232def read_number(string): 

233 number = 0 

234 llen = 0 

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

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

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

238 # byte 

239 while True: 

240 if llen >= len(string): 

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

242 number = number << 7 

243 d = str_idx_as_int(string, llen) 

244 number += d & 0x7F 

245 llen += 1 

246 if not d & 0x80: 

247 break 

248 return number, llen 

249 

250 

251def encode_length(l): 

252 assert l >= 0 

253 if l < 0x80: 

254 return int2byte(l) 

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

256 if len(s) % 2: 

257 s = b"0" + s 

258 s = binascii.unhexlify(s) 

259 llen = len(s) 

260 return int2byte(0x80 | llen) + s 

261 

262 

263def read_length(string): 

264 if not string: 

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

266 num = str_idx_as_int(string, 0) 

267 if not (num & 0x80): 

268 # short form 

269 return (num & 0x7F), 1 

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

271 # big-endian 

272 llen = num & 0x7F 

273 if not llen: 

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

275 if llen > len(string) - 1: 

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

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

278 msb = str_idx_as_int(string, 1) 

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

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

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

282 

283 

284def remove_bitstring(string, expect_unused=_sentry): 

285 """ 

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

287 

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

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

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

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

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

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

294 

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

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

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

298 

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

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

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

302 correctness. 

303 

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

305 to be specified. 

306 

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

308 :type string: bytes like object 

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

310 STRING, or None, to return it to caller 

311 :type expect_unused: int or None 

312 

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

314 

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

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

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

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

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

320 end of the byte array as an integer 

321 :rtype: tuple 

322 """ 

323 if not string: 

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

325 if expect_unused is _sentry: 

326 warnings.warn( 

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

328 " specified", 

329 DeprecationWarning, 

330 ) 

331 num = str_idx_as_int(string, 0) 

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

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

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

335 if not length: 

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

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

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

339 if expect_unused is not _sentry: 

340 unused = str_idx_as_int(body, 0) 

341 if not 0 <= unused <= 7: 

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

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

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

345 body = body[1:] 

346 if unused: 

347 if not body: 

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

349 last = str_idx_as_int(body, -1) 

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

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

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

353 if expect_unused is None: 

354 body = (body, unused) 

355 return body, rest 

356 

357 

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

359 

360 

361# signatures: (from RFC3279) 

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

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

364# 

365# id-ecSigType OBJECT IDENTIFIER ::= { 

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

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

368# id-ecSigType 1 } 

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

370# so 0x42, .. .. 

371 

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

373# r INTEGER, 

374# s INTEGER } 

375 

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

377# 

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

379 

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

381# secp224r1 OBJECT IDENTIFIER ::= { 

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

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

384# secp384r1 OBJECT IDENTIFIER ::= { 

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

386 

387 

388def unpem(pem): 

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

390 pem = pem.encode() 

391 

392 d = b"".join( 

393 [ 

394 l.strip() 

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

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

397 ] 

398 ) 

399 return base64.b64decode(d) 

400 

401 

402def topem(der, name): 

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

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

405 lines.extend( 

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

407 ) 

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

409 return b"".join(lines)