Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 107 additions & 41 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@
from .ecdsa import RSZeroError
from .util import string_to_number, number_to_string, randrange
from .util import sigencode_string, sigdecode_string
from .util import oid_ecPublicKey, encoded_oid_ecPublicKey, MalformedSignature
from .util import (
oid_ecPublicKey,
encoded_oid_ecPublicKey,
oid_ecDH,
oid_ecMQV,
MalformedSignature,
)
from ._compat import normalise_bytes


Expand Down Expand Up @@ -841,16 +847,13 @@ def from_pem(cls, string, hashfunc=sha1):
"""
Initialise from key stored in :term:`PEM` format.

Note, the only PEM format supported is the un-encrypted RFC5915
(the sslay format) supported by OpenSSL, the more common PKCS#8 format
is NOT supported (see:
https://github.com/warner/python-ecdsa/issues/113 )

``openssl ec -in pkcs8.pem -out sslay.pem`` can be used to
convert PKCS#8 file to this legacy format.
The PEM formats supported are the un-encrypted RFC5915
(the sslay format) supported by OpenSSL, and the more common RFC5958
(the PKCS #8 format).

The legacy format files have the header with the string
``BEGIN EC PRIVATE KEY``.
PKCS#8 files have the header ``BEGIN PRIVATE KEY``.
Encrypted files (ones that include the string
``Proc-Type: 4,ENCRYPTED``
right after the PEM header) are not supported.
Expand All @@ -870,30 +873,36 @@ def from_pem(cls, string, hashfunc=sha1):
:return: Initialised SigningKey object
:rtype: SigningKey
"""
# the privkey pem may have multiple sections, commonly it also has
# "EC PARAMETERS", we need just "EC PRIVATE KEY".
if not PY2 and isinstance(string, str):
string = string.encode()
privkey_pem = string[
string.index(b("-----BEGIN EC PRIVATE KEY-----")) :
]
return cls.from_der(der.unpem(privkey_pem), hashfunc)

# the privkey pem may have multiple sections, commonly it also has
# "EC PARAMETERS", we need just "EC PRIVATE KEY".
ec_private_key_index = string.find(b"-----BEGIN EC PRIVATE KEY-----")
if ec_private_key_index != -1:
return cls.from_der(
der.unpem(string[ec_private_key_index:]), hashfunc, pkcs8=False
)

private_key_index = string.find(b"-----BEGIN PRIVATE KEY-----")
if private_key_index != -1:
return cls.from_der(
der.unpem(string[private_key_index:]), hashfunc, pkcs8=True
)

raise ValueError("No EC PRIVATE KEY or PRIVATE KEY section in PEM")

@classmethod
def from_der(cls, string, hashfunc=sha1):
def from_der(cls, string, hashfunc=sha1, pkcs8=False):
"""
Initialise from key stored in :term:`DER` format.

Note, the only DER format supported is the RFC5915
(the sslay format) supported by OpenSSL, the more common PKCS#8 format
is NOT supported (see:
https://github.com/warner/python-ecdsa/issues/113 )
The DER formats supported are the un-encrypted RFC5915
(the sslay format) supported by OpenSSL, and the more common RFC5958
(the PKCS #8 format).

``openssl ec -in pkcs8.pem -outform der -out sslay.der`` can be
used to convert PKCS#8 file to this legacy format.

The encoding of the ASN.1 object in those files follows following
syntax specified in RFC5915::
Both formats contain an ASN.1 object following the syntax specified
in RFC5915::

ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) }} (ecPrivkeyVer1),
Expand All @@ -904,16 +913,31 @@ def from_der(cls, string, hashfunc=sha1):

The only format supported for the `parameters` field is the named
curve method. Explicit encoding of curve parameters is not supported.

While `parameters` field is defined as optional, this implementation
requires its presence for correct parsing of the keys.
In the legacy sslay format, this implementation requires the optional
`parameters` field to get the curve name.

The PKCS #8 format includes this object as the `privateKey` field
within a larger structure:

OneAsymmetricKey ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] Attributes OPTIONAL,
...,
[[2: publicKey [1] PublicKey OPTIONAL ]],
...
}

`publicKey` field is ignored completely (errors, if any, in it will
be undetected).

:param string: binary string with DER-encoded private ECDSA key
:type string: bytes like object

:param pkcs8: whether to expect the data in PKCS #8 format
:type pkcs8: boolean

:raises MalformedPointError: if the length of encoding doesn't match
the provided curve or the encoded values is too large
:raises RuntimeError: if the generation of public key from private
Expand All @@ -923,8 +947,48 @@ def from_der(cls, string, hashfunc=sha1):
:return: Initialised SigningKey object
:rtype: SigningKey
"""
string = normalise_bytes(string)
s, empty = der.remove_sequence(string)
s = normalise_bytes(string)
curve = None

# PKCS #8 has the algorithm identifier, including the curve name,
# before the actual key. Then it contains the key data within an
# octet string.
if pkcs8:
s, empty = der.remove_sequence(s)
if empty != b(""):
raise der.UnexpectedDER(
"trailing junk after DER privkey: %s"
% binascii.hexlify(empty)
)

version, s = der.remove_integer(s)
if version != 0 and version != 1:
raise der.UnexpectedDER(
"expected '0' or '1' at start of DER privkey, got %d"
% version
)

algorithm_identifier, s = der.remove_sequence(s)
algorithm_oid, algorithm_identifier = der.remove_object(
algorithm_identifier
)
curve_oid, empty = der.remove_object(algorithm_identifier)
curve = find_curve(curve_oid)

if algorithm_oid not in (oid_ecPublicKey, oid_ecDH, oid_ecMQV):
raise der.UnexpectedDER(
"unexpected algorithm identifier '%s'" % algorithm_oid
)
if empty != b"":
raise der.UnexpectedDER(
"unexpected data after algorithm identifier: %s"
% binascii.hexlify(empty)
)

# We don't care about the optional fields after the key data.
s, _ = der.remove_octet_string(s)

s, empty = der.remove_sequence(s)
if empty != b(""):
raise der.UnexpectedDER(
"trailing junk after DER privkey: %s" % binascii.hexlify(empty)
Expand All @@ -935,18 +999,20 @@ def from_der(cls, string, hashfunc=sha1):
"expected '1' at start of DER privkey, got %d" % one
)
privkey_str, s = der.remove_octet_string(s)
tag, curve_oid_str, s = der.remove_constructed(s)
if tag != 0:
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)

if not curve:
tag, curve_oid_str, s = der.remove_constructed(s)
if tag != 0:
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)

# we don't actually care about the following fields
#
Expand Down
10 changes: 10 additions & 0 deletions src/ecdsa/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ def setUpClass(cls):
)
cls.sk1 = SigningKey.from_pem(prv_key_str)

prv_key_str = (
"-----BEGIN PRIVATE KEY-----\n"
"MG8CAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEVTBTAgEBBBheyEIL1u+SUqlC6YkE\n"
"PKKfVh+lJXcOscWhNAMyAAS4gXfQhO8X9eRWOUCAKDYPn1m0pNcmTmLaBlHc5Ho1\n"
"pMW0XPUVk0I6i1V7nCCZ82w=\n"
"-----END PRIVATE KEY-----\n"
)
cls.sk1_pkcs8 = SigningKey.from_pem(prv_key_str)

prv_key_str = (
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIKlL2EAm5NPPZuXwxRf4nXMk0A80y6UUbiQ17be/qFhRoAoGCCqGSM49\n"
Expand All @@ -233,6 +242,7 @@ def test_equality_on_signing_keys(self):
self.sk1.privkey.secret_multiplier, self.sk1.curve
)
self.assertEqual(self.sk1, sk)
self.assertEqual(self.sk1_pkcs8, sk)

def test_inequality_on_signing_keys(self):
self.assertNotEqual(self.sk1, self.sk2)
Expand Down
16 changes: 16 additions & 0 deletions src/ecdsa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
oid_ecPublicKey = (1, 2, 840, 10045, 2, 1)
encoded_oid_ecPublicKey = der.encode_oid(*oid_ecPublicKey)

# RFC5480:
# The ECDH algorithm uses the following object identifier:
# id-ecDH OBJECT IDENTIFIER ::= {
# iso(1) identified-organization(3) certicom(132) schemes(1)
# ecdh(12) }

oid_ecDH = (1, 3, 132, 1, 12)

# RFC5480:
# The ECMQV algorithm uses the following object identifier:
# id-ecMQV OBJECT IDENTIFIER ::= {
# iso(1) identified-organization(3) certicom(132) schemes(1)
# ecmqv(13) }

oid_ecMQV = (1, 3, 132, 1, 13)

if sys.version_info >= (3,):

def entropy_to_bits(ent_256):
Expand Down