diff options
| author | Hubert Kario <hubert@kario.pl> | 2021-05-04 00:53:28 +0200 |
|---|---|---|
| committer | Hubert Kario <hubert@kario.pl> | 2021-05-21 00:11:06 +0200 |
| commit | c7e285a102ef2e06ace32e838318bf11d628d969 (patch) | |
| tree | e4468769d554dbbb07073259b5aaf6ba08272760 /src/ecdsa | |
| parent | 2574d215ee5e6115235dd0b941e3b5b341763e76 (diff) | |
| download | ecdsa-c7e285a102ef2e06ace32e838318bf11d628d969.tar.gz | |
support reading keys with explicitly encoded curve parameters
Diffstat (limited to 'src/ecdsa')
| -rw-r--r-- | src/ecdsa/keys.py | 72 | ||||
| -rw-r--r-- | src/ecdsa/test_keys.py | 87 |
2 files changed, 133 insertions, 26 deletions
diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 6d44cb3..19f3b49 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -77,7 +77,7 @@ from . import ecdsa from . import der from . import rfc6979 from . import ellipticcurve -from .curves import NIST192p, find_curve +from .curves import NIST192p, Curve from .ecdsa import RSZeroError from .util import string_to_number, number_to_string, randrange from .util import sigencode_string, sigdecode_string, bit_length @@ -315,7 +315,13 @@ class VerifyingKey(object): return cls.from_public_point(point, curve, hashfunc, validate_point) @classmethod - def from_pem(cls, string, hashfunc=sha1, valid_encodings=None): + def from_pem( + cls, + string, + hashfunc=sha1, + valid_encodings=None, + valid_curve_encodings=None, + ): """ Initialise from public key stored in :term:`PEM` format. @@ -324,7 +330,7 @@ class VerifyingKey(object): See the :func:`~VerifyingKey.from_der()` method for details of the format supported. - Note: only a single PEM object encoding is supported in provided + Note: only a single PEM object decoding is supported in provided string. :param string: text with PEM-encoded public ECDSA key @@ -334,6 +340,11 @@ class VerifyingKey(object): :term:`hybrid`. To read malformed files, include :term:`raw encoding` with ``raw`` in the list. :type valid_encodings: :term:`set-like object + :param valid_curve_encodings: list of allowed encoding formats + for curve parameters. By default (``None``) all are supported: + ``named_curve`` and ``explicit``. + :type valid_curve_encodings: :term:`set-like object` + :return: Initialised VerifyingKey object :rtype: VerifyingKey @@ -342,10 +353,17 @@ class VerifyingKey(object): der.unpem(string), hashfunc=hashfunc, valid_encodings=valid_encodings, + valid_curve_encodings=valid_curve_encodings, ) @classmethod - def from_der(cls, string, hashfunc=sha1, valid_encodings=None): + def from_der( + cls, + string, + hashfunc=sha1, + valid_encodings=None, + valid_curve_encodings=None, + ): """ Initialise the key stored in :term:`DER` format. @@ -375,6 +393,10 @@ class VerifyingKey(object): :term:`hybrid`. To read malformed files, include :term:`raw encoding` with ``raw`` in the list. :type valid_encodings: :term:`set-like object + :param valid_curve_encodings: list of allowed encoding formats + for curve parameters. By default (``None``) all are supported: + ``named_curve`` and ``explicit``. + :type valid_curve_encodings: :term:`set-like object` :return: Initialised VerifyingKey object :rtype: VerifyingKey @@ -391,18 +413,12 @@ class VerifyingKey(object): 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"": - 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) + curve = Curve.from_der(rest, valid_curve_encodings) point_str, empty = der.remove_bitstring(point_str_bitstring, 0) if empty != b"": raise der.UnexpectedDER( @@ -849,7 +865,7 @@ class SigningKey(object): return cls.from_secret_exponent(secexp, curve, hashfunc) @classmethod - def from_pem(cls, string, hashfunc=sha1): + def from_pem(cls, string, hashfunc=sha1, valid_curve_encodings=None): """ Initialise from key stored in :term:`PEM` format. @@ -869,6 +885,11 @@ class SigningKey(object): :param string: text with PEM-encoded private ECDSA key :type string: str + :param valid_curve_encodings: list of allowed encoding formats + for curve parameters. By default (``None``) all are supported: + ``named_curve`` and ``explicit``. + :type valid_curve_encodings: :term:`set-like object` + :raises MalformedPointError: if the length of encoding doesn't match the provided curve or the encoded values is too large @@ -889,10 +910,14 @@ class SigningKey(object): if private_key_index == -1: private_key_index = string.index(b"-----BEGIN PRIVATE KEY-----") - return cls.from_der(der.unpem(string[private_key_index:]), hashfunc) + return cls.from_der( + der.unpem(string[private_key_index:]), + hashfunc, + valid_curve_encodings, + ) @classmethod - def from_der(cls, string, hashfunc=sha1): + def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None): """ Initialise from key stored in :term:`DER` format. @@ -913,8 +938,8 @@ class SigningKey(object): `publicKey` field is ignored completely (errors, if any, in it will be undetected). - The only format supported for the `parameters` field is the named - curve method. Explicit encoding of curve parameters is not supported. + Two formats are supported for the `parameters` field: the named + curve and the explicit encoding of curve parameters. In the legacy ssleay format, this implementation requires the optional `parameters` field to get the curve name. In PKCS #8 format, the curve is part of the PrivateKeyAlgorithmIdentifier. @@ -937,6 +962,10 @@ class SigningKey(object): :param string: binary string with DER-encoded private ECDSA key :type string: bytes like object + :param valid_curve_encodings: list of allowed encoding formats + for curve parameters. By default (``None``) all are supported: + ``named_curve`` and ``explicit``. + :type valid_curve_encodings: :term:`set-like object` :raises MalformedPointError: if the length of encoding doesn't match the provided curve or the encoded values is too large @@ -971,8 +1000,7 @@ class SigningKey(object): sequence, s = der.remove_sequence(s) algorithm_oid, algorithm_identifier = der.remove_object(sequence) - curve_oid, empty = der.remove_object(algorithm_identifier) - curve = find_curve(curve_oid) + curve = Curve.from_der(algorithm_identifier, valid_curve_encodings) if algorithm_oid not in (oid_ecPublicKey, oid_ecDH, oid_ecMQV): raise der.UnexpectedDER( @@ -1014,13 +1042,7 @@ class SigningKey(object): raise der.UnexpectedDER( "expected tag 0 in DER privkey, got %d" % tag ) - curve_oid, empty = der.remove_object(curve_oid_str) - if empty != b(""): - raise der.UnexpectedDER( - "trailing junk after DER privkey " - "curve_oid: %s" % binascii.hexlify(empty) - ) - curve = find_curve(curve_oid) + curve = Curve.from_der(curve_oid_str, valid_curve_encodings) # we don't actually care about the following fields # diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 31353f4..0cf7cc9 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -14,7 +14,7 @@ import pytest import hashlib from .keys import VerifyingKey, SigningKey, MalformedPointError -from .der import unpem +from .der import unpem, UnexpectedDER from .util import ( sigencode_string, sigencode_der, @@ -153,6 +153,47 @@ class TestVerifyingKeyFromDer(unittest.TestCase): cls.sk2 = SigningKey.generate(vk.curve) + def test_load_key_with_explicit_parameters(self): + pub_key_str = ( + "-----BEGIN PUBLIC KEY-----\n" + "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n" + "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n" + "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n" + "NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n" + "RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n" + "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIr1UkgYs5jmbFc7it1/YI2X\n" + "T//IlaEjMNZft1owjqpBYH2ErJHk4U5Pp4WvWq1xmHwIZlsH7Ig4KmefCfR6SmU=\n" + "-----END PUBLIC KEY-----" + ) + pk = VerifyingKey.from_pem(pub_key_str) + + pk_exp = VerifyingKey.from_string( + b"\x04\x8a\xf5\x52\x48\x18\xb3\x98\xe6\x6c\x57\x3b\x8a\xdd\x7f" + b"\x60\x8d\x97\x4f\xff\xc8\x95\xa1\x23\x30\xd6\x5f\xb7\x5a\x30" + b"\x8e\xaa\x41\x60\x7d\x84\xac\x91\xe4\xe1\x4e\x4f\xa7\x85\xaf" + b"\x5a\xad\x71\x98\x7c\x08\x66\x5b\x07\xec\x88\x38\x2a\x67\x9f" + b"\x09\xf4\x7a\x4a\x65", + curve=NIST256p, + ) + self.assertEqual(pk, pk_exp) + + def test_load_key_with_explicit_with_explicit_disabled(self): + pub_key_str = ( + "-----BEGIN PUBLIC KEY-----\n" + "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n" + "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n" + "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n" + "NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n" + "RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n" + "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIr1UkgYs5jmbFc7it1/YI2X\n" + "T//IlaEjMNZft1owjqpBYH2ErJHk4U5Pp4WvWq1xmHwIZlsH7Ig4KmefCfR6SmU=\n" + "-----END PUBLIC KEY-----" + ) + with self.assertRaises(UnexpectedDER): + VerifyingKey.from_pem( + pub_key_str, valid_curve_encodings=["named_curve"] + ) + def test_load_key_with_disabled_format(self): with self.assertRaises(MalformedPointError) as e: VerifyingKey.from_der(self.key_bytes, valid_encodings=["raw"]) @@ -263,6 +304,50 @@ class TestSigningKey(unittest.TestCase): ) cls.sk2 = SigningKey.from_pem(prv_key_str) + def test_decoding_explicit_curve_parameters(self): + prv_key_str = ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB\n" + "AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA\n" + "///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV\n" + "AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg\n" + "9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A\n" + "AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQgIXtREfUmR16r\n" + "ZbmvDGD2lAEFPZa2DLPyz0czSja58yChRANCAASK9VJIGLOY5mxXO4rdf2CNl0//\n" + "yJWhIzDWX7daMI6qQWB9hKyR5OFOT6eFr1qtcZh8CGZbB+yIOCpnnwn0ekpl\n" + "-----END PRIVATE KEY-----\n" + ) + + sk = SigningKey.from_pem(prv_key_str) + + sk2 = SigningKey.from_string( + b"\x21\x7b\x51\x11\xf5\x26\x47\x5e\xab\x65\xb9\xaf\x0c\x60\xf6" + b"\x94\x01\x05\x3d\x96\xb6\x0c\xb3\xf2\xcf\x47\x33\x4a\x36\xb9" + b"\xf3\x20", + curve=NIST256p, + ) + + self.assertEqual(sk, sk2) + + def test_decoding_explicit_curve_parameters_with_explicit_disabled(self): + prv_key_str = ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB\n" + "AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA\n" + "///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV\n" + "AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg\n" + "9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A\n" + "AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQgIXtREfUmR16r\n" + "ZbmvDGD2lAEFPZa2DLPyz0czSja58yChRANCAASK9VJIGLOY5mxXO4rdf2CNl0//\n" + "yJWhIzDWX7daMI6qQWB9hKyR5OFOT6eFr1qtcZh8CGZbB+yIOCpnnwn0ekpl\n" + "-----END PRIVATE KEY-----\n" + ) + + with self.assertRaises(UnexpectedDER): + SigningKey.from_pem( + prv_key_str, valid_curve_encodings=["named_curve"] + ) + def test_equality_on_signing_keys(self): sk = SigningKey.from_secret_exponent( self.sk1.privkey.secret_multiplier, self.sk1.curve |
