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
538 changes: 538 additions & 0 deletions src/ecdsa/ber.py

Large diffs are not rendered by default.

60 changes: 56 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 @@ -240,6 +254,45 @@ 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:])
if not length:
raise UnexpectedDER("Invalid length of bit string, can't be 0")
body = string[1 + lengthlength : 1 + lengthlength + length]
rest = string[1 + lengthlength + length :]
if not body:
raise UnexpectedDER("Empty BOOLEAN value")
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 UTF-8 string
# (0xff is an invalid start byte)
if isinstance(body, text_type): # pragma: no branch
body = body.encode("utf-8")
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 @@ -298,8 +351,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 @@ -399,8 +451,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
46 changes: 43 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 All @@ -80,6 +81,33 @@ class InvalidPointError(RuntimeError):
pass


# Plain integers in Python 2 are implemented using long in C,
# which gives them at least 32 bits of precision.
# Long integers have unlimited precision.
# In python 3 int and long were 'unified'

if sys.version_info < (3, 0): # pragma:no branch
# flake8 is complaining on python3
INT_TYPE = long # noqa: F821
else:
INT_TYPE = int


class RValue(INT_TYPE):
"""An r signature value that also carries the originating EC point.

Behaves as a regular ``int`` (equal to ``point.x() % order``) so
existing :func:`sigencode_*` functions that expect an integer work
unchanged. Functions that need the full EC point can access it via
the :attr:`point` attribute.
"""

def __new__(cls, r, point):
obj = super(RValue, cls).__new__(cls, INT_TYPE(r))
obj.point = point
return obj


class Signature(object):
"""
ECDSA signature.
Expand Down Expand Up @@ -192,10 +220,22 @@ 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

if sys.version_info < (3, 0): # pragma: no branch
# 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change though?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When encoding is in format of r in the siganture is ECPoint (ECPoint ::= OCTET STRING) to get the r, I firstly have to get the point from bytes and then extract the x and provide the same operation that is in the sign:
r = p1.x() % 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 @@ -267,7 +307,7 @@ def sign(self, hash, random_k):
) % n
if s == 0:
raise RSZeroError("amazingly unlucky random number s")
return Signature(r, s)
return Signature(RValue(r, p1), s)


def int_to_string(x): # pragma: no cover
Expand Down
Loading
Loading