Skip to content

Commit c7e285a

Browse files
committed
support reading keys with explicitly encoded curve parameters
1 parent 2574d21 commit c7e285a

File tree

2 files changed

+133
-26
lines changed

2 files changed

+133
-26
lines changed

src/ecdsa/keys.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
from . import der
7878
from . import rfc6979
7979
from . import ellipticcurve
80-
from .curves import NIST192p, find_curve
80+
from .curves import NIST192p, Curve
8181
from .ecdsa import RSZeroError
8282
from .util import string_to_number, number_to_string, randrange
8383
from .util import sigencode_string, sigdecode_string, bit_length
@@ -315,7 +315,13 @@ def from_string(
315315
return cls.from_public_point(point, curve, hashfunc, validate_point)
316316

317317
@classmethod
318-
def from_pem(cls, string, hashfunc=sha1, valid_encodings=None):
318+
def from_pem(
319+
cls,
320+
string,
321+
hashfunc=sha1,
322+
valid_encodings=None,
323+
valid_curve_encodings=None,
324+
):
319325
"""
320326
Initialise from public key stored in :term:`PEM` format.
321327
@@ -324,7 +330,7 @@ def from_pem(cls, string, hashfunc=sha1, valid_encodings=None):
324330
See the :func:`~VerifyingKey.from_der()` method for details of the
325331
format supported.
326332
327-
Note: only a single PEM object encoding is supported in provided
333+
Note: only a single PEM object decoding is supported in provided
328334
string.
329335
330336
:param string: text with PEM-encoded public ECDSA key
@@ -334,6 +340,11 @@ def from_pem(cls, string, hashfunc=sha1, valid_encodings=None):
334340
:term:`hybrid`. To read malformed files, include
335341
:term:`raw encoding` with ``raw`` in the list.
336342
:type valid_encodings: :term:`set-like object
343+
:param valid_curve_encodings: list of allowed encoding formats
344+
for curve parameters. By default (``None``) all are supported:
345+
``named_curve`` and ``explicit``.
346+
:type valid_curve_encodings: :term:`set-like object`
347+
337348
338349
:return: Initialised VerifyingKey object
339350
:rtype: VerifyingKey
@@ -342,10 +353,17 @@ def from_pem(cls, string, hashfunc=sha1, valid_encodings=None):
342353
der.unpem(string),
343354
hashfunc=hashfunc,
344355
valid_encodings=valid_encodings,
356+
valid_curve_encodings=valid_curve_encodings,
345357
)
346358

347359
@classmethod
348-
def from_der(cls, string, hashfunc=sha1, valid_encodings=None):
360+
def from_der(
361+
cls,
362+
string,
363+
hashfunc=sha1,
364+
valid_encodings=None,
365+
valid_curve_encodings=None,
366+
):
349367
"""
350368
Initialise the key stored in :term:`DER` format.
351369
@@ -375,6 +393,10 @@ def from_der(cls, string, hashfunc=sha1, valid_encodings=None):
375393
:term:`hybrid`. To read malformed files, include
376394
:term:`raw encoding` with ``raw`` in the list.
377395
:type valid_encodings: :term:`set-like object
396+
:param valid_curve_encodings: list of allowed encoding formats
397+
for curve parameters. By default (``None``) all are supported:
398+
``named_curve`` and ``explicit``.
399+
:type valid_curve_encodings: :term:`set-like object`
378400
379401
:return: Initialised VerifyingKey object
380402
:rtype: VerifyingKey
@@ -391,18 +413,12 @@ def from_der(cls, string, hashfunc=sha1, valid_encodings=None):
391413
s2, point_str_bitstring = der.remove_sequence(s1)
392414
# s2 = oid_ecPublicKey,oid_curve
393415
oid_pk, rest = der.remove_object(s2)
394-
oid_curve, empty = der.remove_object(rest)
395-
if empty != b"":
396-
raise der.UnexpectedDER(
397-
"trailing junk after DER pubkey objects: %s"
398-
% binascii.hexlify(empty)
399-
)
400416
if not oid_pk == oid_ecPublicKey:
401417
raise der.UnexpectedDER(
402418
"Unexpected object identifier in DER "
403419
"encoding: {0!r}".format(oid_pk)
404420
)
405-
curve = find_curve(oid_curve)
421+
curve = Curve.from_der(rest, valid_curve_encodings)
406422
point_str, empty = der.remove_bitstring(point_str_bitstring, 0)
407423
if empty != b"":
408424
raise der.UnexpectedDER(
@@ -849,7 +865,7 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1):
849865
return cls.from_secret_exponent(secexp, curve, hashfunc)
850866

851867
@classmethod
852-
def from_pem(cls, string, hashfunc=sha1):
868+
def from_pem(cls, string, hashfunc=sha1, valid_curve_encodings=None):
853869
"""
854870
Initialise from key stored in :term:`PEM` format.
855871
@@ -869,6 +885,11 @@ def from_pem(cls, string, hashfunc=sha1):
869885
870886
:param string: text with PEM-encoded private ECDSA key
871887
:type string: str
888+
:param valid_curve_encodings: list of allowed encoding formats
889+
for curve parameters. By default (``None``) all are supported:
890+
``named_curve`` and ``explicit``.
891+
:type valid_curve_encodings: :term:`set-like object`
892+
872893
873894
:raises MalformedPointError: if the length of encoding doesn't match
874895
the provided curve or the encoded values is too large
@@ -889,10 +910,14 @@ def from_pem(cls, string, hashfunc=sha1):
889910
if private_key_index == -1:
890911
private_key_index = string.index(b"-----BEGIN PRIVATE KEY-----")
891912

892-
return cls.from_der(der.unpem(string[private_key_index:]), hashfunc)
913+
return cls.from_der(
914+
der.unpem(string[private_key_index:]),
915+
hashfunc,
916+
valid_curve_encodings,
917+
)
893918

894919
@classmethod
895-
def from_der(cls, string, hashfunc=sha1):
920+
def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None):
896921
"""
897922
Initialise from key stored in :term:`DER` format.
898923
@@ -913,8 +938,8 @@ def from_der(cls, string, hashfunc=sha1):
913938
`publicKey` field is ignored completely (errors, if any, in it will
914939
be undetected).
915940
916-
The only format supported for the `parameters` field is the named
917-
curve method. Explicit encoding of curve parameters is not supported.
941+
Two formats are supported for the `parameters` field: the named
942+
curve and the explicit encoding of curve parameters.
918943
In the legacy ssleay format, this implementation requires the optional
919944
`parameters` field to get the curve name. In PKCS #8 format, the curve
920945
is part of the PrivateKeyAlgorithmIdentifier.
@@ -937,6 +962,10 @@ def from_der(cls, string, hashfunc=sha1):
937962
938963
:param string: binary string with DER-encoded private ECDSA key
939964
:type string: bytes like object
965+
:param valid_curve_encodings: list of allowed encoding formats
966+
for curve parameters. By default (``None``) all are supported:
967+
``named_curve`` and ``explicit``.
968+
:type valid_curve_encodings: :term:`set-like object`
940969
941970
:raises MalformedPointError: if the length of encoding doesn't match
942971
the provided curve or the encoded values is too large
@@ -971,8 +1000,7 @@ def from_der(cls, string, hashfunc=sha1):
9711000

9721001
sequence, s = der.remove_sequence(s)
9731002
algorithm_oid, algorithm_identifier = der.remove_object(sequence)
974-
curve_oid, empty = der.remove_object(algorithm_identifier)
975-
curve = find_curve(curve_oid)
1003+
curve = Curve.from_der(algorithm_identifier, valid_curve_encodings)
9761004

9771005
if algorithm_oid not in (oid_ecPublicKey, oid_ecDH, oid_ecMQV):
9781006
raise der.UnexpectedDER(
@@ -1014,13 +1042,7 @@ def from_der(cls, string, hashfunc=sha1):
10141042
raise der.UnexpectedDER(
10151043
"expected tag 0 in DER privkey, got %d" % tag
10161044
)
1017-
curve_oid, empty = der.remove_object(curve_oid_str)
1018-
if empty != b(""):
1019-
raise der.UnexpectedDER(
1020-
"trailing junk after DER privkey "
1021-
"curve_oid: %s" % binascii.hexlify(empty)
1022-
)
1023-
curve = find_curve(curve_oid)
1045+
curve = Curve.from_der(curve_oid_str, valid_curve_encodings)
10241046

10251047
# we don't actually care about the following fields
10261048
#

src/ecdsa/test_keys.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import hashlib
1515

1616
from .keys import VerifyingKey, SigningKey, MalformedPointError
17-
from .der import unpem
17+
from .der import unpem, UnexpectedDER
1818
from .util import (
1919
sigencode_string,
2020
sigencode_der,
@@ -153,6 +153,47 @@ def setUpClass(cls):
153153

154154
cls.sk2 = SigningKey.generate(vk.curve)
155155

156+
def test_load_key_with_explicit_parameters(self):
157+
pub_key_str = (
158+
"-----BEGIN PUBLIC KEY-----\n"
159+
"MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
160+
"AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
161+
"///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
162+
"NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n"
163+
"RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n"
164+
"//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIr1UkgYs5jmbFc7it1/YI2X\n"
165+
"T//IlaEjMNZft1owjqpBYH2ErJHk4U5Pp4WvWq1xmHwIZlsH7Ig4KmefCfR6SmU=\n"
166+
"-----END PUBLIC KEY-----"
167+
)
168+
pk = VerifyingKey.from_pem(pub_key_str)
169+
170+
pk_exp = VerifyingKey.from_string(
171+
b"\x04\x8a\xf5\x52\x48\x18\xb3\x98\xe6\x6c\x57\x3b\x8a\xdd\x7f"
172+
b"\x60\x8d\x97\x4f\xff\xc8\x95\xa1\x23\x30\xd6\x5f\xb7\x5a\x30"
173+
b"\x8e\xaa\x41\x60\x7d\x84\xac\x91\xe4\xe1\x4e\x4f\xa7\x85\xaf"
174+
b"\x5a\xad\x71\x98\x7c\x08\x66\x5b\x07\xec\x88\x38\x2a\x67\x9f"
175+
b"\x09\xf4\x7a\x4a\x65",
176+
curve=NIST256p,
177+
)
178+
self.assertEqual(pk, pk_exp)
179+
180+
def test_load_key_with_explicit_with_explicit_disabled(self):
181+
pub_key_str = (
182+
"-----BEGIN PUBLIC KEY-----\n"
183+
"MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
184+
"AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
185+
"///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
186+
"NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n"
187+
"RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n"
188+
"//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABIr1UkgYs5jmbFc7it1/YI2X\n"
189+
"T//IlaEjMNZft1owjqpBYH2ErJHk4U5Pp4WvWq1xmHwIZlsH7Ig4KmefCfR6SmU=\n"
190+
"-----END PUBLIC KEY-----"
191+
)
192+
with self.assertRaises(UnexpectedDER):
193+
VerifyingKey.from_pem(
194+
pub_key_str, valid_curve_encodings=["named_curve"]
195+
)
196+
156197
def test_load_key_with_disabled_format(self):
157198
with self.assertRaises(MalformedPointError) as e:
158199
VerifyingKey.from_der(self.key_bytes, valid_encodings=["raw"])
@@ -263,6 +304,50 @@ def setUpClass(cls):
263304
)
264305
cls.sk2 = SigningKey.from_pem(prv_key_str)
265306

307+
def test_decoding_explicit_curve_parameters(self):
308+
prv_key_str = (
309+
"-----BEGIN PRIVATE KEY-----\n"
310+
"MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB\n"
311+
"AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA\n"
312+
"///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV\n"
313+
"AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg\n"
314+
"9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A\n"
315+
"AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQgIXtREfUmR16r\n"
316+
"ZbmvDGD2lAEFPZa2DLPyz0czSja58yChRANCAASK9VJIGLOY5mxXO4rdf2CNl0//\n"
317+
"yJWhIzDWX7daMI6qQWB9hKyR5OFOT6eFr1qtcZh8CGZbB+yIOCpnnwn0ekpl\n"
318+
"-----END PRIVATE KEY-----\n"
319+
)
320+
321+
sk = SigningKey.from_pem(prv_key_str)
322+
323+
sk2 = SigningKey.from_string(
324+
b"\x21\x7b\x51\x11\xf5\x26\x47\x5e\xab\x65\xb9\xaf\x0c\x60\xf6"
325+
b"\x94\x01\x05\x3d\x96\xb6\x0c\xb3\xf2\xcf\x47\x33\x4a\x36\xb9"
326+
b"\xf3\x20",
327+
curve=NIST256p,
328+
)
329+
330+
self.assertEqual(sk, sk2)
331+
332+
def test_decoding_explicit_curve_parameters_with_explicit_disabled(self):
333+
prv_key_str = (
334+
"-----BEGIN PRIVATE KEY-----\n"
335+
"MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB\n"
336+
"AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA\n"
337+
"///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV\n"
338+
"AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg\n"
339+
"9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A\n"
340+
"AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQgIXtREfUmR16r\n"
341+
"ZbmvDGD2lAEFPZa2DLPyz0czSja58yChRANCAASK9VJIGLOY5mxXO4rdf2CNl0//\n"
342+
"yJWhIzDWX7daMI6qQWB9hKyR5OFOT6eFr1qtcZh8CGZbB+yIOCpnnwn0ekpl\n"
343+
"-----END PRIVATE KEY-----\n"
344+
)
345+
346+
with self.assertRaises(UnexpectedDER):
347+
SigningKey.from_pem(
348+
prv_key_str, valid_curve_encodings=["named_curve"]
349+
)
350+
266351
def test_equality_on_signing_keys(self):
267352
sk = SigningKey.from_secret_exponent(
268353
self.sk1.privkey.secret_multiplier, self.sk1.curve

0 commit comments

Comments
 (0)