From 00b8cbaa31d48326b36928228269ac14276fd5ee Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 19 Feb 2015 14:12:15 -0300 Subject: Fall back to plain ZMQ if Curve is not available. Use global flag for ZMQ_HAS_CURVE. Closes #6646 --- changes/non-curve-zmq | 1 + src/leap/bitmask/backend/backend.py | 31 ++++++++++-------- src/leap/bitmask/backend/backend_proxy.py | 18 ++++++----- src/leap/bitmask/backend/signaler.py | 18 ++++++----- src/leap/bitmask/backend/signaler_qt.py | 29 ++++++++++------- src/leap/bitmask/backend/utils.py | 54 +++++++++++++++++++++++++++++-- src/leap/bitmask/backend_app.py | 4 ++- src/leap/bitmask/config/flags.py | 3 ++ 8 files changed, 114 insertions(+), 44 deletions(-) create mode 100644 changes/non-curve-zmq diff --git a/changes/non-curve-zmq b/changes/non-curve-zmq new file mode 100644 index 00000000..8c1097ee --- /dev/null +++ b/changes/non-curve-zmq @@ -0,0 +1 @@ +- Gracefully fall back to plain ZMQ if CurveZMQ is not available (Feature #6646) diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py index 75eff8a9..32f5c953 100644 --- a/src/leap/bitmask/backend/backend.py +++ b/src/leap/bitmask/backend/backend.py @@ -28,10 +28,14 @@ import psutil from twisted.internet import defer, reactor, threads import zmq -from zmq.auth.thread import ThreadAuthenticator +try: + from zmq.auth.thread import ThreadAuthenticator +except ImportError: + pass from leap.bitmask.backend.api import API, PING_REQUEST from leap.bitmask.backend.utils import get_backend_certificates +from leap.bitmask.config import flags from leap.bitmask.backend.signaler import Signaler import logging @@ -73,18 +77,19 @@ class Backend(object): context = zmq.Context() socket = context.socket(zmq.REP) - # Start an authenticator for this context. - auth = ThreadAuthenticator(context) - auth.start() - # XXX do not hardcode this here. - auth.allow('127.0.0.1') - - # Tell authenticator to use the certificate in a directory - auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) - public, secret = get_backend_certificates() - socket.curve_publickey = public - socket.curve_secretkey = secret - socket.curve_server = True # must come before bind + if flags.ZMQ_HAS_CURVE: + # Start an authenticator for this context. + auth = ThreadAuthenticator(context) + auth.start() + # XXX do not hardcode this here. + auth.allow('127.0.0.1') + + # Tell authenticator to use the certificate in a directory + auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) + public, secret = get_backend_certificates() + socket.curve_publickey = public + socket.curve_secretkey = secret + socket.curve_server = True # must come before bind socket.bind(self.BIND_ADDR) diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 3e79289f..06e6d840 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -30,6 +30,7 @@ import zmq from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST from leap.bitmask.backend.utils import generate_zmq_certificates_if_needed from leap.bitmask.backend.utils import get_backend_certificates +from leap.bitmask.config import flags import logging logger = logging.getLogger(__name__) @@ -59,15 +60,16 @@ class BackendProxy(object): logger.debug("Connecting to server...") socket = context.socket(zmq.REQ) - # public, secret = zmq.curve_keypair() - client_keys = zmq.curve_keypair() - socket.curve_publickey = client_keys[0] - socket.curve_secretkey = client_keys[1] + if flags.ZMQ_HAS_CURVE: + # public, secret = zmq.curve_keypair() + client_keys = zmq.curve_keypair() + socket.curve_publickey = client_keys[0] + socket.curve_secretkey = client_keys[1] - # The client must know the server's public key to make a CURVE - # connection. - public, _ = get_backend_certificates() - socket.curve_serverkey = public + # The client must know the server's public key to make a CURVE + # connection. + public, _ = get_backend_certificates() + socket.curve_serverkey = public socket.setsockopt(zmq.RCVTIMEO, 1000) socket.setsockopt(zmq.LINGER, 0) # Terminate early diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py index 43cba994..a8498d11 100644 --- a/src/leap/bitmask/backend/signaler.py +++ b/src/leap/bitmask/backend/signaler.py @@ -26,6 +26,7 @@ import zmq from leap.bitmask.backend.api import SIGNALS from leap.bitmask.backend.utils import get_frontend_certificates +from leap.bitmask.config import flags import logging logger = logging.getLogger(__name__) @@ -49,15 +50,16 @@ class Signaler(object): logger.debug("Connecting to signaling server...") socket = context.socket(zmq.REQ) - # public, secret = zmq.curve_keypair() - client_keys = zmq.curve_keypair() - socket.curve_publickey = client_keys[0] - socket.curve_secretkey = client_keys[1] + if flags.ZMQ_HAS_CURVE: + # public, secret = zmq.curve_keypair() + client_keys = zmq.curve_keypair() + socket.curve_publickey = client_keys[0] + socket.curve_secretkey = client_keys[1] - # The client must know the server's public key to make a CURVE - # connection. - public, _ = get_frontend_certificates() - socket.curve_serverkey = public + # The client must know the server's public key to make a CURVE + # connection. + public, _ = get_frontend_certificates() + socket.curve_serverkey = public socket.setsockopt(zmq.RCVTIMEO, 1000) socket.setsockopt(zmq.LINGER, 0) # Terminate early diff --git a/src/leap/bitmask/backend/signaler_qt.py b/src/leap/bitmask/backend/signaler_qt.py index 433f18ed..94c24648 100644 --- a/src/leap/bitmask/backend/signaler_qt.py +++ b/src/leap/bitmask/backend/signaler_qt.py @@ -24,10 +24,14 @@ import time from PySide import QtCore import zmq -from zmq.auth.thread import ThreadAuthenticator +try: + from zmq.auth.thread import ThreadAuthenticator +except ImportError: + pass from leap.bitmask.backend.api import SIGNALS from leap.bitmask.backend.utils import get_frontend_certificates +from leap.bitmask.config import flags import logging logger = logging.getLogger(__name__) @@ -67,17 +71,18 @@ class SignalerQt(QtCore.QObject): context = zmq.Context() socket = context.socket(zmq.REP) - # Start an authenticator for this context. - auth = ThreadAuthenticator(context) - auth.start() - auth.allow('127.0.0.1') - - # Tell authenticator to use the certificate in a directory - auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) - public, secret = get_frontend_certificates() - socket.curve_publickey = public - socket.curve_secretkey = secret - socket.curve_server = True # must come before bind + if flags.ZMQ_HAS_CURVE: + # Start an authenticator for this context. + auth = ThreadAuthenticator(context) + auth.start() + auth.allow('127.0.0.1') + + # Tell authenticator to use the certificate in a directory + auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY) + public, secret = get_frontend_certificates() + socket.curve_publickey = public + socket.curve_secretkey = secret + socket.curve_server = True # must come before bind socket.bind(self.BIND_ADDR) diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py index 18e70743..b2674330 100644 --- a/src/leap/bitmask/backend/utils.py +++ b/src/leap/bitmask/backend/utils.py @@ -22,20 +22,63 @@ import os import shutil import stat -import zmq.auth +import zmq +try: + import zmq.auth +except ImportError: + pass + +from leap.bitmask.config import flags from leap.bitmask.util import get_path_prefix from leap.common.files import mkdir_p +from leap.common.check import leap_assert logger = logging.getLogger(__name__) KEYS_DIR = os.path.join(get_path_prefix(), 'leap', 'zmq_certificates') +def _zmq_has_curve(): + """ + Return whether the current ZMQ has support for auth and CurveZMQ security. + + :rtype: bool + + Version notes: + `zmq.curve_keypair()` is new in version 14.0, new in version libzmq-4.0. + Requires libzmq (>= 4.0) to have been linked with libsodium. + `zmq.auth` module is new in version 14.1 + `zmq.has()` is new in version 14.1, new in version libzmq-4.1. + """ + zmq_version = zmq.zmq_version_info() + pyzmq_version = zmq.pyzmq_version_info() + + if pyzmq_version >= (14, 1, 0) and zmq_version >= (4, 1): + return zmq.has('curve') + + if pyzmq_version < (14, 1, 0): + return False + + if zmq_version < (4, 0): + # security is new in libzmq 4.0 + return False + + try: + zmq.curve_keypair() + except zmq.error.ZMQError: + # security requires libzmq to be linked against libsodium + return False + + return True + + def generate_zmq_certificates(): """ Generate client and server CURVE certificate files. """ + leap_assert(flags.ZMQ_HAS_CURVE, "CurveZMQ not supported!") + # Create directory for certificates, remove old content if necessary if os.path.exists(KEYS_DIR): shutil.rmtree(KEYS_DIR) @@ -53,6 +96,8 @@ def get_frontend_certificates(): """ Return the frontend's public and secret certificates. """ + leap_assert(flags.ZMQ_HAS_CURVE, "CurveZMQ not supported!") + frontend_secret_file = os.path.join(KEYS_DIR, "frontend.key_secret") public, secret = zmq.auth.load_certificate(frontend_secret_file) return public, secret @@ -62,6 +107,8 @@ def get_backend_certificates(base_dir='.'): """ Return the backend's public and secret certificates. """ + leap_assert(flags.ZMQ_HAS_CURVE, "CurveZMQ not supported!") + backend_secret_file = os.path.join(KEYS_DIR, "backend.key_secret") public, secret = zmq.auth.load_certificate(backend_secret_file) return public, secret @@ -84,5 +131,8 @@ def generate_zmq_certificates_if_needed(): Generate the needed ZMQ certificates for backend/frontend communication if needed. """ - if not _certificates_exist(): + if flags.ZMQ_HAS_CURVE and not _certificates_exist(): generate_zmq_certificates() + + +flags.ZMQ_HAS_CURVE = _zmq_has_curve() diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 286b04f7..3e88a95a 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -23,6 +23,7 @@ import signal from leap.bitmask.backend.leapbackend import LeapBackend from leap.bitmask.backend.utils import generate_zmq_certificates +from leap.bitmask.config import flags from leap.bitmask.logs.utils import create_logger from leap.bitmask.util import dict_to_flags @@ -57,7 +58,8 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): """ # The backend is the one who always creates the certificates. Either if it # is run separately or in a process in the same app as the frontend. - generate_zmq_certificates() + if flags.ZMQ_HAS_CURVE: + generate_zmq_certificates() # ignore SIGINT since app.py takes care of signaling SIGTERM to us. signal.signal(signal.SIGINT, signal.SIG_IGN) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 6b70659d..cdde1971 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -55,3 +55,6 @@ OPENVPN_VERBOSITY = 1 # Skip the checks in the wizard, use for testing purposes only! SKIP_WIZARD_CHECKS = False + +# This flag tells us whether the current pyzmq supports using CurveZMQ or not. +ZMQ_HAS_CURVE = None -- cgit v1.2.3