Skip to content

Instantly share code, notes, and snippets.

@hintjens
Last active December 15, 2015 07:39
Show Gist options
  • Save hintjens/5224958 to your computer and use it in GitHub Desktop.
Save hintjens/5224958 to your computer and use it in GitHub Desktop.
CurveZMQ prototype - see http://hintjens.com/blog:36 for the story.
/* =========================================================================
CurveZMQ - authentication and confidentiality for 0MQ
-------------------------------------------------------------------------
Copyright (c) 1991-2013 iMatix Corporation <www.imatix.com>
Copyright other contributors as noted in the AUTHORS file.
This is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at
your option) any later version.
This software is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<http://www.gnu.org/licenses/>.
=========================================================================
*/
#include <czmq.h>
#include <sodium.h>
#include "zcurve.h"
#if crypto_box_PUBLICKEYBYTES != 32 \
|| crypto_box_SECRETKEYBYTES != 32 \
|| crypto_box_BEFORENMBYTES != 32
# error "libsodium not built correctly"
#endif
typedef enum {
pending, // Waiting for first event
expect_hello, // S: accepts HELLO from client
expect_cookie, // C: accepts COOKIE from server
expect_initiate, // S: accepts INITIATE from client
connected // C/S: accepts MESSAGE from server
} state_t;
// Structure of our class
struct _zcurve_t {
// Long term keys, if known
byte public_key [32]; // Our public key
byte secret_key [32]; // Our secret key
// Server connection properties
byte cookie_key [32]; // Server cookie key
byte cn_client [32]; // Client's short-term public key
// Client connection properties
byte server_key [32]; // Server public key
byte cn_server [32]; // Server's short-term public key
byte cn_cookie [96]; // Connection cookie from server
// Symmetric connection properties
bool is_server; // True or false
state_t state; // Connection state
int64_t cn_nonce; // Connection nonce
byte cn_public [32]; // Connection public key
byte cn_secret [32]; // Connection secret key
byte cn_precom [32]; // Connection precomputed key
zmq_msg_t msg; // ZMQ message to send to peer
};
// Command types, taken from CurveCP handshake
#define C_HELLO "QvnQ5XlH"
#define S_COOKIE "RL3aNMXK"
#define C_INITIATE "QvnQ5XlI"
#define CS_MESSAGE "RL3Q5XlM"
// Command structures
typedef struct {
byte id [8]; // Command ID
byte cn_client [32]; // Client short-term public key C'
byte cn_nonce [8]; // Client short-term nonce
byte box [80]; // Box [64 * %x0](C'->S)
byte padding [64]; // Anti-amplification padding
} c_hello_t;
typedef struct {
byte id [8]; // Command ID
byte nonce [16]; // Server long-term nonce
byte box [144]; // Box [S' + cookie](C'->S)
} s_cookie_t;
typedef struct {
byte id [8]; // Command ID
byte cn_client [32]; // Client short-term public key C'
byte cn_cookie [96]; // Server connection cookie
byte cn_nonce [8]; // Client short-term nonce
byte box [112]; // Box [C + nonce + vouch](C'->S')
} c_initiate_t;
typedef struct {
byte id [8]; // Command ID
byte cn_nonce [8]; // Server short-term nonce
byte box [116]; // Box [M](A'->B') (from A to B)
} cs_message_t;
// --------------------------------------------------------------------------
// Constructor. Always generates a new public/secret key pair which
// you can override if you need to.
zcurve_t *
zcurve_new (void)
{
zcurve_t
*self;
self = (zcurve_t *) zmalloc (sizeof (zcurve_t));
int rc;
// Generate long-term key pair, in case we need it
rc = crypto_box_keypair (self->public_key, self->secret_key);
assert (rc == 0);
// Generate short-term key pair
rc = crypto_box_keypair (self->cn_public, self->cn_secret);
assert (rc == 0);
self->state = pending;
return self;
}
// --------------------------------------------------------------------------
// Destructor
void
zcurve_destroy (zcurve_t **self_p)
{
assert (self_p);
if (*self_p) {
zcurve_t *self = *self_p;
zmq_msg_close (&self->msg);
free (self);
*self_p = NULL;
}
}
// --------------------------------------------------------------------------
// Return value of public key
byte *
zcurve_public_key (zcurve_t *self)
{
assert (self);
return self->public_key;
}
// --------------------------------------------------------------------------
// Set public key from provided value
void
zcurve_public_key_set (zcurve_t *self, byte *public_key)
{
assert (self);
memcpy (self->public_key, public_key, 32);
}
// --------------------------------------------------------------------------
// Return value of secret key
byte *
zcurve_secret_key (zcurve_t *self)
{
assert (self);
return self->secret_key;
}
// --------------------------------------------------------------------------
// Set secret key from provided value
void
zcurve_secret_key_set (zcurve_t *self, byte *secret_key)
{
assert (self);
memcpy (self->secret_key, secret_key, 32);
}
static void
s_dump (byte *buffer, int size, char *prefix)
{
printf ("\n%s: ", prefix);
int byte_nbr;
for (byte_nbr = 0; byte_nbr < size; byte_nbr++)
printf ("%02X ", buffer [byte_nbr]);
printf ("\n");
}
// --------------------------------------------------------------------------
// Start zcurve instance as client (if you provide a server key) or as
// server. May return a zmq_msg_t to send to the peer, or NULL if there
// is nothing to send.
zmq_msg_t *
zcurve_start (zcurve_t *self, byte *server_key)
{
assert (self);
assert (self->state == pending);
zmq_msg_t *outgoing = NULL;
// If caller provides us with server's long term public key,
// we are acting as client, so send HELLO command
if (server_key) {
// Store server public key
memcpy (self->server_key, server_key, 32);
// Working variables for crypto calls
byte nonce [24];
byte box [256];
byte plain [256];
// Create Box [64 * %x0](C'->S)
assert (crypto_box_ZEROBYTES == 32);
memset (plain, 0, crypto_box_ZEROBYTES + 64);
// Prepare the full nonce and encrypt
assert (crypto_box_NONCEBYTES == 24);
memcpy (nonce, (byte *) "CurveCP-client-H", 16);
memcpy (nonce + 16, &self->cn_nonce, 8);
int rc = crypto_box (box, plain, crypto_box_ZEROBYTES + 64,
nonce, self->server_key, self->cn_secret);
assert (rc == 0);
// Prepare full command ready for sending
zmq_msg_close (&self->msg);
zmq_msg_init_size (&self->msg, sizeof (c_hello_t));
c_hello_t *hello = (c_hello_t *) zmq_msg_data (&self->msg);
memcpy (hello->id, C_HELLO, 8);
memcpy (hello->cn_client, self->cn_public, 32);
memcpy (hello->cn_nonce, &self->cn_nonce, 8);
memcpy (hello->box, box + crypto_box_BOXZEROBYTES, sizeof (hello->box));
outgoing = &self->msg;
// Set next state for our connection
self->cn_nonce++;
self->is_server = false;
self->state = expect_cookie;
}
else {
self->is_server = true;
self->state = expect_hello;
}
return outgoing;
}
// --------------------------------------------------------------------------
// Decode and process incoming commands, return reply message if any
zmq_msg_t *
s_process_hello (zcurve_t *self, c_hello_t *hello)
{
// Working variables for crypto calls
byte nonce [24];
byte box [256];
byte plain [256];
// Decode HELLO command
printf ("C:HELLO: ");
// Open Box [64 * %x0](C'->S)
memset (box, 0, crypto_box_BOXZEROBYTES);
memcpy (box + crypto_box_BOXZEROBYTES, hello->box, sizeof (hello->box));
// Prepare the full nonce and decrypt
memcpy (nonce, (byte *) "CurveCP-client-H", 16);
memcpy (nonce + 16, hello->cn_nonce, 8);
int rc = crypto_box_open (plain, box,
crypto_box_BOXZEROBYTES + sizeof (hello->box),
nonce, hello->cn_client, self->secret_key);
assert (rc == 0); // In practice, if box doesn't open, discard it
puts ("OK");
// Generate cookie = Box [C' + s'](t),
memset (plain, 0, crypto_box_ZEROBYTES);
memcpy (plain + crypto_box_ZEROBYTES, hello->cn_client, 32);
memcpy (plain + crypto_box_ZEROBYTES + 32, self->cn_secret, 32);
// Create full nonce for encryption
// 8-byte prefix plus 16-byte random nonce
assert (crypto_box_BOXZEROBYTES == 16);
byte cookie_nonce [16];
randombytes (cookie_nonce, 16);
memcpy (nonce, (byte *) "Cookie--", 8);
memcpy (nonce + 8, cookie_nonce, 16);
// Encrypt using symmetric cookie key
byte cookie_box [96];
// Generate fresh cookie key
randombytes (self->cookie_key, 32);
rc = crypto_secretbox (cookie_box, plain, 96, nonce, self->cookie_key);
assert (rc == 0);
// Now create 144-byte Box [S' + cookie] (S->C')
memset (plain, 0, crypto_box_ZEROBYTES);
memcpy (plain + crypto_box_ZEROBYTES, self->cn_public, 32);
memcpy (plain + crypto_box_ZEROBYTES + 32, cookie_nonce, 16);
memcpy (plain + crypto_box_ZEROBYTES + 48,
cookie_box + crypto_box_BOXZEROBYTES, 80);
// Prepare the full nonce and encrypt
// 8-byte prefix plus 16-byte random nonce
randombytes (cookie_nonce, 16);
memcpy (nonce, (byte *) "CurveCPK", 8);
memcpy (nonce + 8, cookie_nonce, 16);
rc = crypto_box (box, plain, crypto_box_ZEROBYTES + 128,
nonce, hello->cn_client, self->secret_key);
assert (rc == 0);
// Prepare full command ready for sending
zmq_msg_close (&self->msg);
zmq_msg_init_size (&self->msg, sizeof (s_cookie_t));
s_cookie_t *cookie = (s_cookie_t *) zmq_msg_data (&self->msg);
memcpy (cookie->id, S_COOKIE, 8);
memcpy (cookie->nonce, cookie_nonce, 16);
memcpy (cookie->box,
box + crypto_box_BOXZEROBYTES, sizeof (cookie->box));
return &self->msg;
}
zmq_msg_t *
s_process_cookie (zcurve_t *self, s_cookie_t *cookie)
{
// Working variables for crypto calls
byte nonce [24];
byte box [256];
byte plain [256];
// Decode COOKIE command
printf ("S:COOKIE: ");
// Open Box [S' + cookie](C'->S)
memset (box, 0, crypto_box_BOXZEROBYTES);
memcpy (box + crypto_box_BOXZEROBYTES,
cookie->box, sizeof (cookie->box));
// Prepare the full nonce and decrypt
memcpy (nonce, (byte *) "CurveCPK", 8);
memcpy (nonce + 8, cookie->nonce, 16);
int rc = crypto_box_open (plain, box,
crypto_box_BOXZEROBYTES + sizeof (cookie->box),
nonce, self->server_key, self->cn_secret);
assert (rc == 0);
puts ("OK");
// Get server cookie and short-term key
memcpy (self->cn_server, plain + crypto_box_ZEROBYTES, 32);
memcpy (self->cn_cookie, plain + crypto_box_ZEROBYTES + 32, 96);
// Precompute connection secret from server key
rc = crypto_box_beforenm (self->cn_precom,
self->cn_server, self->cn_secret);
assert (rc == 0);
// Create vouch = Box [C'](C->S)
memset (plain, 0, crypto_box_ZEROBYTES);
memcpy (plain + crypto_box_ZEROBYTES, self->cn_public, 32);
// Prepare the full nonce and encrypt
byte vouch_nonce [16];
randombytes (vouch_nonce, 16);
memcpy (nonce, (byte *) "CurveCPV", 8);
memcpy (nonce + 8, vouch_nonce, 16);
rc = crypto_box (box, plain, crypto_box_ZEROBYTES + 32,
nonce, self->server_key, self->secret_key);
assert (rc == 0);
// Vouch is in box after initial null padding
byte *vouch_box = box + crypto_box_BOXZEROBYTES;
// Create Box [C + nonce + vouch](C'->S')
memset (plain, 0, crypto_box_ZEROBYTES);
memcpy (plain + crypto_box_ZEROBYTES, self->public_key, 32);
memcpy (plain + crypto_box_ZEROBYTES + 32, vouch_nonce, 16);
memcpy (plain + crypto_box_ZEROBYTES + 48, vouch_box, 48);
// Prepare the full nonce and encrypt
memcpy (nonce, (byte *) "CurveCP-client-I", 16);
memcpy (nonce + 16, &self->cn_nonce, 8);
rc = crypto_box (box, plain, crypto_box_ZEROBYTES + 96,
nonce, self->cn_server, self->cn_secret);
assert (rc == 0);
// Prepare full command ready for sending
zmq_msg_close (&self->msg);
zmq_msg_init_size (&self->msg, sizeof (c_initiate_t));
c_initiate_t *initiate = (c_initiate_t *) zmq_msg_data (&self->msg);
memcpy (initiate->id, C_INITIATE, 8);
memcpy (initiate->cn_client, self->cn_public, 32);
memcpy (initiate->cn_cookie, self->cn_cookie, sizeof (initiate->cn_cookie));
memcpy (initiate->cn_nonce, &self->cn_nonce, 8);
memcpy (initiate->box,
box + crypto_box_BOXZEROBYTES, sizeof (initiate->box));
self->cn_nonce++;
return &self->msg;
}
zmq_msg_t *
s_process_initiate (zcurve_t *self, c_initiate_t *initiate)
{
// Working variables for crypto calls
byte nonce [24];
byte box [256];
byte plain [256];
// Decode INITIATE command
printf ("C:INITIATE: ");
// Check cookie is valid
// We could but don't expire cookie key after 60 seconds
// Cookie nonce is first 16 bytes of cookie
memcpy (nonce, (byte *) "Cookie--", 8);
memcpy (nonce + 8, initiate->cn_cookie, 16);
// Cookie box is next 80 bytes of cookie
memset (box, 0, crypto_box_BOXZEROBYTES);
memcpy (box + crypto_box_BOXZEROBYTES, initiate->cn_cookie + 16, 80);
int rc = crypto_secretbox_open (plain, box,
crypto_box_BOXZEROBYTES + 80,
nonce, self->cookie_key);
assert (rc == 0);
// Check cookie plain text is as expected [C' + s']
// In practice we'd reject mismatched cookies, not assert
assert (memcmp (plain + crypto_box_ZEROBYTES, initiate->cn_client, 32) == 0);
assert (memcmp (plain + crypto_box_ZEROBYTES + 32, self->cn_secret, 32) == 0);
// Open Box [C + nonce + vouch](C'->S')
memset (box, 0, crypto_box_BOXZEROBYTES);
memcpy (box + crypto_box_BOXZEROBYTES,
initiate->box, sizeof (initiate->box));
// Prepare the full nonce and decrypt
memcpy (nonce, (byte *) "CurveCP-client-I", 16);
memcpy (nonce + 16, initiate->cn_nonce, 8);
rc = crypto_box_open (plain, box,
crypto_box_BOXZEROBYTES + sizeof (initiate->box),
nonce, initiate->cn_client, self->cn_secret);
assert (rc == 0);
// This is where we'd check the decrypted client public key
byte *client_key = plain + crypto_box_ZEROBYTES;
// Open vouch Box [C'](C->S) and check contents
memset (box, 0, crypto_box_BOXZEROBYTES);
memcpy (box + crypto_box_BOXZEROBYTES, plain + 80, 48);
// Prepare the full nonce and decrypt
memcpy (nonce, (byte *) "CurveCPV", 8);
memcpy (nonce + 8, plain + 64, 16);
rc = crypto_box_open (plain, box, crypto_box_BOXZEROBYTES + 48,
nonce, client_key, self->secret_key);
assert (rc == 0);
// What we decrypted must be the short term client public key
assert (memcmp (plain + crypto_box_ZEROBYTES, initiate->cn_client, 32) == 0);
memcpy (self->cn_client, initiate->cn_client, 32);
// Precompute connection secret from client key
rc = crypto_box_beforenm (self->cn_precom,
self->cn_client, self->cn_secret);
assert (rc == 0);
puts ("OK");
return NULL;
}
zmq_msg_t *
s_process_message (zcurve_t *self, cs_message_t *message)
{
// Working variables for crypto calls
byte nonce [24];
byte box [256];
byte plain [256];
// Decode MESSAGE command
printf ("-:MESSAGE: ");
// Open Box [M](S'->C') or Box [M](C'->S')
memset (box, 0, crypto_box_BOXZEROBYTES);
memcpy (box + crypto_box_BOXZEROBYTES,
message->box, 100 + crypto_box_BOXZEROBYTES);
// Prepare the full nonce and decrypt
char *prefix = self->is_server? "CurveCP-client-M": "CurveCP-server-M";
memcpy (nonce, (byte *) prefix, 16);
memcpy (nonce + 16, message->cn_nonce, 8);
int rc = crypto_box_open_afternm (
plain, box,
crypto_box_BOXZEROBYTES + sizeof (message->box),
nonce, self->cn_precom);
assert (rc == 0);
puts ("OK");
// Convert to 0MQ message
zmq_msg_close (&self->msg);
zmq_msg_init_size (&self->msg, 100);
memcpy (zmq_msg_data (&self->msg),
plain + crypto_box_ZEROBYTES, 100);
return &self->msg;
}
// --------------------------------------------------------------------------
// Accept command from peer. If the command is invalid, it is discarded
// silently (in this prototype, that causes an assertion failure). May
// return a zmq_msg_t to send to the peer, or NULL if there is nothing
// to send.
zmq_msg_t *
zcurve_accept (zcurve_t *self, zmq_msg_t *message)
{
assert (self);
assert (message);
size_t size = zmq_msg_size (message);
byte *data = zmq_msg_data (message);
zmq_msg_t *response = NULL;
// Now process command according to current state
if (size == sizeof (c_hello_t)
&& memcmp (data, C_HELLO, 8) == 0
&& self->state == expect_hello) {
response = s_process_hello (self, (c_hello_t *) data);
self->state = expect_initiate;
}
else
if (size == sizeof (s_cookie_t)
&& memcmp (data, S_COOKIE, 8) == 0
&& self->state == expect_cookie) {
response = s_process_cookie (self, (s_cookie_t *) data);
self->state = connected;
}
else
if (size == sizeof (c_initiate_t)
&& memcmp (data, C_INITIATE, 8) == 0
&& self->state == expect_initiate) {
response = s_process_initiate (self, (c_initiate_t *) data);
self->state = connected;
}
else
if (size == sizeof (cs_message_t)
&& memcmp (data, CS_MESSAGE, 8) == 0
&& self->state == connected) {
response = s_process_message (self, (cs_message_t *) data);
}
else {
puts ("E: invalid command");
assert (false);
}
return response;
}
// --------------------------------------------------------------------------
// Encode message from peer. Returns a zmq_msg_t ready to send on the wire.
zmq_msg_t *
zcurve_encode (zcurve_t *self, byte *data, size_t size)
{
assert (self);
assert (data);
assert (self->state == connected);
// Working variables for crypto calls
byte nonce [24];
byte box [256];
byte plain [256];
// Create message Box [M](C'->S'), max M is 100
memset (plain, 0, crypto_box_ZEROBYTES + 100);
memcpy (plain + crypto_box_ZEROBYTES, data, size);
// Prepare the full nonce and encrypt
char *prefix = self->is_server? "CurveCP-server-M": "CurveCP-client-M";
memcpy (nonce, (byte *) prefix, 16);
memcpy (nonce + 16, &self->cn_nonce, 8);
int rc = crypto_box_afternm (
box, plain,
crypto_box_ZEROBYTES + 100,
nonce, self->cn_precom);
assert (rc == 0);
// Prepare full command ready for sending
zmq_msg_close (&self->msg);
zmq_msg_init_size (&self->msg, sizeof (cs_message_t));
cs_message_t *message = (cs_message_t *) zmq_msg_data (&self->msg);
memcpy (message->id, CS_MESSAGE, 8);
memcpy (message->cn_nonce, &self->cn_nonce, 8);
memcpy (message->box, box + crypto_box_BOXZEROBYTES,
sizeof (message->box));
self->cn_nonce++;
return &self->msg;
}
// --------------------------------------------------------------------------
// Selftest
int
zcurve_test (bool verbose)
{
printf (" * zcurve: ");
// @selftest
zmq_msg_t *msg;
// Start server peer
zcurve_t *server = zcurve_new ();
msg = zcurve_start (server, NULL);
assert (msg == NULL);
// Start client peer and ping/pong through the handshake
zcurve_t *client = zcurve_new ();
msg = zcurve_start (client, zcurve_public_key (server));
while (msg) {
msg = zcurve_accept (server, msg);
if (msg)
msg = zcurve_accept (client, msg);
}
msg = zcurve_encode (client, (byte *) "Hello", 5);
assert (msg);
// Send encoded message over the wire
// ...
// Receive encoded message from the wire
msg = zcurve_accept (server, msg);
assert (msg);
assert (memcmp (zmq_msg_data (msg), "Hello", 5) == 0);
msg = zcurve_encode (server, (byte *) "World", 5);
assert (msg);
// Send encoded message over the wire
// ...
// Receive encoded message from the wire
msg = zcurve_accept (client, msg);
assert (msg);
assert (memcmp (zmq_msg_data (msg), "World", 5) == 0);
zcurve_destroy (&server);
zcurve_destroy (&client);
// @end
return 0;
}
/* =========================================================================
CurveZMQ - authentication and confidentiality for 0MQ
-------------------------------------------------------------------------
Copyright (c) 1991-2013 iMatix Corporation <www.imatix.com>
Copyright other contributors as noted in the AUTHORS file.
This is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at
your option) any later version.
This software is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<http://www.gnu.org/licenses/>.
=========================================================================
*/
#ifndef __ZCURVE_H_INCLUDED__
#define __ZCURVE_H_INCLUDED__
#ifdef __cplusplus
extern "C" {
#endif
// Opaque class structure
typedef struct _zcurve_t zcurve_t;
// Constructor. Always generates a new public/secret key pair which
// you can override if you need to.
zcurve_t *
zcurve_new (void);
// Destructor
void
zcurve_destroy (zcurve_t **self_p);
// Return value of public key
byte *
zcurve_public_key (zcurve_t *self);
// Return value of secret key
byte *
zcurve_secret_key (zcurve_t *self);
// Start zcurve instance as client (if you provide a server key) or as
// server. May return a zmq_msg_t to send to the peer, or NULL if there
// is nothing to send.
zmq_msg_t *
zcurve_start (zcurve_t *self, byte *server_key);
// Accept command from peer. If the command is invalid, it is discarded
// silently (in this prototype, that causes an assertion failure). May
// return a zmq_msg_t to send to the peer, or NULL if there is nothing
// to send.
zmq_msg_t *
zcurve_accept (zcurve_t *self, zmq_msg_t *message);
// Encode message from peer. Returns a zmq_msg_t ready to send on the wire.
zmq_msg_t *
zcurve_encode (zcurve_t *self, byte *data, size_t size);
// Selftest
int
zcurve_test (bool verbose);
#ifdef __cplusplus
}
#endif
#endif
#include "czmq.h"
#include "zcurve.h"
int main (int argc, char *argv [])
{
Bool verbose;
if (argc == 2 && streq (argv [1], "-v")) {
argc--;
verbose = TRUE;
}
else
verbose = FALSE;
// Do normal checks if run without arguments
if (argc < 2) {
printf ("Running self tests...\n");
zcurve_test (verbose);
printf ("Tests passed OK\n");
return 0;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment