• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tlsfuzzer / python-ecdsa / 13832902924

13 Mar 2025 10:54AM UTC coverage: 99.579% (+0.001%) from 99.578%
13832902924

Pull #359

github

web-flow
Merge 658ddc81b into 3c5df06ae
Pull Request #359: add release notes for 0.19.1 release

2560 of 2571 branches covered (99.57%)

Branch coverage included in aggregate %.

6663 of 6691 relevant lines covered (99.58%)

19.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/ecdsa/der.py
1
from __future__ import division
20✔
2

3
import binascii
20✔
4
import base64
20✔
5
import warnings
20✔
6
from itertools import chain
20✔
7
from six import int2byte, text_type
20✔
8
from ._compat import compat26_str, str_idx_as_int
20✔
9

10

11
class UnexpectedDER(Exception):
20✔
12
    pass
20✔
13

14

15
def encode_constructed(tag, value):
20✔
16
    return int2byte(0xA0 + tag) + encode_length(len(value)) + value
20✔
17

18

19
def encode_implicit(tag, value, cls="context-specific"):
20✔
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"):
20✔
30
        raise ValueError("invalid tag class")
20✔
31
    if tag > 31:
20✔
32
        raise ValueError("Long tags not supported")
20✔
33

34
    if cls == "application":
20✔
35
        tag_class = 0b01000000
20✔
36
    elif cls == "context-specific":
20✔
37
        tag_class = 0b10000000
20✔
38
    else:
39
        assert cls == "private"
20✔
40
        tag_class = 0b11000000
20✔
41

42
    return int2byte(tag_class + tag) + encode_length(len(value)) + value
20✔
43

44

45
def encode_integer(r):
20✔
46
    assert r >= 0  # can't support negative numbers yet
20✔
47
    h = ("%x" % r).encode()
20✔
48
    if len(h) % 2:
20✔
49
        h = b"0" + h
20✔
50
    s = binascii.unhexlify(h)
20✔
51
    num = str_idx_as_int(s, 0)
20✔
52
    if num <= 0x7F:
20✔
53
        return b"\x02" + encode_length(len(s)) + s
20✔
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
20✔
59

60

61
# sentry object to check if an argument was specified (used to detect
62
# deprecated calling convention)
63
_sentry = object()
20✔
64

65

66
def encode_bitstring(s, unused=_sentry):
20✔
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""
20✔
103
    len_extra = 0
20✔
104
    if unused is _sentry:
20✔
105
        warnings.warn(
20✔
106
            "Legacy call convention used, unused= needs to be specified",
107
            DeprecationWarning,
108
        )
109
    elif unused is not None:
20✔
110
        if not 0 <= unused <= 7:
20✔
111
            raise ValueError("unused must be integer between 0 and 7")
20✔
112
        if unused:
20✔
113
            if not s:
20✔
114
                raise ValueError("unused is non-zero but s is empty")
20✔
115
            last = str_idx_as_int(s, -1)
20✔
116
            if last & (2**unused - 1):
20✔
117
                raise ValueError("unused bits must be zeros in DER")
20✔
118
        encoded_unused = int2byte(unused)
20✔
119
        len_extra = 1
20✔
120
    return b"\x03" + encode_length(len(s) + len_extra) + encoded_unused + s
20✔
121

122

123
def encode_octet_string(s):
20✔
124
    return b"\x04" + encode_length(len(s)) + s
20✔
125

126

127
def encode_oid(first, second, *pieces):
20✔
128
    assert 0 <= first < 2 and 0 <= second <= 39 or first == 2 and 0 <= second
20✔
129
    body = b"".join(
20✔
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
20✔
136

137

138
def encode_sequence(*encoded_pieces):
20✔
139
    total_len = sum([len(p) for p in encoded_pieces])
20✔
140
    return b"\x30" + encode_length(total_len) + b"".join(encoded_pieces)
20✔
141

142

143
def encode_number(n):
20✔
144
    b128_digits = []
20✔
145
    while n:
20✔
146
        b128_digits.insert(0, (n & 0x7F) | 0x80)
20✔
147
        n = n >> 7
20✔
148
    if not b128_digits:
20✔
149
        b128_digits.append(0)
20✔
150
    b128_digits[-1] &= 0x7F
20✔
151
    return b"".join([int2byte(d) for d in b128_digits])
20✔
152

153

154
def is_sequence(string):
20✔
155
    return string and string[:1] == b"\x30"
20✔
156

157

158
def remove_constructed(string):
20✔
159
    s0 = str_idx_as_int(string, 0)
20✔
160
    if (s0 & 0xE0) != 0xA0:
20✔
161
        raise UnexpectedDER(
20✔
162
            "wanted type 'constructed tag' (0xa0-0xbf), got 0x%02x" % s0
163
        )
164
    tag = s0 & 0x1F
20✔
165
    length, llen = read_length(string[1:])
20✔
166
    body = string[1 + llen : 1 + llen + length]
20✔
167
    rest = string[1 + llen + length :]
20✔
168
    return tag, body, rest
20✔
169

170

171
def remove_implicit(string, exp_class="context-specific"):
20✔
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"):
20✔
186
        raise ValueError("invalid `exp_class` value")
20✔
187
    if exp_class == "application":
20✔
188
        tag_class = 0b01000000
20✔
189
    elif exp_class == "context-specific":
20✔
190
        tag_class = 0b10000000
20✔
191
    else:
192
        assert exp_class == "private"
20✔
193
        tag_class = 0b11000000
20✔
194
    tag_mask = 0b11000000
20✔
195

196
    s0 = str_idx_as_int(string, 0)
20✔
197

198
    if (s0 & tag_mask) != tag_class:
20✔
199
        raise UnexpectedDER(
20✔
200
            "wanted class {0}, got 0x{1:02x} tag".format(exp_class, s0)
201
        )
202
    if s0 & 0b00100000 != 0:
20✔
203
        raise UnexpectedDER(
20✔
204
            "wanted type primitive, got 0x{0:02x} tag".format(s0)
205
        )
206

207
    tag = s0 & 0x1F
20✔
208
    length, llen = read_length(string[1:])
20✔
209
    body = string[1 + llen : 1 + llen + length]
20✔
210
    rest = string[1 + llen + length :]
20✔
211
    return tag, body, rest
20✔
212

213

214
def remove_sequence(string):
20✔
215
    if not string:
20✔
216
        raise UnexpectedDER("Empty string does not encode a sequence")
20✔
217
    if string[:1] != b"\x30":
20✔
218
        n = str_idx_as_int(string, 0)
20✔
219
        raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n)
20✔
220
    length, lengthlength = read_length(string[1:])
20✔
221
    if length > len(string) - 1 - lengthlength:
20✔
222
        raise UnexpectedDER("Length longer than the provided buffer")
20✔
223
    endseq = 1 + lengthlength + length
20✔
224
    return string[1 + lengthlength : endseq], string[endseq:]
20✔
225

226

227
def remove_octet_string(string):
20✔
228
    if string[:1] != b"\x04":
20✔
229
        n = str_idx_as_int(string, 0)
20✔
230
        raise UnexpectedDER("wanted type 'octetstring' (0x04), got 0x%02x" % n)
20✔
231
    length, llen = read_length(string[1:])
20✔
232
    body = string[1 + llen : 1 + llen + length]
20✔
233
    rest = string[1 + llen + length :]
20✔
234
    return body, rest
20✔
235

236

237
def remove_object(string):
20✔
238
    if not string:
20✔
239
        raise UnexpectedDER(
20✔
240
            "Empty string does not encode an object identifier"
241
        )
242
    if string[:1] != b"\x06":
20✔
243
        n = str_idx_as_int(string, 0)
20✔
244
        raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n)
20✔
245
    length, lengthlength = read_length(string[1:])
20✔
246
    body = string[1 + lengthlength : 1 + lengthlength + length]
20✔
247
    rest = string[1 + lengthlength + length :]
20✔
248
    if not body:
20✔
249
        raise UnexpectedDER("Empty object identifier")
20✔
250
    if len(body) != length:
20✔
251
        raise UnexpectedDER(
20✔
252
            "Length of object identifier longer than the provided buffer"
253
        )
254
    numbers = []
20✔
255
    while body:
20✔
256
        n, ll = read_number(body)
20✔
257
        numbers.append(n)
20✔
258
        body = body[ll:]
20✔
259
    n0 = numbers.pop(0)
20✔
260
    if n0 < 80:
20✔
261
        first = n0 // 40
20✔
262
    else:
263
        first = 2
20✔
264
    second = n0 - (40 * first)
20✔
265
    numbers.insert(0, first)
20✔
266
    numbers.insert(1, second)
20✔
267
    return tuple(numbers), rest
20✔
268

269

270
def remove_integer(string):
20✔
271
    if not string:
20✔
272
        raise UnexpectedDER(
20✔
273
            "Empty string is an invalid encoding of an integer"
274
        )
275
    if string[:1] != b"\x02":
20✔
276
        n = str_idx_as_int(string, 0)
20✔
277
        raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n)
20✔
278
    length, llen = read_length(string[1:])
20✔
279
    if length > len(string) - 1 - llen:
20✔
280
        raise UnexpectedDER("Length longer than provided buffer")
20✔
281
    if length == 0:
20✔
282
        raise UnexpectedDER("0-byte long encoding of integer")
20✔
283
    numberbytes = string[1 + llen : 1 + llen + length]
20✔
284
    rest = string[1 + llen + length :]
20✔
285
    msb = str_idx_as_int(numberbytes, 0)
20✔
286
    if not msb < 0x80:
20✔
287
        raise UnexpectedDER("Negative integers are not supported")
20✔
288
    # check if the encoding is the minimal one (DER requirement)
289
    if length > 1 and not msb:
20✔
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)
20✔
293
        if smsb < 0x80:
20✔
294
            raise UnexpectedDER(
20✔
295
                "Invalid encoding of integer, unnecessary "
296
                "zero padding bytes"
297
            )
298
    return int(binascii.hexlify(numberbytes), 16), rest
20✔
299

300

301
def read_number(string):
20✔
302
    number = 0
20✔
303
    llen = 0
20✔
304
    if str_idx_as_int(string, 0) == 0x80:
20✔
305
        raise UnexpectedDER("Non minimal encoding of OID subidentifier")
20✔
306
    # base-128 big endian, with most significant bit set in all but the last
307
    # byte
308
    while True:
18✔
309
        if llen >= len(string):
20✔
310
            raise UnexpectedDER("ran out of length bytes")
20✔
311
        number = number << 7
20✔
312
        d = str_idx_as_int(string, llen)
20✔
313
        number += d & 0x7F
20✔
314
        llen += 1
20✔
315
        if not d & 0x80:
20✔
316
            break
20✔
317
    return number, llen
20✔
318

319

320
def encode_length(l):
20✔
321
    assert l >= 0
20✔
322
    if l < 0x80:
20✔
323
        return int2byte(l)
20✔
324
    s = ("%x" % l).encode()
20✔
325
    if len(s) % 2:
20✔
326
        s = b"0" + s
20✔
327
    s = binascii.unhexlify(s)
20✔
328
    llen = len(s)
20✔
329
    return int2byte(0x80 | llen) + s
20✔
330

331

332
def read_length(string):
20✔
333
    if not string:
20✔
334
        raise UnexpectedDER("Empty string can't encode valid length value")
20✔
335
    num = str_idx_as_int(string, 0)
20✔
336
    if not (num & 0x80):
20✔
337
        # short form
338
        return (num & 0x7F), 1
20✔
339
    # else long-form: b0&0x7f is number of additional base256 length bytes,
340
    # big-endian
341
    llen = num & 0x7F
20✔
342
    if not llen:
20✔
343
        raise UnexpectedDER("Invalid length encoding, length of length is 0")
20✔
344
    if llen > len(string) - 1:
20✔
345
        raise UnexpectedDER("Length of length longer than provided buffer")
20✔
346
    # verify that the encoding is minimal possible (DER requirement)
347
    msb = str_idx_as_int(string, 1)
20✔
348
    if not msb or llen == 1 and msb < 0x80:
20✔
349
        raise UnexpectedDER("Not minimal encoding of length")
20✔
350
    return int(binascii.hexlify(string[1 : 1 + llen]), 16), 1 + llen
20✔
351

352

353
def remove_bitstring(string, expect_unused=_sentry):
20✔
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:
20✔
393
        raise UnexpectedDER("Empty string does not encode a bitstring")
20✔
394
    if expect_unused is _sentry:
20✔
395
        warnings.warn(
20✔
396
            "Legacy call convention used, expect_unused= needs to be"
397
            " specified",
398
            DeprecationWarning,
399
        )
400
    num = str_idx_as_int(string, 0)
20✔
401
    if string[:1] != b"\x03":
20✔
402
        raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num)
20✔
403
    length, llen = read_length(string[1:])
20✔
404
    if not length:
20✔
405
        raise UnexpectedDER("Invalid length of bit string, can't be 0")
20✔
406
    body = string[1 + llen : 1 + llen + length]
20✔
407
    rest = string[1 + llen + length :]
20✔
408
    if expect_unused is not _sentry:
20✔
409
        unused = str_idx_as_int(body, 0)
20✔
410
        if not 0 <= unused <= 7:
20✔
411
            raise UnexpectedDER("Invalid encoding of unused bits")
20✔
412
        if expect_unused is not None and expect_unused != unused:
20✔
413
            raise UnexpectedDER("Unexpected number of unused bits")
20✔
414
        body = body[1:]
20✔
415
        if unused:
20✔
416
            if not body:
20✔
417
                raise UnexpectedDER("Invalid encoding of empty bit string")
20✔
418
            last = str_idx_as_int(body, -1)
20✔
419
            # verify that all the unused bits are set to zero (DER requirement)
420
            if last & (2**unused - 1):
20✔
421
                raise UnexpectedDER("Non zero padding bits in bit string")
20✔
422
        if expect_unused is None:
20✔
423
            body = (body, unused)
20✔
424
    return body, rest
20✔
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

457
def unpem(pem):
20✔
458
    if isinstance(pem, text_type):  # pragma: no branch
20✔
459
        pem = pem.encode()
12✔
460

461
    d = b"".join(
20✔
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)
20✔
469

470

471
def topem(der, name):
20✔
472
    b64 = base64.b64encode(compat26_str(der))
20✔
473
    lines = [("-----BEGIN %s-----\n" % name).encode()]
20✔
474
    lines.extend(
20✔
475
        [b64[start : start + 76] + b"\n" for start in range(0, len(b64), 76)]
476
    )
477
    lines.append(("-----END %s-----\n" % name).encode())
20✔
478
    return b"".join(lines)
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc