Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions src/ecdsa/der.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import binascii
import base64
import warnings
from itertools import chain
from six import int2byte, b, text_type
from ._compat import str_idx_as_int

Expand Down Expand Up @@ -97,12 +98,10 @@ def encode_octet_string(s):


def encode_oid(first, second, *pieces):
assert first <= 2
assert second <= 39
encoded_pieces = [int2byte(40*first+second)] + [encode_number(p)
for p in pieces]
body = b('').join(encoded_pieces)
return b('\x06') + encode_length(len(body)) + body
assert 0 <= first < 2 and 0 <= second <= 39 or first == 2 and 0 <= second
body = b''.join(chain([encode_number(40*first+second)],
(encode_number(p) for p in pieces)))
return b'\x06' + encode_length(len(body)) + body


def encode_sequence(*encoded_pieces):
Expand Down Expand Up @@ -157,20 +156,31 @@ def remove_octet_string(string):


def remove_object(string):
if not string:
raise UnexpectedDER(
"Empty string does not encode an object identifier")
if string[:1] != b"\x06":
n = str_idx_as_int(string, 0)
raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n)
length, lengthlength = read_length(string[1:])
body = string[1+lengthlength:1+lengthlength+length]
rest = string[1+lengthlength+length:]
if not body:
raise UnexpectedDER("Empty object identifier")
if len(body) != length:
raise UnexpectedDER(
"Length of object identifier longer than the provided buffer")
numbers = []
while body:
n, ll = read_number(body)
numbers.append(n)
body = body[ll:]
n0 = numbers.pop(0)
first = n0//40
second = n0-(40*first)
if n0 < 80:
first = n0 // 40
else:
first = 2
second = n0 - (40 * first)
numbers.insert(0, first)
numbers.insert(1, second)
return tuple(numbers), rest
Expand Down Expand Up @@ -207,9 +217,12 @@ def remove_integer(string):
def read_number(string):
number = 0
llen = 0
# base-128 big endian, with b7 set in all but the last byte
if str_idx_as_int(string, 0) == 0x80:
raise UnexpectedDER("Non minimal encoding of OID subidentifier")
# base-128 big endian, with most significant bit set in all but the last
# byte
while True:
if llen > len(string):
if llen >= len(string):
raise UnexpectedDER("ran out of length bytes")
number = number << 7
d = str_idx_as_int(string, llen)
Expand Down
146 changes: 143 additions & 3 deletions src/ecdsa/test_der.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@

# compatibility with Python 2.6, for that we need unittest2 package,
# which is not available on 3.3 or 3.4
import warnings
from binascii import hexlify
try:
import unittest2 as unittest
except ImportError:
import unittest
from .der import remove_integer, UnexpectedDER, read_length, encode_bitstring,\
remove_bitstring
from six import b
import hypothesis.strategies as st
from hypothesis import given, example
import pytest
import warnings
from ._compat import str_idx_as_int
from .curves import NIST256p, NIST224p
from .der import remove_integer, UnexpectedDER, read_length, encode_bitstring,\
remove_bitstring, remove_object, encode_oid


class TestRemoveInteger(unittest.TestCase):
Expand Down Expand Up @@ -242,3 +246,139 @@ def test_bytes(self):

def test_bytearray(self):
self.assertEqual(115, str_idx_as_int(bytearray(b'str'), 0))


class TestEncodeOid(unittest.TestCase):
def test_pub_key_oid(self):
oid_ecPublicKey = encode_oid(1, 2, 840, 10045, 2, 1)
self.assertEqual(hexlify(oid_ecPublicKey), b("06072a8648ce3d0201"))

def test_nist224p_oid(self):
self.assertEqual(hexlify(NIST224p.encoded_oid), b("06052b81040021"))

def test_nist256p_oid(self):
self.assertEqual(hexlify(NIST256p.encoded_oid),
b"06082a8648ce3d030107")

def test_large_second_subid(self):
# from X.690, section 8.19.5
oid = encode_oid(2, 999, 3)
self.assertEqual(oid, b'\x06\x03\x88\x37\x03')

def test_with_two_subids(self):
oid = encode_oid(2, 999)
self.assertEqual(oid, b'\x06\x02\x88\x37')

def test_zero_zero(self):
oid = encode_oid(0, 0)
self.assertEqual(oid, b'\x06\x01\x00')

def test_with_wrong_types(self):
with self.assertRaises((TypeError, AssertionError)):
encode_oid(0, None)

def test_with_small_first_large_second(self):
with self.assertRaises(AssertionError):
encode_oid(1, 40)

def test_small_first_max_second(self):
oid = encode_oid(1, 39)
self.assertEqual(oid, b'\x06\x01\x4f')

def test_with_invalid_first(self):
with self.assertRaises(AssertionError):
encode_oid(3, 39)


class TestRemoveObject(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.oid_ecPublicKey = encode_oid(1, 2, 840, 10045, 2, 1)

def test_pub_key_oid(self):
oid, rest = remove_object(self.oid_ecPublicKey)
self.assertEqual(rest, b'')
self.assertEqual(oid, (1, 2, 840, 10045, 2, 1))

def test_with_extra_bytes(self):
oid, rest = remove_object(self.oid_ecPublicKey + b'more')
self.assertEqual(rest, b'more')
self.assertEqual(oid, (1, 2, 840, 10045, 2, 1))

def test_with_large_second_subid(self):
# from X.690, section 8.19.5
oid, rest = remove_object(b'\x06\x03\x88\x37\x03')
self.assertEqual(rest, b'')
self.assertEqual(oid, (2, 999, 3))

def test_with_padded_first_subid(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06\x02\x80\x00')

def test_with_padded_second_subid(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06\x04\x88\x37\x80\x01')

def test_with_missing_last_byte_of_multi_byte(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06\x03\x88\x37\x83')

def test_with_two_subids(self):
oid, rest = remove_object(b'\x06\x02\x88\x37')
self.assertEqual(rest, b'')
self.assertEqual(oid, (2, 999))

def test_zero_zero(self):
oid, rest = remove_object(b'\x06\x01\x00')
self.assertEqual(rest, b'')
self.assertEqual(oid, (0, 0))

def test_empty_string(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'')

def test_missing_length(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06')

def test_empty_oid(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06\x00')

def test_empty_oid_overflow(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06\x01')

def test_with_wrong_type(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x04\x02\x88\x37')

def test_with_too_long_length(self):
with self.assertRaises(UnexpectedDER):
remove_object(b'\x06\x03\x88\x37')


@st.composite
def st_oid(draw, max_value=2**512, max_size=50):
"""
Hypothesis strategy that returns valid OBJECT IDENTIFIERs as tuples

:param max_value: maximum value of any single sub-identifier
:param max_size: maximum length of the generated OID
"""
first = draw(st.integers(min_value=0, max_value=2))
if first < 2:
second = draw(st.integers(min_value=0, max_value=39))
else:
second = draw(st.integers(min_value=0, max_value=max_value))
rest = draw(st.lists(st.integers(min_value=0, max_value=max_value),
max_size=max_size))
return (first, second) + tuple(rest)


@given(st_oid())
def test_oids(ids):
encoded_oid = encode_oid(*ids)
decoded_oid, rest = remove_object(encoded_oid)
assert rest == b''
assert decoded_oid == ids
5 changes: 4 additions & 1 deletion src/ecdsa/test_malformed_sigs.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,10 @@ def st_der_oid(draw):
Hypothesis strategy that returns DER OBJECT IDENTIFIER objects.
"""
first = draw(st.integers(min_value=0, max_value=2))
second = draw(st.integers(min_value=0, max_value=39))
if first < 2:
second = draw(st.integers(min_value=0, max_value=39))
else:
second = draw(st.integers(min_value=0, max_value=2**512))
rest = draw(st.lists(st.integers(min_value=0, max_value=2**512),
max_size=50))
return encode_oid(first, second, *rest)
Expand Down
11 changes: 0 additions & 11 deletions src/ecdsa/test_pyecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,17 +952,6 @@ def do_test_to_openssl(self, curve):


class DER(unittest.TestCase):
def test_oids(self):
oid_ecPublicKey = der.encode_oid(1, 2, 840, 10045, 2, 1)
self.assertEqual(hexlify(oid_ecPublicKey), b("06072a8648ce3d0201"))
self.assertEqual(hexlify(NIST224p.encoded_oid), b("06052b81040021"))
self.assertEqual(hexlify(NIST256p.encoded_oid),
b("06082a8648ce3d030107"))
x = oid_ecPublicKey + b("more")
x1, rest = der.remove_object(x)
self.assertEqual(x1, (1, 2, 840, 10045, 2, 1))
self.assertEqual(rest, b("more"))

def test_integer(self):
self.assertEqual(der.encode_integer(0), b("\x02\x01\x00"))
self.assertEqual(der.encode_integer(1), b("\x02\x01\x01"))
Expand Down