Skip to content

Instantly share code, notes, and snippets.

@hintjens
Last active May 23, 2016 00:41
Show Gist options
  • Select an option

  • Save hintjens/5137685 to your computer and use it in GitHub Desktop.

Select an option

Save hintjens/5137685 to your computer and use it in GitHub Desktop.

Revisions

  1. hintjens revised this gist Mar 11, 2013. 1 changed file with 0 additions and 5 deletions.
    5 changes: 0 additions & 5 deletions zcurve-poc.c
    Original file line number Diff line number Diff line change
    @@ -87,11 +87,6 @@ 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)
    {
  2. hintjens created this gist Mar 11, 2013.
    446 changes: 446 additions & 0 deletions zcurve-poc.c
    Original file line number Diff line number Diff line change
    @@ -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;
    }