Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/ecdsa/der.py: 18%
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
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
1from __future__ import division
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
11class UnexpectedDER(Exception):
12 pass
15def encode_constructed(tag, value):
16 return int2byte(0xA0 + tag) + encode_length(len(value)) + value
19def encode_implicit(tag, value, cls="context-specific"):
20 """
21 Encode and IMPLICIT value using :term:`DER`.
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")
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
42 return int2byte(tag_class + tag) + encode_length(len(value)) + value
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
61# sentry object to check if an argument was specified (used to detect
62# deprecated calling convention)
63_sentry = object()
66def encode_bitstring(s, unused=_sentry):
67 """
68 Encode a binary string as a BIT STRING using :term:`DER` encoding.
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.
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.
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).
86 Empty string must be encoded with `unused` specified as 0.
88 Future version of python-ecdsa will make specifying the `unused` argument
89 mandatory.
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
97 :raises ValueError: when `unused` is too large or too small
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
123def encode_octet_string(s):
124 return b"\x04" + encode_length(len(s)) + s
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
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)
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])
154def is_sequence(string):
155 return string and string[:1] == b"\x30"
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
171def remove_implicit(string, exp_class="context-specific"):
172 """
173 Removes an IMPLICIT tagged value from ``string`` following :term:`DER`.
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
196 s0 = str_idx_as_int(string, 0)
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 )
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
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:]
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
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
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
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
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
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
353def remove_bitstring(string, expect_unused=_sentry):
354 """
355 Remove a BIT STRING object from `string` following :term:`DER`.
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).
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.
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.
373 Future version of python will require the `expected_unused` parameter
374 to be specified.
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
382 :raises UnexpectedDER: when the encoding does not follow DER.
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
427# SEQUENCE([1, STRING(secexp), cont[0], OBJECT(curvename), cont[1], BINTSTRING)
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, .. ..
441# Ecdsa-Sig-Value ::= SEQUENCE {
442# r INTEGER,
443# s INTEGER }
445# id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 }
446#
447# id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 }
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 }
457def unpem(pem):
458 if isinstance(pem, text_type): # pragma: no branch
459 pem = pem.encode()
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)
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)