Repository: pyca/cryptography

Issue:
Invalid ecdsa certificate can trigger "cryptography.exceptions.InternalError: Unknown OpenSSL error."
Trying to access the public numbers of an ECDSA key where the public key is "0" (which I believe is the encoding for point at infinity, it's not a valid key, but one can of course encode such a key) causes an InternalError. Based on the error output I believe this is not supposed to happen.

Such a key is part of OpenSSL's test suite:
https://github.com/openssl/openssl/blob/master/test/recipes/91-test_pkey_check_data/ec_p256_bad_0.pem

reproducer:
```
#!/usr/bin/python3

from cryptography.hazmat.primitives.serialization import load_pem_private_key

privkey = """-----BEGIN PRIVATE KEY-----
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCD/////AAAAAP//////
////vOb6racXnoTzucrC/GMlUQ==
-----END PRIVATE KEY-----"""

pk = load_pem_private_key(privkey.encode(), password=None)

print(pk.public_key().public_numbers())
```

Exception message:
```
cryptography.exceptions.InternalError: Unknown OpenSSL error. This error is commonly encountered when another library is not cleaning up the OpenSSL error stack. If you are using cryptography with another library that uses OpenSSL try disabling it before reporting a bug. Otherwise please file an issue at https://github.com/pyca/cryptography/issues with information on how to reproduce this. ([_OpenSSLErrorWithText(code=269635690, lib=16, reason=106, reason_text=b'error:1012506A:elliptic curve routines:EC_POINT_get_affine_coordinates:point at infinity')])
```


Discussion:
You are correct, we should handle this error internally. Thanks for the report!
Just for future reference, the key here actually is a private key with the scalar value `ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551`, which is also the curve order of P256.

Irritatingly, OpenSSL is totally fine with this key to load it and even generate an `EVP_PKEY *` of the "public key". But if you attempt to get the public numbers then `EC_POINT_get_affine_coordinates` gets upset. I would much rather error before an attempt to call `public_numbers()`...
This is also reproducible with a public key loaded directly (rather than via a private key):

```
serialization.load_pem_public_key(b"""
-----BEGIN PUBLIC KEY-----
MBkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDAgAA
-----END PUBLIC KEY-----
""").public_numbers()
```

Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py
--- a/src/cryptography/hazmat/backends/openssl/ec.py
+++ b/src/cryptography/hazmat/backends/openssl/ec.py
@@ -72,6 +72,17 @@ def _mark_asn1_named_ec_curve(backend: "Backend", ec_cdata):
     )
 
 
+def _check_key_infinity(backend: "Backend", ec_cdata) -> None:
+    point = backend._lib.EC_KEY_get0_public_key(ec_cdata)
+    backend.openssl_assert(point != backend._ffi.NULL)
+    group = backend._lib.EC_KEY_get0_group(ec_cdata)
+    backend.openssl_assert(group != backend._ffi.NULL)
+    if backend._lib.EC_POINT_is_at_infinity(group, point):
+        raise ValueError(
+            "Cannot load an EC public key where the point is at infinity"
+        )
+
+
 def _sn_to_elliptic_curve(backend: "Backend", sn: str) -> ec.EllipticCurve:
     try:
         return ec._CURVE_TYPES[sn]()
@@ -120,6 +131,7 @@ def __init__(self, backend: "Backend", ec_key_cdata, evp_pkey):
         sn = _ec_key_curve_sn(backend, ec_key_cdata)
         self._curve = _sn_to_elliptic_curve(backend, sn)
         _mark_asn1_named_ec_curve(backend, ec_key_cdata)
+        _check_key_infinity(backend, ec_key_cdata)
 
     @property
     def curve(self) -> ec.EllipticCurve:
@@ -211,6 +223,7 @@ def __init__(self, backend: "Backend", ec_key_cdata, evp_pkey):
         sn = _ec_key_curve_sn(backend, ec_key_cdata)
         self._curve = _sn_to_elliptic_curve(backend, sn)
         _mark_asn1_named_ec_curve(backend, ec_key_cdata)
+        _check_key_infinity(backend, ec_key_cdata)
 
     @property
     def curve(self) -> ec.EllipticCurve:


Test patch:
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
@@ -6,6 +6,7 @@
 import binascii
 import itertools
 import os
+import textwrap
 import typing
 from binascii import hexlify
 
@@ -472,6 +473,39 @@ def test_load_invalid_public_ec_key_from_numbers(self, backend):
         with pytest.raises(ValueError):
             numbers.public_key(backend)
 
+    def test_load_invalid_ec_key_from_pem(self, backend):
+        _skip_curve_unsupported(backend, ec.SECP256R1())
+
+        # BoringSSL rejects infinity points before it ever gets to us, so it
+        # uses a more generic error message.
+        match = (
+            "infinity" if not backend._lib.CRYPTOGRAPHY_IS_BORINGSSL else None
+        )
+        with pytest.raises(ValueError, match=match):
+            serialization.load_pem_public_key(
+                textwrap.dedent(
+                    """
+            -----BEGIN PUBLIC KEY-----
+            MBkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDAgAA
+            -----END PUBLIC KEY-----
+            """
+                ).encode(),
+                backend=backend,
+            )
+        with pytest.raises(ValueError, match=match):
+            serialization.load_pem_private_key(
+                textwrap.dedent(
+                    """
+            -----BEGIN PRIVATE KEY-----
+            MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCD/////AAAAAP//////
+            ////vOb6racXnoTzucrC/GMlUQ==
+            -----END PRIVATE KEY-----
+            """
+                ).encode(),
+                password=None,
+                backend=backend,
+            )
+
     def test_signatures(self, backend, subtests):
         vectors = itertools.chain(
             load_vectors_from_file(