|
|
@@ -0,0 +1,446 @@ |
|
|
// 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 |
|
|
|
|
|
// No message in initiate |
|
|
// Messages max 256 bytes |
|
|
// Long terms keys |
|
|
// Single client connection |
|
|
|
|
|
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; |
|
|
} |