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 From e3ddc783ca9fb21105845d275a4b38ad6b2cd3e2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 23 Feb 2015 11:54:16 -0400 Subject: Use unix sockets for the fallback, and set permissions ... on them, to user-writeable only. I think we could consider ipc for the curve-case too, at least for the platforms that support them. --- src/leap/bitmask/backend/backend.py | 14 ++++++++++---- src/leap/bitmask/backend/backend_proxy.py | 7 +++++-- src/leap/bitmask/backend/signaler.py | 7 +++++-- src/leap/bitmask/backend/signaler_qt.py | 12 ++++++++++-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py index 32f5c953..cff731ba 100644 --- a/src/leap/bitmask/backend/backend.py +++ b/src/leap/bitmask/backend/backend.py @@ -20,6 +20,7 @@ # TODO use txzmq bindings instead. import json +import os import threading import time @@ -47,12 +48,15 @@ class Backend(object): Backend server. Receives signals from backend_proxy and emit signals if needed. """ - # XXX this should not be hardcoded. Make it configurable. - PORT = '5556' - # XXX we might want to make this configurable per-platform, # and use the most performant socket type on each one. - BIND_ADDR = "tcp://127.0.0.1:%s" % PORT + if flags.ZMQ_HAS_CURVE: + # XXX this should not be hardcoded. Make it configurable. + PORT = '5556' + BIND_ADDR = "tcp://127.0.0.1:%s" % PORT + else: + SOCKET_FILE = "/tmp/bitmask.socket.0" + BIND_ADDR = "ipc://%s" % SOCKET_FILE PING_INTERVAL = 2 # secs @@ -92,6 +96,8 @@ class Backend(object): socket.curve_server = True # must come before bind socket.bind(self.BIND_ADDR) + if not flags.ZMQ_HAS_CURVE: + os.chmod(self.SOCKET_FILE, 0600) self._zmq_socket = socket diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 06e6d840..b2f79a70 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -42,8 +42,11 @@ class BackendProxy(object): to the backend. """ - PORT = '5556' - SERVER = "tcp://localhost:%s" % PORT + if flags.ZMQ_HAS_CURVE: + PORT = '5556' + SERVER = "tcp://localhost:%s" % PORT + else: + SERVER = "ipc:///tmp/bitmask.socket.0" POLL_TIMEOUT = 4000 # ms POLL_TRIES = 3 diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py index a8498d11..aec2f606 100644 --- a/src/leap/bitmask/backend/signaler.py +++ b/src/leap/bitmask/backend/signaler.py @@ -37,8 +37,11 @@ class Signaler(object): Signaler client. Receives signals from the backend and sends to the signaling server. """ - PORT = "5667" - SERVER = "tcp://localhost:%s" % PORT + if flags.ZMQ_HAS_CURVE: + PORT = "5667" + SERVER = "tcp://localhost:%s" % PORT + else: + SERVER = "ipc:///tmp/bitmask.socket.1" POLL_TIMEOUT = 2000 # ms POLL_TRIES = 500 diff --git a/src/leap/bitmask/backend/signaler_qt.py b/src/leap/bitmask/backend/signaler_qt.py index 94c24648..b7f48d21 100644 --- a/src/leap/bitmask/backend/signaler_qt.py +++ b/src/leap/bitmask/backend/signaler_qt.py @@ -18,6 +18,7 @@ Signaling server. Receives signals from the signaling client and emit Qt signals for the GUI. """ +import os import threading import time @@ -42,8 +43,12 @@ class SignalerQt(QtCore.QObject): Signaling server. Receives signals from the signaling client and emit Qt signals for the GUI. """ - PORT = "5667" - BIND_ADDR = "tcp://127.0.0.1:%s" % PORT + if flags.ZMQ_HAS_CURVE: + PORT = "5667" + BIND_ADDR = "tcp://127.0.0.1:%s" % PORT + else: + SOCKET_FILE = "/tmp/bitmask.socket.1" + BIND_ADDR = "ipc://%s" % SOCKET_FILE def __init__(self): QtCore.QObject.__init__(self) @@ -86,6 +91,9 @@ class SignalerQt(QtCore.QObject): socket.bind(self.BIND_ADDR) + if not flags.ZMQ_HAS_CURVE: + os.chmod(self.SOCKET_FILE, 0600) + while self._do_work.is_set(): # Wait for next request from client try: -- cgit v1.2.3 From 04be045242b0fd0c669610f54db7a9fdeb3b77f3 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 23 Feb 2015 15:36:38 -0300 Subject: Update changes file to reflect tcp->ipc change. --- changes/non-curve-zmq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/non-curve-zmq b/changes/non-curve-zmq index 8c1097ee..58737723 100644 --- a/changes/non-curve-zmq +++ b/changes/non-curve-zmq @@ -1 +1 @@ -- Gracefully fall back to plain ZMQ if CurveZMQ is not available (Feature #6646) +- Gracefully fall back to ZMQ ipc sockets with restricted access if CurveZMQ is not available (Feature #6646) -- cgit v1.2.3