aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/example.mux25
-rw-r--r--src/Makefile.am10
-rw-r--r--src/ParserConfigfile.cpp7
-rw-r--r--src/dabInputZmq.cpp141
-rw-r--r--src/dabInputZmq.h38
-rw-r--r--src/zmqinput-keygen.c107
6 files changed, 323 insertions, 5 deletions
diff --git a/doc/example.mux b/doc/example.mux
index 26a231c..c1657a1 100644
--- a/doc/example.mux
+++ b/doc/example.mux
@@ -141,6 +141,31 @@ subchannels {
; Network latency jitter can make it temporarily go lower or higher.
; Encoder clock drift will make the buffer either slowly fill or
; empty, which will create intermittent glitches.
+
+
+ ; the ZMQ inputs support encryption using the CURVE method.
+ ; The multiplexer must have a public and a private key, which
+ ; can be shared among several zmq inputs.
+ ;
+ ; each encoder also has a public and private key, and the
+ ; encoder *public* key has to be known to the multiplexer.
+ ; Using this system, the multiplexer can be sure that
+ ; only the encoder possessing the right secret key can
+ ; connect here. This inhibits third parties to hijack the
+ ; input.
+
+ ; by default, it is disabled, set encryption to 1 to enable
+ encryption 1
+
+ ; the multiplexer key pair. Keep these secret.
+ secret-key "keys/mux.sec"
+ public-key "keys/mux.pub"
+
+ ; The public key from the encoder. Only the encoder you want
+ ; to accept must know the corresponding secret key.
+ encoder-key "keys/encoder1.pub"
+
+ ; key pairs can be generated using the zmqinput-keygen tool.
}
sub-ri2 {
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 <http://www.gnu.org/licenses/>.
-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<int>("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<string>("encoder-key","");
+ zmqconfig.curve_secret_keyfile = pt.get<string>("secret-key","");
+ zmqconfig.curve_public_keyfile = pt.get<string>("public-key","");
+
+ zmqconfig.enable_encryption = pt.get<int>("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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <zmq_utils.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+
+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 <name>\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;
+}
+