summaryrefslogtreecommitdiff
path: root/src/ecdsa
diff options
context:
space:
mode:
authorHubert Kario <hubert@kario.pl>2021-05-04 00:53:28 +0200
committerHubert Kario <hubert@kario.pl>2021-05-21 00:11:06 +0200
commitc7e285a102ef2e06ace32e838318bf11d628d969 (patch)
treee4468769d554dbbb07073259b5aaf6ba08272760 /src/ecdsa
parent2574d215ee5e6115235dd0b941e3b5b341763e76 (diff)
downloadecdsa-c7e285a102ef2e06ace32e838318bf11d628d969.tar.gz
support reading keys with explicitly encoded curve parameters
Diffstat (limited to 'src/ecdsa')
-rw-r--r--src/ecdsa/keys.py72
-rw-r--r--src/ecdsa/test_keys.py87
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