aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Stapleton <alexs@prol.etari.at>2014-01-21 20:19:17 +0000
committerAlex Stapleton <alexs@prol.etari.at>2014-01-24 08:11:43 +0000
commit17ed58daa9573458157b02f822f5dc471d954298 (patch)
tree4e5a4fe5516384bccac219bad8a0e4872b0b67b7
parentadbe0801270ac5ba247b78568ad42381419193d2 (diff)
downloadcryptography-17ed58daa9573458157b02f822f5dc471d954298.tar.gz
cryptography-17ed58daa9573458157b02f822f5dc471d954298.tar.bz2
cryptography-17ed58daa9573458157b02f822f5dc471d954298.zip
Python implementation of OpenSSL locking callback
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py2
-rw-r--r--cryptography/hazmat/bindings/openssl/binding.py45
-rw-r--r--cryptography/hazmat/bindings/openssl/crypto.py7
-rw-r--r--tests/hazmat/bindings/test_openssl.py106
4 files changed, 159 insertions, 1 deletions
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index ee82ba71..504ad551 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -44,6 +44,8 @@ class Backend(object):
self._ffi = self._binding.ffi
self._lib = self._binding.lib
+ self._binding.init_static_locks()
+
# adds all ciphers/digests for EVP
self._lib.OpenSSL_add_all_algorithms()
# registers available SSL/TLS ciphers and digests
diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py
index 1c17a5b2..cde3bdbd 100644
--- a/cryptography/hazmat/bindings/openssl/binding.py
+++ b/cryptography/hazmat/bindings/openssl/binding.py
@@ -14,6 +14,7 @@
from __future__ import absolute_import, division, print_function
import sys
+import threading
from cryptography.hazmat.bindings.utils import build_ffi
@@ -70,6 +71,10 @@ class Binding(object):
"x509v3",
]
+ _locks = None
+ _lock_cb_handle = None
+ _lock_init_lock = threading.Lock()
+
ffi = None
lib = None
@@ -95,3 +100,43 @@ class Binding(object):
def is_available(cls):
# OpenSSL is the only binding so for now it must always be available
return True
+
+ @classmethod
+ def init_static_locks(cls):
+ with cls._lock_init_lock:
+ cls._ensure_ffi_initialized()
+
+ if not cls._lock_cb_handle:
+ cls._lock_cb_handle = cls.ffi.callback(
+ "void(int, int, const char *, int)",
+ cls._lock_cb
+ )
+
+ # use Python's implementation if available
+
+ __import__("_ssl")
+
+ if cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL:
+ return
+
+ # otherwise setup our version
+
+ num_locks = cls.lib.CRYPTO_num_locks()
+ cls._locks = [threading.Lock() for n in range(num_locks)]
+
+ cls.lib.CRYPTO_set_locking_callback(cls._lock_cb_handle)
+
+ @classmethod
+ def _lock_cb(cls, mode, n, file, line):
+ lock = cls._locks[n]
+
+ if mode & cls.lib.CRYPTO_LOCK:
+ lock.acquire()
+ elif mode & cls.lib.CRYPTO_UNLOCK:
+ lock.release()
+ else:
+ raise RuntimeError(
+ "Unknown lock mode {0}: lock={1}, file={2}, line={3}".format(
+ mode, n, file, line
+ )
+ )
diff --git a/cryptography/hazmat/bindings/openssl/crypto.py b/cryptography/hazmat/bindings/openssl/crypto.py
index 40d91bf2..81d13b73 100644
--- a/cryptography/hazmat/bindings/openssl/crypto.py
+++ b/cryptography/hazmat/bindings/openssl/crypto.py
@@ -27,6 +27,11 @@ static const int CRYPTO_MEM_CHECK_ON;
static const int CRYPTO_MEM_CHECK_OFF;
static const int CRYPTO_MEM_CHECK_ENABLE;
static const int CRYPTO_MEM_CHECK_DISABLE;
+static const int CRYPTO_LOCK;
+static const int CRYPTO_UNLOCK;
+static const int CRYPTO_READ;
+static const int CRYPTO_WRITE;
+static const int CRYPTO_LOCK_SSL;
"""
FUNCTIONS = """
@@ -43,6 +48,7 @@ void CRYPTO_set_locking_callback(void(*)(int, int, const char *, int));
void CRYPTO_set_id_callback(unsigned long (*)(void));
unsigned long (*CRYPTO_get_id_callback(void))(void);
void (*CRYPTO_get_locking_callback(void))(int, int, const char *, int);
+void CRYPTO_lock(int, int, const char *, int);
void OPENSSL_free(void *);
"""
@@ -51,7 +57,6 @@ MACROS = """
void CRYPTO_add(int *, int, int);
void CRYPTO_malloc_init(void);
void CRYPTO_malloc_debug_init(void);
-
"""
CUSTOMIZATIONS = """
diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py
index d1e85058..b2264fb5 100644
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -11,6 +11,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import threading
+import time
+
+import pytest
+
from cryptography.hazmat.bindings.openssl.binding import Binding
@@ -23,3 +28,104 @@ class TestOpenSSL(object):
def test_is_available(self):
assert Binding.is_available() is True
+
+ def test_crypto_lock_init(self):
+ b = Binding()
+ b.init_static_locks()
+ lock_cb = b.lib.CRYPTO_get_locking_callback()
+ assert lock_cb != b.ffi.NULL
+
+ def test_our_crypto_lock(self, capfd):
+ b = Binding()
+ b.init_static_locks()
+
+ # only run this test if we are using our locking cb
+ original_cb = b.lib.CRYPTO_get_locking_callback()
+ if original_cb != b._lock_cb_handle:
+ pytest.skip("Not using Python locking callback implementation")
+
+ # check that the lock state changes appropriately
+ lock = b._locks[b.lib.CRYPTO_LOCK_SSL]
+
+ assert lock.acquire(False)
+
+ lock.release()
+
+ b.lib.CRYPTO_lock(
+ b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL,
+ b.ffi.NULL,
+ 0
+ )
+
+ assert not lock.acquire(False)
+
+ b.lib.CRYPTO_lock(
+ b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL,
+ b.ffi.NULL,
+ 0
+ )
+
+ assert lock.acquire(False)
+ lock.release()
+
+ # force the error path to run.
+
+ b.lib.CRYPTO_lock(
+ 0,
+ b.lib.CRYPTO_LOCK_SSL,
+ b.ffi.NULL,
+ 0
+ )
+
+ lock.acquire(False)
+ lock.release()
+
+ out, err = capfd.readouterr()
+ assert "RuntimeError: Unknown lock mode" in err
+
+ def test_crypto_lock_mutex(self):
+ b = Binding()
+ b.init_static_locks()
+
+ # make sure whatever locking system we end up with actually acts
+ # like a mutex.
+
+ self._shared_value = 0
+
+ def critical_loop():
+ for i in range(10):
+ b.lib.CRYPTO_lock(
+ b.lib.CRYPTO_LOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL,
+ b.ffi.NULL,
+ 0
+ )
+
+ assert self._shared_value == 0
+ self._shared_value += 1
+ time.sleep(0.01)
+ assert self._shared_value == 1
+ self._shared_value = 0
+
+ b.lib.CRYPTO_lock(
+ b.lib.CRYPTO_UNLOCK | b.lib.CRYPTO_READ,
+ b.lib.CRYPTO_LOCK_SSL,
+ b.ffi.NULL,
+ 0
+ )
+
+ threads = []
+ for x in range(10):
+ t = threading.Thread(target=critical_loop)
+ t.daemon = True
+ t.start()
+
+ threads.append(t)
+
+ while threads:
+ for t in threads:
+ t.join(0.1)
+ if not t.is_alive():
+ threads.remove(t)