pieterh wrote on 19 Sep 2013 21:07
In the previous article I gave an overview of how and why ZeroMQ's security layers work. In this article I'll develop a simple secure application, step by step. We'll use a simple example of a server PUSH socket sending "Hello" to a client PULL socket. We'll work through the ZeroMQ NULL, PLAIN, and CURVE security mechanisms, up to full authentication. The examples are in C but the principles apply to all languages.
Requirements & Kicking Off
For this tutorial you will ZeroMQ v4.0, CZMQ v2.0, and Sodium (the crypto library). We will build from source, since we're going to be coding anyhow. You can grab these either as download packages (here's Sodium, ZeroMQ, and CZMQ), or from GitHub (here's Sodium, ZeroMQ, and CZMQ).
Sodium is a packaging of the NaCl crypto library, which implements the Curve25519 elliptic curve cryptography (ECC) algorithm. It doesn't matter for this tutorial, but when you use ECC in real work, people will ask you (a) isn't ECC backdoored by the NSA?, and (b) how secure is this, and (c) how fast is it, and (d) who else uses it?
The answers are, at least today, (a) some ECC curves may be, and older crypto like RSA probably is, but Curve25519 is not, (b) it provides 256 bit ECC keys which is as strong as 3072 RSA keys, and (c) it's one of the fastest elliptic curves out there. Also, (d) it's not exactly mainstream, but is gaining popularity and Google is using it for their Quik protocol.
I'll assume you're working on Linux or OS/X and have gcc or similar installed, and some idea of how to compile and link C programs. The API I'll use is that provided by CZMQ. If you are working in another language, it may or may not provide the same level of abstraction. I'll explain what CZMQ is doing, in any case.
Here's how to build from GitHub (building from packages is very similar, you don't clone a repo but unpack a tarball):
git clone git://github.com/jedisct1/libsodium.git
cd libsodium
./autogen.sh
./configure && make check
sudo make install
sudo ldconfig
cd ..
git clone git://github.com/zeromq/libzmq.git
cd libzmq
./autogen.sh
./configure && make check
sudo make install
sudo ldconfig
cd ..
git clone git://github.com/zeromq/czmq.git
cd czmq
./autogen.sh
./configure && make check
sudo make install
sudo ldconfig
cd ..
Next, let's check that things are working, with a minimal example:
#include <czmq.h>
int main (void) {
zctx_t *ctx = zctx_new ();
void *publisher = zsocket_new (ctx, ZMQ_PUB);
zsocket_set_curve_server (publisher, true);
puts ("Hello, Curve!");
zctx_destroy (&ctx);
return 0;
}
hello.c: Hello, Curve
Here's how I build and run this:
gcc -o hello hello.c -lczmq -lzmq -lsodium
./hello
If that prints "Hello, Curve", then you're on the road to success. If not, check you have the necessary packages. On Debian/Ubuntu, you'll need build-essential, for instance.
What We'll Make
We'll make a minimal server-to-client PUSH-PULL flow with one server and one client. To make things really simple, the server will bind, the client will connect, and the server will send one message to the client:
Figure 1 - Minimal Server to Client
I'm going to make this in five steps, so you can see the different security patterns that are possible with today's ZeroMQ. To make it easy to remember these five patterns, I've given them mnemonic names:
- Grasslands, which is plain text with no security at all. All connections are accepted, there is no authentication, and no privacy. This is how ZeroMQ always worked until we build security into the wire protocol in early 2013. Internally, it uses a security mechanism called "NULL", but you don't even see that.
- Strawhouse, which is plain text with filtering on IP addresses. We still use the NULL mechanism, but we install an authentication hook (I'll explain this in a second) that checks the IP address against a whitelist or blacklist and allows or denies it accordingly.
- Woodhouse, which extends Strawhouse with a name and password check. For this, we use a mechanism called "PLAIN" (which does plain-text username and password authentication). It's still really not secure, and anyone sniffing the network (trivial with WiFi) can capture passwords and then login as if they were the real Joe.
- Stonehouse, which switches to the "CURVE" security mechanism, giving 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.
- Ironhouse, which 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).
As I said in my last article, privacy is about cost, and these patterns get increasingly expensive to use. Stonehouse and Ironhouse are not as easy to deploy as TLS, for instance. But they are also much more expensive to break, and for most ZeroMQ users that is a good tradeoff.
The Grasslands Pattern
We'll kick off with the simplest possible example, which looks just like classic ZeroMQ code, because it is:
// The Grasslands Pattern
//
// The Classic ZeroMQ model, plain text with no protection at all.
#include <czmq.h>
int main (void)
{
// Create context
zctx_t *ctx = zctx_new ();
// Create and bind server socket
void *server = zsocket_new (ctx, ZMQ_PUSH);
zsocket_bind (server, "tcp://*:9000");
// Create and connect client socket
void *client = zsocket_new (ctx, ZMQ_PULL);
zsocket_connect (client, "tcp://127.0.0.1:9000");
// Send a single message from server to client
zstr_send (server, "Hello");
char *message = zstr_recv (client);
assert (streq (message, "Hello"));
free (message);
puts ("Grasslands test OK");
zctx_destroy (&ctx);
return 0;
}
grasslands.c: Grasslands Pattern
To keep the code short and focus on the API calls, I'm not handling errors here. This is fun, but dangerous, like driving a motorbike without a seatbelt. In practice, since your code will fail in lots of unexpected places, it's wise to catch at least the more basic problems early on.
When you start to use the ZeroMQ and CZMQ security APIs, the main symptom of confusion will be "nothing happens", in other words, there's no connection when you expect one. To make sure it's not a basic bind/connect error, at least check the return codes from those two calls. For example:
int rc = zsocket_bind (server, "tcp://*:9000");
// If the bind fails, don't even attempt to recover
assert (rc != -1);
There are people who hate asserts, but they're an extremely useful and brutal tool for forcing your code through a single sane path, and giving you accurate and early proof of errors.
The Strawhouse Pattern
Our next model takes the same code and adds address checking. I'll explain what we're doing and how it works in detail, first here is the application code:
// The Strawhouse Pattern
//
// We allow or deny clients according to their IP address. It may keep
// spammers and idiots away, but won't stop a real attacker for more
// than a heartbeat.
#include <czmq.h>
int main (void)
{
// Create context
zctx_t *ctx = zctx_new ();
// Start an authentication engine for this context. This engine
// allows or denies incoming connections (talking to the libzmq
// core over a protocol called ZAP).
zauth_t *auth = zauth_new (ctx);
// Get some indication of what the authenticator is deciding
zauth_set_verbose (auth, true);
// Whitelist our address; any other address will be rejected
zauth_allow (auth, "127.0.0.1");
// Create and bind server socket
void *server = zsocket_new (ctx, ZMQ_PUSH);
zsocket_set_zap_domain (server, "global");
zsocket_bind (server, "tcp://*:9000");
// Create and connect client socket
void *client = zsocket_new (ctx, ZMQ_PULL);
zsocket_connect (client, "tcp://127.0.0.1:9000");
// Send a single message from server to client
zstr_send (server, "Hello");
char *message = zstr_recv (client);
assert (streq (message, "Hello"));
free (message);
puts ("Strawhouse test OK");
zauth_destroy (&auth);
zctx_destroy (&ctx);
return 0;
}
strawhouse.c: Strawhouse Pattern
When you use CZMQ, all authentication happens through a zauth object, aka an "authenticator". In the Strawhouse code we create an authenticator, we enable verbose tracing (during development, at least), and we whitelist one address. Then we do the same work as Grasslands, and finally we destroy the authenticator:
zauth_t *auth = zauth_new (ctx);
zauth_set_verbose (auth, true);
zauth_allow (auth, "127.0.0.1");
...
zauth_destroy (&auth);
Internally, the authenticator talks to libzmq across a protocol called ZAP, aka RFC 27. Every single time a client tries to connect to a server, libzmq asks the ZAP handler (the authenticator), if there is one installed, to allow or deny the connection.
We tell libzmq that the server socket is in fact a "server" in the sense that it authenticates clients, by using this call:
zsocket_set_zap_domain (server, "global");
The ZAP domain concept lets you define authentication groups. For instance a set of server sockets could say, "We're in a domain called "test" and we only accept localhost IP addresses". CZMQ doesn't implement domains yet, and the "global" domain we use here has no significance at all. Remember that in this example we're using the NULL security mechanism, and by default libzmq just accepts NULL connections, period. But the simple fact of telling libzmq, "Please set domain X on this socket (with the default NULL security mechanism)" will cause it to act as a server, and call the ZAP handler for every client request.
You can mix different kinds of security in one application, by using different sockets, and then configuring each security mechanism separately in the authenticator. So it will handle PLAIN in one way, CURVE another way, and so on.
Now, to CZMQ's IP address filtering. This uses whitelisting and blacklisting, and works thus:
- You can whitelist one or more addresses, or blacklist one or more addresses. We do this through the zauth_allow() and zauth_deny() calls, rather than loading external files.
- The whitelist or blacklist applies to all requests that the authenticator gets from libzmq. That is, any NULL socket with a ZAP domain set, any PLAIN server socket, or any CURVE server socket, on that ZeroMQ context.
- If you have a whitelist (one or more addresses), that means, "reject any address that's not in the whitelist". Think of a private party, a very large bouncer, and a sheet of paper with names on it. "Sorry, buddy, you're not on the list. Please move out of the way."
- If you have a blacklist (one or more addresses), that means, "reject any address that's in the blacklist". Think of a public function with a couple of very large bouncers and a sheet of paper with names on it. "Hey buddy, didn't we tell you to never show your face here again?" You won't use both a whitelist and a blacklist, unless you want to make the bouncers confused, and angry.
- If not denied, and the security mechanism is NULL, then the connection is allowed.
- If not denied, and the mechanism is PLAIN or CURVE, then the authenticator carries on with its checking. So perhaps you're on the whitelist, or not on the blacklist, but now we need to see some identification, please.
So in our example, where we allow "127.0.0.1", we effectively deny every other client address.
The Woodhouse Pattern
Next, let's see how to do username and password authentication. Here is the full example:
// The Woodhouse Pattern
//
// It may keep some malicious people out but all it takes is a bit
// of network sniffing, and they'll be able to fake their way in.
#include <czmq.h>
int main (void)
{
// Create context and start authentication engine
zctx_t *ctx = zctx_new ();
zauth_t *auth = zauth_new (ctx);
zauth_set_verbose (auth, true);
zauth_allow (auth, "127.0.0.1");
// Tell the authenticator how to handle PLAIN requests
zauth_configure_plain (auth, "*", "passwords");
// Create and bind server socket
void *server = zsocket_new (ctx, ZMQ_PUSH);
zsocket_set_plain_server (server, 1);
zsocket_bind (server, "tcp://*:9000");
// Create and connect client socket
void *client = zsocket_new (ctx, ZMQ_PULL);
zsocket_set_plain_username (client, "admin");
zsocket_set_plain_password (client, "secret");
zsocket_connect (client, "tcp://127.0.0.1:9000");
// Send a single message from server to client
zstr_send (server, "Hello");
char *message = zstr_recv (client);
assert (streq (message, "Hello"));
free (message);
puts ("Woodhouse test OK");
zauth_destroy (&auth);
zctx_destroy (&ctx);
return 0;
}
woodhouse.c: Woodhouse Pattern
Setting up the authenticator and the whitelist is the same as in Strawhouse, and I won't explain that again. The new API calls happen in three places in the code:
// Tell the authenticator how to handle PLAIN requests
zauth_configure_plain (auth, "*", "passwords");
...
// Before binding the server
zsocket_set_plain_server (server, 1);
...
// Before connecting the client
zsocket_set_plain_username (client, "admin");
zsocket_set_plain_password (client, "secret");
Bear this in mind: you have to configure a socket before doing a _bind or _connect on it. If you run the Woodhouse example without a password file, it'll never complete. Since we enabled tracing, you'll see it print out this:
I: PASSED (whitelist) address=127.0.0.1
I: DENIED (PLAIN) username=admin password=secret
The message in fact repeats because ZeroMQ re-tries the connection over and over. We mught change this behavior but it's not significant here. Let's create a password file, "passwords", in the current directory, with some entries, one per line:
guest=guest
tourist=1234
admin=secret
Then run the Woodhouse example again, and it'll print this:
I: PASSED (whitelist) address=127.0.0.1
I: ALLOWED (PLAIN) username=admin password=secret
Woodhouse test OK
The Stonehouse Pattern
Plain text is what it is. Anyone with half a malicious intent can sniff traffic and impersonate real clients, and make a mockery of your so-called security. That may be fine, or it may be a problem. The next level up is Stonehouse, which changes the game somewhat. Here's the example code:
// The Stonehouse Pattern
//
// Where we allow any clients to connect, but we promise clients
// that we are who we claim to be, and our conversations won't be
// tampered with or modified, or spied on.
#include <czmq.h>
int main (void)
{
// Create context and start authentication engine
zctx_t *ctx = zctx_new ();
zauth_t *auth = zauth_new (ctx);
zauth_set_verbose (auth, true);
zauth_allow (auth, "127.0.0.1");
// Tell the authenticator how to handle CURVE requests
zauth_configure_curve (auth, "*", CURVE_ALLOW_ANY);
// 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.
zcert_t *client_cert = zcert_new ();
zcert_t *server_cert = zcert_new ();
char *server_key = zcert_public_txt (server_cert);
// Create and bind server socket
void *server = zsocket_new (ctx, ZMQ_PUSH);
zcert_apply (server_cert, server);
zsocket_set_curve_server (server, 1);
zsocket_bind (server, "tcp://*:9000");
// Create and connect client socket
void *client = zsocket_new (ctx, ZMQ_PULL);
zcert_apply (client_cert, client);
zsocket_set_curve_serverkey (client, server_key);
zsocket_connect (client, "tcp://127.0.0.1:9000");
// Send a single message from server to client
zstr_send (server, "Hello");
char *message = zstr_recv (client);
assert (streq (message, "Hello"));
free (message);
puts ("Stonehouse test OK");
zcert_destroy (&client_cert);
zcert_destroy (&server_cert);
zauth_destroy (&auth);
zctx_destroy (&ctx);
return 0;
}
stonehouse.c: Stonehouse Pattern
Stonehouse starts with a single call to the authenticator that switches on the CURVE_ALLOW_ANY feature:
zauth_configure_curve (auth, "*", CURVE_ALLOW_ANY);
Here we do that for the domain "*", meaning all domains. Recall that CZMQ doesn't deal with domains yet, so if you use Stonehouse on one server socket, it will apply to all server sockets in the same context.
Now, let's talk about certificates. For any CURVE security you need a "long-term public / secret keypair" for the client, and for the server. CZMQ wraps up a keypair along with metadata (a name, email address, etc.) as a "certificate", which can be an in-memory object, or a disk file. Here is the explanation from the zcert class:
The zcert class provides a way to create and work with security certificates for the ZMQ CURVE mechanism. A certificate contains a public + secret key pair, plus metadata. It can be used as a temporary object in memory, or persisted to disk. On disk, a certificate is stored as two files. One is public and contains only the public key. The second is secret and contains both keys. The two have the same filename, with the secret file adding "_secret". To exchange certificates, send the public file via some secure route. Certificates are not signed but are text files that can be verified by eye.
Certificates are stored in the ZPL (ZMQ RFC 4) format. They have two sections, "metadata" and "curve". The first contains a list of 'name = value' pairs, one per line. Values may be enclosed in quotes. The curve section has a 'public-key = keyvalue' and, for secret certificates, a 'secret-key = keyvalue' line. The keyvalue is a Z85-encoded CURVE key."
What you would do in practice is generate certificates up-front (and CZMQ provides a simple tool to do this, in addons/makecert), and then put them carefully on each node in your network. It's like handing out neat little membership cards for an exclusive club, up front. Makes life easier for guests and security alike.
In the Ironhouse example we'll see how to work with "real" certificates on disk, using a so-called "certificate store". However for this example, we can generate certificates on-the-fly, in memory, and pretend we got them from disk.
We need a client certificate, a server certificate, and then we also have to pass the server's public key to the client. This is the total setup to use the Stonehouse pattern. CZMQ provides a zcert class that generates a new certificate, and which we can "apply" to a socket in order to enable it for CURVE security. Finally, we have to tell the client socket what our server public key is. Here are the relevant API calls:
// Generate certificates at runtime, since this is a test
zcert_t *client_cert = zcert_new ();
zcert_t *server_cert = zcert_new ();
char *server_key = zcert_public_txt (server_cert);
...
// Before binding the server
zcert_apply (server_cert, server);
zsocket_set_curve_server (server, 1);
...
// Before connecting the client
zcert_apply (client_cert, client);
zsocket_set_curve_serverkey (client, server_key);
...
// Destroy these in-memory objects when done
zcert_destroy (&client_cert);
zcert_destroy (&server_cert);
The zcert_new() call generates a public/secret key pair. We can ornament that with metadata, e.g. name, email, and organization, which can be helpful when managing certificates. Metadata doesn't play any role in authentication however.
The Ironhouse Pattern
Now, let's explore full authentication of clients. I call this the Ironhouse pattern, just to remind us that security is never total or perfect. It's always about cost. Even an iron house may have holes in the floor, or the roof, and your security is only as good as the systems it runs on. If you run Ironhouse on a box with an insecure or outdated operating system, don't expect privacy.
Here's the Ironhouse example:
// The Ironhouse Pattern
//
// Security doesn't get any stronger than this. An attacker is going to
// have to break into your systems to see data before/after encryption.
#include <czmq.h>
int main (void)
{
// Create context and start authentication engine
zctx_t *ctx = zctx_new ();
zauth_t *auth = zauth_new (ctx);
zauth_set_verbose (auth, true);
zauth_allow (auth, "127.0.0.1");
// Tell authenticator to use the certificate store in .curve
zauth_configure_curve (auth, "*", ".curve");
// We'll generate a new client certificate and save the public part
// in the certificate store (in practice this would be done by hand
// or some out-of-band process).
zcert_t *client_cert = zcert_new ();
zsys_dir_create (".curve");
zcert_set_meta (client_cert, "name", "Client test certificate");
zcert_save_public (client_cert, ".curve/testcert.pub");
// Prepare the server certificate as we did in Stonehouse
zcert_t *server_cert = zcert_new ();
char *server_key = zcert_public_txt (server_cert);
// Create and bind server socket
void *server = zsocket_new (ctx, ZMQ_PUSH);
zcert_apply (server_cert, server);
zsocket_set_curve_server (server, 1);
zsocket_bind (server, "tcp://*:9000");
// Create and connect client socket
void *client = zsocket_new (ctx, ZMQ_PULL);
zcert_apply (client_cert, client);
zsocket_set_curve_serverkey (client, server_key);
zsocket_connect (client, "tcp://127.0.0.1:9000");
// Send a single message from server to client
zstr_send (server, "Hello");
char *message = zstr_recv (client);
assert (streq (message, "Hello"));
free (message);
puts ("Ironhouse test OK");
zcert_destroy (&client_cert);
zcert_destroy (&server_cert);
zauth_destroy (&auth);
zctx_destroy (&ctx);
return 0;
}
ironhouse.c: Ironhouse Pattern
Ironhouse is very similar to Stonehouse except it uses a "certificate store". That is a directory somewhere on the server's file system that contains a set of client public certificates. This is the API we use:
// Tell authenticator to use the certificate store in .curve
zauth_configure_curve (auth, "*", ".curve");
...
// Stick the client public certificate into the directory
zcert_t *client_cert = zcert_new ();
zcert_save_public (client_cert, ".curve/testcert.pub");
For the client, nothing changes — it's the same code as for Stonehouse, as you'd expect. If this wasn't clear already, the zauth object belongs to the server, not the client.
A word about certificate stores. This is a CZMQ concept, and implemented by the zcertstore class. Normally you won't use that class directly, but like this example, allow the authenticator to manage it. All we do is (a) tell the authenticator what directory will hold certificates, and then make sure our clients' public certificates end up safely there.
In practice a server application will be a long-running process, so the certificate store reloads itself automatically if there is a change. This means we can copy new certificates into it at any time, to allow new clients, or remove certificates to reject clients.
Certificate filenames don't matter. Here we use "testcert.pub" but in practice I'd use a filename which is based of the certificate public key, for instance, to ensure it's unique. In our example we create the directory, and add some meta data, before saving the certificate:
// Create the directory
zsys_dir_create (".curve");
zcert_set_meta (client_cert, "name", "Client test certificate");
zcert_save_public (client_cert, ".curve/testcert.pub");
When you save a certificate using the zcert_save_public (or zcert_save) method, it looks like this:
# **** Generated on 2013-09-19 21:25:34 by CZMQ ****
# ZeroMQ CURVE Public Certificate
# Exchange securely, or use a secure mechanism to verify the contents
# of this file after exchange. Store public certificates in your home
# directory, in the .curve subdirectory.
metadata
name = "Client test certificate"
curve
public-key = "I7[{YV4[}q[9a)]b&d>bisoT]UXa/7b$Tp:6yoyq"
In own applications I'm using $HOME/.curve to hold certificates, as zcert recommends, but you can put them anywhere. Some guidelines do apply:
- A certificate produced by makecert has a secret and a public file. Never share the secret file with anyone.
- When you transfer public certificates, use a secure route. If an attacker can modify this data without you knowing about it, they can construct a man-in-the-middle attack.
- For the same reason, make sure public certificates are not writeable by anyone except the current user.
The Ironhouse Pattern, Model 2
When you read the examples so far, you might be tempted to copy the code for your own applications. However it's not meant for that. It doesn't have a clean separation into client and server, and doesn't do any error checking. Here's a reworking of the Ironhouse example that is much more explicit. It's also twice the amount of code:
// The Ironhouse Pattern
//
// This is exactly the same example but broken into two threads
// so you can better see what client and server do, separately.
#include <czmq.h>
// The client task runs in its own context, and receives the
// server public key as an argument.
static void *
client_task (void *args)
{
// Load our persistent certificate from disk
zcert_t *client_cert = zcert_load ("client_cert.txt");
assert (client_cert);
// Create client socket and configure it to use full encryption
zctx_t *ctx = zctx_new ();
assert (ctx);
void *client = zsocket_new (ctx, ZMQ_PULL);
assert (client);
zcert_apply (client_cert, client);
zsocket_set_curve_serverkey (client, (char *) args);
int rc = zsocket_connect (client, "tcp://127.0.0.1:9000");
assert (rc == 0);
// Wait for our message, that signals the test was successful
char *message = zstr_recv (client);
assert (streq (message, "Hello"));
free (message);
puts ("Ironhouse test OK");
// Free all memory we used
zcert_destroy (&client_cert);
zctx_destroy (&ctx);
return NULL;
}
static void *
server_task (void *args)
{
zctx_t *ctx = zctx_new ();
assert (ctx);
// Start the authenticator and tell it do authenticate clients
// via the certificates stored in the .curve directory.
zauth_t *auth = zauth_new (ctx);
assert (auth);
zauth_set_verbose (auth, true);
zauth_allow (auth, "127.0.0.1");
zauth_configure_curve (auth, "*", ".curve");
// Create server socket and configure it to use full encryption
void *server = zsocket_new (ctx, ZMQ_PUSH);
assert (server);
zcert_apply ((zcert_t *) args, server);
zsocket_set_curve_server (server, 1);
int rc = zsocket_bind (server, "tcp://*:9000");
assert (rc != -1);
// Send our test message, just once
zstr_send (server, "Hello");
zclock_sleep (200);
// Free all memory we used
zauth_destroy (&auth);
zctx_destroy (&ctx);
return NULL;
}
int main (void)
{
// Create the certificate store directory and client certs
zcert_t *client_cert = zcert_new ();
int rc = zsys_dir_create (".curve");
assert (rc == 0);
zcert_set_meta (client_cert, "name", "Client test certificate");
zcert_save_public (client_cert, ".curve/testcert.pub");
rc = zcert_save (client_cert, "client_cert.txt");
assert (rc == 0);
zcert_destroy (&client_cert);
// Create the server certificate
zcert_t *server_cert = zcert_new ();
// Now start the two detached threads; each of these has their
// own ZeroMQ context.
zthread_new (server_task, server_cert);
zthread_new (client_task, zcert_public_txt (server_cert));
// As we're using detached threads this is the simplest way //
// to ensure they both end, before we exit the process. 200
// milliseconds should be enough for anyone. In real code,
// you would use messages to synchronize threads.//
zclock_sleep (200);
// Free the memory we used
zcert_destroy (&server_cert);
return 0;
}
ironhouse2.c: Ironhouse Pattern, model 2
Here we see how to save and load client certificates from disk, to create truly persistent certificates:
zcert_t *client_cert = zcert_new ();
zcert_save (client_cert, "client_cert.txt");
zcert_destroy (&client_cert);
And then, in a different place and time, or at least a separate thread:
// Load our persistent certificate from disk
zcert_t *client_cert = zcert_load ("client_cert.txt");
Wrapping Up
OK, that's enough code. After half a dozen examples, we start to get code blindness. What is striking however is how short the code examples are, even in C. For me, this is success.
There's one last thing I want to discuss about security, which is the constant harping on about "server" and "client", which you may know are not ZeroMQ native concepts at all. The closest we get in ZeroMQ is having one side binding to an endpoint, and the other side connecting to it.
When using the PLAIN or CURVE security mechanisms (wood, stone, or iron), you can bind the client and connect the server. You can try this in stonehouse.c for example. Simply switch the bind and connect statements (change the socket arguments :) and observe that it still works. The server still authenticates the client.
You'll get the best results by typing the code, because muscle memory is a fantastic thing for programming. But if you're in a hurry, it is in the CZMQ project, in the examples/security directory. These examples are licensed under MIT/X11 so you can reuse the code in your own applications.
Let me know how you get on. Comment below, or come to the zeromq-dev mailing list. If you need commercial support in using these new security tools, or you're an open source project that wants to use them, drop me an email at moc.xitami|hp#moc.xitami|hp and tell me your use case.
Comments
Hi,
I would like to know if CURVE is supported for zmq_proxy.
Kind regards,
Rob
Hi,
I have been using ZMQ for some time in several projects using the zmq library (not the libczmq).
I would like to see the translation of the Strawhouse, Woodhouse, Stonehouse and Ironhouse examples (based on the czmq.h api) to the zmq.h api.
Thank you in advance,
Rob
I've posted updated examples compatible with CZMQ 4.x here as a GitHub Gist:
/AlainODea/f247d8bc6c2e46e4899bdea86f8a9af4
This particularly addresses the removal/hiding of zcontext and the switch from zsocket to zsock and from zthread to zactor.
I'm not allowed to post links yet, so I'm stuck posting the path on Gist which hopefully will get you to my fixed examples.
Because auth is destroyed by zauth_destroy(&auth) before connection. If I add zclock_sleep after zstr_send I can test authentification.
Thanks for finding this problem.
Portfolio
I just tried the ironhouse2.c example and it claimed to have passed but strangely did not print out the "I: ALLOWED (CURVE) client_key=…" bit that ironhouse.c did. Suspicious, I changed the zauth_allow whitelist to a random IP and it still let me go through with sending the message. It seems something is making the server not care about auth mechanisms. OS X 10.7.5
Problem 1:
I had to remove all options "-pedantic" from the Makefiles of ZeroMQ (version 4.0.1) to get it to compile with libsodium (version 0.4.3). After that, 'make check' gave two fails, for test_linger and test_stream.
Problem 2:
I wrote Go bindings for ZeroMQ versions 2 and 3. With these, I implemented all examples from "ØMQ - The Guide" in Go.
Now I want to do the same for version 4, including the examples on this page. But these examples all use czmq, without an explanation of what is going on under the hood, in core ZeroMQ.
To get this stuff right, and secure, I really need C examples without czmq. I could poke around in the sources of czmq, but chances are, I will miss some important detail, and my implementation might seem to work, when actually it's full of security holes.
I expect people who write bindings in other languages would need this too. Any change you can provide these examples without czmq?
For the compilation, could you start a thread on the zeromq-dev list?
For understanding the security API, yes, I need to explain the low level mechanisms better. The place to start is tests/test_security_xxx.cpp, which show each model. What CZMQ does is build on top of that to e.g. manage and use keys. A binding just needs to export the libzmq, and it can leave key management and authentication to the application.
Portfolio
For 1:
A posted a message to the zeromq-dev list. I got a reply that the message was being held for review. I haven't seen it appear in the zeromq Google group.
For 2:
First I translated the tests/test_security_xxx.cpp into Go. That gave me a good idea of how things work.
Then I wrote a version of the czmq xauth module in Go. I think it's a reasonable implementation. See pebbe/zmq4 on github, subdirectory examples_security.
I didn't see your message to the zeromq-dev list; if it's not there in a while could you resend it? If you join the email list, there's no moderation.
Portfolio
My life was happy. Well, at least it was simple. ZeroMQ might saved us from being Iced up by Corba, but it wasn't an option. I reviewed them all, and ended up with a horrible implementation of AMQP, with SSL that makes it pretty much unusable in any real kind of code.
ZeroMQ was an amazing academic toy for those having the luxury of an isolated and private network filled with only happy packets and gilded rainbows. It was unnecessary to even consider it for our applications, because it would get chewed up by the big scary internet unless we rolled out squabs of VPNs and stunnels.
But now, oh dear, you have fixed it. Oh spite. I just wonder if hubris will beat laziness on this …
does it work for that?
doesn't seem to be working
i have a server application that tracks other server applications
i needed bi-directional targeted communication so i used ROUTER on both sides.
You won't be able to authenticate at both sides at the same time. Look at the Harmony pattern in the Guide, which is Router-to-Dealer in both directions. That will allow peer-to-peer authentication.
Portfolio
works fine, i only need to make sure its not rogue server applications that are connecting. my problem was with zcert_new_from(), which requires 32byte binary strings and not the z85 encoded strings provided by makecert.
Usually I'd load the certificate from disk with zcert_load(), or apply the keys directly to the socket with individual zsocket_set options.
Portfolio