Repository: pyca/cryptography

Issue:
Throw a specific exception when no backends are available
In e.g. #1958, an exception is thrown that in some cases appears to be due to the fact that no backends are available. It might be nice if in these cases a specific exception would be thrown.



Discussion:
Thinking more, is there any reason we shouldn't make this a constructor error for `MultiBackend`? /cc @reaperhulk 

The only obstacle I see there is that we currently use an empty multibackend to test for various unsupported calls, but that's exactly the scenario that's causing confusion now so it makes sense to me that we should change it.

I don't think there's any legitimate configuration with an empty
multibackend.

On Tue, Mar 1, 2016 at 11:16 PM, Paul Kehrer notifications@github.com
wrote:

> The only obstacle I see there is that we currently use an empty
> multibackend to test for various unsupported calls, but that's exactly the
> scenario that's causing confusion now so it makes sense to me that we
> should change it.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/2717#issuecomment-191050445.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

Promoting to 1.3.


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py
--- a/src/cryptography/hazmat/backends/multibackend.py
+++ b/src/cryptography/hazmat/backends/multibackend.py
@@ -28,6 +28,13 @@ class MultiBackend(object):
     name = "multibackend"
 
     def __init__(self, backends):
+        if len(backends) == 0:
+            raise ValueError(
+                "Multibackend cannot be initialized with no backends. If you "
+                "are seeing this error when trying to use default_backend() "
+                "please try uninstalling and reinstalling cryptography."
+            )
+
         self._backends = backends
 
     def _filtered_backends(self, interface):


Test patch:
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -4,6 +4,8 @@
 
 from __future__ import absolute_import, division, print_function
 
+import pytest
+
 from cryptography import utils
 from cryptography.exceptions import (
     UnsupportedAlgorithm, _Reasons
@@ -21,6 +23,10 @@
 from ...utils import raises_unsupported_algorithm
 
 
+class DummyBackend(object):
+    pass
+
+
 @utils.register_interface(CipherBackend)
 class DummyCipherBackend(object):
     def __init__(self, supported_ciphers):
@@ -226,6 +232,10 @@ def create_x509_revoked_certificate(self, builder):
 
 
 class TestMultiBackend(object):
+    def test_raises_error_with_empty_list(self):
+        with pytest.raises(ValueError):
+            MultiBackend([])
+
     def test_ciphers(self):
         backend = MultiBackend([
             DummyHashBackend([]),
@@ -310,7 +320,7 @@ def test_rsa(self):
 
         backend.load_rsa_public_numbers("public_numbers")
 
-        backend = MultiBackend([])
+        backend = MultiBackend([DummyBackend()])
         with raises_unsupported_algorithm(
             _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
         ):
@@ -353,7 +363,7 @@ def test_dsa(self):
         backend.load_dsa_public_numbers("numbers")
         backend.load_dsa_parameter_numbers("numbers")
 
-        backend = MultiBackend([])
+        backend = MultiBackend([DummyBackend()])
         with raises_unsupported_algorithm(
             _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
         ):
@@ -491,7 +501,7 @@ def test_pem_serialization_backend(self):
         backend.load_pem_private_key(b"keydata", None)
         backend.load_pem_public_key(b"keydata")
 
-        backend = MultiBackend([])
+        backend = MultiBackend([DummyBackend()])
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
             backend.load_pem_private_key(b"keydata", None)
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
@@ -503,7 +513,7 @@ def test_der_serialization_backend(self):
         backend.load_der_private_key(b"keydata", None)
         backend.load_der_public_key(b"keydata")
 
-        backend = MultiBackend([])
+        backend = MultiBackend([DummyBackend()])
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
             backend.load_der_private_key(b"keydata", None)
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION):
@@ -523,7 +533,7 @@ def test_x509_backend(self):
         backend.create_x509_crl(object(), b"privatekey", hashes.SHA1())
         backend.create_x509_revoked_certificate(object())
 
-        backend = MultiBackend([])
+        backend = MultiBackend([DummyBackend()])
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):
             backend.load_pem_x509_certificate(b"certdata")
         with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509):