From 37b99c0b4b706437e4298de7440c681b1b6ff218 Mon Sep 17 00:00:00 2001 From: Martin Hurton Date: Tue, 18 Jun 2013 23:38:24 +0200 Subject: [PATCH] Implement ZMTP/3.0 CURVE handshake --- CMakeLists.txt | 2 + src/Makefile.am | 4 + src/curve_client.cpp | 344 +++++++++++++++++++++++++++ src/curve_client.hpp | 109 +++++++++ src/curve_server.cpp | 547 +++++++++++++++++++++++++++++++++++++++++++ src/curve_server.hpp | 113 +++++++++ src/options.hpp | 9 + 7 files changed, 1128 insertions(+) create mode 100644 src/curve_client.cpp create mode 100644 src/curve_client.hpp create mode 100644 src/curve_server.cpp create mode 100644 src/curve_server.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index faa8d138..756eb8ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,6 +291,8 @@ set(cxx-sources address.cpp clock.cpp ctx.cpp + curve_client.cpp + curve_server.cpp dealer.cpp devpoll.cpp dist.cpp diff --git a/src/Makefile.am b/src/Makefile.am index b3e9d166..75842367 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,6 +15,8 @@ libzmq_la_SOURCES = \ command.hpp \ config.hpp \ ctx.hpp \ + curve_client.hpp \ + curve_server.hpp \ decoder.hpp \ devpoll.hpp \ dist.hpp \ @@ -89,6 +91,8 @@ libzmq_la_SOURCES = \ address.cpp \ clock.cpp \ ctx.cpp \ + curve_client.cpp \ + curve_server.cpp \ devpoll.cpp \ dist.cpp \ epoll.cpp \ diff --git a/src/curve_client.cpp b/src/curve_client.cpp new file mode 100644 index 00000000..6a4851ef --- /dev/null +++ b/src/curve_client.cpp @@ -0,0 +1,344 @@ +/* + Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file + + This file is part of 0MQ. + + 0MQ 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. + + 0MQ 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 . +*/ + +#include "platform.hpp" + +#ifdef HAVE_LIBSODIUM + +#include + +#ifdef ZMQ_HAVE_WINDOWS +#include "windows.hpp" +#endif + +#include "msg.hpp" +#include "session_base.hpp" +#include "err.hpp" +#include "curve_client.hpp" +#include "wire.hpp" + +zmq::curve_client_t::curve_client_t (const options_t &options_) : + mechanism_t (options_), + state (send_hello) +{ + zmq_assert (options_.public_key_size == crypto_box_PUBLICKEYBYTES); + memcpy (public_key, options_.public_key, crypto_box_PUBLICKEYBYTES); + + zmq_assert (options_.secret_key_size == crypto_box_SECRETKEYBYTES); + memcpy (secret_key, options_.secret_key, crypto_box_SECRETKEYBYTES); + + zmq_assert (options_.server_key_size == crypto_box_PUBLICKEYBYTES); + memcpy (server_key, options_.server_key, crypto_box_PUBLICKEYBYTES); + + // Generate short-term key pair + const int rc = crypto_box_keypair (cn_public, cn_secret); + zmq_assert (rc == 0); +} + +zmq::curve_client_t::~curve_client_t () +{ +} + +int zmq::curve_client_t::next_handshake_message (msg_t *msg_) +{ + int rc = 0; + + switch (state) { + case send_hello: + rc = hello_msg (msg_); + if (rc == 0) + state = expect_welcome; + break; + case send_initiate: + rc = initiate_msg (msg_); + if (rc == 0) + state = expect_ready; + break; + default: + errno = EAGAIN; + rc = -1; + } + return rc; +} + +int zmq::curve_client_t::process_handshake_message (msg_t *msg_) +{ + int rc = 0; + + switch (state) { + case expect_welcome: + rc = process_welcome (msg_); + if (rc == 0) + state = send_initiate; + break; + case expect_ready: + rc = process_ready (msg_); + if (rc == 0) + state = connected; + break; + default: + errno = EAGAIN; + rc = -1; + } + if (rc == 0) { + rc = msg_->close (); + errno_assert (rc == 0); + rc = msg_->init (); + errno_assert (rc == 0); + } + return rc; +} + +bool zmq::curve_client_t::is_handshake_complete () const +{ + return state == connected; +} + +int zmq::curve_client_t::hello_msg (msg_t *msg_) +{ + uint8_t hello_nonce [crypto_box_NONCEBYTES]; + uint8_t hello_plaintext [crypto_box_ZEROBYTES + 64]; + uint8_t hello_box [crypto_box_BOXZEROBYTES + 80]; + + // Prepare the full nonce + memcpy (hello_nonce, "CurveZMQHELLO---", 16); + memcpy (hello_nonce + 16, &cn_nonce, 8); + + // Create Box [64 * %x0](C'->S) + memset (hello_plaintext, 0, sizeof hello_plaintext); + + int rc = crypto_box (hello_box, hello_plaintext, + sizeof hello_plaintext, + hello_nonce, server_key, cn_secret); + zmq_assert (rc == 0); + + rc = msg_->init_size (200); + errno_assert (rc == 0); + uint8_t *hello = static_cast (msg_->data ()); + + memcpy (hello, "HELLO ", 8); + // CurveZMQ major and minor version numbers + memcpy (hello + 8, "\1\0", 2); + // Anti-amplification padding + memset (hello + 10, 0, 70); + // Client public connection key + memcpy (hello + 80, cn_public, crypto_box_PUBLICKEYBYTES); + // Short nonce, prefixed by "CurveZMQHELLO---" + memcpy (hello + 112, hello_nonce + 16, 8); + // Signature, Box [64 * %x0](C'->S) + memcpy (hello + 120, hello_box + crypto_box_BOXZEROBYTES, 80); + + cn_nonce++; + + return 0; +} + +int zmq::curve_client_t::process_welcome (msg_t *msg_) +{ + if (msg_->size () != 168) { + errno = EPROTO; + return -1; + } + + const uint8_t * welcome = static_cast (msg_->data ()); + if (memcmp (welcome, "WELCOME ", 8)) { + errno = EPROTO; + return -1; + } + + uint8_t welcome_nonce [crypto_box_NONCEBYTES]; + uint8_t welcome_plaintext [crypto_box_ZEROBYTES + 128]; + uint8_t welcome_box [crypto_box_BOXZEROBYTES + 144]; + + // Open Box [S' + cookie](C'->S) + memset (welcome_box, 0, crypto_box_BOXZEROBYTES); + memcpy (welcome_box + crypto_box_BOXZEROBYTES, welcome + 24, 144); + + memcpy (welcome_nonce, "WELCOME-", 8); + memcpy (welcome_nonce + 8, welcome + 8, 16); + + int rc = crypto_box_open (welcome_plaintext, welcome_box, + sizeof welcome_box, + welcome_nonce, server_key, cn_secret); + if (rc != 0) { + errno = EPROTO; + return -1; + } + + memcpy (cn_server, welcome_plaintext + crypto_box_ZEROBYTES, 32); + memcpy (cn_cookie, welcome_plaintext + crypto_box_ZEROBYTES + 32, 16 + 80); + + // Message independent precomputation + rc = crypto_box_beforenm (cn_precom, cn_server, cn_secret); + zmq_assert (rc == 0); + + return 0; +} + +int zmq::curve_client_t::initiate_msg (msg_t *msg_) +{ + uint8_t vouch_nonce [crypto_box_NONCEBYTES]; + uint8_t vouch_plaintext [crypto_box_ZEROBYTES + 32]; + uint8_t vouch_box [crypto_box_BOXZEROBYTES + 48]; + + // Create vouch = Box [C'](C->S) + memset (vouch_plaintext, 0, crypto_box_ZEROBYTES); + memcpy (vouch_plaintext + crypto_box_ZEROBYTES, cn_public, 32); + + memcpy (vouch_nonce, "VOUCH---", 8); + randombytes (vouch_nonce + 8, 16); + + int rc = crypto_box (vouch_box, vouch_plaintext, + sizeof vouch_plaintext, + vouch_nonce, server_key, secret_key); + zmq_assert (rc == 0); + + uint8_t initiate_nonce [crypto_box_NONCEBYTES]; + uint8_t initiate_plaintext [crypto_box_ZEROBYTES + 96 + 256]; + uint8_t initiate_box [crypto_box_BOXZEROBYTES + 112 + 256]; + + // Create Box [C + vouch + metadata](C'->S') + memset (initiate_plaintext, 0, crypto_box_ZEROBYTES); + memcpy (initiate_plaintext + crypto_box_ZEROBYTES, public_key, 32); + memcpy (initiate_plaintext + crypto_box_ZEROBYTES + 32, + vouch_nonce + 8, 16); + memcpy (initiate_plaintext + crypto_box_ZEROBYTES + 48, + vouch_box + crypto_box_BOXZEROBYTES, 48); + + uint8_t *ptr = initiate_plaintext + crypto_box_ZEROBYTES + 96; + + // Add socket type property + const char *socket_type = socket_type_string (options.type); + ptr += add_property (ptr, "Socket-Type", socket_type, strlen (socket_type)); + + // Add identity property + if (options.type == ZMQ_REQ + || options.type == ZMQ_DEALER + || options.type == ZMQ_ROUTER) + ptr += add_property (ptr, "Identity", + options.identity, options.identity_size); + + const size_t mlen = ptr - initiate_plaintext; + + memcpy (initiate_nonce, "CurveZMQINITIATE", 16); + memcpy (initiate_nonce + 16, &cn_nonce, 8); + + rc = crypto_box (initiate_box, initiate_plaintext, + mlen, initiate_nonce, cn_server, cn_secret); + zmq_assert (rc == 0); + + rc = msg_->init_size (112 + mlen - crypto_box_BOXZEROBYTES); + errno_assert (rc == 0); + + uint8_t *initiate = static_cast (msg_->data ()); + + memcpy (initiate, "INITIATE", 8); + // Cookie provided by the server in the WELCOME command + memcpy (initiate + 8, cn_cookie, 96); + // Short nonce, prefixed by "CurveZMQINITIATE" + memcpy (initiate + 104, &cn_nonce, 8); + // Box [C + vouch + metadata](C'->S') + memcpy (initiate + 112, initiate_box + crypto_box_BOXZEROBYTES, + mlen - crypto_box_BOXZEROBYTES); + + cn_nonce++; + + return 0; +} + +int zmq::curve_client_t::process_ready (msg_t *msg_) +{ + if (msg_->size () < 32) { + errno = EPROTO; + return -1; + } + + const uint8_t *ready = static_cast (msg_->data ()); + if (memcmp (ready, "READY ", 8)) { + errno = EPROTO; + return -1; + } + + const size_t clen = (msg_->size () - 16) + crypto_box_BOXZEROBYTES; + + uint8_t ready_nonce [crypto_box_NONCEBYTES]; + uint8_t ready_plaintext [crypto_box_ZEROBYTES + 256]; + uint8_t ready_box [crypto_box_BOXZEROBYTES + 16 + 256]; + + memset (ready_box, 0, crypto_box_BOXZEROBYTES); + memcpy (ready_box + crypto_box_BOXZEROBYTES, + ready + 16, clen - crypto_box_BOXZEROBYTES); + + memcpy (ready_nonce, "CurveZMQREADY---", 16); + memcpy (ready_nonce + 16, ready + 8, 8); + + int rc = crypto_box_open_afternm (ready_plaintext, ready_box, + clen, ready_nonce, cn_precom); + + if (rc != 0) { + errno = EPROTO; + return -1; + } + + rc = parse_property_list (ready_plaintext + crypto_box_ZEROBYTES, + clen - crypto_box_ZEROBYTES); + return rc; +} + +int zmq::curve_client_t::parse_property_list (const uint8_t *ptr, + size_t bytes_left) +{ + while (bytes_left > 1) { + const size_t name_length = static_cast (*ptr); + ptr += 1; + bytes_left -= 1; + if (bytes_left < name_length) + break; + + const std::string name = std::string ((const char *) ptr, name_length); + ptr += name_length; + bytes_left -= name_length; + if (bytes_left < 4) + break; + + const size_t value_length = static_cast (get_uint32 (ptr)); + ptr += 4; + bytes_left -= 4; + if (bytes_left < value_length) + break; + + const uint8_t * const value = ptr; + ptr += value_length; + bytes_left -= value_length; + + if (name == "Socket-Type") { + // TODO: Implement socket type checking + } + else + if (name == "Identity" && options.recv_identity) + set_peer_identity (value, value_length); + } + if (bytes_left > 0) { + errno = EPROTO; + return -1; + } + return 0; +} + +#endif diff --git a/src/curve_client.hpp b/src/curve_client.hpp new file mode 100644 index 00000000..254d94f0 --- /dev/null +++ b/src/curve_client.hpp @@ -0,0 +1,109 @@ +/* + Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file + + This file is part of 0MQ. + + 0MQ 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. + + 0MQ 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 . +*/ + +#ifndef __ZMQ_CURVE_CLIENT_HPP_INCLUDED__ +#define __ZMQ_CURVE_CLIENT_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef HAVE_LIBSODIUM +#include + +#if crypto_box_NONCEBYTES != 24 \ +|| crypto_box_PUBLICKEYBYTES != 32 \ +|| crypto_box_SECRETKEYBYTES != 32 \ +|| crypto_box_ZEROBYTES != 32 \ +|| crypto_box_BOXZEROBYTES != 16 +#error "libsodium not built properly" +#endif + +#include "mechanism.hpp" +#include "options.hpp" + +namespace zmq +{ + + class msg_t; + class session_base_t; + + class curve_client_t : public mechanism_t + { + public: + + curve_client_t (const options_t &options_); + virtual ~curve_client_t (); + + // mechanism implementation + virtual int next_handshake_message (msg_t *msg_); + virtual int process_handshake_message (msg_t *msg_); + virtual bool is_handshake_complete () const; + + private: + + enum state_t { + send_hello, + expect_welcome, + send_initiate, + expect_ready, + connected + }; + + // Current FSM state + state_t state; + + // Our public key (C) + uint8_t public_key [crypto_box_PUBLICKEYBYTES]; + + // Our secret key (c) + uint8_t secret_key [crypto_box_SECRETKEYBYTES]; + + // Our short-term public key (C') + uint8_t cn_public [crypto_box_PUBLICKEYBYTES]; + + // Our short-term secret key (c') + uint8_t cn_secret [crypto_box_SECRETKEYBYTES]; + + // Server's public key (S) + uint8_t server_key [crypto_box_PUBLICKEYBYTES]; + + // Server's short-term public key (S') + uint8_t cn_server [crypto_box_PUBLICKEYBYTES]; + + // Cookie received from server + uint8_t cn_cookie [16 + 80]; + + // Intermediary buffer used to seepd up boxing and unboxing. + uint8_t cn_precom [crypto_box_BEFORENMBYTES]; + + // Nonce + uint64_t cn_nonce; + + int hello_msg (msg_t *msg_); + int process_welcome (msg_t *msg_); + int initiate_msg (msg_t *msg_); + int process_ready (msg_t *msg_); + + int parse_property_list (const uint8_t *ptr, size_t length); + }; + +} + +#endif + +#endif diff --git a/src/curve_server.cpp b/src/curve_server.cpp new file mode 100644 index 00000000..96c3e996 --- /dev/null +++ b/src/curve_server.cpp @@ -0,0 +1,547 @@ +/* + Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file + + This file is part of 0MQ. + + 0MQ 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. + + 0MQ 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 . +*/ + +#include "platform.hpp" + +#ifdef HAVE_LIBSODIUM +#include + +#ifdef ZMQ_HAVE_WINDOWS +#include "windows.hpp" +#endif + +#include "msg.hpp" +#include "session_base.hpp" +#include "err.hpp" +#include "curve_server.hpp" +#include "wire.hpp" + +zmq::curve_server_t::curve_server_t (session_base_t *session_, + const options_t &options_) : + mechanism_t (options_), + session (session_), + state (expect_hello), + cn_nonce (1) +{ + // Fetch our secret key from socket options + zmq_assert (options_.secret_key_size == crypto_box_SECRETKEYBYTES); + memcpy (secret_key, options_.secret_key, crypto_box_SECRETKEYBYTES); + + // Generate short-term key pair + const int rc = crypto_box_keypair (cn_public, cn_secret); + zmq_assert (rc == 0); +} + +zmq::curve_server_t::~curve_server_t () +{ +} + +int zmq::curve_server_t::next_handshake_message (msg_t *msg_) +{ + int rc = 0; + + switch (state) { + case send_welcome: + rc = welcome_msg (msg_); + if (rc == 0) + state = expect_initiate; + break; + case send_ready: + rc = ready_msg (msg_); + if (rc == 0) + state = connected; + break; + default: + errno = EAGAIN; + rc = -1; + break; + } + return rc; +} + +int zmq::curve_server_t::process_handshake_message (msg_t *msg_) +{ + int rc = 0; + + switch (state) { + case expect_hello: + rc = process_hello (msg_); + if (rc == 0) + state = send_welcome; + break; + case expect_initiate: + rc = process_initiate (msg_); + if (rc == 0) { + rc = receive_and_process_zap_reply (); + if (rc == 0) + state = send_ready; + else + if (errno == EAGAIN) { + rc = 0; + state = expect_zap_reply; + } + } + break; + default: + errno = EAGAIN; + rc = -1; + break; + } + if (rc == 0) { + rc = msg_->close (); + errno_assert (rc == 0); + rc = msg_->init (); + errno_assert (rc == 0); + } + return rc; +} + +int zmq::curve_server_t::zap_msg_available () +{ + if (state != expect_zap_reply) { + errno = EFSM; + return -1; + } + const int rc = receive_and_process_zap_reply (); + if (rc == 0) + state = send_ready; + return rc; +} + +bool zmq::curve_server_t::is_handshake_complete () const +{ + return state == connected; +} + +int zmq::curve_server_t::process_hello (msg_t *msg_) +{ + if (msg_->size () != 200) { + errno = EPROTO; + return -1; + } + + const uint8_t * const hello = static_cast (msg_->data ()); + if (memcmp (hello, "HELLO ", 8)) { + errno = EPROTO; + return -1; + } + + const uint8_t major = hello [8]; + const uint8_t minor = hello [9]; + + if (major != 1 || minor != 0) { + errno = EPROTO; + return -1; + } + + // Save client's short-term public key (C') + memcpy (cn_client, hello + 80, 32); + + uint8_t hello_nonce [crypto_box_NONCEBYTES]; + uint8_t hello_plaintext [crypto_box_ZEROBYTES + 64]; + uint8_t hello_box [crypto_box_BOXZEROBYTES + 80]; + + memcpy (hello_nonce, "CurveZMQHELLO---", 16); + memcpy (hello_nonce + 16, hello + 112, 8); + + memset (hello_box, 0, crypto_box_BOXZEROBYTES); + memcpy (hello_box + crypto_box_BOXZEROBYTES, hello + 120, 80); + + // Open Box [64 * %x0](C'->S) + int rc = crypto_box_open (hello_plaintext, hello_box, + sizeof hello_box, + hello_nonce, cn_client, secret_key); + if (rc != 0) { + errno = EPROTO; + return -1; + } + + return rc; +} + +int zmq::curve_server_t::welcome_msg (msg_t *msg_) +{ + uint8_t cookie_nonce [crypto_secretbox_NONCEBYTES]; + uint8_t cookie_plaintext [crypto_secretbox_ZEROBYTES + 64]; + uint8_t cookie_ciphertext [crypto_secretbox_BOXZEROBYTES + 80]; + + // Create full nonce for encryption + // 8-byte prefix plus 16-byte random nonce + memcpy (cookie_nonce, "COOKIE--", 8); + randombytes (cookie_nonce + 8, 16); + + // Generate cookie = Box [C' + s'](t) + memset (cookie_plaintext, 0, crypto_secretbox_ZEROBYTES); + memcpy (cookie_plaintext + crypto_secretbox_ZEROBYTES, + cn_client, 32); + memcpy (cookie_plaintext + crypto_secretbox_ZEROBYTES + 32, + cn_secret, 32); + + // Generate fresh cookie key + randombytes (cookie_key, crypto_secretbox_KEYBYTES); + + // Encrypt using symmetric cookie key + int rc = crypto_secretbox (cookie_ciphertext, cookie_plaintext, + sizeof cookie_plaintext, + cookie_nonce, cookie_key); + zmq_assert (rc == 0); + + uint8_t welcome_nonce [crypto_box_NONCEBYTES]; + uint8_t welcome_plaintext [crypto_box_ZEROBYTES + 128]; + uint8_t welcome_ciphertext [crypto_box_BOXZEROBYTES + 144]; + + // Create full nonce for encryption + // 8-byte prefix plus 16-byte random nonce + memcpy (welcome_nonce, "WELCOME-", 8); + randombytes (welcome_nonce + 8, crypto_box_NONCEBYTES - 8); + + // Create 144-byte Box [S' + cookie](S->C') + memset (welcome_plaintext, 0, crypto_box_ZEROBYTES); + memcpy (welcome_plaintext + crypto_box_ZEROBYTES, cn_public, 32); + memcpy (welcome_plaintext + crypto_box_ZEROBYTES + 32, + cookie_nonce + 8, 16); + memcpy (welcome_plaintext + crypto_box_ZEROBYTES + 48, + cookie_ciphertext + crypto_secretbox_BOXZEROBYTES, 80); + + rc = crypto_box (welcome_ciphertext, welcome_plaintext, + sizeof welcome_plaintext, + welcome_nonce, cn_client, secret_key); + zmq_assert (rc == 0); + + rc = msg_->init_size (168); + errno_assert (rc == 0); + + uint8_t * const welcome = static_cast (msg_->data ()); + memcpy (welcome, "WELCOME ", 8); + memcpy (welcome + 8, welcome_nonce + 8, 16); + memcpy (welcome + 24, welcome_ciphertext + crypto_box_BOXZEROBYTES, 144); + + return 0; +} + +int zmq::curve_server_t::process_initiate (msg_t *msg_) +{ + if (msg_->size () < 224) { + errno = EPROTO; + return -1; + } + + const uint8_t *initiate = static_cast (msg_->data ()); + if (memcmp (initiate, "INITIATE", 8)) { + errno = EPROTO; + return -1; + } + + uint8_t cookie_nonce [crypto_secretbox_NONCEBYTES]; + uint8_t cookie_plaintext [crypto_secretbox_ZEROBYTES + 64]; + uint8_t cookie_box [crypto_secretbox_BOXZEROBYTES + 80]; + + // Open Box [C' + s'](t) + memset (cookie_box, 0, crypto_secretbox_BOXZEROBYTES); + memcpy (cookie_box + crypto_secretbox_BOXZEROBYTES, initiate + 24, 80); + + memcpy (cookie_nonce, "COOKIE--", 8); + memcpy (cookie_nonce + 8, initiate + 8, 16); + + int rc = crypto_secretbox_open (cookie_plaintext, cookie_box, + sizeof cookie_box, + cookie_nonce, cookie_key); + if (rc != 0) { + errno = EPROTO; + return -1; + } + + // Check cookie plain text is as expected [C' + s'] + if (memcmp (cookie_plaintext + crypto_secretbox_ZEROBYTES, + cn_client, 32) + || memcmp (cookie_plaintext + crypto_secretbox_ZEROBYTES + 32, + cn_secret, 32)) { + errno = EAGAIN; + return -1; + } + + const size_t clen = (msg_->size () - 112) + crypto_box_BOXZEROBYTES; + + uint8_t initiate_nonce [crypto_box_NONCEBYTES]; + uint8_t initiate_plaintext [crypto_box_ZEROBYTES + 96 + 256]; + uint8_t initiate_box [crypto_box_BOXZEROBYTES + 112 + 256]; + + // Open Box [C + vouch + metadata](C'->S') + memset (initiate_box, 0, crypto_box_BOXZEROBYTES); + memcpy (initiate_box + crypto_box_BOXZEROBYTES, + initiate + 112, clen - crypto_box_BOXZEROBYTES); + + memcpy (initiate_nonce, "CurveZMQINITIATE", 16); + memcpy (initiate_nonce + 16, initiate + 104, 8); + + rc = crypto_box_open (initiate_plaintext, initiate_box, + clen, initiate_nonce, cn_client, cn_secret); + if (rc != 0) { + errno = EPROTO; + return -1; + } + + // Use ZAP protocol (RFC 27) to authenticate user. + rc = session->zap_connect (); + if (rc == -1) { + errno = EPROTO; + return -1; + } + + // Check the decrypted client public key + const uint8_t *client_key = initiate_plaintext + crypto_box_ZEROBYTES; + rc = send_zap_request (client_key); + if (rc != 0) { + errno = EPROTO; + return -1; + } + + uint8_t vouch_nonce [crypto_box_NONCEBYTES]; + uint8_t vouch_plaintext [crypto_box_ZEROBYTES + 32]; + uint8_t vouch_box [crypto_box_BOXZEROBYTES + 48]; + + // Open Box [C'](C->S) and check contents + memset (vouch_box, 0, crypto_box_BOXZEROBYTES); + memcpy (vouch_box + crypto_box_BOXZEROBYTES, + initiate_plaintext + crypto_box_ZEROBYTES + 48, 48); + + memcpy (vouch_nonce, "VOUCH---", 8); + memcpy (vouch_nonce + 8, + initiate_plaintext + crypto_box_ZEROBYTES + 32, 16); + + rc = crypto_box_open (vouch_plaintext, vouch_box, + sizeof vouch_box, + vouch_nonce, client_key, secret_key); + if (rc != 0) { + errno = EPROTO; + return -1; + } + + // What we decrypted must be the client's short-term public key + if (memcmp (vouch_plaintext + crypto_box_ZEROBYTES, cn_client, 32)) { + errno = EPROTO; + return -1; + } + + // Precompute connection secret from client key + rc = crypto_box_beforenm (cn_precom, cn_client, cn_secret); + zmq_assert (rc == 0); + + return parse_property_list (initiate_plaintext + crypto_box_ZEROBYTES + 96, + clen - crypto_box_ZEROBYTES - 96); +} + +int zmq::curve_server_t::ready_msg (msg_t *msg_) +{ + uint8_t ready_nonce [crypto_box_NONCEBYTES]; + uint8_t ready_plaintext [crypto_box_ZEROBYTES + 256]; + uint8_t ready_box [crypto_box_BOXZEROBYTES + 16 + 256]; + + // Create Box [metadata](S'->C') + memset (ready_plaintext, 0, crypto_box_ZEROBYTES); + uint8_t *ptr = ready_plaintext + crypto_box_ZEROBYTES; + + // Add socket type property + const char *socket_type = socket_type_string (options.type); + ptr += add_property (ptr, "Socket-Type", socket_type, strlen (socket_type)); + + // Add identity property + if (options.type == ZMQ_REQ + || options.type == ZMQ_DEALER + || options.type == ZMQ_ROUTER) + ptr += add_property (ptr, "Identity", + options.identity, options.identity_size); + + const size_t mlen = ptr - ready_plaintext; + + memcpy (ready_nonce, "CurveZMQREADY---", 16); + memcpy (ready_nonce + 16, &cn_nonce, 8); + + int rc = crypto_box_afternm (ready_box, ready_plaintext, + mlen, ready_nonce, cn_precom); + zmq_assert (rc == 0); + + rc = msg_->init_size (16 + mlen - crypto_box_BOXZEROBYTES); + errno_assert (rc == 0); + + uint8_t *ready = static_cast (msg_->data ()); + + memcpy (ready, "READY ", 8); + // Short nonce, prefixed by "CurveZMQREADY---" + memcpy (ready + 8, &cn_nonce, 8); + // Box [metadata](S'->C') + memcpy (ready + 16, ready_box + crypto_box_BOXZEROBYTES, + mlen - crypto_box_BOXZEROBYTES); + + cn_nonce++; + + return 0; +} + +int zmq::curve_server_t::send_zap_request (const uint8_t *key) +{ + int rc; + msg_t msg; + + // Address delimiter frame + rc = msg.init (); + errno_assert (rc == 0); + msg.set_flags (msg_t::more); + rc = session->write_zap_msg (&msg); + errno_assert (rc == 0); + + // Version frame + rc = msg.init_size (3); + errno_assert (rc == 0); + memcpy (msg.data (), "1.0", 3); + msg.set_flags (msg_t::more); + rc = session->write_zap_msg (&msg); + errno_assert (rc == 0); + + // Sequence frame + rc = msg.init_size (1); + errno_assert (rc == 0); + memcpy (msg.data (), "1", 1); + msg.set_flags (msg_t::more); + rc = session->write_zap_msg (&msg); + errno_assert (rc == 0); + + // Domain frame + rc = msg.init (); + errno_assert (rc == 0); + msg.set_flags (msg_t::more); + rc = session->write_zap_msg (&msg); + errno_assert (rc == 0); + + // Mechanism frame + rc = msg.init_size (5); + errno_assert (rc == 0); + memcpy (msg.data (), "CURVE", 5); + msg.set_flags (msg_t::more); + rc = session->write_zap_msg (&msg); + errno_assert (rc == 0); + + // Credentials frame + rc = msg.init_size (crypto_box_PUBLICKEYBYTES); + errno_assert (rc == 0); + memcpy (msg.data (), key, crypto_box_PUBLICKEYBYTES); + rc = session->write_zap_msg (&msg); + errno_assert (rc == 0); + + return 0; +} + +int zmq::curve_server_t::parse_property_list (const uint8_t *ptr, + size_t bytes_left) +{ + while (bytes_left > 1) { + const size_t name_length = static_cast (*ptr); + ptr += 1; + bytes_left -= 1; + if (bytes_left < name_length) + break; + + const std::string name = std::string ((const char *) ptr, name_length); + ptr += name_length; + bytes_left -= name_length; + if (bytes_left < 4) + break; + + const size_t value_length = static_cast (get_uint32 (ptr)); + ptr += 4; + bytes_left -= 4; + if (bytes_left < value_length) + break; + + const uint8_t * const value = ptr; + ptr += value_length; + bytes_left -= value_length; + + if (name == "Socket-Type") { + // TODO: Implement socket type checking + } + else + if (name == "Identity" && options.recv_identity) + set_peer_identity (value, value_length); + } + if (bytes_left > 0) { + errno = EPROTO; + return -1; + } + return 0; +} + +int zmq::curve_server_t::receive_and_process_zap_reply () +{ + int rc = 0; + msg_t msg [6]; + + for (int i = 0; i < 6; i++) { + rc = msg [i].init (); + errno_assert (rc == 0); + } + + for (int i = 0; i < 6; i++) { + rc = session->read_zap_msg (&msg [i]); + if (rc == -1) + break; + if ((msg [i].flags () & msg_t::more) == (i < 5? 0: msg_t::more)) { + errno = EPROTO; + rc = -1; + break; + } + } + + if (rc != 0) + goto error; + + // Address delimiter frame + if (msg [0].size () > 0) { + errno = EPROTO; + goto error; + } + + // Version frame + if (msg [1].size () != 3 || memcmp (msg [1].data (), "1.0", 3)) { + errno = EPROTO; + goto error; + } + + // Sequence number frame + if (msg [2].size () != 1 || memcmp (msg [2].data (), "1", 1)) { + errno = EPROTO; + goto error; + } + + // Status code frame + if (msg [3].size () != 3 || memcmp (msg [3].data (), "200", 3)) { + errno = EACCES; + goto error; + } + +error: + for (int i = 0; i < 6; i++) { + const int rc2 = msg [i].close (); + errno_assert (rc2 == 0); + } + + return rc; +} + +#endif diff --git a/src/curve_server.hpp b/src/curve_server.hpp new file mode 100644 index 00000000..2d6b6cd0 --- /dev/null +++ b/src/curve_server.hpp @@ -0,0 +1,113 @@ +/* + Copyright (c) 2007-2013 Contributors as noted in the AUTHORS file + + This file is part of 0MQ. + + 0MQ 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. + + 0MQ 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 . +*/ + +#ifndef __ZMQ_CURVE_SERVER_HPP_INCLUDED__ +#define __ZMQ_CURVE_SERVER_HPP_INCLUDED__ + +#include "platform.hpp" + +#ifdef HAVE_LIBSODIUM +#include + +#if crypto_box_NONCEBYTES != 24 \ +|| crypto_box_PUBLICKEYBYTES != 32 \ +|| crypto_box_SECRETKEYBYTES != 32 \ +|| crypto_box_ZEROBYTES != 32 \ +|| crypto_box_BOXZEROBYTES != 16 \ +|| crypto_secretbox_NONCEBYTES != 24 \ +|| crypto_secretbox_ZEROBYTES != 32 \ +|| crypto_secretbox_BOXZEROBYTES != 16 +#error "libsodium not built properly" +#endif + +#include "mechanism.hpp" +#include "options.hpp" + +namespace zmq +{ + + class msg_t; + class session_base_t; + + class curve_server_t : public mechanism_t + { + public: + + curve_server_t (session_base_t *session_, + const options_t &options_); + virtual ~curve_server_t (); + + // mechanism implementation + virtual int next_handshake_message (msg_t *msg_); + virtual int process_handshake_message (msg_t *msg_); + virtual int zap_msg_available (); + virtual bool is_handshake_complete () const; + + private: + + enum state_t { + expect_hello, + send_welcome, + expect_initiate, + expect_zap_reply, + send_ready, + connected + }; + + session_base_t * const session; + + // Current FSM state + state_t state; + + uint64_t cn_nonce; + + // Our secret key (s) + uint8_t secret_key [crypto_box_SECRETKEYBYTES]; + + // Our short-term public key (S') + uint8_t cn_public [crypto_box_PUBLICKEYBYTES]; + + // Our short-term secret key (s') + uint8_t cn_secret [crypto_box_SECRETKEYBYTES]; + + // Client's short-term public key (C') + uint8_t cn_client [crypto_box_PUBLICKEYBYTES]; + + // Key used to produce cookie + uint8_t cookie_key [crypto_secretbox_KEYBYTES]; + + // Intermediary buffer used to speed up boxing and unboxing. + uint8_t cn_precom [crypto_box_BEFORENMBYTES]; + + int process_hello (msg_t *msg_); + int welcome_msg (msg_t *msg_); + int process_initiate (msg_t *msg_); + int ready_msg (msg_t *msg_); + + int send_zap_request (const uint8_t *key); + int receive_and_process_zap_reply (); + int parse_property_list (const uint8_t *ptr, size_t length); + }; + +} + +#endif + +#endif + diff --git a/src/options.hpp b/src/options.hpp index 312d5093..29d38787 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -132,6 +132,15 @@ namespace zmq std::string plain_username; std::string plain_password; + unsigned char public_key_size; + unsigned char public_key [32]; + + unsigned char secret_key_size; + unsigned char secret_key [32]; + + unsigned char server_key_size; + unsigned char server_key [32]; + // ID of the socket. int socket_id; };