Repository: pyca/cryptography

Issue:
Internal assertion error on some corrupt private keys
This has popped up since cryptography 2.x and was discovered in our unit tests where we test for signing JWTs with malformed/corrupted/invalid private keys. The way we constructed these bad private keys was to take a valid private key and replace the `a` bytes with `b` bytes. Very sophisticated, I know.

I brought this up in #4635 because I thought it might be related to that but it turns out it's something else. It only happens some of the time (~10%) which makes sense since the byte-swap will be somewhat variable.

Stack trace:
```
Traceback (most recent call last):
  File "bad_crypto.py", line 5, in <module>
    jwt.encode({}, my_key, algorithm='RS256')
  File "/Users/matt/.pyenv/versions/apidev/lib/python3.5/site-packages/jwt/api_jwt.py", line 65, in encode
    json_payload, key, algorithm, headers, json_encoder
  File "/Users/matt/.pyenv/versions/apidev/lib/python3.5/site-packages/jwt/api_jws.py", line 114, in encode
    signature = alg_obj.sign(signing_input, key)
  File "/Users/matt/.pyenv/versions/apidev/lib/python3.5/site-packages/jwt/algorithms.py", line 313, in sign
    return key.sign(msg, padding.PKCS1v15(), self.hash_alg())
  File "/Users/matt/.pyenv/versions/apidev/lib/python3.5/site-packages/cryptography/hazmat/backends/openssl/rsa.py", line 415, in sign
    return _rsa_sig_sign(self._backend, padding, algorithm, self, data)
  File "/Users/matt/.pyenv/versions/apidev/lib/python3.5/site-packages/cryptography/hazmat/backends/openssl/rsa.py", line 239, in _rsa_sig_sign
    assert errors[0].lib == backend._lib.ERR_LIB_RSA
AssertionError
```

Run this code snippet a handful of times and you should see the assertion error:
```python
from Crypto.PublicKey import RSA
import jwt
my_key = RSA.generate(2048).exportKey(passphrase=None, pkcs=1, protection='scryptAndAES128-CBC')
my_key = my_key.decode().replace('a', 'b').encode()
jwt.encode({}, my_key, algorithm='RS256')
```

My system/test specs:
 * Mac OS: 10.13.6
 * Python: 3.5.4
 * cryptography: 2.4.2
 * PyJWT: 1.7.1
 * pyOpenSSL: 16.2.0
 * cffi: 1.11.5
 * OpenSSL: LibreSSL 2.2.7 and 1.1.0h  27 Mar 2018

Here is a private key (with a and b swapped already) that causes the issue for me:
```
b'-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAuYE4k09MAsi1yjMrXekMe6sT9bEt3ko47dnmN8YBgO8DiiCc\n226TnQPvuX3FGxU+Y1zTJpcvVL3L37UOvh4CSb9zKyrFK9/x/UcCfK3Eq8JdS98P\nCVeGpkp5E+vwIKY72rc1RSSSCs0PtFdYbSn4trwf5BjPxIqXwIOS3R7zC7cLPHY4\nYdsM4gLGVOP17uXJr/MPoAtWTBVm5zx4bHm6Xclzgf86sbPdL3LxNs0fz4HqJZgA\n6EUtyl6Qypq2LjXbdmm2i3vC+MxW6nEPItPqgComhq0zBmVonsiEO87rEtD548Yq\nDKvxwHhlcODcVkAYebJ+W5L6PPJBNYA3t5wYyQIDAQABAoIBAAbHkg5msftpGt5Z\nVb3yUuepem7hWTF5YFlIRw5l2wNcURNpbswEhOVNJbuG+KCple7Dw4TuDmhHs/zr\nBRqpDhXldhrUtb2uc3ihqWiVFJbieqE4jUbGvMJusvtXXeDwU6wGWzV/V4qndCrk\nu4PGypk4Cbbq6ZP2oufPryQ3D4Ff1TA06RSWdP3Cg673VqwLtkXwsRDhymAviiqU\nhxQg8bRNiD7mYoUKyLVeV7YRDLTBugfiFmy54yC99NJclLkYmzCgRt1EuoW0Hixx\nEIQFEOLftgpc+sKpbbiOileMsc/stytHXXqfgozhBxDNeSzdNYfwEpkLJpLZSUNV\nEhS4X1cCgYEAz+7DkXksWw9zLqYniMIcvcBnHQcy3Anqbcu8Zbw+I9wOwzNt44Bo\nf88i2idvWvMsRq/LX4WD4jjPB4Z3wAzGBCq+2cy0GrWByMu+VbpwCrntRBkS5huY\nIIf1nr1+BuySNt8TL6nZNKz0D8+5c8wT+VbVdPH//4MzfDrK81PPnesCgYEA5GMy\nji4l+8zO33LFMlWQGYgfSMd4jGMQD0VCvfhlosK0Py0AfZj/GKEGHduo/37KVVvb\n6XdJqYgB7OxPmdEqbMGeYPKv7pKkG1jXRuEtmXXJ9hS1t0oIvXJLHJnQrOOoRRAR\n+xJZbI7WjemY+ZCMOAPT1tm97pxjs81WgSJ6ExsCgYEAze5ADfEeNskkYAz6lnz4\njgzhkmQwwK+pVzgxy9g8brNkg3qJ2Iix9fKlJ71qkX7IWPF9z4qhxQhSMbfBHZkI\n+9OB1J7huJoOgVkXliwIbvcYvxq+Fts5XO6KGb699AmT/XgMvmXO0lbAGLC3kLGL\nDqQrH3kU+m9sLBrmKPrWYiUCgYEA3/8etW4zmMvd1jAFkoFyzGfCbyocZGxAcwm2\nFQYMAN8/03p6sbSd9XTwv9YR4Uxke+WURkjVuW2IneuDgtQv6QCFKob74Jx4Uc4H\njiAKDioFg9H6C6OUAOKZIpsFnJvIDLxfNkVf6WYKrrL+cz6/F61BVsbGTsGZ094/\nynWbDyMCgYEAh44C/wkebe0zz/llG+KTRGENsw1c7+pm0/l3wPYAlH02ewbyRjFf\nOKPfyyBtBkoD5rG3IbLyPxsbd3wWwyUzSYq02qRJq43XqyMZhRnNlYhEnNu/Gr5H\nsN1f13zqkKoRxxbIjyh4RDYlAv4Sehk27z2Q3gBe9bI5xKkoQ/VfF2w=\n-----END RSA PRIVATE KEY-----'
```



Discussion:
You're generating the initial key with pycrypto (which is a dependency you don't need since you've got cryptography installed of course), but the more interesting issue is that the following provides different signatures every loop (except when it errors!):

```python
from cryptography.hazmat.backends.openssl.backend import backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15

key_bytes = b'-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAuYE4k09MAsi1yjMrXekMe6sT9bEt3ko47dnmN8YBgO8DiiCc\n226TnQPvuX3FGxU+Y1zTJpcvVL3L37UOvh4CSb9zKyrFK9/x/UcCfK3Eq8JdS98P\nCVeGpkp5E+vwIKY72rc1RSSSCs0PtFdYbSn4trwf5BjPxIqXwIOS3R7zC7cLPHY4\nYdsM4gLGVOP17uXJr/MPoAtWTBVm5zx4bHm6Xclzgf86sbPdL3LxNs0fz4HqJZgA\n6EUtyl6Qypq2LjXbdmm2i3vC+MxW6nEPItPqgComhq0zBmVonsiEO87rEtD548Yq\nDKvxwHhlcODcVkAYebJ+W5L6PPJBNYA3t5wYyQIDAQABAoIBAAbHkg5msftpGt5Z\nVb3yUuepem7hWTF5YFlIRw5l2wNcURNpbswEhOVNJbuG+KCple7Dw4TuDmhHs/zr\nBRqpDhXldhrUtb2uc3ihqWiVFJbieqE4jUbGvMJusvtXXeDwU6wGWzV/V4qndCrk\nu4PGypk4Cbbq6ZP2oufPryQ3D4Ff1TA06RSWdP3Cg673VqwLtkXwsRDhymAviiqU\nhxQg8bRNiD7mYoUKyLVeV7YRDLTBugfiFmy54yC99NJclLkYmzCgRt1EuoW0Hixx\nEIQFEOLftgpc+sKpbbiOileMsc/stytHXXqfgozhBxDNeSzdNYfwEpkLJpLZSUNV\nEhS4X1cCgYEAz+7DkXksWw9zLqYniMIcvcBnHQcy3Anqbcu8Zbw+I9wOwzNt44Bo\nf88i2idvWvMsRq/LX4WD4jjPB4Z3wAzGBCq+2cy0GrWByMu+VbpwCrntRBkS5huY\nIIf1nr1+BuySNt8TL6nZNKz0D8+5c8wT+VbVdPH//4MzfDrK81PPnesCgYEA5GMy\nji4l+8zO33LFMlWQGYgfSMd4jGMQD0VCvfhlosK0Py0AfZj/GKEGHduo/37KVVvb\n6XdJqYgB7OxPmdEqbMGeYPKv7pKkG1jXRuEtmXXJ9hS1t0oIvXJLHJnQrOOoRRAR\n+xJZbI7WjemY+ZCMOAPT1tm97pxjs81WgSJ6ExsCgYEAze5ADfEeNskkYAz6lnz4\njgzhkmQwwK+pVzgxy9g8brNkg3qJ2Iix9fKlJ71qkX7IWPF9z4qhxQhSMbfBHZkI\n+9OB1J7huJoOgVkXliwIbvcYvxq+Fts5XO6KGb699AmT/XgMvmXO0lbAGLC3kLGL\nDqQrH3kU+m9sLBrmKPrWYiUCgYEA3/8etW4zmMvd1jAFkoFyzGfCbyocZGxAcwm2\nFQYMAN8/03p6sbSd9XTwv9YR4Uxke+WURkjVuW2IneuDgtQv6QCFKob74Jx4Uc4H\njiAKDioFg9H6C6OUAOKZIpsFnJvIDLxfNkVf6WYKrrL+cz6/F61BVsbGTsGZ094/\nynWbDyMCgYEAh44C/wkebe0zz/llG+KTRGENsw1c7+pm0/l3wPYAlH02ewbyRjFf\nOKPfyyBtBkoD5rG3IbLyPxsbd3wWwyUzSYq02qRJq43XqyMZhRnNlYhEnNu/Gr5H\nsN1f13zqkKoRxxbIjyh4RDYlAv4Sehk27z2Q3gBe9bI5xKkoQ/VfF2w=\n-----END RSA PRIVATE KEY-----'
key = serialization.load_pem_private_key(key_bytes, None, backend)
for _ in range(100):
    key.sign(b"hi", PKCS1v15(), hashes.SHA1())
```

I'm not sure why it's doing that, as the signature should be deterministic (but clearly an invalid key can break that aspect of the output)
FTR, here's what the error stack contains:

- `b'error:0308B06C:bignum routines:BN_mod_inverse_no_branch:no inverse'`
- `b'error:04088003:rsa routines:RSA_setup_blinding:BN lib'`
- `b'error:04066044:rsa routines:rsa_ossl_private_encrypt:internal error'`


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ b/src/cryptography/hazmat/backends/openssl/rsa.py
@@ -314,6 +314,11 @@ def verify(self):
 @utils.register_interface(RSAPrivateKeyWithSerialization)
 class _RSAPrivateKey(object):
     def __init__(self, backend, rsa_cdata, evp_pkey):
+        res = backend._lib.RSA_check_key(rsa_cdata)
+        if res != 1:
+            errors = backend._consume_errors_with_text()
+            raise ValueError("Invalid private key", errors)
+
         self._backend = backend
         self._rsa_cdata = rsa_cdata
         self._evp_pkey = evp_pkey


Test patch:
diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py
--- a/tests/hazmat/primitives/fixtures_rsa.py
+++ b/tests/hazmat/primitives/fixtures_rsa.py
@@ -784,3 +784,33 @@
         ),
     ),
 )
+
+RSA_KEY_CORRUPTED = b"""
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAuYE4k09MAsi1yjMrXekMe6sT9bEt3ko47dnmN8YBgO8DiiCc
+226TnQPvuX3FGxU+Y1zTJpcvVL3L37UOvh4CSb9zKyrFK9/x/UcCfK3Eq8JdS98P
+CVeGpkp5E+vwIKY72rc1RSSSCs0PtFdYbSn4trwf5BjPxIqXwIOS3R7zC7cLPHY4
+YdsM4gLGVOP17uXJr/MPoAtWTBVm5zx4bHm6Xclzgf86sbPdL3LxNs0fz4HqJZgA
+6EUtyl6Qypq2LjXbdmm2i3vC+MxW6nEPItPqgComhq0zBmVonsiEO87rEtD548Yq
+DKvxwHhlcODcVkAYebJ+W5L6PPJBNYA3t5wYyQIDAQABAoIBAAbHkg5msftpGt5Z
+Vb3yUuepem7hWTF5YFlIRw5l2wNcURNpbswEhOVNJbuG+KCple7Dw4TuDmhHs/zr
+BRqpDhXldhrUtb2uc3ihqWiVFJbieqE4jUbGvMJusvtXXeDwU6wGWzV/V4qndCrk
+u4PGypk4Cbbq6ZP2oufPryQ3D4Ff1TA06RSWdP3Cg673VqwLtkXwsRDhymAviiqU
+hxQg8bRNiD7mYoUKyLVeV7YRDLTBugfiFmy54yC99NJclLkYmzCgRt1EuoW0Hixx
+EIQFEOLftgpc+sKpbbiOileMsc/stytHXXqfgozhBxDNeSzdNYfwEpkLJpLZSUNV
+EhS4X1cCgYEAz+7DkXksWw9zLqYniMIcvcBnHQcy3Anqbcu8Zbw+I9wOwzNt44Bo
+f88i2idvWvMsRq/LX4WD4jjPB4Z3wAzGBCq+2cy0GrWByMu+VbpwCrntRBkS5huY
+IIf1nr1+BuySNt8TL6nZNKz0D8+5c8wT+VbVdPH//4MzfDrK81PPnesCgYEA5GMy
+ji4l+8zO33LFMlWQGYgfSMd4jGMQD0VCvfhlosK0Py0AfZj/GKEGHduo/37KVVvb
+6XdJqYgB7OxPmdEqbMGeYPKv7pKkG1jXRuEtmXXJ9hS1t0oIvXJLHJnQrOOoRRAR
++xJZbI7WjemY+ZCMOAPT1tm97pxjs81WgSJ6ExsCgYEAze5ADfEeNskkYAz6lnz4
+jgzhkmQwwK+pVzgxy9g8brNkg3qJ2Iix9fKlJ71qkX7IWPF9z4qhxQhSMbfBHZkI
++9OB1J7huJoOgVkXliwIbvcYvxq+Fts5XO6KGb699AmT/XgMvmXO0lbAGLC3kLGL
+DqQrH3kU+m9sLBrmKPrWYiUCgYEA3/8etW4zmMvd1jAFkoFyzGfCbyocZGxAcwm2
+FQYMAN8/03p6sbSd9XTwv9YR4Uxke+WURkjVuW2IneuDgtQv6QCFKob74Jx4Uc4H
+jiAKDioFg9H6C6OUAOKZIpsFnJvIDLxfNkVf6WYKrrL+cz6/F61BVsbGTsGZ094/
+ynWbDyMCgYEAh44C/wkebe0zz/llG+KTRGENsw1c7+pm0/l3wPYAlH02ewbyRjFf
+OKPfyyBtBkoD5rG3IbLyPxsbd3wWwyUzSYq02qRJq43XqyMZhRnNlYhEnNu/Gr5H
+sN1f13zqkKoRxxbIjyh4RDYlAv4Sehk27z2Q3gBe9bI5xKkoQ/VfF2w=
+-----END RSA PRIVATE KEY-----
+"""
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -49,6 +49,7 @@
     RSA_KEY_599,
     RSA_KEY_745,
     RSA_KEY_768,
+    RSA_KEY_CORRUPTED,
 )
 from .utils import (
     _check_rsa_private_numbers,
@@ -729,6 +730,12 @@ def test_prehashed_unsupported_in_verifier_ctx(self, backend):
                 asym_utils.Prehashed(hashes.SHA1()),
             )
 
+    def test_corrupted_private_key(self, backend):
+        with pytest.raises(ValueError):
+            serialization.load_pem_private_key(
+                RSA_KEY_CORRUPTED, password=None, backend=backend
+            )
+
 
 @pytest.mark.requires_backend_interface(interface=RSABackend)
 class TestRSAVerification(object):