-
Notifications
You must be signed in to change notification settings - Fork 330
Adds to DER format of ECDSA signature with extended ECDSA-Sig-Value and ECDSA-Full-R #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
||
|
|
@@ -234,6 +248,43 @@ 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") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not an object identifier |
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this method shouldn't be passed Unicode strings ever... |
||
| # (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( | ||
|
|
@@ -292,8 +343,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 | ||
|
|
||
|
|
@@ -393,8 +443,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) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,7 @@ | |
| modified as part of the python-ecdsa package. | ||
| """ | ||
|
|
||
| import sys | ||
| import warnings | ||
| from six import int2byte | ||
| from . import ellipticcurve | ||
|
|
@@ -192,10 +193,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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this change though? |
||
|
|
||
| if r < 1 or r > n - 1: | ||
| return False | ||
| c = numbertheory.inverse_mod(s, n) | ||
| u1 = (hash * c) % n | ||
| u2 = (r * c) % n | ||
|
|
@@ -231,7 +244,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 | ||
|
|
@@ -267,6 +280,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) | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1318,6 +1318,7 @@ def sign_deterministic( | |
| hashfunc=None, | ||
| sigencode=sigencode_string, | ||
| extra_entropy=b"", | ||
| accelerate=False, | ||
| ): | ||
| """ | ||
| Create signature over data. | ||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but we already are using the |
||
|
|
||
| :return: encoded signature over `data` | ||
| :rtype: bytes or sigencode function dependent type | ||
|
|
@@ -1373,6 +1378,7 @@ def sign_deterministic( | |
| sigencode=sigencode, | ||
| extra_entropy=extra_entropy, | ||
| allow_truncate=True, | ||
| accelerate=accelerate, | ||
| ) | ||
|
|
||
| def sign_digest_deterministic( | ||
|
|
@@ -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. | ||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, |
||
|
|
||
| :return: encoded signature for the `digest` hash | ||
| :rtype: bytes or sigencode function dependent type | ||
|
|
@@ -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: | ||
|
|
@@ -1462,6 +1474,7 @@ def sign( | |
| sigencode=sigencode_string, | ||
| k=None, | ||
| allow_truncate=True, | ||
| accelerate=False, | ||
| ): | ||
| """ | ||
| Create signature over data. | ||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
| 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 | ||
|
|
@@ -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, | ||
|
|
@@ -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. | ||
|
|
@@ -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 | ||
|
|
@@ -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. | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -26,6 +27,7 @@ | |
| remove_octet_string, | ||
| remove_sequence, | ||
| encode_implicit, | ||
| encode_boolean, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -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" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you also test with |
||
|
|
||
| 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): | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to split it?