Create a gist now

Instantly share code, notes, and snippets.

zcurve -- CurveCP over ZeroMQ -- proof of concept
// Proof of concept CurveCP handshake over 0MQ.
//
// Demonstrates a confidential, authenticated connection between
// two 0MQ peers (two DEALER sockets in this example). See the
// curvecp.org website for details of the security design.
//
// This is a flat walk-through in code with minimal abstraction.
// The next version of this code will be more packaged.
//
// IMPORTANT NOTE: this code has not been reviewed by security experts
// and is for demonstration purposes only.
// ---------------------------------------------------------------------
// Copyright (c) 2013 iMatix Corporation <www.imatix.com>
//
// 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.
// ---------------------------------------------------------------------
#include <czmq.h>
#include <crypto_box.h>
#include <crypto_secretbox.h>
#include <randombytes.h>
#if crypto_box_PUBLICKEYBYTES != 32 || crypto_box_SECRETKEYBYTES != 32
# error "NaCl not built correctly"
#endif
// Command types
#define CMD_C_HELLO "QvnQ5XlH"
#define CMD_S_COOKIE "RL3aNMXK"
#define CMD_C_INITIATE "QvnQ5XlI"
#define CMD_C_MESSAGE "RL3aNMXM"
#define CMD_S_MESSAGE "QvnQ5XlM"
// Command structures
typedef struct {
byte cn_public [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
} cmd_c_hello_t;
typedef struct {
byte nonce [16]; // Server long-term nonce
byte box [144]; // Box [S' + cookie](C'->S)
} cmd_s_cookie_t;
typedef struct {
byte cn_public [32]; // Client short-term public key C'
byte cn_cookie [96]; // Server connection cookie
byte cn_nonce [8]; // Client short-term nonce
byte box [368]; // Box [C + nonce + vouch + host](C'->S')
} cmd_c_initiate_t;
typedef struct {
byte cn_nonce [8]; // Server short-term nonce
byte box [272]; // Box [M](S'->C'), max M is 256
} cmd_s_message_t;
typedef struct {
byte cn_public [32]; // Client short-term public key C'
byte cn_nonce [8]; // Client short-term nonce
byte box [272]; // Box [M](C'->S'), max M is 256
} cmd_c_message_t;
typedef struct {
byte type [8];
union {
cmd_c_hello_t c_hello;
cmd_s_cookie_t s_cookie;
cmd_c_initiate_t c_initiate;
cmd_s_message_t s_message;
cmd_c_message_t c_message;
};
} cmd_t;
// Announce public keys simple via static memory
static byte
server_public [32], // Server public key
client_public [32]; // Client public key
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");
}
static void
s_server (void *args, zctx_t *ctx, void *pipe)
{
// Generate our long-term keys
byte server_secret [32];
int rc = crypto_box_keypair (server_public, server_secret);
assert (rc == 0);
void *server = zsocket_new (ctx, ZMQ_DEALER);
assert (server);
rc = zsocket_bind (server, "tcp://*:9999");
assert (rc != -1);
// Short-term connection keys
byte cn_client [32]; // Client short-term public key
byte cn_public [32]; // Server short-term public key
byte cn_secret [32]; // Server short-term secret key
rc = crypto_box_keypair (cn_public, cn_secret);
assert (rc == 0);
// Initialize connection nonce
int64_t cn_nonce = 0;
// Cookie key; we'd rotate this every minute
byte cookie_key [32];
randombytes (cookie_key, 32);
// Working variables for crypto calls
// Assert sizes are same for secretbox calls
# if crypto_secretbox_NONCEBYTES != crypto_box_NONCEBYTES
# error
# endif
byte nonce [crypto_box_NONCEBYTES];
byte text [crypto_box_ZEROBYTES + 1024];
byte box [crypto_box_ZEROBYTES + 1024];
// Now process commands from client
while (true) {
cmd_t command;
rc = zmq_recv (server, &command, sizeof (command), 0);
if (rc == -1)
break;
if (rc == 8 + sizeof (cmd_c_hello_t)
&& memcmp (command.type, CMD_C_HELLO, 8) == 0) {
// Check HELLO command is valid
printf ("C:HELLO: ");
memcpy (cn_client, command.c_hello.cn_public, 32);
// Open Box [64 * %x0](C'->S)
memcpy (nonce, (byte *) "CurveCP-client-H", 16);
memcpy (nonce + 16, command.c_hello.cn_nonce, 8);
memset (box, 0, 16);
memcpy (box + 16, command.c_hello.box, 80);
rc = crypto_box_open (text, box, 96, nonce, cn_client, server_secret);
assert (rc == 0);
puts ("OK");
// Reply to every HELLO with a COOKIE
cmd_t cookie = { CMD_S_COOKIE };
// Generate cookie = Box [C' + s'](t),
memset (text, 0, 32);
memcpy (text + 32, cn_client, 32);
memcpy (text + 64, cn_secret, 32);
// Create full nonce for encryption
byte cookie_nonce [16];
randombytes (cookie_nonce, 16);
assert (crypto_secretbox_NONCEBYTES == 24);
memcpy (nonce, (byte *) "Cookie--", 8);
memcpy (nonce + 8, cookie_nonce, 16);
// Encrypt using symmetric cookie key
byte cookie_box [96];
rc = crypto_secretbox (cookie_box, text, 96, nonce, cookie_key);
assert (rc == 0);
// Put 16-byte nonce into start of encrypted text
// Use 16 bytes of leading zero padding for this
assert (crypto_secretbox_BOXZEROBYTES == 16);
memcpy (cookie_box, cookie_nonce, 16);
// Now create 144-byte Box [S' + cookie] (S->C')
memset (text, 0, 32);
memcpy (text + 32, cn_public, 32);
memcpy (text + 64, cookie_box, 96);
// Create full nonce for encryption
randombytes (cookie.s_cookie.nonce, 16);
memcpy (nonce, (byte *) "CurveCPK", 8);
memcpy (nonce + 8, cookie.s_cookie.nonce, 16);
rc = crypto_box (box, text, 160, nonce, cn_client, server_secret);
assert (rc == 0);
memcpy (cookie.s_cookie.box, box + 16, 144);
zmq_send (server, &cookie, 8 + sizeof (cmd_s_cookie_t), 0);
}
else
if (rc == 8 + sizeof (cmd_c_initiate_t)
&& memcmp (command.type, CMD_C_INITIATE, 8) == 0) {
// Check INITIATE command is valid
printf ("C:INITIATE: ");
memcpy (cn_client, command.c_initiate.cn_public, 32);
// Check cookie is valid
// Cookie nonce is first 16 bytes of cookie
memcpy (nonce, (byte *) "Cookie--", 8);
memcpy (nonce + 8, command.c_initiate.cn_cookie, 16);
// Cookie box is next 80 bytes of cookie
memset (box, 0, 16);
memcpy (box + 16, command.c_initiate.cn_cookie + 16, 80);
rc = crypto_secretbox_open (text, box, 96, nonce, cookie_key);
assert (rc == 0);
// Check cookie plain text is as expected [C' + s']
assert (memcmp (text + 32, cn_client, 32) == 0);
assert (memcmp (text + 64, cn_secret, 32) == 0);
// Open Box [C + nonce + vouch + host](C'->S')
memcpy (nonce, (byte *) "CurveCP-client-I", 16);
memcpy (nonce + 16, command.c_initiate.cn_nonce, 8);
memset (box, 0, 16);
memcpy (box + 16, command.c_initiate.box, 368);
rc = crypto_box_open (text, box, 384, nonce, cn_client, cn_secret);
assert (rc == 0);
// Get & check contents of box
assert (memcmp (text + 32, client_public, 32) == 0);
printf ("(host=%s) ", text + 128);
// Open vouch Box [C'](C->S) and check contents
memcpy (nonce, (byte *) "CurveCPV", 8);
memcpy (nonce + 8, text + 64, 16);
memset (box, 0, 16);
memcpy (box + 16, text + 80, 48);
rc = crypto_box_open (text, box, 64, nonce, client_public, server_secret);
assert (rc == 0);
assert (memcmp (text + 32, cn_client, 32) == 0);
puts ("OK");
}
else
if (rc == 8 + sizeof (cmd_c_message_t)
&& memcmp (command.type, CMD_C_MESSAGE, 8) == 0) {
// Check MESSAGE command is valid
printf ("C:MESSAGE: ");
memcpy (cn_client, command.c_message.cn_public, 32);
// Open Box [M](C'->S')
memcpy (nonce, (byte *) "CurveCP-client-M", 16);
memcpy (nonce + 16, command.c_message.cn_nonce, 8);
memset (box, 0, 16);
memcpy (box + 16, command.c_message.box, 272);
rc = crypto_box_open (text, box, 288, nonce, cn_client, cn_secret);
assert (rc == 0);
printf ("(request=%s) ", text + 32);
puts ("OK");
// Send MESSAGE now with "World"
// Create message Box [M](S'->C'), max M is 256
memcpy (nonce, (byte *) "CurveCP-server-M", 16);
memcpy (nonce + 16, &cn_nonce, 8);
memset (text, 0, 256);
memcpy (text + 32, "World", 5);
rc = crypto_box (box, text, 288, nonce, cn_client, cn_secret);
assert (rc == 0);
cmd_t message = { CMD_S_MESSAGE };
memcpy (message.s_message.cn_nonce, &cn_nonce, 8);
memcpy (message.s_message.box, box + 16, 272);
zmq_send (server, &message, 8 + sizeof (cmd_s_message_t), 0);
cn_nonce++;
}
else
puts ("E: invalid client command, rejected");
}
}
static void
s_client (void *args, zctx_t *ctx, void *pipe)
{
// Generate our long-term keys
byte client_secret [32];
int rc = crypto_box_keypair (client_public, client_secret);
assert (rc == 0);
// Open new connection to server
void *client = zsocket_new (ctx, ZMQ_DEALER);
assert (client);
rc = zsocket_connect (client, "tcp://localhost:9999");
assert (rc == 0);
// Short-term connection keys
byte cn_server [32]; // Server short-term public key
byte cn_public [32]; // Client short-term public key
byte cn_secret [32]; // Client short-term secret key
rc = crypto_box_keypair (cn_public, cn_secret);
assert (rc == 0);
// Initialize connection nonce
int64_t cn_nonce = 0;
// Working variables for crypto calls
byte nonce [crypto_box_NONCEBYTES];
byte text [crypto_box_ZEROBYTES + 1024];
byte box [crypto_box_ZEROBYTES + 1024];
// Create Box [64 * %x0](C'->S)
memcpy (nonce, (byte *) "CurveCP-client-H", 16);
memcpy (nonce + 16, &cn_nonce, 8);
assert (crypto_box_ZEROBYTES == 32);
memset (text, 0, 96);
rc = crypto_box (box, text, 96, nonce, server_public, cn_secret);
assert (rc == 0);
// Send HELLO command to start connection
cmd_t hello = { CMD_C_HELLO };
memcpy (hello.c_hello.cn_public, cn_public, 32);
memcpy (hello.c_hello.cn_nonce, &cn_nonce, 8);
assert (crypto_box_BOXZEROBYTES == 16);
memcpy (hello.c_hello.box, box + 16, 80);
zmq_send (client, &hello, 8 + sizeof (cmd_c_hello_t), 0);
cn_nonce++;
// Now process commands from server
while (true) {
cmd_t command;
rc = zmq_recv (client, &command, sizeof (command), 0);
if (rc == -1)
break;
if (rc == 8 + sizeof (cmd_s_cookie_t)
&& memcmp (command.type, CMD_S_COOKIE, 8) == 0) {
// Check COOKIE command is valid
printf ("S:COOKIE: ");
// Open Box [S' + cookie](C'->S)
memcpy (nonce, (byte *) "CurveCPK", 8);
memcpy (nonce + 8, command.s_cookie.nonce, 16);
memset (box, 0, 16);
memcpy (box + 16, command.s_cookie.box, 144);
rc = crypto_box_open (text, box, 160, nonce, server_public, cn_secret);
assert (rc == 0);
puts ("OK");
// Get server cookie and short-term key
byte cn_cookie [96];
memcpy (cn_server, text + 32, 32);
memcpy (cn_cookie, text + 64, 96);
// Create vouch = Box [C'](C->S)
memset (text, 0, 32);
memcpy (text + 32, cn_public, 32);
memcpy (nonce, (byte *) "CurveCPV", 8);
byte vouch_nonce [16];
randombytes (vouch_nonce, 16);
memcpy (nonce + 8, vouch_nonce, 16);
rc = crypto_box (box, text, 64, nonce, server_public, client_secret);
assert (rc == 0);
// Create Box [C + nonce + vouch + host](C'->S')
memcpy (nonce, (byte *) "CurveCP-client-I", 16);
memcpy (nonce + 16, &cn_nonce, 8);
memset (text, 0, sizeof (text));
memcpy (text + 32, client_public, 32);
memcpy (text + 64, vouch_nonce, 16);
memcpy (text + 80, box + 16, 48); // Vouch is still in box
memcpy (text + 128, (byte *) "localhost", 9);
rc = crypto_box (box, text, 384, nonce, cn_server, cn_secret);
assert (rc == 0);
// We have server short-term key and cookie, send INITIATE
cmd_t initiate = { CMD_C_INITIATE };
memcpy (initiate.c_initiate.cn_public, cn_public, 32);
memcpy (initiate.c_initiate.cn_cookie, cn_cookie, 96);
memcpy (initiate.c_initiate.cn_nonce, &cn_nonce, 8);
memcpy (initiate.c_initiate.box, box + 16, 368);
zmq_send (client, &initiate, 8 + sizeof (cmd_c_initiate_t), 0);
cn_nonce++;
// Send MESSAGE now with "Hello"
// Create message Box [M](C'->S'), max M is 256
// Don't bother splitting into two steps for this PoC
memcpy (nonce, (byte *) "CurveCP-client-M", 16);
memcpy (nonce + 16, &cn_nonce, 8);
memset (text, 0, 256);
memcpy (text + 32, "Hello", 5);
rc = crypto_box (box, text, 288, nonce, cn_server, cn_secret);
assert (rc == 0);
cmd_t message = { CMD_C_MESSAGE };
memcpy (message.c_message.cn_public, cn_public, 32);
memcpy (message.c_message.cn_nonce, &cn_nonce, 8);
memcpy (message.c_message.box, box + 16, 272);
zmq_send (client, &message, 8 + sizeof (cmd_c_message_t), 0);
cn_nonce++;
}
else
if (rc == 8 + sizeof (cmd_s_message_t)
&& memcmp (command.type, CMD_S_MESSAGE, 8) == 0) {
// Check MESSAGE command is valid
printf ("S:MESSAGE: ");
// Open Box Box [M](S'->C')
memcpy (nonce, (byte *) "CurveCP-server-M", 16);
memcpy (nonce + 16, command.s_message.cn_nonce, 8);
memset (box, 0, 16);
memcpy (box + 16, command.s_message.box, 272);
rc = crypto_box_open (text, box, 288, nonce, cn_server, cn_secret);
assert (rc == 0);
printf ("(reply=%s) ", text + 32);
puts ("OK");
puts ("-> 'Hello World' test successful");
break;
}
else
puts ("E: invalid server command, rejected");
}
zstr_send (pipe, "Test successful");
}
// Main loop starts server and client threads and then waits
// for a user interrupt.
int main (void)
{
zctx_t *ctx = zctx_new ();
assert (ctx);
// One server, one client for this proof of concept
void *server = zthread_fork (ctx, s_server, NULL);
void *client = zthread_fork (ctx, s_client, NULL);
// Client runs test and tells us all is OK
char *ready = zstr_recv (client);
free (ready);
zctx_destroy (&ctx);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment