0a781dee44b3027e171bb718fc5063c909a43f3a
[leap_pycommon.git] / src / leap / common / zmq_utils.py
1 # -*- coding: utf-8 -*-
2 # zmq.py
3 # Copyright (C) 2013, 2014 LEAP
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 """
18 Utilities to handle ZMQ certificates.
19 """
20 import os
21 import logging
22 import stat
23 import shutil
24
25 import zmq
26
27 try:
28     import zmq.auth
29 except ImportError:
30     pass
31
32 from leap.common.files import mkdir_p
33 from leap.common.check import leap_assert
34
35 logger = logging.getLogger(__name__)
36
37
38 KEYS_PREFIX = "zmq_certificates"
39 PUBLIC_KEYS_PREFIX = os.path.join(KEYS_PREFIX, "public_keys")
40 PRIVATE_KEYS_PREFIX = os.path.join(KEYS_PREFIX, "private_keys")
41
42
43 def zmq_has_curve():
44     """
45     Return whether the current ZMQ has support for auth and CurveZMQ security.
46
47     :rtype: bool
48
49      Version notes:
50        `zmq.curve_keypair()` is new in version 14.0, new in version libzmq-4.0.
51             Requires libzmq (>= 4.0) to have been linked with libsodium.
52        `zmq.auth` module is new in version 14.1
53        `zmq.has()` is new in version 14.1, new in version libzmq-4.1.
54     """
55     zmq_version = zmq.zmq_version_info()
56     pyzmq_version = zmq.pyzmq_version_info()
57
58     if pyzmq_version >= (14, 1, 0) and zmq_version >= (4, 1):
59         return zmq.has('curve')
60
61     if pyzmq_version < (14, 1, 0):
62         return False
63
64     if zmq_version < (4, 0):
65         # security is new in libzmq 4.0
66         return False
67
68     try:
69         zmq.curve_keypair()
70     except zmq.error.ZMQError:
71         # security requires libzmq to be linked against libsodium
72         return False
73
74     return True
75
76
77 def assert_zmq_has_curve():
78     leap_assert(zmq_has_curve, "CurveZMQ not supported!")
79
80
81 def maybe_create_and_get_certificates(basedir, name):
82     """
83     Generate the needed ZMQ certificates for backend/frontend communication if
84     needed.
85     """
86     assert_zmq_has_curve()
87     private_keys_dir = os.path.join(basedir, PRIVATE_KEYS_PREFIX)
88     private_key = os.path.join(
89         private_keys_dir, name + ".key_secret")
90     if not os.path.isfile(private_key):
91         mkdir_p(private_keys_dir)
92         zmq.auth.create_certificates(private_keys_dir, name)
93         # set permissions to: 0700 (U:rwx G:--- O:---)
94         os.chmod(private_key, stat.S_IRUSR | stat.S_IWUSR)
95         # move public key to public keys directory
96         public_keys_dir = os.path.join(basedir, PUBLIC_KEYS_PREFIX)
97         old_public_key = os.path.join(
98             private_keys_dir, name + ".key")
99         new_public_key = os.path.join(
100             public_keys_dir, name + ".key")
101         mkdir_p(public_keys_dir)
102         shutil.move(old_public_key, new_public_key)
103     return zmq.auth.load_certificate(private_key)