From 3f35a946ca00996b354a73831ef51aa269e8e623 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 17 Apr 2014 22:06:12 +0200 Subject: Add CURVE authentification support for dabInputZMQ --- src/Makefile.am | 10 +++- src/ParserConfigfile.cpp | 7 ++- src/dabInputZmq.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++++++- src/dabInputZmq.h | 38 +++++++++++++ src/zmqinput-keygen.c | 107 +++++++++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 src/zmqinput-keygen.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 921c94f..738168c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,8 +20,6 @@ # You should have received a copy of the GNU General Public License # along with ODR-DabMux. If not, see . -bin_PROGRAMS=odr-dabmux odr-bridgetest - if IS_GIT_REPO GITVERSION_FLAGS = -DGITVERSION="\"`git describe`\"" else @@ -32,8 +30,12 @@ FEC_FLAGS = FEC_LIBS =-lfec if HAVE_ZEROMQ_TEST +bin_PROGRAMS=odr-dabmux odr-bridgetest zmqinput-keygen + ZMQ_LIBS =-lzmq else +bin_PROGRAMS=odr-dabmux odr-bridgetest + ZMQ_LIBS = endif @@ -95,3 +97,7 @@ odr_bridgetest_CFLAGS =-DBRIDGE_TEST odr_bridgetest_SOURCES =bridge.c \ crc.c crc.h +zmqinput_keygen_SOURCES = zmqinput-keygen.c +zmqinput_keygen_LDADD = $(ZMQ_LIBS) +zmqinput_keygen_CFLAGS = -Wall $(GITVERSION_FLAGS) + diff --git a/src/ParserConfigfile.cpp b/src/ParserConfigfile.cpp index c59f5e5..b477f1d 100644 --- a/src/ParserConfigfile.cpp +++ b/src/ParserConfigfile.cpp @@ -676,6 +676,7 @@ void setup_subchannel_from_ptree(dabSubchannel* subchan, " has no zmq-buffer defined!"; throw runtime_error(ss.str()); } + try { zmqconfig.prebuffering = pt.get("zmq-prebuffering"); } @@ -686,7 +687,11 @@ void setup_subchannel_from_ptree(dabSubchannel* subchan, throw runtime_error(ss.str()); } - zmqconfig.enable_encryption = false; + zmqconfig.curve_encoder_keyfile = pt.get("encoder-key",""); + zmqconfig.curve_secret_keyfile = pt.get("secret-key",""); + zmqconfig.curve_public_keyfile = pt.get("public-key",""); + + zmqconfig.enable_encryption = pt.get("encryption", 0); DabInputZmqAAC* inzmq = new DabInputZmqAAC(subchanuid, zmqconfig); diff --git a/src/dabInputZmq.cpp b/src/dabInputZmq.cpp index 753d6da..ee38440 100644 --- a/src/dabInputZmq.cpp +++ b/src/dabInputZmq.cpp @@ -67,13 +67,140 @@ using namespace std; extern StatsServer* global_stats; +int readkey(string& keyfile, char* key) +{ + int fd = open(keyfile.c_str(), O_RDONLY); + if (fd < 0) + return fd; + int ret = read(fd, key, CURVE_KEYLEN); + if (ret < 0) + return ret; + close(fd); + + /* It needs to be zero-terminated */ + key[CURVE_KEYLEN] = '\0'; + + return 0; +} + /***** Common functions (MPEG and AAC) ******/ -int DabInputZmqBase::open(const std::string inputUri) +/* If necessary, unbind the socket, then check the keys, + * if they are ok and encryption is required, set the + * keys to the socket, and finally bind the socket + * to the new address + */ +void DabInputZmqBase::rebind() { + if (! m_zmq_sock_bound_to.empty()) { + try { + m_zmq_sock.unbind(m_zmq_sock_bound_to.c_str()); + } + catch (zmq::error_t& err) { + etiLog.level(warn) << "ZMQ unbind for input " << m_name << " failed"; + } + } + + m_zmq_sock_bound_to = ""; + + /* Load each key independently */ + if (! m_config.curve_public_keyfile.empty()) { + int rc = readkey(m_config.curve_public_keyfile, m_curve_public_key); + + if (rc < 0) { + etiLog.level(warn) << "Invalid public key for input " << + m_name; + + INVALIDATE_KEY(m_curve_public_key); + } + } + + if (! m_config.curve_secret_keyfile.empty()) { + int rc = readkey(m_config.curve_secret_keyfile, m_curve_secret_key); + + if (rc < 0) { + etiLog.level(warn) << "Invalid secret key for input " << + m_name; + + INVALIDATE_KEY(m_curve_secret_key); + } + } + + if (! m_config.curve_encoder_keyfile.empty()) { + int rc = readkey(m_config.curve_encoder_keyfile, m_curve_encoder_key); + + if (rc < 0) { + etiLog.level(warn) << "Invalid encoder key for input " << + m_name; + + INVALIDATE_KEY(m_curve_encoder_key); + } + } + + /* If you want encryption, you need to have defined all + * key files + */ + if ( m_config.enable_encryption && + ( ! (KEY_VALID(m_curve_public_key) && + KEY_VALID(m_curve_secret_key) && + KEY_VALID(m_curve_encoder_key) ) ) ) { + throw std::runtime_error("When enabling encryption, all three " + "keyfiles must be set!"); + } + + if (m_config.enable_encryption) { + try { + /* We want to check that the encoder is the right one, + * so the encoder is the CURVE server. + */ + m_zmq_sock.setsockopt(ZMQ_CURVE_SERVERKEY, + m_curve_encoder_key, CURVE_KEYLEN); + } + catch (zmq::error_t& err) { + std::ostringstream os; + os << "ZMQ set encoder key for input " << m_name << " failed"; + throw std::runtime_error(os.str()); + } + + try { + m_zmq_sock.setsockopt(ZMQ_CURVE_PUBLICKEY, + m_curve_public_key, CURVE_KEYLEN); + } + catch (zmq::error_t& err) { + std::ostringstream os; + os << "ZMQ set public key for input " << m_name << " failed"; + throw std::runtime_error(os.str()); + } + + try { + m_zmq_sock.setsockopt(ZMQ_CURVE_SECRETKEY, + m_curve_secret_key, CURVE_KEYLEN); + } + catch (zmq::error_t& err) { + std::ostringstream os; + os << "ZMQ set secret key for input " << m_name << " failed"; + throw std::runtime_error(os.str()); + } + } + else { + try { + /* This forces the socket to go to the ZMQ_NULL auth + * mechanism + */ + const int no = 0; + m_zmq_sock.setsockopt(ZMQ_CURVE_SERVER, &no, sizeof(no)); + } + catch (zmq::error_t& err) { + std::ostringstream os; + os << "ZMQ remove keys for input " << m_name << " failed"; + throw std::runtime_error(os.str()); + } + + } + // Prepare the ZMQ socket to accept connections try { - m_zmq_sock.bind(inputUri.c_str()); + m_zmq_sock.bind(m_inputUri.c_str()); } catch (zmq::error_t& err) { std::ostringstream os; @@ -81,6 +208,8 @@ int DabInputZmqBase::open(const std::string inputUri) throw std::runtime_error(os.str()); } + m_zmq_sock_bound_to = m_inputUri; + try { m_zmq_sock.setsockopt(ZMQ_SUBSCRIBE, NULL, 0); } @@ -89,6 +218,14 @@ int DabInputZmqBase::open(const std::string inputUri) os << "ZMQ set socket options for input " << m_name << " failed"; throw std::runtime_error(os.str()); } +} + +int DabInputZmqBase::open(const std::string inputUri) +{ + m_inputUri = inputUri; + + /* Let caller handle exceptions when we open() */ + rebind(); // We want to appear in the statistics ! global_stats->registerInput(m_name); diff --git a/src/dabInputZmq.h b/src/dabInputZmq.h index 50357f5..871676e 100644 --- a/src/dabInputZmq.h +++ b/src/dabInputZmq.h @@ -76,6 +76,24 @@ // want. #define INPUT_ZMQ_MAX_BUFFER_SIZE (5*500) // 60s +/* The ZeroMQ Curve key is 40 bytes long in Z85 representation + * + * But we need to store it as zero-terminated string. + */ +#define CURVE_KEYLEN 40 + +/* helper to invalidate a key */ +#define INVALIDATE_KEY(k) memset(k, 0, CURVE_KEYLEN+1) + +/* Verification for key validity */ +#define KEY_VALID(k) (k[0] != '\0') + +/* Read a key from file into key + * + * Returns 0 on success, negative value on failure + */ +int readkey(std::string& keyfile, char* key); + struct dab_input_zmq_config_t { /* The size of the internal buffer, measured in number @@ -116,12 +134,18 @@ class DabInputZmqBase : public DabInputBase, public RemoteControllable { : RemoteControllable(name), m_zmq_context(1), m_zmq_sock(m_zmq_context, ZMQ_SUB), + m_zmq_sock_bound_to(""), m_bitrate(0), m_enable_input(true), m_config(config), m_prebuf_current(0) { RC_ADD_PARAMETER(enable, "If the input is enabled. Set to zero to empty the buffer."); + + /* Set all keys to zero */ + INVALIDATE_KEY(m_curve_public_key); + INVALIDATE_KEY(m_curve_secret_key); + INVALIDATE_KEY(m_curve_encoder_key); } virtual int open(const std::string inputUri); @@ -139,8 +163,15 @@ class DabInputZmqBase : public DabInputBase, public RemoteControllable { protected: virtual int readFromSocket(size_t framesize) = 0; + virtual void rebind(); + zmq::context_t m_zmq_context; zmq::socket_t m_zmq_sock; // handle for the zmq socket + + /* If the socket is bound, this saves the endpoint, + * otherwise, it's an empty string + */ + std::string m_zmq_sock_bound_to; int m_bitrate; /* set this to zero to empty the input buffer */ @@ -151,6 +182,13 @@ class DabInputZmqBase : public DabInputBase, public RemoteControllable { dab_input_zmq_config_t m_config; + /* Key management, keys need to be zero-terminated */ + char m_curve_public_key[CURVE_KEYLEN+1]; + char m_curve_secret_key[CURVE_KEYLEN+1]; + char m_curve_encoder_key[CURVE_KEYLEN+1]; + + std::string m_inputUri; + private: int m_prebuf_current; }; diff --git a/src/zmqinput-keygen.c b/src/zmqinput-keygen.c new file mode 100644 index 0000000..0169837 --- /dev/null +++ b/src/zmqinput-keygen.c @@ -0,0 +1,107 @@ +/* Create a key file for the ZMQinput + * and save to file. + * + * Copyright (c) 2014 Matthias P. Braendli + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +int main(int argc, char** argv) +{ + if (argc == 1) { + fprintf(stderr, "Generate a random key for dabInputZMQ and save it to two files.\n\n"); + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char* keyname = argv[1]; + + if (strlen(keyname) > 2048) { + fprintf(stderr, "name too long\n"); + return 1; + } + + char pubkeyfile[strlen(keyname) + 10]; + char seckeyfile[strlen(keyname) + 10]; + + sprintf(pubkeyfile, "%s.pub", keyname); + sprintf(seckeyfile, "%s.sec", keyname); + + char public_key [41]; + char secret_key [41]; + int rc = zmq_curve_keypair(public_key, secret_key); + if (rc != 0) { + fprintf(stderr, "key generation failed\n"); + } + + int fdpub = creat(pubkeyfile, S_IRUSR | S_IWUSR); + if (fdpub < 0) { + perror("File creation failed"); + return 1; + } + + int fdsec = creat(seckeyfile, S_IRUSR | S_IWUSR); + if (fdsec < 0) { + perror("File creation failed"); + return 1; + } + + int r = write(fdpub, public_key, 41); + + int ret = 0; + + if (r < 0) { + perror("write failed"); + ret = 1; + } + else if (r != 41) { + fprintf(stderr, "Not enough key data written to file\n"); + ret = 1; + } + + close(fdpub); + + if (ret == 0) { + r = write(fdsec, secret_key, 41); + + if (r < 0) { + perror("write failed"); + ret = 1; + } + else if (r != 41) { + fprintf(stderr, "Not enough key data written to file\n"); + ret = 1; + } + } + + close(fdsec); + + return ret; +} + -- cgit v1.2.3