Skip to content

Commit 9a8f13f

Browse files
willemdjdrozzy
authored andcommitted
Change default: don't support CURVE security model by default. (#8)
* Add curve security (work in progress). * Add authentication, tests, docs, improve build process. * Fix compilation on Linux. * Change default: do not support CURVE by default.
1 parent 3a6ce8f commit 9a8f13f

40 files changed

+2819
-326
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
rebar.lock
12
.DS_Store
23
.rebar3
34
_*

README.md

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ Features
2222
5. Exclusive Pair Pattern
2323
6. Version Negotiation
2424
7. NULL Security Mechanism
25-
8. Error Handling
26-
9. Framing
27-
10. Socket-Type Property & Identity Property
28-
11. Backwards Interoperability with ZMTP 3.0
25+
8. CURVE Security Mechanism
26+
9. Error Handling
27+
10. Framing
28+
11. Socket-Type Property & Identity Property
29+
12. Backwards Interoperability with ZMTP 3.0
2930

3031

3132
Install
@@ -55,6 +56,37 @@ Build
5556
$ rebar3 compile
5657
```
5758

59+
By default, this will try to build a version of the application that
60+
does not include support for the CURVE security model.
61+
62+
The environment variable `CHUMAK_CURVE_LIB` can be used to specify a
63+
NIF that implements the encrytion fucntions that are required to support
64+
the CURVE security model.
65+
66+
The following values for `CHUMAK_CURVE_LIB` are supported:
67+
68+
- nacerl - this is the minimal variant using the tweetnacl C library. By
69+
default it is fetched and built from https://github.com/willemdj/NaCerl.
70+
71+
Compilation of nacerl requires gcc and make. Since these tools
72+
may not be available on windows systems, a check on the
73+
availability of these tools will be done. If they are not
74+
available the dependency will not be fetched and there will be
75+
no support for the CURVE security model.
76+
77+
- nacl - this is similar to nacerl, but it depends on libsodium. The
78+
repository for this is https://github.com/tonyg/erlang-nacl. The
79+
the build process for Chumak will not automatically fetch and
80+
build it, but if `CHUMAK_CURVE_LIB` is set to "nacl", it will be
81+
assumed that this library is available and it will be used.
82+
83+
- enacl - this also depends on libsodium, but it also requires
84+
an Erlang VM that supports dirty schedulers. The repository is
85+
https://github.com/jlouis/enacl. The build process for
86+
Chumak will not automatically fetch and build it, but if
87+
`CHUMAK_CURVE_LIB` is set to "enacl", it will be assumed that
88+
this library is available and it will be used.
89+
5890
Test
5991
----
6092
```
@@ -100,10 +132,6 @@ FAQ
100132
Please see [Contributing](CONTRIBUTING.md) for details.
101133

102134

103-
Future work
104-
------------
105-
1. CurveZMQ - add security, with which chumak is compatible.
106-
107135
License
108136
--------
109137
This project is licensed under Mozilla Public License Version 2.0.
@@ -114,4 +142,4 @@ Etymology
114142
From [Wikipedia](https://en.wikipedia.org/wiki/Chumak):
115143

116144
>Chumak (Ukrainian: чумак) is a historic occupation on the territory of the modern Ukraine
117-
>as merchants or traders, primarily known for the trade in salt.
145+
>as merchants or traders, primarily known for the trade in salt.

include/chumak.hrl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@
1313
sub | xsub |
1414
push | pull |
1515
pair.
16+
17+
-type z85_key() :: string().
18+
19+
-type socket_option() :: curve_server | %% true | false
20+
curve_publickey | %% binary()
21+
curve_secretkey | %% binary()
22+
curve_serverkey | %% binary()
23+
curve_clientkeys. %% [binary() | z85_key()]
24+
25+
-type security_mechanism() :: null |
26+
curve.
27+
1628
-define(SOCKET_OPTS(Opts), lists:append([binary, {active, false}, {reuseaddr, true}], Opts)).
1729
-define(GREETINGS_TIMEOUT, 1000).
1830
-define(RECONNECT_TIMEOUT, 2000).

python-test/client.key

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# **** Generated on 2017-01-04 08:09:07.403000 by pyzmq ****
2+
# ZeroMQ CURVE **Secret** Certificate
3+
# DO NOT PROVIDE THIS FILE TO OTHER USERS nor change its permissions.
4+
5+
metadata
6+
curve
7+
public-key = "T-jI=s%kd#Dm!5bD9AO-Gqu(:jruz2<[k]3Dzz2K"
8+
secret-key = "7u1$v?jp[jHd=wn!x?h@k-wNpBr!1!FM8LhOv+NR"

python-test/iron_house_client.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env python
2+
3+
'''
4+
Stonehouse uses the "CURVE" security mechanism.
5+
This gives us strong encryption on data, and (as far as we know) unbreakable
6+
authentication. Stonehouse is the minimum you would use over public networks,
7+
and assures clients that they are speaking to an authentic server, while
8+
allowing any client to connect.
9+
Author: Chris Laws
10+
11+
Modified by Willem de Jong - only start the Python client, the server is the
12+
Chumak Erlang implementation.
13+
14+
To run, start an Erlang shell and issue the following commands:
15+
cd("python-test"),
16+
{ok, ServerKeys} = chumak_cert:read("server.key"),
17+
SK = proplists:get_value(secret_key, ServerKeys),
18+
{ok, ClientKeys} = chumak_cert:read("client.key"),
19+
CK = proplists:get_value(public_key, ClientKeys),
20+
application:start(chumak),
21+
{ok, Socket} = chumak:socket(push),
22+
ok = chumak:set_socket_option(Socket, curve_server, true),
23+
ok = chumak:set_socket_option(Socket, curve_secretkey, SK),
24+
ok = chumak:set_socket_option(Socket, curve_clientkeys, [CK]),
25+
{ok, _BindProc} = chumak:bind(Socket, tcp, "127.0.0.1", 9000).
26+
timer:sleep(1000),
27+
chumak:send(Socket, <<"Hello">>),
28+
halt().
29+
30+
'''
31+
32+
import logging
33+
import os
34+
import sys
35+
import time
36+
37+
import zmq
38+
import zmq.auth
39+
from zmq.auth.thread import ThreadAuthenticator
40+
41+
42+
def run():
43+
''' Run Ironhouse example '''
44+
45+
# These directories are generated by the generate_certificates script
46+
keys_dir = os.path.dirname(__file__)
47+
48+
ctx = zmq.Context.instance()
49+
50+
# # Start an authenticator for this context.
51+
# auth = ThreadAuthenticator(ctx)
52+
# auth.start()
53+
# auth.allow('127.0.0.1')
54+
# # Tell the authenticator how to handle CURVE requests
55+
# auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)
56+
57+
client = ctx.socket(zmq.PULL)
58+
# We need two certificates, one for the client and one for
59+
# the server. The client must know the server's public key
60+
# to make a CURVE connection.
61+
client_secret_file = os.path.join(keys_dir, "client.key")
62+
client_public, client_secret = zmq.auth.load_certificate(client_secret_file)
63+
client.curve_secretkey = client_secret
64+
client.curve_publickey = client_public
65+
66+
# The client must know the server's public key to make a CURVE connection.
67+
server_public_file = os.path.join(keys_dir, "server.key")
68+
server_public, _ = zmq.auth.load_certificate(server_public_file)
69+
client.curve_serverkey = server_public
70+
71+
client.connect('tcp://127.0.0.1:9000')
72+
73+
if client.poll(100000):
74+
msg = client.recv()
75+
if msg == b"Hello":
76+
logging.info("Ironhouse test OK")
77+
else:
78+
logging.error("Ironhouse test FAIL")
79+
80+
# stop auth thread
81+
# auth.stop()
82+
83+
if __name__ == '__main__':
84+
if zmq.zmq_version_info() < (4,0):
85+
raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version()))
86+
87+
if '-v' in sys.argv:
88+
level = logging.DEBUG
89+
else:
90+
level = logging.INFO
91+
92+
logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")
93+
94+
run()

python-test/iron_house_server.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python
2+
3+
'''
4+
Ironhouse extends Stonehouse with client public key authentication.
5+
6+
This is the strongest security model we have today, protecting against every
7+
attack we know about, except end-point attacks (where an attacker plants
8+
spyware on a machine to capture data before it's encrypted, or after it's
9+
decrypted).
10+
11+
Author: Chris Laws
12+
13+
Modified by Willem de Jong - only start the Python server, the client is the
14+
Chumak Erlang implementation.
15+
16+
To run from an erlang shell:
17+
cd("python-test"),
18+
{ok, ServerKeys} = chumak_cert:read("server.key"),
19+
SPK = proplists:get_value(public_key, ServerKeys),
20+
{ok, ClientKeys} = chumak_cert:read("client.key"),
21+
CSK = proplists:get_value(secret_key, ClientKeys),
22+
CPK = proplists:get_value(public_key, ClientKeys),
23+
application:start(chumak),
24+
{ok, Socket} = chumak:socket(pull),
25+
ok = chumak:set_socket_option(Socket, curve_secretkey, CSK),
26+
ok = chumak:set_socket_option(Socket, curve_publickey, CPK),
27+
ok = chumak:set_socket_option(Socket, curve_serverkey, SPK),
28+
{ok, _} = chumak:connect(Socket, tcp, "127.0.0.1", 9000),
29+
{ok, Message} = chumak:recv(Socket),
30+
io:format("received: ~p~n", [Message]),
31+
halt().
32+
33+
'''
34+
35+
import logging
36+
import os
37+
import sys
38+
import time
39+
40+
import zmq
41+
import zmq.auth
42+
from zmq.auth.thread import ThreadAuthenticator
43+
44+
45+
def run():
46+
''' Run Ironhouse example '''
47+
48+
# These directories are generated by the generate_certificates script
49+
keys_dir = os.path.dirname(__file__)
50+
51+
ctx = zmq.Context.instance()
52+
53+
# Start an authenticator for this context.
54+
auth = ThreadAuthenticator(ctx)
55+
auth.start()
56+
auth.allow('127.0.0.1')
57+
# Tell authenticator to use the certificate in a directory
58+
print(keys_dir)
59+
#auth.configure_curve(domain='*', location=keys_dir)
60+
auth.configure_curve(domain='*', location=".")
61+
62+
server_key_file = os.path.join(keys_dir, "server.key")
63+
server_public, server_secret = zmq.auth.load_certificate(server_key_file)
64+
65+
server = ctx.socket(zmq.PUSH)
66+
server.curve_secretkey = server_secret
67+
server.curve_publickey = server_public
68+
server.curve_server = True # must come before bind
69+
server.bind('tcp://*:9000')
70+
71+
server.send(b"Hello")
72+
# Make sure that there is time to finish the handshake
73+
time.sleep(2)
74+
75+
# stop auth thread
76+
auth.stop()
77+
78+
if __name__ == '__main__':
79+
if zmq.zmq_version_info() < (4,0):
80+
raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version()))
81+
82+
if '-v' in sys.argv:
83+
level = logging.DEBUG
84+
else:
85+
level = logging.INFO
86+
87+
logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")
88+
89+
run()

python-test/server.key

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# **** Generated on 2017-01-04 08:09:07.380000 by pyzmq ****
2+
# ZeroMQ CURVE **Secret** Certificate
3+
# DO NOT PROVIDE THIS FILE TO OTHER USERS nor change its permissions.
4+
5+
metadata
6+
curve
7+
public-key = "O#t%6HF5/T^CEh99=L$cnmF%:k2Iv#ncM=w01w@q"
8+
secret-key = "7U(aroF:XLH)>{^k&m#-F*p!Nyi4wSy2/PKq1TIB"

python-test/stone_house_client.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env python
2+
3+
'''
4+
Stonehouse uses the "CURVE" security mechanism.
5+
This gives us strong encryption on data, and (as far as we know) unbreakable
6+
authentication. Stonehouse is the minimum you would use over public networks,
7+
and assures clients that they are speaking to an authentic server, while
8+
allowing any client to connect.
9+
Author: Chris Laws
10+
11+
Modified by Willem de Jong - only start the Python client, the server is the
12+
Chumak Erlang implementation.
13+
14+
To run, start an Erlang shell and issue the following commands:
15+
cd("python-test"),
16+
{ok, ServerKeys} = chumak_cert:read("server.key"),
17+
SK = proplists:get_value(secret_key, ServerKeys),
18+
application:start(chumak),
19+
{ok, Socket} = chumak:socket(push),
20+
ok = chumak:set_socket_option(Socket, curve_server, true),
21+
ok = chumak:set_socket_option(Socket, curve_secretkey, SK),
22+
{ok, _BindProc} = chumak:bind(Socket, tcp, "127.0.0.1", 9000).
23+
timer:sleep(1000),
24+
chumak:send(Socket, <<"Hello">>),
25+
halt().
26+
27+
'''
28+
29+
import logging
30+
import os
31+
import sys
32+
import time
33+
34+
import zmq
35+
import zmq.auth
36+
from zmq.auth.thread import ThreadAuthenticator
37+
38+
39+
def run():
40+
''' Run Stonehouse example '''
41+
42+
# These directories are generated by the generate_certificates script
43+
keys_dir = os.path.dirname(__file__)
44+
45+
ctx = zmq.Context.instance()
46+
47+
# Start an authenticator for this context.
48+
auth = ThreadAuthenticator(ctx)
49+
auth.start()
50+
auth.allow('127.0.0.1')
51+
# Tell the authenticator how to handle CURVE requests
52+
auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)
53+
54+
client = ctx.socket(zmq.PULL)
55+
# We need two certificates, one for the client and one for
56+
# the server. The client must know the server's public key
57+
# to make a CURVE connection.
58+
client_secret_file = os.path.join(keys_dir, "client.key")
59+
client_public, client_secret = zmq.auth.load_certificate(client_secret_file)
60+
client.curve_secretkey = client_secret
61+
client.curve_publickey = client_public
62+
63+
# The client must know the server's public key to make a CURVE connection.
64+
server_public_file = os.path.join(keys_dir, "server.key")
65+
server_public, _ = zmq.auth.load_certificate(server_public_file)
66+
client.curve_serverkey = server_public
67+
68+
client.connect('tcp://127.0.0.1:9000')
69+
70+
if client.poll(100000):
71+
msg = client.recv()
72+
if msg == b"Hello":
73+
logging.info("Stonehouse test OK")
74+
else:
75+
logging.error("Stonehouse test FAIL")
76+
77+
# stop auth thread
78+
auth.stop()
79+
80+
if __name__ == '__main__':
81+
if zmq.zmq_version_info() < (4,0):
82+
raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version()))
83+
84+
if '-v' in sys.argv:
85+
level = logging.DEBUG
86+
else:
87+
level = logging.INFO
88+
89+
logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")
90+
91+
run()

0 commit comments

Comments
 (0)