Skip to content
Open
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
62 changes: 58 additions & 4 deletions src/ecdsa/der.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@ def encode_number(n):
return b"".join([int2byte(d) for d in b128_digits])


def encode_boolean(b):
"""
Encodes BOOLEAN acording to ASN.1 DER format.
The ASN.1 BOOLEAN type has two possible values: TRUE and FALSE.
True is encoded as ff", False is encoded as a zero.

:param boolean b: the boolean value to be encoded
:return: a byte string
:rtype: bytes
"""

return b"\x01" + encode_length(1) + (b"\xff" if b else b"\x00")


def is_sequence(string):
return string and string[:1] == b"\x30"

Expand Down Expand Up @@ -234,6 +248,47 @@ def remove_octet_string(string):
return body, rest


def remove_boolean(string):
"""
Removes the ASN.1 BOOLEAN type.
For BOOLEAN types, in DER FALSE is always encoded as zero
and TRUE is always encoded as ff.

:param bytes string: the boolean value to be encoded
:return: a boolean value and the rest of the string
:rtype: tuple(boolean, bytes)
"""
if not string:
raise UnexpectedDER(
"Empty string is an invalid " "encoding of a boolean"
)
if string[:1] != b"\x01":
n = str_idx_as_int(string, 0)
raise UnexpectedDER("wanted type 'boolean' (0x01), 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."
)
if length != 1:
raise UnexpectedDER(
"The contents octets of boolean shall consist of a single octet."
)
if body == b"\x00":
return False, rest
# the workaround due to instrumental, that
# saves the binary data as UF-8 string
# (0xff is an invalid start byte)
num = int(binascii.hexlify(body), 16)
if num == 0xFF:
return True, rest
raise UnexpectedDER("Invalid encoding of BOOLEAN.")


def remove_object(string):
if not string:
raise UnexpectedDER(
Expand Down Expand Up @@ -292,8 +347,7 @@ def remove_integer(string):
smsb = str_idx_as_int(numberbytes, 1)
if smsb < 0x80:
raise UnexpectedDER(
"Invalid encoding of integer, unnecessary "
"zero padding bytes"
"Invalid encoding of integer, unnecessary zero padding bytes"
)
return int(binascii.hexlify(numberbytes), 16), rest

Expand Down Expand Up @@ -393,8 +447,8 @@ def remove_bitstring(string, expect_unused=_sentry):
raise UnexpectedDER("Empty string does not encode a bitstring")
if expect_unused is _sentry:
warnings.warn(
"Legacy call convention used, expect_unused= needs to be"
" specified",
"Legacy call convention used, "
"expect_unused= needs to be specified",
DeprecationWarning,
)
num = str_idx_as_int(string, 0)
Expand Down
23 changes: 20 additions & 3 deletions src/ecdsa/ecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
modified as part of the python-ecdsa package.
"""

import sys
import warnings
from six import int2byte
from . import ellipticcurve
Expand Down Expand Up @@ -192,10 +193,24 @@ def verifies(self, hash, signature):
n = G.order()
r = signature.r
s = signature.s
if r < 1 or r > n - 1:
return False
if s < 1 or s > n - 1:
return False

# check if the r value is a point
version = sys.version_info
if version[0] < 3 and version[1] < 7:
# memoryview was introduced in py 2.7
byte_objects = set((bytearray, bytes))
else:
byte_objects = set((bytearray, bytes, memoryview))
if type(r) in byte_objects:
point = ellipticcurve.AbstractPoint.from_bytes(
self.generator.curve(), r
)
r = point[0] % n

if r < 1 or r > n - 1:
return False
c = numbertheory.inverse_mod(s, n)
u1 = (hash * c) % n
u2 = (r * c) % n
Expand Down Expand Up @@ -231,7 +246,7 @@ def __ne__(self, other):
"""Return False if the points are identical, True otherwise."""
return not self == other

def sign(self, hash, random_k):
def sign(self, hash, random_k, accelerate=False):
"""Return a signature for the provided hash, using the provided
random nonce. It is absolutely vital that random_k be an unpredictable
number in the range [1, self.public_key.point.order()-1]. If
Expand Down Expand Up @@ -267,6 +282,8 @@ def sign(self, hash, random_k):
) % n
if s == 0:
raise RSZeroError("amazingly unlucky random number s")
if accelerate:
return Signature(p1, s)
return Signature(r, s)


Expand Down
36 changes: 32 additions & 4 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,7 @@ def sign_deterministic(
hashfunc=None,
sigencode=sigencode_string,
extra_entropy=b"",
accelerate=False,
):
"""
Create signature over data.
Expand Down Expand Up @@ -1354,6 +1355,10 @@ def sign_deterministic(
number generator used in the RFC6979 process. Entirely optional.
Ignored with EdDSA.
:type extra_entropy: :term:`bytes-like object`
:param accelerate: an indicator for ECDSA sign operation to return
an ECPoint instead of a number of "r" parameter.
Applicable only for ECDSA key.
:type accelerate: boolean

:return: encoded signature over `data`
:rtype: bytes or sigencode function dependent type
Expand All @@ -1373,6 +1378,7 @@ def sign_deterministic(
sigencode=sigencode,
extra_entropy=extra_entropy,
allow_truncate=True,
accelerate=accelerate,
)

def sign_digest_deterministic(
Expand All @@ -1382,6 +1388,7 @@ def sign_digest_deterministic(
sigencode=sigencode_string,
extra_entropy=b"",
allow_truncate=False,
accelerate=False,
):
"""
Create signature for digest using the deterministic RFC6979 algorithm.
Expand Down Expand Up @@ -1417,6 +1424,10 @@ def sign_digest_deterministic(
bigger bit-size than the order of the curve, the extra bits (at
the end of the digest) will be truncated. Use it when signing
SHA-384 output using NIST256p or in similar situations.
:param accelerate: an indicator for ECDSA sign operation to return
an ECPoint instead of a number of "r" parameter.
Applicable only for ECDSA key.
:type accelerate: boolean

:return: encoded signature for the `digest` hash
:rtype: bytes or sigencode function dependent type
Expand Down Expand Up @@ -1447,6 +1458,7 @@ def simple_r_s(r, s, order):
sigencode=simple_r_s,
k=k,
allow_truncate=allow_truncate,
accelerate=accelerate,
)
break
except RSZeroError:
Expand All @@ -1462,6 +1474,7 @@ def sign(
sigencode=sigencode_string,
k=None,
allow_truncate=True,
accelerate=False,
):
"""
Create signature over data.
Expand Down Expand Up @@ -1525,6 +1538,10 @@ def sign(
leak the key. Caller should try a better entropy source, retry with
different ``k``, or use the
:func:`~SigningKey.sign_deterministic` in such case.
:param accelerate: an indicator for ECDSA sign operation to return
an ECPoint instead of a number of "r" parameter.
Applicable only for ECDSA key.
:type accelerate: boolean

:return: encoded signature of the hash of `data`
:rtype: bytes or sigencode function dependent type
Expand All @@ -1534,7 +1551,9 @@ def sign(
if isinstance(self.curve.curve, CurveEdTw):
return self.sign_deterministic(data)
h = hashfunc(data).digest()
return self.sign_digest(h, entropy, sigencode, k, allow_truncate)
return self.sign_digest(
h, entropy, sigencode, k, allow_truncate, accelerate
)

def sign_digest(
self,
Expand All @@ -1543,6 +1562,7 @@ def sign_digest(
sigencode=sigencode_string,
k=None,
allow_truncate=False,
accelerate=False,
):
"""
Create signature over digest using the probabilistic ECDSA algorithm.
Expand Down Expand Up @@ -1579,6 +1599,10 @@ def sign_digest(
leak the key. Caller should try a better entropy source, retry with
different 'k', or use the
:func:`~SigningKey.sign_digest_deterministic` in such case.
:param accelerate: an indicator for ECDSA sign operation to return
an ECPoint instead of a number of "r" parameter.
Applicable only for ECDSA key.
:type accelerate: boolean

:return: encoded signature for the `digest` hash
:rtype: bytes or sigencode function dependent type
Expand All @@ -1591,10 +1615,10 @@ def sign_digest(
self.curve,
allow_truncate,
)
r, s = self.sign_number(number, entropy, k)
r, s = self.sign_number(number, entropy, k, accelerate)
return sigencode(r, s, self.privkey.order)

def sign_number(self, number, entropy=None, k=None):
def sign_number(self, number, entropy=None, k=None, accelerate=False):
"""
Sign an integer directly.

Expand All @@ -1613,6 +1637,10 @@ def sign_number(self, number, entropy=None, k=None):
leak the key. Caller should try a better entropy source, retry with
different 'k', or use the
:func:`~SigningKey.sign_digest_deterministic` in such case.
:param accelerate: an indicator for ECDSA sign operation to return
an ECPoint instead of a number of "r" parameter.
Applicable only for ECDSA key.
:type accelerate: boolean

:return: the "r" and "s" parameters of the signature
:rtype: tuple of ints
Expand All @@ -1627,5 +1655,5 @@ def sign_number(self, number, entropy=None, k=None):
_k = randrange(order, entropy)

assert 1 <= _k < order
sig = self.privkey.sign(number, _k)
sig = self.privkey.sign(number, _k, accelerate)
return sig.r, sig.s
68 changes: 68 additions & 0 deletions src/ecdsa/test_der.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ._compat import str_idx_as_int
from .curves import NIST256p, NIST224p
from .der import (
remove_boolean,
remove_integer,
UnexpectedDER,
read_length,
Expand All @@ -26,6 +27,7 @@
remove_octet_string,
remove_sequence,
encode_implicit,
encode_boolean,
)


Expand Down Expand Up @@ -565,6 +567,72 @@ def test_with_wrong_length(self):
self.assertIn("Length longer", str(e.exception))


class TestEncodeBoolean(unittest.TestCase):
def test_simple_true(self):
der = encode_boolean(True)
self.assertEqual(len(der), 3)
self.assertEqual(der, b"\x01\x01\xff")

def test_simle_false(self):
der = encode_boolean(False)
self.assertEqual(len(der), 3)
self.assertEqual(der, b"\x01\x01\x00")


class TestRemoveBoolean(unittest.TestCase):
def test_simple_false(self):
data = b"\x01\x01\x00"
body, rest = remove_boolean(data)
self.assertEqual(body, False)
self.assertEqual(rest, b"")

def test_simple_true(self):
data = b"\x01\x01\xff"
body, rest = remove_boolean(data)
self.assertEqual(body, True)
self.assertEqual(rest, b"")

def test_empty_string(self):
with self.assertRaises(UnexpectedDER) as e:
remove_boolean(b"")

def test_with_wrong_tag(self):
data = b"\x02\x01\x00"

with self.assertRaises(UnexpectedDER) as e:
remove_boolean(data)

self.assertIn("wanted type 'boolean' (0x01)", str(e.exception))

def test_with_wrong_encoded_value(self):
data = b"\x01\x01\x01"

with self.assertRaises(UnexpectedDER) as e:
remove_boolean(data)

self.assertIn("Invalid encoding of BOOLEAN", str(e.exception))

def test_empty_body(self):
data = b"\x01\x01"

with self.assertRaises(UnexpectedDER) as e:
remove_boolean(data)

self.assertIn("Empty object identifier", str(e.exception))

def test_several_boolean_octets(self):
data = b"\x01\x02\x01\x01"

with self.assertRaises(UnexpectedDER) as e:
remove_boolean(data)

self.assertIn(
"The contents octets of boolean "
"shall consist of a single octet",
str(e.exception),
)


@st.composite
def st_oid(draw, max_value=2**512, max_size=50):
"""
Expand Down
1 change: 1 addition & 0 deletions src/ecdsa/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,7 @@ def test_SigningKey_from_string(convert):
key_bytes = unpem(prv_key_str)
assert isinstance(key_bytes, bytes)


# last two converters are for array.array of ints, those require input
# that's multiple of 4, which no curve we support produces
@pytest.mark.parametrize("convert", converters[:-2])
Expand Down
Loading
Loading