Skip to content

Commit

Permalink
Change default: don't support CURVE security model by default. (#8)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
willemdj authored and drozzy committed Feb 26, 2017
1 parent 3a6ce8f commit 9a8f13f
Show file tree
Hide file tree
Showing 40 changed files with 2,819 additions and 326 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
rebar.lock
.DS_Store
.rebar3
_*
Expand Down
46 changes: 37 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ Features
5. Exclusive Pair Pattern
6. Version Negotiation
7. NULL Security Mechanism
8. Error Handling
9. Framing
10. Socket-Type Property & Identity Property
11. Backwards Interoperability with ZMTP 3.0
8. CURVE Security Mechanism
9. Error Handling
10. Framing
11. Socket-Type Property & Identity Property
12. Backwards Interoperability with ZMTP 3.0


Install
Expand Down Expand Up @@ -55,6 +56,37 @@ Build
$ rebar3 compile
```

By default, this will try to build a version of the application that
does not include support for the CURVE security model.

The environment variable `CHUMAK_CURVE_LIB` can be used to specify a
NIF that implements the encrytion fucntions that are required to support
the CURVE security model.

The following values for `CHUMAK_CURVE_LIB` are supported:

- nacerl - this is the minimal variant using the tweetnacl C library. By
default it is fetched and built from https://github.com/willemdj/NaCerl.

Compilation of nacerl requires gcc and make. Since these tools
may not be available on windows systems, a check on the
availability of these tools will be done. If they are not
available the dependency will not be fetched and there will be
no support for the CURVE security model.

- nacl - this is similar to nacerl, but it depends on libsodium. The
repository for this is https://github.com/tonyg/erlang-nacl. The
the build process for Chumak will not automatically fetch and
build it, but if `CHUMAK_CURVE_LIB` is set to "nacl", it will be
assumed that this library is available and it will be used.

- enacl - this also depends on libsodium, but it also requires
an Erlang VM that supports dirty schedulers. The repository is
https://github.com/jlouis/enacl. The build process for
Chumak will not automatically fetch and build it, but if
`CHUMAK_CURVE_LIB` is set to "enacl", it will be assumed that
this library is available and it will be used.

Test
----
```
Expand Down Expand Up @@ -100,10 +132,6 @@ FAQ
Please see [Contributing](CONTRIBUTING.md) for details.


Future work
------------
1. CurveZMQ - add security, with which chumak is compatible.

License
--------
This project is licensed under Mozilla Public License Version 2.0.
Expand All @@ -114,4 +142,4 @@ Etymology
From [Wikipedia](https://en.wikipedia.org/wiki/Chumak):

>Chumak (Ukrainian: чумак) is a historic occupation on the territory of the modern Ukraine
>as merchants or traders, primarily known for the trade in salt.
>as merchants or traders, primarily known for the trade in salt.
12 changes: 12 additions & 0 deletions include/chumak.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
sub | xsub |
push | pull |
pair.

-type z85_key() :: string().

-type socket_option() :: curve_server | %% true | false
curve_publickey | %% binary()
curve_secretkey | %% binary()
curve_serverkey | %% binary()
curve_clientkeys. %% [binary() | z85_key()]

-type security_mechanism() :: null |
curve.

-define(SOCKET_OPTS(Opts), lists:append([binary, {active, false}, {reuseaddr, true}], Opts)).
-define(GREETINGS_TIMEOUT, 1000).
-define(RECONNECT_TIMEOUT, 2000).
8 changes: 8 additions & 0 deletions python-test/client.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# **** Generated on 2017-01-04 08:09:07.403000 by pyzmq ****
# ZeroMQ CURVE **Secret** Certificate
# DO NOT PROVIDE THIS FILE TO OTHER USERS nor change its permissions.

metadata
curve
public-key = "T-jI=s%kd#Dm!5bD9AO-Gqu(:jruz2<[k]3Dzz2K"
secret-key = "7u1$v?jp[jHd=wn!x?h@k-wNpBr!1!FM8LhOv+NR"
94 changes: 94 additions & 0 deletions python-test/iron_house_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python

'''
Stonehouse uses the "CURVE" security mechanism.
This gives us strong encryption on data, and (as far as we know) unbreakable
authentication. Stonehouse is the minimum you would use over public networks,
and assures clients that they are speaking to an authentic server, while
allowing any client to connect.
Author: Chris Laws
Modified by Willem de Jong - only start the Python client, the server is the
Chumak Erlang implementation.
To run, start an Erlang shell and issue the following commands:
cd("python-test"),
{ok, ServerKeys} = chumak_cert:read("server.key"),
SK = proplists:get_value(secret_key, ServerKeys),
{ok, ClientKeys} = chumak_cert:read("client.key"),
CK = proplists:get_value(public_key, ClientKeys),
application:start(chumak),
{ok, Socket} = chumak:socket(push),
ok = chumak:set_socket_option(Socket, curve_server, true),
ok = chumak:set_socket_option(Socket, curve_secretkey, SK),
ok = chumak:set_socket_option(Socket, curve_clientkeys, [CK]),
{ok, _BindProc} = chumak:bind(Socket, tcp, "127.0.0.1", 9000).
timer:sleep(1000),
chumak:send(Socket, <<"Hello">>),
halt().
'''

import logging
import os
import sys
import time

import zmq
import zmq.auth
from zmq.auth.thread import ThreadAuthenticator


def run():
''' Run Ironhouse example '''

# These directories are generated by the generate_certificates script
keys_dir = os.path.dirname(__file__)

ctx = zmq.Context.instance()

# # Start an authenticator for this context.
# auth = ThreadAuthenticator(ctx)
# auth.start()
# auth.allow('127.0.0.1')
# # Tell the authenticator how to handle CURVE requests
# auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)

client = ctx.socket(zmq.PULL)
# We need two certificates, one for the client and one for
# the server. The client must know the server's public key
# to make a CURVE connection.
client_secret_file = os.path.join(keys_dir, "client.key")
client_public, client_secret = zmq.auth.load_certificate(client_secret_file)
client.curve_secretkey = client_secret
client.curve_publickey = client_public

# The client must know the server's public key to make a CURVE connection.
server_public_file = os.path.join(keys_dir, "server.key")
server_public, _ = zmq.auth.load_certificate(server_public_file)
client.curve_serverkey = server_public

client.connect('tcp://127.0.0.1:9000')

if client.poll(100000):
msg = client.recv()
if msg == b"Hello":
logging.info("Ironhouse test OK")
else:
logging.error("Ironhouse test FAIL")

# stop auth thread
# auth.stop()

if __name__ == '__main__':
if zmq.zmq_version_info() < (4,0):
raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version()))

if '-v' in sys.argv:
level = logging.DEBUG
else:
level = logging.INFO

logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")

run()
89 changes: 89 additions & 0 deletions python-test/iron_house_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python

'''
Ironhouse extends Stonehouse with client public key authentication.
This is the strongest security model we have today, protecting against every
attack we know about, except end-point attacks (where an attacker plants
spyware on a machine to capture data before it's encrypted, or after it's
decrypted).
Author: Chris Laws
Modified by Willem de Jong - only start the Python server, the client is the
Chumak Erlang implementation.
To run from an erlang shell:
cd("python-test"),
{ok, ServerKeys} = chumak_cert:read("server.key"),
SPK = proplists:get_value(public_key, ServerKeys),
{ok, ClientKeys} = chumak_cert:read("client.key"),
CSK = proplists:get_value(secret_key, ClientKeys),
CPK = proplists:get_value(public_key, ClientKeys),
application:start(chumak),
{ok, Socket} = chumak:socket(pull),
ok = chumak:set_socket_option(Socket, curve_secretkey, CSK),
ok = chumak:set_socket_option(Socket, curve_publickey, CPK),
ok = chumak:set_socket_option(Socket, curve_serverkey, SPK),
{ok, _} = chumak:connect(Socket, tcp, "127.0.0.1", 9000),
{ok, Message} = chumak:recv(Socket),
io:format("received: ~p~n", [Message]),
halt().
'''

import logging
import os
import sys
import time

import zmq
import zmq.auth
from zmq.auth.thread import ThreadAuthenticator


def run():
''' Run Ironhouse example '''

# These directories are generated by the generate_certificates script
keys_dir = os.path.dirname(__file__)

ctx = zmq.Context.instance()

# Start an authenticator for this context.
auth = ThreadAuthenticator(ctx)
auth.start()
auth.allow('127.0.0.1')
# Tell authenticator to use the certificate in a directory
print(keys_dir)
#auth.configure_curve(domain='*', location=keys_dir)
auth.configure_curve(domain='*', location=".")

server_key_file = os.path.join(keys_dir, "server.key")
server_public, server_secret = zmq.auth.load_certificate(server_key_file)

server = ctx.socket(zmq.PUSH)
server.curve_secretkey = server_secret
server.curve_publickey = server_public
server.curve_server = True # must come before bind
server.bind('tcp://*:9000')

server.send(b"Hello")
# Make sure that there is time to finish the handshake
time.sleep(2)

# stop auth thread
auth.stop()

if __name__ == '__main__':
if zmq.zmq_version_info() < (4,0):
raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version()))

if '-v' in sys.argv:
level = logging.DEBUG
else:
level = logging.INFO

logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")

run()
8 changes: 8 additions & 0 deletions python-test/server.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# **** Generated on 2017-01-04 08:09:07.380000 by pyzmq ****
# ZeroMQ CURVE **Secret** Certificate
# DO NOT PROVIDE THIS FILE TO OTHER USERS nor change its permissions.

metadata
curve
public-key = "O#t%6HF5/T^CEh99=L$cnmF%:k2Iv#ncM=w01w@q"
secret-key = "7U(aroF:XLH)>{^k&m#-F*p!Nyi4wSy2/PKq1TIB"
91 changes: 91 additions & 0 deletions python-test/stone_house_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python

'''
Stonehouse uses the "CURVE" security mechanism.
This gives us strong encryption on data, and (as far as we know) unbreakable
authentication. Stonehouse is the minimum you would use over public networks,
and assures clients that they are speaking to an authentic server, while
allowing any client to connect.
Author: Chris Laws
Modified by Willem de Jong - only start the Python client, the server is the
Chumak Erlang implementation.
To run, start an Erlang shell and issue the following commands:
cd("python-test"),
{ok, ServerKeys} = chumak_cert:read("server.key"),
SK = proplists:get_value(secret_key, ServerKeys),
application:start(chumak),
{ok, Socket} = chumak:socket(push),
ok = chumak:set_socket_option(Socket, curve_server, true),
ok = chumak:set_socket_option(Socket, curve_secretkey, SK),
{ok, _BindProc} = chumak:bind(Socket, tcp, "127.0.0.1", 9000).
timer:sleep(1000),
chumak:send(Socket, <<"Hello">>),
halt().
'''

import logging
import os
import sys
import time

import zmq
import zmq.auth
from zmq.auth.thread import ThreadAuthenticator


def run():
''' Run Stonehouse example '''

# These directories are generated by the generate_certificates script
keys_dir = os.path.dirname(__file__)

ctx = zmq.Context.instance()

# Start an authenticator for this context.
auth = ThreadAuthenticator(ctx)
auth.start()
auth.allow('127.0.0.1')
# Tell the authenticator how to handle CURVE requests
auth.configure_curve(domain='*', location=zmq.auth.CURVE_ALLOW_ANY)

client = ctx.socket(zmq.PULL)
# We need two certificates, one for the client and one for
# the server. The client must know the server's public key
# to make a CURVE connection.
client_secret_file = os.path.join(keys_dir, "client.key")
client_public, client_secret = zmq.auth.load_certificate(client_secret_file)
client.curve_secretkey = client_secret
client.curve_publickey = client_public

# The client must know the server's public key to make a CURVE connection.
server_public_file = os.path.join(keys_dir, "server.key")
server_public, _ = zmq.auth.load_certificate(server_public_file)
client.curve_serverkey = server_public

client.connect('tcp://127.0.0.1:9000')

if client.poll(100000):
msg = client.recv()
if msg == b"Hello":
logging.info("Stonehouse test OK")
else:
logging.error("Stonehouse test FAIL")

# stop auth thread
auth.stop()

if __name__ == '__main__':
if zmq.zmq_version_info() < (4,0):
raise RuntimeError("Security is not supported in libzmq version < 4.0. libzmq version {0}".format(zmq.zmq_version()))

if '-v' in sys.argv:
level = logging.DEBUG
else:
level = logging.INFO

logging.basicConfig(level=level, format="[%(levelname)s] %(message)s")

run()
Loading

0 comments on commit 9a8f13f

Please sign in to comment.