Skip to content

Commit 19ac7b2

Browse files
committed
Adds to DER format of ECDSA signature with extended ECDSA-Sig-Value and ECDSA-Full-R.
1 parent 55aca78 commit 19ac7b2

File tree

8 files changed

+767
-19
lines changed

8 files changed

+767
-19
lines changed

src/ecdsa/der.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,20 @@ def encode_number(n):
151151
return b"".join([int2byte(d) for d in b128_digits])
152152

153153

154+
def encode_boolean(b):
155+
"""
156+
Encodes BOOLEAN acording to ASN.1 DER format.
157+
The ASN.1 BOOLEAN type has two possible values: TRUE and FALSE.
158+
True is encoded as ff", False is encoded as a zero.
159+
160+
:param boolean b: the boolean value to be encoded
161+
:return: a byte string
162+
:rtype: bytes
163+
"""
164+
165+
return b"\x01" + encode_length(1) + (b"\xff" if b else b"\x00")
166+
167+
154168
def is_sequence(string):
155169
return string and string[:1] == b"\x30"
156170

@@ -234,6 +248,47 @@ def remove_octet_string(string):
234248
return body, rest
235249

236250

251+
def remove_boolean(string):
252+
"""
253+
Removes the ASN.1 BOOLEAN type.
254+
For BOOLEAN types, in DER FALSE is always encoded as zero
255+
and TRUE is always encoded as ff.
256+
257+
:param bytes string: the boolean value to be encoded
258+
:return: a boolean value and the rest of the string
259+
:rtype: tuple(boolean, bytes)
260+
"""
261+
if not string:
262+
raise UnexpectedDER(
263+
"Empty string is an invalid " "encoding of a boolean"
264+
)
265+
if string[:1] != b"\x01":
266+
n = str_idx_as_int(string, 0)
267+
raise UnexpectedDER("wanted type 'boolean' (0x01), got 0x%02x" % n)
268+
length, lengthlength = read_length(string[1:])
269+
body = string[1 + lengthlength : 1 + lengthlength + length]
270+
rest = string[1 + lengthlength + length :]
271+
if not body:
272+
raise UnexpectedDER("Empty object identifier")
273+
if len(body) != length:
274+
raise UnexpectedDER(
275+
"Length of object identifier longer than the provided buffer."
276+
)
277+
if length != 1:
278+
raise UnexpectedDER(
279+
"The contents octets of boolean shall consist of a single octet."
280+
)
281+
if body == b"\x00":
282+
return False, rest
283+
# the workaround due to instrumental, that
284+
# saves the binary data as UF-8 string
285+
# (0xff is an invalid start byte)
286+
num = int(binascii.hexlify(body), 16)
287+
if num == 0xFF:
288+
return True, rest
289+
raise UnexpectedDER("Invalid encoding of BOOLEAN.")
290+
291+
237292
def remove_object(string):
238293
if not string:
239294
raise UnexpectedDER(
@@ -292,8 +347,7 @@ def remove_integer(string):
292347
smsb = str_idx_as_int(numberbytes, 1)
293348
if smsb < 0x80:
294349
raise UnexpectedDER(
295-
"Invalid encoding of integer, unnecessary "
296-
"zero padding bytes"
350+
"Invalid encoding of integer, unnecessary zero padding bytes"
297351
)
298352
return int(binascii.hexlify(numberbytes), 16), rest
299353

@@ -393,8 +447,8 @@ def remove_bitstring(string, expect_unused=_sentry):
393447
raise UnexpectedDER("Empty string does not encode a bitstring")
394448
if expect_unused is _sentry:
395449
warnings.warn(
396-
"Legacy call convention used, expect_unused= needs to be"
397-
" specified",
450+
"Legacy call convention used, "
451+
"expect_unused= needs to be specified",
398452
DeprecationWarning,
399453
)
400454
num = str_idx_as_int(string, 0)

src/ecdsa/ecdsa.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
modified as part of the python-ecdsa package.
6565
"""
6666

67+
import sys
6768
import warnings
6869
from six import int2byte
6970
from . import ellipticcurve
@@ -192,10 +193,24 @@ def verifies(self, hash, signature):
192193
n = G.order()
193194
r = signature.r
194195
s = signature.s
195-
if r < 1 or r > n - 1:
196-
return False
197196
if s < 1 or s > n - 1:
198197
return False
198+
199+
# check if the r value is a point
200+
version = sys.version_info
201+
if version[0] < 3 and version[1] < 7:
202+
# memoryview was introduced in py 2.7
203+
byte_objects = set((bytearray, bytes))
204+
else:
205+
byte_objects = set((bytearray, bytes, memoryview))
206+
if type(r) in byte_objects:
207+
point = ellipticcurve.AbstractPoint.from_bytes(
208+
self.generator.curve(), r
209+
)
210+
r = point[0] % n
211+
212+
if r < 1 or r > n - 1:
213+
return False
199214
c = numbertheory.inverse_mod(s, n)
200215
u1 = (hash * c) % n
201216
u2 = (r * c) % n
@@ -231,7 +246,7 @@ def __ne__(self, other):
231246
"""Return False if the points are identical, True otherwise."""
232247
return not self == other
233248

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

272289

src/ecdsa/keys.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,7 @@ def sign_deterministic(
13181318
hashfunc=None,
13191319
sigencode=sigencode_string,
13201320
extra_entropy=b"",
1321+
accelerate=False,
13211322
):
13221323
"""
13231324
Create signature over data.
@@ -1354,6 +1355,10 @@ def sign_deterministic(
13541355
number generator used in the RFC6979 process. Entirely optional.
13551356
Ignored with EdDSA.
13561357
:type extra_entropy: :term:`bytes-like object`
1358+
:param accelerate: an indicator for ECDSA sign operation to return
1359+
an ECPoint instead of a number of "r" parameter.
1360+
Applicable only for ECDSA key.
1361+
:type accelerate: boolean
13571362
13581363
:return: encoded signature over `data`
13591364
:rtype: bytes or sigencode function dependent type
@@ -1373,6 +1378,7 @@ def sign_deterministic(
13731378
sigencode=sigencode,
13741379
extra_entropy=extra_entropy,
13751380
allow_truncate=True,
1381+
accelerate=accelerate,
13761382
)
13771383

13781384
def sign_digest_deterministic(
@@ -1382,6 +1388,7 @@ def sign_digest_deterministic(
13821388
sigencode=sigencode_string,
13831389
extra_entropy=b"",
13841390
allow_truncate=False,
1391+
accelerate=False,
13851392
):
13861393
"""
13871394
Create signature for digest using the deterministic RFC6979 algorithm.
@@ -1417,6 +1424,10 @@ def sign_digest_deterministic(
14171424
bigger bit-size than the order of the curve, the extra bits (at
14181425
the end of the digest) will be truncated. Use it when signing
14191426
SHA-384 output using NIST256p or in similar situations.
1427+
:param accelerate: an indicator for ECDSA sign operation to return
1428+
an ECPoint instead of a number of "r" parameter.
1429+
Applicable only for ECDSA key.
1430+
:type accelerate: boolean
14201431
14211432
:return: encoded signature for the `digest` hash
14221433
:rtype: bytes or sigencode function dependent type
@@ -1447,6 +1458,7 @@ def simple_r_s(r, s, order):
14471458
sigencode=simple_r_s,
14481459
k=k,
14491460
allow_truncate=allow_truncate,
1461+
accelerate=accelerate,
14501462
)
14511463
break
14521464
except RSZeroError:
@@ -1462,6 +1474,7 @@ def sign(
14621474
sigencode=sigencode_string,
14631475
k=None,
14641476
allow_truncate=True,
1477+
accelerate=False,
14651478
):
14661479
"""
14671480
Create signature over data.
@@ -1525,6 +1538,10 @@ def sign(
15251538
leak the key. Caller should try a better entropy source, retry with
15261539
different ``k``, or use the
15271540
:func:`~SigningKey.sign_deterministic` in such case.
1541+
:param accelerate: an indicator for ECDSA sign operation to return
1542+
an ECPoint instead of a number of "r" parameter.
1543+
Applicable only for ECDSA key.
1544+
:type accelerate: boolean
15281545
15291546
:return: encoded signature of the hash of `data`
15301547
:rtype: bytes or sigencode function dependent type
@@ -1534,7 +1551,9 @@ def sign(
15341551
if isinstance(self.curve.curve, CurveEdTw):
15351552
return self.sign_deterministic(data)
15361553
h = hashfunc(data).digest()
1537-
return self.sign_digest(h, entropy, sigencode, k, allow_truncate)
1554+
return self.sign_digest(
1555+
h, entropy, sigencode, k, allow_truncate, accelerate
1556+
)
15381557

15391558
def sign_digest(
15401559
self,
@@ -1543,6 +1562,7 @@ def sign_digest(
15431562
sigencode=sigencode_string,
15441563
k=None,
15451564
allow_truncate=False,
1565+
accelerate=False,
15461566
):
15471567
"""
15481568
Create signature over digest using the probabilistic ECDSA algorithm.
@@ -1579,6 +1599,10 @@ def sign_digest(
15791599
leak the key. Caller should try a better entropy source, retry with
15801600
different 'k', or use the
15811601
:func:`~SigningKey.sign_digest_deterministic` in such case.
1602+
:param accelerate: an indicator for ECDSA sign operation to return
1603+
an ECPoint instead of a number of "r" parameter.
1604+
Applicable only for ECDSA key.
1605+
:type accelerate: boolean
15821606
15831607
:return: encoded signature for the `digest` hash
15841608
:rtype: bytes or sigencode function dependent type
@@ -1591,10 +1615,10 @@ def sign_digest(
15911615
self.curve,
15921616
allow_truncate,
15931617
)
1594-
r, s = self.sign_number(number, entropy, k)
1618+
r, s = self.sign_number(number, entropy, k, accelerate)
15951619
return sigencode(r, s, self.privkey.order)
15961620

1597-
def sign_number(self, number, entropy=None, k=None):
1621+
def sign_number(self, number, entropy=None, k=None, accelerate=False):
15981622
"""
15991623
Sign an integer directly.
16001624
@@ -1613,6 +1637,10 @@ def sign_number(self, number, entropy=None, k=None):
16131637
leak the key. Caller should try a better entropy source, retry with
16141638
different 'k', or use the
16151639
:func:`~SigningKey.sign_digest_deterministic` in such case.
1640+
:param accelerate: an indicator for ECDSA sign operation to return
1641+
an ECPoint instead of a number of "r" parameter.
1642+
Applicable only for ECDSA key.
1643+
:type accelerate: boolean
16161644
16171645
:return: the "r" and "s" parameters of the signature
16181646
:rtype: tuple of ints
@@ -1627,5 +1655,5 @@ def sign_number(self, number, entropy=None, k=None):
16271655
_k = randrange(order, entropy)
16281656

16291657
assert 1 <= _k < order
1630-
sig = self.privkey.sign(number, _k)
1658+
sig = self.privkey.sign(number, _k, accelerate)
16311659
return sig.r, sig.s

src/ecdsa/test_der.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ._compat import str_idx_as_int
1515
from .curves import NIST256p, NIST224p
1616
from .der import (
17+
remove_boolean,
1718
remove_integer,
1819
UnexpectedDER,
1920
read_length,
@@ -26,6 +27,7 @@
2627
remove_octet_string,
2728
remove_sequence,
2829
encode_implicit,
30+
encode_boolean,
2931
)
3032

3133

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

567569

570+
class TestEncodeBoolean(unittest.TestCase):
571+
def test_simple_true(self):
572+
der = encode_boolean(True)
573+
self.assertEqual(len(der), 3)
574+
self.assertEqual(der, b"\x01\x01\xff")
575+
576+
def test_simle_false(self):
577+
der = encode_boolean(False)
578+
self.assertEqual(len(der), 3)
579+
self.assertEqual(der, b"\x01\x01\x00")
580+
581+
582+
class TestRemoveBoolean(unittest.TestCase):
583+
def test_simple_false(self):
584+
data = b"\x01\x01\x00"
585+
body, rest = remove_boolean(data)
586+
self.assertEqual(body, False)
587+
self.assertEqual(rest, b"")
588+
589+
def test_simple_true(self):
590+
data = b"\x01\x01\xff"
591+
body, rest = remove_boolean(data)
592+
self.assertEqual(body, True)
593+
self.assertEqual(rest, b"")
594+
595+
def test_empty_string(self):
596+
with self.assertRaises(UnexpectedDER) as e:
597+
remove_boolean(b"")
598+
599+
def test_with_wrong_tag(self):
600+
data = b"\x02\x01\x00"
601+
602+
with self.assertRaises(UnexpectedDER) as e:
603+
remove_boolean(data)
604+
605+
self.assertIn("wanted type 'boolean' (0x01)", str(e.exception))
606+
607+
def test_with_wrong_encoded_value(self):
608+
data = b"\x01\x01\x01"
609+
610+
with self.assertRaises(UnexpectedDER) as e:
611+
remove_boolean(data)
612+
613+
self.assertIn("Invalid encoding of BOOLEAN", str(e.exception))
614+
615+
def test_empty_body(self):
616+
data = b"\x01\x01"
617+
618+
with self.assertRaises(UnexpectedDER) as e:
619+
remove_boolean(data)
620+
621+
self.assertIn("Empty object identifier", str(e.exception))
622+
623+
def test_several_boolean_octets(self):
624+
data = b"\x01\x02\x01\x01"
625+
626+
with self.assertRaises(UnexpectedDER) as e:
627+
remove_boolean(data)
628+
629+
self.assertIn(
630+
"The contents octets of boolean "
631+
"shall consist of a single octet",
632+
str(e.exception),
633+
)
634+
635+
568636
@st.composite
569637
def st_oid(draw, max_value=2**512, max_size=50):
570638
"""

src/ecdsa/test_keys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,7 @@ def test_SigningKey_from_string(convert):
10371037
key_bytes = unpem(prv_key_str)
10381038
assert isinstance(key_bytes, bytes)
10391039

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

0 commit comments

Comments
 (0)