fix libressl error/refactor some error handling (#3609)

* add libre so I can see the error

* add the libre error needed and refactor error handling a bit

We were historically matching on lib + func + reason, but func is
somewhat unstable so now we match on lib + reason only. Of course, in
this case libressl changed both lib and reason so it wouldn't
have mattered. All error handling from the error queue in
openssl is an illusion

* fix a typo, probably an unneeded branch

* review feedback

* refactor tests to support libressl

insert additional rant about libre here, although admittedly these tests
were assuming stability where openssl itself guarantees none

* better assert, fix flake8
diff --git a/Jenkinsfile b/Jenkinsfile
index 6cdf162..a45d0b4 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -55,6 +55,11 @@
     ],
     [
         label: 'docker',
+        imageName: 'pyca/cryptography-runner-jessie-libressl:2.5.4',
+        toxenvs: ['py27'],
+    ],
+    [
+        label: 'docker',
         imageName: 'pyca/cryptography-runner-ubuntu-xenial',
         toxenvs: ['py27', 'py35'],
     ],
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index a259d66..9900d05 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -710,10 +710,13 @@
         )
         if res == 0:
             errors = self._consume_errors()
-            self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA)
             self.openssl_assert(
-                errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+                errors[0]._lib_reason_match(
+                    self._lib.ERR_LIB_RSA,
+                    self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+                )
             )
+
             raise ValueError("Digest too big for RSA key")
 
         return _CertificateSigningRequest(self, x509_req)
@@ -792,9 +795,11 @@
         )
         if res == 0:
             errors = self._consume_errors()
-            self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA)
             self.openssl_assert(
-                errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+                errors[0]._lib_reason_match(
+                    self._lib.ERR_LIB_RSA,
+                    self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+                )
             )
             raise ValueError("Digest too big for RSA key")
 
@@ -802,9 +807,11 @@
 
     def _raise_time_set_error(self):
         errors = self._consume_errors()
-        self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_ASN1)
         self.openssl_assert(
-            errors[0][3] == self._lib.ASN1_R_ERROR_GETTING_TIME
+            errors[0]._lib_reason_match(
+                self._lib.ERR_LIB_ASN1,
+                self._lib.ASN1_R_ERROR_GETTING_TIME
+            )
         )
         raise ValueError(
             "Invalid time. This error can occur if you set a time too far in "
@@ -879,9 +886,11 @@
         )
         if res == 0:
             errors = self._consume_errors()
-            self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA)
             self.openssl_assert(
-                errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+                errors[0]._lib_reason_match(
+                    self._lib.ERR_LIB_RSA,
+                    self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY
+                )
             )
             raise ValueError("Digest too big for RSA key")
 
@@ -1173,31 +1182,21 @@
         if not errors:
             raise ValueError("Could not deserialize key data.")
 
-        elif errors[0][1:] in (
-            (
-                self._lib.ERR_LIB_EVP,
-                self._lib.EVP_F_EVP_DECRYPTFINAL_EX,
-                self._lib.EVP_R_BAD_DECRYPT
-            ),
-            (
+        elif (
+            errors[0]._lib_reason_match(
+                self._lib.ERR_LIB_EVP, self._lib.EVP_R_BAD_DECRYPT
+            ) or errors[0]._lib_reason_match(
                 self._lib.ERR_LIB_PKCS12,
-                self._lib.PKCS12_F_PKCS12_PBE_CRYPT,
-                self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR,
+                self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR
             )
         ):
             raise ValueError("Bad decrypt. Incorrect password?")
 
-        elif errors[0][1:] in (
-            (
-                self._lib.ERR_LIB_PEM,
-                self._lib.PEM_F_PEM_GET_EVP_CIPHER_INFO,
-                self._lib.PEM_R_UNSUPPORTED_ENCRYPTION
-            ),
-
-            (
-                self._lib.ERR_LIB_EVP,
-                self._lib.EVP_F_EVP_PBE_CIPHERINIT,
-                self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM
+        elif (
+            errors[0]._lib_reason_match(
+                self._lib.ERR_LIB_EVP, self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM
+            ) or errors[0]._lib_reason_match(
+                self._lib.ERR_LIB_PEM, self._lib.PEM_R_UNSUPPORTED_ENCRYPTION
             )
         ):
             raise UnsupportedAlgorithm(
@@ -1206,9 +1205,8 @@
             )
 
         elif any(
-            error[1:] == (
+            error._lib_reason_match(
                 self._lib.ERR_LIB_EVP,
-                self._lib.EVP_F_EVP_PKCS82PKEY,
                 self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM
             )
             for error in errors
@@ -1216,7 +1214,7 @@
             raise ValueError("Unsupported public key algorithm.")
 
         else:
-            assert errors[0][1] in (
+            assert errors[0].lib in (
                 self._lib.ERR_LIB_EVP,
                 self._lib.ERR_LIB_PEM,
                 self._lib.ERR_LIB_ASN1,
@@ -1235,9 +1233,8 @@
             errors = self._consume_errors()
             self.openssl_assert(
                 curve_nid == self._lib.NID_undef or
-                errors[0][1:] == (
+                errors[0]._lib_reason_match(
                     self._lib.ERR_LIB_EC,
-                    self._lib.EC_F_EC_GROUP_NEW_BY_CURVE_NAME,
                     self._lib.EC_R_UNKNOWN_GROUP
                 )
             )
diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py
index 13c9fa5..4ca2fee 100644
--- a/src/cryptography/hazmat/backends/openssl/ciphers.py
+++ b/src/cryptography/hazmat/backends/openssl/ciphers.py
@@ -160,13 +160,11 @@
                 raise InvalidTag
 
             self._backend.openssl_assert(
-                errors[0][1:] == (
+                errors[0]._lib_reason_match(
                     self._backend._lib.ERR_LIB_EVP,
-                    self._backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX,
                     self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
-                ) or errors[0][1:] == (
+                ) or errors[0]._lib_reason_match(
                     self._backend._lib.ERR_LIB_EVP,
-                    self._backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
                     self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
                 )
             )
diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py
index 88c876f..456e9be 100644
--- a/src/cryptography/hazmat/backends/openssl/dh.py
+++ b/src/cryptography/hazmat/backends/openssl/dh.py
@@ -63,11 +63,11 @@
 def _handle_dh_compute_key_error(errors, backend):
     lib = backend._lib
 
-    backend.openssl_assert(errors[0][1:] == (
-        lib.ERR_LIB_DH,
-        lib.DH_F_COMPUTE_KEY,
-        lib.DH_R_INVALID_PUBKEY
-    ))
+    backend.openssl_assert(
+        errors[0]._lib_reason_match(
+            lib.ERR_LIB_DH, lib.DH_R_INVALID_PUBKEY
+        )
+    )
 
     raise ValueError("Public key value is invalid for this exchange.")
 
diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py
index 6b3d50c..d00fc79 100644
--- a/src/cryptography/hazmat/bindings/openssl/binding.py
+++ b/src/cryptography/hazmat/bindings/openssl/binding.py
@@ -8,17 +8,32 @@
 import threading
 import types
 
+from cryptography import utils
 from cryptography.exceptions import InternalError
 from cryptography.hazmat.bindings._openssl import ffi, lib
 from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
 
-_OpenSSLError = collections.namedtuple("_OpenSSLError",
-                                       ["code", "lib", "func", "reason"])
 _OpenSSLErrorWithText = collections.namedtuple(
     "_OpenSSLErrorWithText", ["code", "lib", "func", "reason", "reason_text"]
 )
 
 
+class _OpenSSLError(object):
+    def __init__(self, code, lib, func, reason):
+        self._code = code
+        self._lib = lib
+        self._func = func
+        self._reason = reason
+
+    def _lib_reason_match(self, lib, reason):
+        return lib == self.lib and reason == self.reason
+
+    code = utils.read_only_property("_code")
+    lib = utils.read_only_property("_lib")
+    func = utils.read_only_property("_func")
+    reason = utils.read_only_property("_reason")
+
+
 def _consume_errors(lib):
     errors = []
     while True:
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 20c073a..e857ff6 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -127,10 +127,7 @@
     def test_error_strings_loaded(self):
         # returns a value in a static buffer
         err = backend._lib.ERR_error_string(101183626, backend._ffi.NULL)
-        assert backend._ffi.string(err) == (
-            b"error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:"
-            b"data not multiple of block length"
-        )
+        assert b"data not multiple of block length" in backend._ffi.string(err)
 
     def test_unknown_error_in_cipher_finalize(self):
         cipher = Cipher(AES(b"\0" * 16), CBC(b"\0" * 16), backend=backend)
diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py
index 9b0da67..488f64e 100644
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -8,7 +8,7 @@
 
 from cryptography.exceptions import InternalError
 from cryptography.hazmat.bindings.openssl.binding import (
-    Binding, _OpenSSLErrorWithText, _consume_errors, _openssl_assert
+    Binding, _consume_errors, _openssl_assert
 )
 
 
@@ -94,16 +94,12 @@
         with pytest.raises(InternalError) as exc_info:
             _openssl_assert(b.lib, False)
 
-        assert exc_info.value.err_code == [_OpenSSLErrorWithText(
-            code=101183626,
-            lib=b.lib.ERR_LIB_EVP,
-            func=b.lib.EVP_F_EVP_ENCRYPTFINAL_EX,
-            reason=b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH,
-            reason_text=(
-                b'error:0607F08A:digital envelope routines:EVP_EncryptFinal_'
-                b'ex:data not multiple of block length'
-            )
-        )]
+        error = exc_info.value.err_code[0]
+        assert error.code == 101183626
+        assert error.lib == b.lib.ERR_LIB_EVP
+        assert error.func == b.lib.EVP_F_EVP_ENCRYPTFINAL_EX
+        assert error.reason == b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
+        assert b"data not multiple of block length" in error.reason_text
 
     def test_check_startup_errors_are_allowed(self):
         b = Binding()