Skip to content

Commit

Permalink
GH#819: import ECPrivateKey also if [0] and [1] are not present
Browse files Browse the repository at this point in the history
  • Loading branch information
Legrandin committed Aug 18, 2024
1 parent 0a0c86f commit 959c93a
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 19 deletions.
3 changes: 3 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
++++++++++++++++++++++++++
Expand Down
47 changes: 28 additions & 19 deletions lib/Crypto/PublicKey/ECC.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,17 +797,24 @@ 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

if curve_oid is None:
raise ValueError("No curve found")
Expand All @@ -818,21 +825,23 @@ 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
# Try to decode 'publicKey'
point_x = point_y = None
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

d = Integer.from_bytes(scalar_bytes)
return construct(curve=curve_name, d=d, point_x=point_x, point_y=point_y)


Expand Down
3 changes: 3 additions & 0 deletions lib/Crypto/PublicKey/ECC.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ _Curve = NamedTuple("_Curve", [('p', Integer),

_curves: Dict[str, _Curve]

def _import_rfc5915_der(encoded: bytes,
passphrase: Optional[str] = None,
curve_oid: Optional[str] = None) -> EccKey: ...

def generate(**kwargs: Union[str, RNG]) -> EccKey: ...
def construct(**kwargs: Union[str, int]) -> EccKey: ...
Expand Down
19 changes: 19 additions & 0 deletions lib/Crypto/SelfTest/PublicKey/test_import_ECC.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):

Expand Down

0 comments on commit 959c93a

Please sign in to comment.