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
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_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
35# sentry object to check if an argument was specified (used to detect
36# deprecated calling convention)
37_sentry = object()
40def encode_bitstring(s, unused=_sentry):
41 """
42 Encode a binary string as a BIT STRING using :term:`DER` encoding.
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.
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.
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).
60 Empty string must be encoded with `unused` specified as 0.
62 Future version of python-ecdsa will make specifying the `unused` argument
63 mandatory.
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
71 :raises ValueError: when `unused` is too large or too small
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
97def encode_octet_string(s):
98 return b"\x04" + encode_length(len(s)) + s
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
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)
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])
128def is_sequence(string):
129 return string and string[:1] == b"\x30"
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
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:]
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
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
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
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
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
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
284def remove_bitstring(string, expect_unused=_sentry):
285 """
286 Remove a BIT STRING object from `string` following :term:`DER`.
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).
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.
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.
304 Future version of python will require the `expected_unused` parameter
305 to be specified.
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
313 :raises UnexpectedDER: when the encoding does not follow DER.
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
358# SEQUENCE([1, STRING(secexp), cont[0], OBJECT(curvename), cont[1], BINTSTRING)
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, .. ..
372# Ecdsa-Sig-Value ::= SEQUENCE {
373# r INTEGER,
374# s INTEGER }
376# id-public-key-type OBJECT IDENTIFIER ::= { ansi-X9.62 2 }
377#
378# id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 }
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 }
388def unpem(pem):
389 if isinstance(pem, text_type): # pragma: no branch
390 pem = pem.encode()
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)
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)