Repository: pyca/cryptography

Issue:
Elliptic Curve loading from PEM files does not include curve type
Loading Elliptic Curve public and private key files from PEM files in the PEM Serialization Backend results in a curve type of None, the easiest argument to initialise the objects. Most functionality remains, but it could be possible to retrieve known curve types from PEM private key parameters given in the file. I don't think that the same can be done for public keys, as they are just a point on a curve, but I may be wrong.



Discussion:
Quote @public:
@alex @michael-hart The curve OID is set in the ASN.1 data last I checked. You shouldn't need to reverse it from the points. e.g.

http://lapo.it/asn1js/#3059301306072A8648CE3D020106082A8648CE3D0301070342000424BCF36EECF6B519CB16538BFBA6D35FA82255ABC0B1CE8D0C5153D0830277689B4D33540C39051AC82AD1C1F92583E0FFAC5494C0452AB5987B7C90E216ACF7

I am fairly sure OpenSSL even provides a convenient way of turning the OID into a short name for mapping to our curve types. We already cope with this stuff in other places.

It makes sense. I'll look at implementing this stuff later on if no one else has claimed it at that point.


Resolving patch:
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -474,12 +474,14 @@ def _evp_pkey_to_private_key(self, evp_pkey):
             assert dsa_cdata != self._ffi.NULL
             dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free)
             return _DSAPrivateKey(self, dsa_cdata)
-        elif self._lib.Cryptography_HAS_EC == 1 \
-                and type == self._lib.EVP_PKEY_EC:
+        elif (self._lib.Cryptography_HAS_EC == 1 and
+              type == self._lib.EVP_PKEY_EC):
             ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey)
             assert ec_cdata != self._ffi.NULL
             ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free)
-            return _EllipticCurvePrivateKey(self, ec_cdata, None)
+            sn = self._ec_key_curve_sn(ec_cdata)
+            curve = self._sn_to_elliptic_curve(sn)
+            return _EllipticCurvePrivateKey(self, ec_cdata, curve)
         else:
             raise UnsupportedAlgorithm("Unsupported key type.")
 
@@ -501,15 +503,30 @@ def _evp_pkey_to_public_key(self, evp_pkey):
             assert dsa_cdata != self._ffi.NULL
             dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free)
             return _DSAPublicKey(self, dsa_cdata)
-        elif self._lib.Cryptography_HAS_EC == 1 \
-                and type == self._lib.EVP_PKEY_EC:
+        elif (self._lib.Cryptography_HAS_EC == 1 and
+              type == self._lib.EVP_PKEY_EC):
             ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey)
             assert ec_cdata != self._ffi.NULL
             ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free)
-            return _EllipticCurvePublicKey(self, ec_cdata, None)
+            sn = self._ec_key_curve_sn(ec_cdata)
+            curve = self._sn_to_elliptic_curve(sn)
+            return _EllipticCurvePublicKey(self, ec_cdata, curve)
         else:
             raise UnsupportedAlgorithm("Unsupported key type.")
 
+    def _ec_key_curve_sn(self, ec_key):
+        group = self._lib.EC_KEY_get0_group(ec_key)
+        assert group != self._ffi.NULL
+
+        nid = self._lib.EC_GROUP_get_curve_name(group)
+        assert nid != self._lib.NID_undef
+
+        curve_name = self._lib.OBJ_nid2sn(nid)
+        assert curve_name != self._ffi.NULL
+
+        sn = self._ffi.string(curve_name).decode('ascii')
+        return sn
+
     def _pem_password_cb(self, password):
         """
         Generate a pem_password_cb function pointer that copied the password to
@@ -1048,6 +1065,15 @@ def _elliptic_curve_to_nid(self, curve):
             )
         return curve_nid
 
+    def _sn_to_elliptic_curve(self, sn):
+        try:
+            return ec._CURVE_TYPES[sn]()
+        except KeyError:
+            raise UnsupportedAlgorithm(
+                "{0} is not a supported elliptic curve".format(sn),
+                _Reasons.UNSUPPORTED_ELLIPTIC_CURVE
+            )
+
     @contextmanager
     def _bn_ctx_manager(self):
         bn_ctx = self._lib.BN_CTX_new()
diff --git a/cryptography/hazmat/primitives/asymmetric/ec.py b/cryptography/hazmat/primitives/asymmetric/ec.py
--- a/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -184,6 +184,30 @@ def key_size(self):
         return 192
 
 
+_CURVE_TYPES = {
+    "prime192v1": SECP192R1,
+    "prime256v1": SECP256R1,
+
+    "secp192r1": SECP192R1,
+    "secp224r1": SECP224R1,
+    "secp256r1": SECP256R1,
+    "secp384r1": SECP384R1,
+    "secp521r1": SECP521R1,
+
+    "sect163k1": SECT163K1,
+    "sect233k1": SECT233K1,
+    "sect283k1": SECT283K1,
+    "sect409k1": SECT409K1,
+    "sect571k1": SECT571K1,
+
+    "sect163r2": SECT163R2,
+    "sect233r1": SECT233R1,
+    "sect283r1": SECT283R1,
+    "sect409r1": SECT409R1,
+    "sect571r1": SECT571R1,
+}
+
+
 @utils.register_interface(interfaces.EllipticCurveSignatureAlgorithm)
 class ECDSA(object):
     def __init__(self, algorithm):


Test patch:
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -493,7 +493,7 @@ def test_very_long_pem_serialization_password(self):
             )
 
 
-class TestOpenSSLNoEllipticCurve(object):
+class TestOpenSSLEllipticCurve(object):
     def test_elliptic_curve_supported(self, monkeypatch):
         monkeypatch.setattr(backend._lib, "Cryptography_HAS_EC", 0)
 
@@ -506,6 +506,10 @@ def test_elliptic_curve_signature_algorithm_supported(self, monkeypatch):
             None, None
         ) is False
 
+    def test_sn_to_elliptic_curve_not_supported(self):
+        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE):
+            backend._sn_to_elliptic_curve(b"fake")
+
 
 class TestDeprecatedRSABackendMethods(object):
     def test_create_rsa_signature_ctx(self):
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -29,26 +29,6 @@
     raises_unsupported_algorithm
 )
 
-_CURVE_TYPES = {
-    "secp192r1": ec.SECP192R1,
-    "secp224r1": ec.SECP224R1,
-    "secp256r1": ec.SECP256R1,
-    "secp384r1": ec.SECP384R1,
-    "secp521r1": ec.SECP521R1,
-
-    "sect163k1": ec.SECT163K1,
-    "sect233k1": ec.SECT233K1,
-    "sect283k1": ec.SECT283K1,
-    "sect409k1": ec.SECT409K1,
-    "sect571k1": ec.SECT571K1,
-
-    "sect163r2": ec.SECT163R2,
-    "sect233r1": ec.SECT233R1,
-    "sect283r1": ec.SECT283R1,
-    "sect409r1": ec.SECT409R1,
-    "sect571r1": ec.SECT571R1,
-}
-
 _HASH_TYPES = {
     "SHA-1": hashes.SHA1,
     "SHA-224": hashes.SHA224,
@@ -162,7 +142,7 @@ class TestECDSAVectors(object):
         ))
     )
     def test_signing_with_example_keys(self, backend, vector, hash_type):
-        curve_type = _CURVE_TYPES[vector['curve']]
+        curve_type = ec._CURVE_TYPES[vector['curve']]
 
         _skip_ecdsa_vector(backend, curve_type, hash_type)
 
@@ -188,7 +168,7 @@ def test_signing_with_example_keys(self, backend, vector, hash_type):
         verifier.verify()
 
     @pytest.mark.parametrize(
-        "curve", _CURVE_TYPES.values()
+        "curve", ec._CURVE_TYPES.values()
     )
     def test_generate_vector_curves(self, backend, curve):
         _skip_curve_unsupported(backend, curve())
@@ -244,7 +224,7 @@ def test_unknown_signature_algoritm(self, backend):
     )
     def test_signatures(self, backend, vector):
         hash_type = _HASH_TYPES[vector['digest_algorithm']]
-        curve_type = _CURVE_TYPES[vector['curve']]
+        curve_type = ec._CURVE_TYPES[vector['curve']]
 
         _skip_ecdsa_vector(backend, curve_type, hash_type)
 
@@ -276,7 +256,7 @@ def test_signatures(self, backend, vector):
     )
     def test_signature_failures(self, backend, vector):
         hash_type = _HASH_TYPES[vector['digest_algorithm']]
-        curve_type = _CURVE_TYPES[vector['curve']]
+        curve_type = ec._CURVE_TYPES[vector['curve']]
 
         _skip_ecdsa_vector(backend, curve_type, hash_type)
 
diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py
--- a/tests/hazmat/primitives/test_serialization.py
+++ b/tests/hazmat/primitives/test_serialization.py
@@ -135,6 +135,8 @@ def test_load_ec_public_key(self, backend):
         )
         assert key
         assert isinstance(key, interfaces.EllipticCurvePublicKey)
+        assert key.curve.name == "secp256r1"
+        assert key.curve.key_size == 256
 
 
 @pytest.mark.traditional_openssl_serialization
@@ -412,6 +414,8 @@ def test_load_pem_ec_private_key(self, key_file, password, backend):
         )
         assert key
         assert isinstance(key, interfaces.EllipticCurvePrivateKey)
+        assert key.curve.name == "secp256r1"
+        assert key.curve.key_size == 256
 
     def test_unused_password(self, backend):
         key_file = os.path.join(