From 9fbcbbca1eca0e499675f41d5e25d150a8b197a4 Mon Sep 17 00:00:00 2001 From: Helder Eijs Date: Sun, 18 Aug 2024 14:40:17 +0200 Subject: [PATCH] GH#819: import ECPrivateKey also if [0] and [1] are not present --- Changelog.rst | 3 ++ lib/Crypto/PublicKey/ECC.py | 48 +++++++++++-------- .../SelfTest/PublicKey/test_import_ECC.py | 19 ++++++++ 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index db626ab8..e33c52f1 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -13,6 +13,9 @@ Under development with the canonical name of the curve. * GH#781: the label for the SP800_108_Counter KDF may now contain zero bytes. Thanks to Julien Rische. +* GH#819: accept an RFC5916 ECPrivateKey even if it doesn't + contain any of the optional elements + (parameters [0] and publicKey[1]). 3.20.0 (9 January 2024) ++++++++++++++++++++++++++ diff --git a/lib/Crypto/PublicKey/ECC.py b/lib/Crypto/PublicKey/ECC.py index 8f9b19a6..ee47afbd 100644 --- a/lib/Crypto/PublicKey/ECC.py +++ b/lib/Crypto/PublicKey/ECC.py @@ -797,17 +797,37 @@ def _import_rfc5915_der(encoded, passphrase, curve_oid=None): # publicKey [1] BIT STRING OPTIONAL # } - private_key = DerSequence().decode(encoded, nr_elements=(3, 4)) - if private_key[0] != 1: + ec_private_key = DerSequence().decode(encoded, nr_elements=(2, 3, 4)) + if ec_private_key[0] != 1: raise ValueError("Incorrect ECC private key version") - try: - parameters = DerObjectId(explicit=0).decode(private_key[2]).value - if curve_oid is not None and parameters != curve_oid: - raise ValueError("Curve mismatch") - curve_oid = parameters - except ValueError: - pass + scalar_bytes = DerOctetString().decode(ec_private_key[1]).payload + + next_element = 2 + + # Try to decode 'parameters' + if next_element < len(ec_private_key): + try: + parameters = DerObjectId(explicit=0).decode(ec_private_key[next_element]).value + if curve_oid is not None and parameters != curve_oid: + raise ValueError("Curve mismatch") + curve_oid = parameters + next_element += 1 + except ValueError: + pass + + # Try to decode 'publicKey' + if next_element < len(ec_private_key): + try: + public_key_enc = DerBitString(explicit=1).decode(ec_private_key[next_element]).value + public_key = _import_public_der(public_key_enc, curve_oid=curve_oid) + point_x = public_key.pointQ.x + point_y = public_key.pointQ.y + next_element += 1 + except ValueError: + pass + else: + point_x = point_y = None if curve_oid is None: raise ValueError("No curve found") @@ -818,21 +838,11 @@ def _import_rfc5915_der(encoded, passphrase, curve_oid=None): else: raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid) - scalar_bytes = DerOctetString().decode(private_key[1]).payload modulus_bytes = curve.p.size_in_bytes() if len(scalar_bytes) != modulus_bytes: raise ValueError("Private key is too small") d = Integer.from_bytes(scalar_bytes) - # Decode public key (if any) - if len(private_key) > 2: - public_key_enc = DerBitString(explicit=1).decode(private_key[-1]).value - public_key = _import_public_der(public_key_enc, curve_oid=curve_oid) - point_x = public_key.pointQ.x - point_y = public_key.pointQ.y - else: - point_x = point_y = None - return construct(curve=curve_name, d=d, point_x=point_x, point_y=point_y) diff --git a/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py b/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py index de685f21..16f21622 100644 --- a/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py +++ b/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py @@ -42,6 +42,8 @@ from Crypto.PublicKey import ECC +from Crypto.PublicKey.ECC import _import_rfc5915_der + try: import pycryptodome_test_vectors # type: ignore test_vectors_available = True @@ -193,6 +195,23 @@ def test_mismatch(self): -----END PRIVATE KEY-----""" self.assertRaises(ValueError, ECC.import_key, mismatch) + def test_import_private_rfc5915_none(self): + # ECPrivateKey with a P256 private key, without [0] and [1] + data_hex = "302502010104205c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052" + key = _import_rfc5915_der(unhexlify(data_hex), None, "1.2.840.10045.3.1.7") + self.assertEqual(key.d, 0x5c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052) + + def test_import_private_rfc5915_only_0(self): + # ECPrivateKey with a P256 private key, with [0] only + data_hex = "303102010104205c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052a00a06082a8648ce3d030107" + key = _import_rfc5915_der(unhexlify(data_hex), None) + self.assertEqual(key.d, 0x5c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052) + + def test_import_private_rfc5915_only_1(self): + # ECPrivateKey with a P256 private key, with [1] only + data_hex = "306b02010104205c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052a14403420004a40ad59a2050ebe92479bd5fb16bb2e45b6465eb3cb2b1effe423fabe6cb7424db8219ef0bab80acf26fd70595b61fe4760d33eed80dd03d2fd0dfb27b8ce75c" + key = _import_rfc5915_der(unhexlify(data_hex), None, "1.2.840.10045.3.1.7") + self.assertEqual(key.d, 0x5c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052) class TestImport_P192(unittest.TestCase):