summaryrefslogtreecommitdiff
path: root/examples/security
diff options
context:
space:
mode:
authorMicah Anderson <micah@riseup.net>2014-08-11 16:33:29 -0400
committerMicah Anderson <micah@riseup.net>2014-08-11 16:33:29 -0400
commitcce638a8adf4e045ca5505afea4bda57753c31dd (patch)
treeb5e139d3359ac5b8c7b1afa8acbb1b5b6051c626 /examples/security
initial import of debian package
Diffstat (limited to 'examples/security')
-rw-r--r--examples/security/generate_certificates.py49
-rw-r--r--examples/security/grasslands.py29
-rw-r--r--examples/security/ioloop-ironhouse.py114
-rw-r--r--examples/security/ironhouse.py93
-rw-r--r--examples/security/stonehouse.py93
-rw-r--r--examples/security/strawhouse.py94
-rw-r--r--examples/security/woodhouse.py90
7 files changed, 562 insertions, 0 deletions
diff --git a/examples/security/generate_certificates.py b/examples/security/generate_certificates.py
new file mode 100644
index 0000000..80db258
--- /dev/null
+++ b/examples/security/generate_certificates.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""
+Generate client and server CURVE certificate files then move them into the
+appropriate store directory, private_keys or public_keys. The certificates
+generated by this script are used by the stonehouse and ironhouse examples.
+
+In practice this would be done by hand or some out-of-band process.
+
+Author: Chris Laws
+"""
+
+import os
+import shutil
+import zmq.auth
+
+def generate_certificates(base_dir):
+ ''' Generate client and server CURVE certificate files'''
+ keys_dir = os.path.join(base_dir, 'certificates')
+ public_keys_dir = os.path.join(base_dir, 'public_keys')
+ secret_keys_dir = os.path.join(base_dir, 'private_keys')
+
+ # Create directories for certificates, remove old content if necessary
+ for d in [keys_dir, public_keys_dir, secret_keys_dir]:
+ if os.path.exists(d):
+ shutil.rmtree(d)
+ os.mkdir(d)
+
+ # create new keys in certificates dir
+ server_public_file, server_secret_file = zmq.auth.create_certificates(keys_dir, "server")
+ client_public_file, client_secret_file = zmq.auth.create_certificates(keys_dir, "client")
+
+ # move public keys to appropriate directory
+ for key_file in os.listdir(keys_dir):
+ if key_file.endswith(".key"):
+ shutil.move(os.path.join(keys_dir, key_file),
+ os.path.join(public_keys_dir, '.'))
+
+ # move secret keys to appropriate directory
+ for key_file in os.listdir(keys_dir):
+ if key_file.endswith(".key_secret"):
+ shutil.move(os.path.join(keys_dir, key_file),
+ os.path.join(secret_keys_dir, '.'))
+
+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()))
+
+ generate_certificates(os.path.dirname(__file__))
diff --git a/examples/security/grasslands.py b/examples/security/grasslands.py
new file mode 100644
index 0000000..cbd3ab9
--- /dev/null
+++ b/examples/security/grasslands.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+'''
+No protection at all.
+
+All connections are accepted, there is no authentication, and no privacy.
+
+This is how ZeroMQ always worked until we built security into the wire
+protocol in early 2013. Internally, it uses a security mechanism called
+"NULL".
+
+Author: Chris Laws
+'''
+
+import zmq
+
+
+ctx = zmq.Context().instance()
+
+server = ctx.socket(zmq.PUSH)
+server.bind('tcp://*:9000')
+
+client = ctx.socket(zmq.PULL)
+client.connect('tcp://127.0.0.1:9000')
+
+server.send(b"Hello")
+msg = client.recv()
+if msg == b"Hello":
+ print("Grasslands test OK")
diff --git a/examples/security/ioloop-ironhouse.py b/examples/security/ioloop-ironhouse.py
new file mode 100644
index 0000000..11fd400
--- /dev/null
+++ b/examples/security/ioloop-ironhouse.py
@@ -0,0 +1,114 @@
+#!/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).
+
+This example demonstrates using the IOLoopAuthenticator.
+
+Author: Chris Laws
+'''
+
+import logging
+import os
+import sys
+
+import zmq
+import zmq.auth
+from zmq.auth.ioloop import IOLoopAuthenticator
+from zmq.eventloop import ioloop, zmqstream
+
+def echo(server, msg):
+ logging.debug("server recvd %s", msg)
+ reply = msg + [b'World']
+ logging.debug("server sending %s", reply)
+ server.send_multipart(reply)
+
+def setup_server(server_secret_file, endpoint='tcp://127.0.0.1:9000'):
+ """setup a simple echo server with CURVE auth"""
+ server = zmq.Context.instance().socket(zmq.ROUTER)
+
+ server_public, server_secret = zmq.auth.load_certificate(server_secret_file)
+ server.curve_secretkey = server_secret
+ server.curve_publickey = server_public
+ server.curve_server = True # must come before bind
+ server.bind(endpoint)
+
+ server_stream = zmqstream.ZMQStream(server)
+ # simple echo
+ server_stream.on_recv_stream(echo)
+ return server_stream
+
+def client_msg_recvd(msg):
+ logging.debug("client recvd %s", msg)
+ logging.info("Ironhouse test OK")
+ # stop the loop when we get the reply
+ ioloop.IOLoop.instance().stop()
+
+def setup_client(client_secret_file, server_public_file, endpoint='tcp://127.0.0.1:9000'):
+ """setup a simple client with CURVE auth"""
+
+ client = zmq.Context.instance().socket(zmq.DEALER)
+
+ # 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_public, client_secret = zmq.auth.load_certificate(client_secret_file)
+ client.curve_secretkey = client_secret
+ client.curve_publickey = client_public
+
+ server_public, _ = zmq.auth.load_certificate(server_public_file)
+ # The client must know the server's public key to make a CURVE connection.
+ client.curve_serverkey = server_public
+ client.connect(endpoint)
+
+ client_stream = zmqstream.ZMQStream(client)
+ client_stream.on_recv(client_msg_recvd)
+ return client_stream
+
+
+def run():
+ '''Run Ironhouse example'''
+
+ # These direcotries are generated by the generate_certificates script
+ base_dir = os.path.dirname(__file__)
+ keys_dir = os.path.join(base_dir, 'certificates')
+ public_keys_dir = os.path.join(base_dir, 'public_keys')
+ secret_keys_dir = os.path.join(base_dir, 'private_keys')
+
+ if not (os.path.exists(keys_dir) and os.path.exists(keys_dir) and os.path.exists(keys_dir)):
+ logging.critical("Certificates are missing - run generate_certificates script first")
+ sys.exit(1)
+
+ # Start an authenticator for this context.
+ auth = IOLoopAuthenticator()
+ auth.allow('127.0.0.1')
+ # Tell authenticator to use the certificate in a directory
+ auth.configure_curve(domain='*', location=public_keys_dir)
+
+ server_secret_file = os.path.join(secret_keys_dir, "server.key_secret")
+ server = setup_server(server_secret_file)
+ server_public_file = os.path.join(public_keys_dir, "server.key")
+ client_secret_file = os.path.join(secret_keys_dir, "client.key_secret")
+ client = setup_client(client_secret_file, server_public_file)
+ client.send(b'Hello')
+
+ auth.start()
+ ioloop.IOLoop.instance().start()
+
+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()
diff --git a/examples/security/ironhouse.py b/examples/security/ironhouse.py
new file mode 100644
index 0000000..5d9faf1
--- /dev/null
+++ b/examples/security/ironhouse.py
@@ -0,0 +1,93 @@
+#!/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
+'''
+
+import logging
+import os
+import sys
+
+import zmq
+import zmq.auth
+from zmq.auth.thread import ThreadAuthenticator
+
+
+def run():
+ ''' Run Ironhouse example '''
+
+ # These direcotries are generated by the generate_certificates script
+ base_dir = os.path.dirname(__file__)
+ keys_dir = os.path.join(base_dir, 'certificates')
+ public_keys_dir = os.path.join(base_dir, 'public_keys')
+ secret_keys_dir = os.path.join(base_dir, 'private_keys')
+
+ if not (os.path.exists(keys_dir) and os.path.exists(keys_dir) and os.path.exists(keys_dir)):
+ logging.critical("Certificates are missing - run generate_certificates.py script first")
+ sys.exit(1)
+
+ 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
+ auth.configure_curve(domain='*', location=public_keys_dir)
+
+ server = ctx.socket(zmq.PUSH)
+
+ server_secret_file = os.path.join(secret_keys_dir, "server.key_secret")
+ server_public, server_secret = zmq.auth.load_certificate(server_secret_file)
+ server.curve_secretkey = server_secret
+ server.curve_publickey = server_public
+ server.curve_server = True # must come before bind
+ server.bind('tcp://*:9000')
+
+ 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(secret_keys_dir, "client.key_secret")
+ client_public, client_secret = zmq.auth.load_certificate(client_secret_file)
+ client.curve_secretkey = client_secret
+ client.curve_publickey = client_public
+
+ server_public_file = os.path.join(public_keys_dir, "server.key")
+ server_public, _ = zmq.auth.load_certificate(server_public_file)
+ # The client must know the server's public key to make a CURVE connection.
+ client.curve_serverkey = server_public
+ client.connect('tcp://127.0.0.1:9000')
+
+ server.send(b"Hello")
+
+ if client.poll(1000):
+ 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()
diff --git a/examples/security/stonehouse.py b/examples/security/stonehouse.py
new file mode 100644
index 0000000..276e87a
--- /dev/null
+++ b/examples/security/stonehouse.py
@@ -0,0 +1,93 @@
+#!/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
+'''
+
+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
+ base_dir = os.path.dirname(__file__)
+ keys_dir = os.path.join(base_dir, 'certificates')
+ public_keys_dir = os.path.join(base_dir, 'public_keys')
+ secret_keys_dir = os.path.join(base_dir, 'private_keys')
+
+ if not (os.path.exists(keys_dir) and os.path.exists(keys_dir) and os.path.exists(keys_dir)):
+ logging.critical("Certificates are missing: run generate_certificates.py script first")
+ sys.exit(1)
+
+ 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)
+
+ server = ctx.socket(zmq.PUSH)
+ server_secret_file = os.path.join(secret_keys_dir, "server.key_secret")
+ server_public, server_secret = zmq.auth.load_certificate(server_secret_file)
+ server.curve_secretkey = server_secret
+ server.curve_publickey = server_public
+ server.curve_server = True # must come before bind
+ server.bind('tcp://*:9000')
+
+ 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(secret_keys_dir, "client.key_secret")
+ 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(public_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')
+
+ server.send(b"Hello")
+
+ if client.poll(1000):
+ 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()
diff --git a/examples/security/strawhouse.py b/examples/security/strawhouse.py
new file mode 100644
index 0000000..dc75bd7
--- /dev/null
+++ b/examples/security/strawhouse.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+'''
+Allow or deny clients based on IP address.
+
+Strawhouse, which is plain text with filtering on IP addresses. It still
+uses the NULL mechanism, but we install an authentication hook that checks
+the IP address against a whitelist or blacklist and allows or denies it
+accordingly.
+
+Author: Chris Laws
+'''
+
+import logging
+import sys
+
+import zmq
+import zmq.auth
+from zmq.auth.thread import ThreadAuthenticator
+
+
+def run():
+ '''Run strawhouse client'''
+
+ allow_test_pass = False
+ deny_test_pass = False
+
+ ctx = zmq.Context().instance()
+
+ # Start an authenticator for this context.
+ auth = ThreadAuthenticator(ctx)
+ auth.start()
+
+ # Part 1 - demonstrate allowing clients based on IP address
+ auth.allow('127.0.0.1')
+
+ server = ctx.socket(zmq.PUSH)
+ server.zap_domain = b'global' # must come before bind
+ server.bind('tcp://*:9000')
+
+ client_allow = ctx.socket(zmq.PULL)
+ client_allow.connect('tcp://127.0.0.1:9000')
+
+ server.send(b"Hello")
+
+ msg = client_allow.recv()
+ if msg == b"Hello":
+ allow_test_pass = True
+
+ client_allow.close()
+
+ # Part 2 - demonstrate denying clients based on IP address
+ auth.stop()
+
+ auth = ThreadAuthenticator(ctx)
+ auth.start()
+
+ auth.deny('127.0.0.1')
+
+ client_deny = ctx.socket(zmq.PULL)
+ client_deny.connect('tcp://127.0.0.1:9000')
+
+ if server.poll(50, zmq.POLLOUT):
+ server.send(b"Hello")
+
+ if client_deny.poll(50):
+ msg = client_deny.recv()
+ else:
+ deny_test_pass = True
+ else:
+ deny_test_pass = True
+
+ client_deny.close()
+
+ auth.stop() # stop auth thread
+
+ if allow_test_pass and deny_test_pass:
+ logging.info("Strawhouse test OK")
+ else:
+ logging.error("Strawhouse test FAIL")
+
+
+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()
diff --git a/examples/security/woodhouse.py b/examples/security/woodhouse.py
new file mode 100644
index 0000000..efedee4
--- /dev/null
+++ b/examples/security/woodhouse.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+'''
+Woodhouse extends Strawhouse with a name and password check.
+
+This uses the PLAIN mechanism which does plain-text username and password authentication).
+It's not really secure, and anyone sniffing the network (trivial with WiFi)
+can capture passwords and then login.
+
+Author: Chris Laws
+'''
+
+import logging
+import sys
+
+import zmq
+import zmq.auth
+from zmq.auth.thread import ThreadAuthenticator
+
+def run():
+ '''Run woodhouse example'''
+
+ valid_client_test_pass = False
+ invalid_client_test_pass = False
+
+ ctx = zmq.Context().instance()
+
+ # Start an authenticator for this context.
+ auth = ThreadAuthenticator(ctx)
+ auth.start()
+ auth.allow('127.0.0.1')
+ # Instruct authenticator to handle PLAIN requests
+ auth.configure_plain(domain='*', passwords={'admin': 'secret'})
+
+ server = ctx.socket(zmq.PUSH)
+ server.plain_server = True # must come before bind
+ server.bind('tcp://*:9000')
+
+ client = ctx.socket(zmq.PULL)
+ client.plain_username = b'admin'
+ client.plain_password = b'secret'
+ client.connect('tcp://127.0.0.1:9000')
+
+ server.send(b"Hello")
+
+ if client.poll():
+ msg = client.recv()
+ if msg == b"Hello":
+ valid_client_test_pass = True
+
+ client.close()
+
+
+ # now use invalid credentials - expect no msg received
+ client2 = ctx.socket(zmq.PULL)
+ client2.plain_username = b'admin'
+ client2.plain_password = b'bogus'
+ client2.connect('tcp://127.0.0.1:9000')
+
+ server.send(b"World")
+
+ if client2.poll(50):
+ msg = client.recv()
+ if msg == "World":
+ invalid_client_test_pass = False
+ else:
+ # no message is expected
+ invalid_client_test_pass = True
+
+ # stop auth thread
+ auth.stop()
+
+ if valid_client_test_pass and invalid_client_test_pass:
+ logging.info("Woodhouse test OK")
+ else:
+ logging.error("Woodhouse test FAIL")
+
+
+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()