Skip to content

Commit

Permalink
Merge d539b8a into 4c92d31
Browse files Browse the repository at this point in the history
  • Loading branch information
tomato42 committed Oct 23, 2019
2 parents 4c92d31 + d539b8a commit 90d6e41
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 13 deletions.
16 changes: 16 additions & 0 deletions src/ecdsa/_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Common functions for providing cross-python version compatibility.
"""
import sys


if sys.version_info < (3, 0):
def normalise_bytes(buffer_object):
"""Cast the input into array of bytes."""
return buffer(buffer_object)


else:
def normalise_bytes(buffer_object):
"""Cast the input into array of bytes."""
return memoryview(buffer_object).cast('B')
8 changes: 4 additions & 4 deletions src/ecdsa/der.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def remove_constructed(string):
def remove_sequence(string):
if not string:
raise UnexpectedDER("Empty string does not encode a sequence")
if not string.startswith(b("\x30")):
if string[:1] != b"\x30":
n = string[0] if isinstance(string[0], integer_types) else \
ord(string[0])
raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n)
Expand All @@ -157,7 +157,7 @@ def remove_octet_string(string):


def remove_object(string):
if not string.startswith(b("\x06")):
if string[:1] != b"\x06":
n = string[0] if isinstance(string[0], integer_types) else ord(string[0])
raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n)
length, lengthlength = read_length(string[1:])
Expand All @@ -180,7 +180,7 @@ def remove_integer(string):
if not string:
raise UnexpectedDER("Empty string is an invalid encoding of an "
"integer")
if not string.startswith(b("\x02")):
if string[:1] != b"\x02":
n = string[0] if isinstance(string[0], integer_types) \
else ord(string[0])
raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n)
Expand Down Expand Up @@ -302,7 +302,7 @@ def remove_bitstring(string, expect_unused=_sentry):
" specified",
DeprecationWarning)
num = string[0] if isinstance(string[0], integer_types) else ord(string[0])
if not string.startswith(b("\x03")):
if string[:1] != b"\x03":
raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num)
length, llen = read_length(string[1:])
if not length:
Expand Down
35 changes: 28 additions & 7 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
Abstract Syntax Notation 1 is a standard description language for
specifying serialisation and deserialisation of data structures in a
portable and cross-platform way.
bytes-like object
All the types that implement the buffer protocol. That includes
``str`` (only on python2), ``bytes``, ``bytesarray``, ``array.array`
and ``memoryview`` of those objects.
Please note that ``array.array` serialisation (converting it to byte
string) is endianess dependant! Signature computed over ``array.array``
of integers on a big-endian system will not be verified on a
little-endian system and vice-versa.
"""

import binascii
Expand All @@ -70,6 +79,7 @@
from .util import string_to_number, number_to_string, randrange
from .util import sigencode_string, sigdecode_string
from .util import oid_ecPublicKey, encoded_oid_ecPublicKey, MalformedSignature
from ._compat import normalise_bytes


__all__ = ["BadSignatureError", "BadDigestError", "VerifyingKey", "SigningKey",
Expand Down Expand Up @@ -231,8 +241,8 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1,
Python 2 days when there were no binary strings. In Python 3 the
input needs to be a bytes-like object.
:param string: :term:`raw encoding` of the public key
:type string: bytes-like object
:param string: single point encoding of the public key
:type string: :term:`bytes-like object`
:param curve: the curve on which the public key is expected to lie
:type curve: ecdsa.curves.Curve
:param hashfunc: The default hash function that will be used for
Expand All @@ -245,6 +255,7 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1,
:return: Initialised VerifyingKey object
:rtype: VerifyingKey
"""
string = normalise_bytes(string)
sig_len = len(string)
if sig_len == curve.verifying_key_length:
point = cls._from_raw_encoding(string, curve, validate_point)
Expand Down Expand Up @@ -317,24 +328,25 @@ def from_der(cls, string):
:return: Initialised VerifyingKey object
:rtype: VerifyingKey
"""
string = normalise_bytes(string)
# [[oid_ecPublicKey,oid_curve], point_str_bitstring]
s1, empty = der.remove_sequence(string)
if empty != b(""):
if empty != b"":
raise der.UnexpectedDER("trailing junk after DER pubkey: %s" %
binascii.hexlify(empty))
s2, point_str_bitstring = der.remove_sequence(s1)
# s2 = oid_ecPublicKey,oid_curve
oid_pk, rest = der.remove_object(s2)
oid_curve, empty = der.remove_object(rest)
if empty != b(""):
if empty != b"":
raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" %
binascii.hexlify(empty))
if not oid_pk == oid_ecPublicKey:
raise der.UnexpectedDER("Unexpected object identifier in DER "
"encoding: {0!r}".format(oid_pk))
curve = find_curve(oid_curve)
point_str, empty = der.remove_bitstring(point_str_bitstring, 0)
if empty != b(""):
if empty != b"":
raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" %
binascii.hexlify(empty))
# raw encoding of point is invalid in DER files
Expand Down Expand Up @@ -371,6 +383,7 @@ def from_public_key_recovery(cls, signature, data, curve, hashfunc=sha1,
:return: Initialised VerifyingKey objects
:rtype: list of VerifyingKey
"""
data = normalise_bytes(data)
digest = hashfunc(data).digest()
return cls.from_public_key_recovery_with_digest(
signature, digest, curve, hashfunc=hashfunc,
Expand Down Expand Up @@ -411,6 +424,7 @@ def from_public_key_recovery_with_digest(
r, s = sigdecode(signature, generator.order())
sig = ecdsa.Signature(r, s)

digest = normalise_bytes(digest)
digest_as_number = string_to_number(digest)
pks = sig.recover_public_keys(digest_as_number, generator)

Expand Down Expand Up @@ -531,7 +545,7 @@ def verify(self, signature, data, hashfunc=None,
as the `sigdecode` parameter.
:param signature: encoding of the signature
:type signature: bytes like object
:type signature: sigdecode method dependant
:param data: data signed by the `signature`, will be hashed using
`hashfunc`, if specified, or default hash function
:type data: bytes like object
Expand All @@ -553,6 +567,10 @@ def verify(self, signature, data, hashfunc=None,
:return: True if the verification was successful
:rtype: bool
"""
# signature doesn't have to be a bytes-like-object so don't normalise
# it, the decoders will do that
data = normalise_bytes(data)

hashfunc = hashfunc or self.default_hashfunc
digest = hashfunc(data).digest()
return self.verify_digest(signature, digest, sigdecode)
Expand All @@ -567,7 +585,7 @@ def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
as the `sigdecode` parameter.
:param signature: encoding of the signature
:type signature: bytes like object
:type signature: sigdecode method dependant
:param digest: raw hash value that the signature authenticates.
:type digest: bytes like object
:param sigdecode: Callable to define the way the signature needs to
Expand All @@ -585,6 +603,9 @@ def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
:return: True if the verification was successful
:rtype: bool
"""
# signature doesn't have to be a bytes-like-object so don't normalise
# it, the decoders will do that
digest = normalise_bytes(digest)
if len(digest) > self.curve.baselen:
raise BadDigestError("this curve (%s) is too short "
"for your digest (%d)" % (self.curve.name,
Expand Down
218 changes: 218 additions & 0 deletions src/ecdsa/test_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
try:
import unittest2 as unittest
except ImportError:
import unittest

try:
buffer
except NameError:
buffer = memoryview

import array
import six
import sys
import pytest
import hashlib

from .keys import VerifyingKey, SigningKey
from .der import unpem
from .util import sigencode_string, sigencode_der, sigencode_strings, \
sigdecode_string, sigdecode_der, sigdecode_strings


class TestVerifyingKeyFromString(unittest.TestCase):
"""
Verify that ecdsa.keys.VerifyingKey.from_string() can be used with
bytes-like objects
"""

@classmethod
def setUpClass(cls):
cls.key_bytes = (b'\x04L\xa2\x95\xdb\xc7Z\xd7\x1f\x93\nz\xcf\x97\xcf'
b'\xd7\xc2\xd9o\xfe8}X!\xae\xd4\xfah\xfa^\rpI\xba\xd1'
b'Y\xfb\x92xa\xebo+\x9cG\xfav\xca')
cls.vk = VerifyingKey.from_string(cls.key_bytes)

def test_bytes(self):
self.assertIsNotNone(self.vk)
self.assertIsInstance(self.vk, VerifyingKey)
self.assertEqual(
self.vk.pubkey.point.x(),
105419898848891948935835657980914000059957975659675736097)
self.assertEqual(
self.vk.pubkey.point.y(),
4286866841217412202667522375431381222214611213481632495306)

def test_bytes_memoryview(self):
vk = VerifyingKey.from_string(buffer(self.key_bytes))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytearray(self):
vk = VerifyingKey.from_string(bytearray(self.key_bytes))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytesarray_memoryview(self):
vk = VerifyingKey.from_string(buffer(bytearray(self.key_bytes)))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_array_array_of_bytes(self):
arr = array.array('B', self.key_bytes)
vk = VerifyingKey.from_string(arr)

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_array_array_of_bytes_memoryview(self):
arr = array.array('B', self.key_bytes)
vk = VerifyingKey.from_string(buffer(arr))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_array_array_of_ints(self):
arr = array.array('I', self.key_bytes)
vk = VerifyingKey.from_string(arr)

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_array_array_of_ints_memoryview(self):
arr = array.array('I', self.key_bytes)
vk = VerifyingKey.from_string(buffer(arr))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytes_uncompressed(self):
vk = VerifyingKey.from_string(b'\x04' + self.key_bytes)

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytearray_uncompressed(self):
vk = VerifyingKey.from_string(bytearray(b'\x04' + self.key_bytes))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytes_compressed(self):
vk = VerifyingKey.from_string(b'\x02' + self.key_bytes[:24])

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytearray_uncompressed(self):
vk = VerifyingKey.from_string(bytearray(b'\x02' + self.key_bytes[:24]))

self.assertEqual(self.vk.to_string(), vk.to_string())


class TestVerifyingKeyFromDer(unittest.TestCase):
"""
Verify that ecdsa.keys.VerifyingKey.from_der() can be used with
bytes-like objects.
"""
@classmethod
def setUpClass(cls):
prv_key_str = (
"-----BEGIN EC PRIVATE KEY-----\n"
"MF8CAQEEGF7IQgvW75JSqULpiQQ8op9WH6Uldw6xxaAKBggqhkjOPQMBAaE0AzIA\n"
"BLiBd9CE7xf15FY5QIAoNg+fWbSk1yZOYtoGUdzkejWkxbRc9RWTQjqLVXucIJnz\n"
"bA==\n"
"-----END EC PRIVATE KEY-----\n")
key_str = (
"-----BEGIN PUBLIC KEY-----\n"
"MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEuIF30ITvF/XkVjlAgCg2D59ZtKTX\n"
"Jk5i2gZR3OR6NaTFtFz1FZNCOotVe5wgmfNs\n"
"-----END PUBLIC KEY-----\n")
cls.key_bytes = unpem(key_str)
assert isinstance(cls.key_bytes, bytes)
cls.vk = VerifyingKey.from_pem(key_str)

def test_bytes(self):
vk = VerifyingKey.from_der(self.key_bytes)

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytes_memoryview(self):
vk = VerifyingKey.from_der(buffer(self.key_bytes))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytearray(self):
vk = VerifyingKey.from_der(bytearray(self.key_bytes))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_bytesarray_memoryview(self):
vk = VerifyingKey.from_der(buffer(bytearray(self.key_bytes)))

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_array_array_of_bytes(self):
arr = array.array('B', self.key_bytes)
vk = VerifyingKey.from_der(arr)

self.assertEqual(self.vk.to_string(), vk.to_string())

def test_array_array_of_bytes_memoryview(self):
arr = array.array('B', self.key_bytes)
vk = VerifyingKey.from_der(buffer(arr))

self.assertEqual(self.vk.to_string(), vk.to_string())


# test VerifyingKey.verify()
prv_key_str = (
"-----BEGIN EC PRIVATE KEY-----\n"
"MF8CAQEEGF7IQgvW75JSqULpiQQ8op9WH6Uldw6xxaAKBggqhkjOPQMBAaE0AzIA\n"
"BLiBd9CE7xf15FY5QIAoNg+fWbSk1yZOYtoGUdzkejWkxbRc9RWTQjqLVXucIJnz\n"
"bA==\n"
"-----END EC PRIVATE KEY-----\n")
key_bytes = unpem(prv_key_str)
assert isinstance(key_bytes, bytes)
sk = SigningKey.from_der(key_bytes)
vk = sk.verifying_key

data = (b"some string for signing"
b"contents don't really matter"
b"but do include also some crazy values: "
b"\x00\x01\t\r\n\x00\x00\x00\x00\xff\xf0")
sha1 = hashlib.sha1()
sha1.update(data)
data_hash = sha1.digest()

sig_raw = sk.sign(data, sigencode=sigencode_string)
assert isinstance(sig_raw, bytes)
sig_der = sk.sign(data, sigencode=sigencode_der)
assert isinstance(sig_der, bytes)
sig_strings = sk.sign(data, sigencode=sigencode_strings)
assert isinstance(sig_strings[0], bytes)

verifiers = []
for modifier, fun in [
("bytes", lambda x: x),
("bytes memoryview", lambda x: buffer(x)),
("bytearray", lambda x: bytearray(x)),
("bytearray memoryview", lambda x: buffer(bytearray(x))),
("array.array of bytes", lambda x: array.array('B', x)),
("array.array of bytes memoryview", lambda x: buffer(array.array('B', x)))
]:
for sig_format, signature, decoder, mod_apply in [
("raw", sig_raw, sigdecode_string, lambda x: fun(x)),
("der", sig_der, sigdecode_der, lambda x: fun(x)),
("strings", sig_strings, sigdecode_strings, lambda x:
tuple(fun(i) for i in x))
]:
for method_name, vrf_mthd, vrf_data in [
("verify", vk.verify, data),
("verify_digest", vk.verify_digest, data_hash)
]:
verifiers.append(pytest.param(
signature, decoder, mod_apply, fun, vrf_mthd, vrf_data,
id="{2}-{0}-{1}".format(modifier, sig_format, method_name)))

@pytest.mark.parametrize(
"signature,decoder,mod_apply,fun,vrf_mthd,vrf_data",
verifiers)
def test_VerifyingKey_verify(
signature, decoder, mod_apply, fun, vrf_mthd, vrf_data):
sig = mod_apply(signature)

assert vrf_mthd(sig, fun(vrf_data), sigdecode=decoder)
Loading

0 comments on commit 90d6e41

Please sign in to comment.