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
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 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
173def remove_implicit(string, exp_class="context-specific"):
174 """
175 Removes an IMPLICIT tagged value from ``string`` following :term:`DER`.
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
198 s0 = str_idx_as_int(string, 0)
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 )
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
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:]
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
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
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
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
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
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
359def remove_bitstring(string, expect_unused=_sentry):
360 """
361 Remove a BIT STRING object from `string` following :term:`DER`.
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).
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.
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.
379 Future version of python will require the `expected_unused` parameter
380 to be specified.
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
388 :raises UnexpectedDER: when the encoding does not follow DER.
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
433# SEQUENCE([1, STRING(secexp), cont[0], OBJECT(curvename), cont[1], BINTSTRING)
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, .. ..
447# Ecdsa-Sig-Value ::= SEQUENCE {
448# r INTEGER,
449# s INTEGER }
451# id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 }
452#
453# id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 }
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 }
463def unpem(pem):
464 if isinstance(pem, text_type): # pragma: no branch
465 pem = pem.encode()
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)
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)