diff options
author | glenvt18 <glenvt18@gmail.com> | 2017-10-28 22:06:44 +0300 |
---|---|---|
committer | Alfred E. Heggestad <alfred.heggestad@gmail.com> | 2017-10-28 21:06:44 +0200 |
commit | b576ea027970338a418be2adb831c2dcf6484b2b (patch) | |
tree | 6d64d8eb50bf190b9b313a400872e1089aa13a8e /modules | |
parent | 4c21ac818a9fed61d02b9f0966645b18265166bb (diff) |
modules/gzrtp: new module using GNU ZRTP C++ library (#314)
Diffstat (limited to 'modules')
-rw-r--r-- | modules/gzrtp/gzrtp.cpp | 220 | ||||
-rw-r--r-- | modules/gzrtp/messages.cpp | 256 | ||||
-rw-r--r-- | modules/gzrtp/module.mk | 34 | ||||
-rw-r--r-- | modules/gzrtp/session.cpp | 214 | ||||
-rw-r--r-- | modules/gzrtp/session.h | 50 | ||||
-rw-r--r-- | modules/gzrtp/stream.cpp | 824 | ||||
-rw-r--r-- | modules/gzrtp/stream.h | 139 |
7 files changed, 1737 insertions, 0 deletions
diff --git a/modules/gzrtp/gzrtp.cpp b/modules/gzrtp/gzrtp.cpp new file mode 100644 index 0000000..19aaf9d --- /dev/null +++ b/modules/gzrtp/gzrtp.cpp @@ -0,0 +1,220 @@ +/** + * @file gzrtp.cpp GNU ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include <string.h> + +#include <libzrtpcpp/ZRtp.h> + +#include "session.h" +#include "stream.h" + + +/** + * @defgroup gzrtp gzrtp + * + * ZRTP: Media Path Key Agreement for Unicast Secure RTP + * + * Experimental support for ZRTP + * + * See http://tools.ietf.org/html/rfc6189 + * + * + * This module is using GNU ZRTP C++ library + * + * https://github.com/wernerd/ZRTPCPP + * + * Configuration options: + * + \verbatim + zrtp_parallel {yes,no} # Start all streams at once + \endverbatim + * + */ + + +static ZRTPConfig *s_zrtp_config = NULL; + + +struct menc_sess { + Session *session; +}; + + +struct menc_media { + Stream *stream; +}; + + +static void session_destructor(void *arg) +{ + struct menc_sess *st = (struct menc_sess *)arg; + + delete st->session; +} + + +static void media_destructor(void *arg) +{ + struct menc_media *st = (struct menc_media *)arg; + + delete st->stream; +} + + +static int session_alloc(struct menc_sess **sessp, struct sdp_session *sdp, + bool offerer, menc_error_h *errorh, void *arg) +{ + struct menc_sess *st; + (void)offerer; + (void)errorh; + (void)arg; + int err = 0; + + if (!sessp || !sdp) + return EINVAL; + + st = (struct menc_sess *)mem_zalloc(sizeof(*st), session_destructor); + if (!st) + return ENOMEM; + + st->session = new Session(*s_zrtp_config); + if (!st->session) + err = ENOMEM; + + if (err) + mem_deref(st); + else + *sessp = st; + + return err; +} + + +static int media_alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_media *st; + int err = 0; + StreamMediaType med_type; + const char *med_name; + + if (!stp || !sess || !sess->session || proto != IPPROTO_UDP) + return EINVAL; + + st = *stp; + if (st) + goto start; + + st = (struct menc_media *)mem_zalloc(sizeof(*st), media_destructor); + if (!st) + return ENOMEM; + + med_name = sdp_media_name(sdpm); + if (str_cmp(med_name, "audio") == 0) + med_type = MT_AUDIO; + else if (str_cmp(med_name, "video") == 0) + med_type = MT_VIDEO; + else if (str_cmp(med_name, "text") == 0) + med_type = MT_TEXT; + else if (str_cmp(med_name, "application") == 0) + med_type = MT_APPLICATION; + else if (str_cmp(med_name, "message") == 0) + med_type = MT_MESSAGE; + else + med_type = MT_UNKNOWN; + + st->stream = sess->session->create_stream( + *s_zrtp_config, + (struct udp_sock *)rtpsock, + (struct udp_sock *)rtcpsock, + rtp_sess_ssrc(rtp), med_type); + if (!st->stream) { + err = ENOMEM; + goto out; + } + + st->stream->sdp_encode(sdpm); + + out: + if (err) { + mem_deref(st); + return err; + } + else + *stp = st; + + start: + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + st->stream->sdp_decode(sdpm); + err = sess->session->start_stream(st->stream); + if (err) { + warning("zrtp: stream start failed: %d\n", err); + } + } + + return err; +} + + +static struct menc menc_zrtp = { + LE_INIT, "zrtp", "RTP/AVP", session_alloc, media_alloc +}; + + +static const struct cmd cmdv[] = { + {"zrtp_verify", 0, CMD_PRM, "Verify ZRTP SAS <session ID>", + Session::cmd_verify_sas }, + {"zrtp_unverify", 0, CMD_PRM, "Unverify ZRTP SAS <session ID>", + Session::cmd_unverify_sas }, +}; + + +static int module_init(void) +{ + char config_path[256]; + int err = 0; + + err = conf_path_get(config_path, sizeof(config_path)); + if (err) { + warning("zrtp: could not get config path: %m\n", err); + return err; + } + + s_zrtp_config = new ZRTPConfig(conf_cur(), config_path); + if (!s_zrtp_config) + return ENOMEM; + + menc_register(baresip_mencl(), &menc_zrtp); + + return cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv)); +} + + +static int module_close(void) +{ + delete s_zrtp_config; + s_zrtp_config = NULL; + + cmd_unregister(baresip_commands(), cmdv); + + menc_unregister(&menc_zrtp); + + return 0; +} + + +extern "C" EXPORT_SYM const struct mod_export DECL_EXPORTS(gzrtp) = { + "gzrtp", + "menc", + module_init, + module_close +}; diff --git a/modules/gzrtp/messages.cpp b/modules/gzrtp/messages.cpp new file mode 100644 index 0000000..db3a7db --- /dev/null +++ b/modules/gzrtp/messages.cpp @@ -0,0 +1,256 @@ +/** + * @file messages.cpp GNU ZRTP: Engine messages + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include <libzrtpcpp/ZRtp.h> + +#include "stream.h" + + +using namespace GnuZrtpCodes; + + +#define NO_MESSAGE "NO MESSAGE DEFINED" + + +static const char *info_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case InfoHelloReceived: + msg = "Hello received and prepared a Commit, " + "ready to get peer's hello hash"; + break; + case InfoCommitDHGenerated: + msg = "Commit: Generated a public DH key"; + break; + case InfoRespCommitReceived: + msg = "Responder: Commit received, preparing DHPart1"; + break; + case InfoDH1DHGenerated: + msg = "DH1Part: Generated a public DH key"; + break; + case InfoInitDH1Received: + msg = "Initiator: DHPart1 received, preparing DHPart2"; + break; + case InfoRespDH2Received: + msg = "Responder: DHPart2 received, preparing Confirm1"; + break; + case InfoInitConf1Received: + msg = "Initiator: Confirm1 received, preparing Confirm2"; + break; + case InfoRespConf2Received: + msg = "Responder: Confirm2 received, preparing Conf2Ack"; + break; + case InfoRSMatchFound: + msg = "At least one retained secret matches - security OK"; + break; + case InfoSecureStateOn: + msg = "Entered secure state"; + break; + case InfoSecureStateOff: + msg = "No more security for this session"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *warning_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case WarningDHAESmismatch: + msg = "Commit contains an AES256 cipher but does not offer a " + "Diffie-Helman 4096 - not used DH4096 was discarded"; + break; + case WarningGoClearReceived: + msg = "Received a GoClear message"; + break; + case WarningDHShort: + msg = "Hello offers an AES256 cipher but does not offer a " + "Diffie-Helman 4096- not used DH4096 was discarded"; + break; + case WarningNoRSMatch: + msg = "No retained shared secrets available - must verify SAS"; + break; + case WarningCRCmismatch: + msg = "Internal ZRTP packet checksum mismatch - " + "packet dropped"; + break; + case WarningSRTPauthError: + msg = "Dropping packet because SRTP authentication failed!"; + break; + case WarningSRTPreplayError: + msg = "Dropping packet because SRTP replay check failed!"; + break; + case WarningNoExpectedRSMatch: + msg = "Valid retained shared secrets availabe but no matches " + "found - must verify SAS"; + break; + case WarningNoExpectedAuxMatch: + msg = "Our AUX secret was set but the other peer's AUX secret " + "does not match ours"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *severe_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case SevereHelloHMACFailed: + msg = "Hash HMAC check of Hello failed!"; + break; + case SevereCommitHMACFailed: + msg = "Hash HMAC check of Commit failed!"; + break; + case SevereDH1HMACFailed: + msg = "Hash HMAC check of DHPart1 failed!"; + break; + case SevereDH2HMACFailed: + msg = "Hash HMAC check of DHPart2 failed!"; + break; + case SevereCannotSend: + msg = "Cannot send data - connection or peer down?"; + break; + case SevereProtocolError: + msg = "Internal protocol error occured!"; + break; + case SevereNoTimer: + msg = "Cannot start a timer - internal resources exhausted?"; + break; + case SevereTooMuchRetries: + msg = "Too much retries during ZRTP negotiation - connection " + "or peer down?"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +static const char *zrtp_msg(int32_t subcode) +{ + const char *msg; + + switch (subcode) { + case MalformedPacket: + msg = "Malformed packet (CRC OK, but wrong structure)"; + break; + case CriticalSWError: + msg = "Critical software error"; + break; + case UnsuppZRTPVersion: + msg = "Unsupported ZRTP version"; + break; + case HelloCompMismatch: + msg = "Hello components mismatch"; + break; + case UnsuppHashType: + msg = "Hash type not supported"; + break; + case UnsuppCiphertype: + msg = "Cipher type not supported"; + break; + case UnsuppPKExchange: + msg = "Public key exchange not supported"; + break; + case UnsuppSRTPAuthTag: + msg = "SRTP auth. tag not supported"; + break; + case UnsuppSASScheme: + msg = "SAS scheme not supported"; + break; + case NoSharedSecret: + msg = "No shared secret available, DH mode required"; + break; + case DHErrorWrongPV: + msg = "DH Error: bad pvi or pvr ( == 1, 0, or p-1)"; + break; + case DHErrorWrongHVI: + msg = "DH Error: hvi != hashed data"; + break; + case SASuntrustedMiTM: + msg = "Received relayed SAS from untrusted MiTM"; + break; + case ConfirmHMACWrong: + msg = "Auth. Error: Bad Confirm pkt HMAC"; + break; + case NonceReused: + msg = "Nonce reuse"; + break; + case EqualZIDHello: + msg = "Equal ZIDs in Hello"; + break; + case GoCleatNotAllowed: + msg = "GoClear packet received, but not allowed"; + break; + default: + msg = NO_MESSAGE; + break; + } + + return msg; +} + + +void Stream::print_message(GnuZrtpCodes::MessageSeverity severity, + int32_t subcode) +{ + switch (severity) { + case Info: + debug("zrtp: INFO<%s>: %s\n", + media_name(), info_msg(subcode)); + break; + case Warning: + warning("zrtp: WARNING<%s>: %s\n", + media_name(), warning_msg(subcode)); + break; + case Severe: + warning("zrtp: SEVERE<%s>: %s\n", + media_name(), severe_msg(subcode)); + break; + case ZrtpError: + warning("zrtp: ZRTP_ERR<%s>: %s\n", + media_name(), zrtp_msg(subcode)); + break; + default: + return; + } +} + + +const char *Stream::media_name() const +{ + switch (m_media_type) { + case MT_AUDIO: return "audio"; + case MT_VIDEO: return "video"; + case MT_TEXT: return "text"; + case MT_APPLICATION: return "application"; + case MT_MESSAGE: return "message"; + default: return "UNKNOWN"; + } +} diff --git a/modules/gzrtp/module.mk b/modules/gzrtp/module.mk new file mode 100644 index 0000000..b95b61d --- /dev/null +++ b/modules/gzrtp/module.mk @@ -0,0 +1,34 @@ +# +# module.mk +# +# Copyright (C) 2010 - 2017 Creytiv.com +# + +# +# To build libzrtpcppcore run the following commands: +# +# git clone https://github.com/wernerd/ZRTPCPP.git +# cd ZRTPCPP +# mkdir build +# cd build +# cmake -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DCORE_LIB=1 -DSDES=1 \ +# -DBUILD_STATIC=1 .. +# make +# + +# GNU ZRTP C++ library (ZRTPCPP) source directory +ZRTP_PATH ?= ../ZRTPCPP + +ZRTP_LIB := $(shell find $(ZRTP_PATH) -name libzrtpcppcore.a) + +MOD := gzrtp +$(MOD)_SRCS += gzrtp.cpp session.cpp stream.cpp messages.cpp +$(MOD)_LFLAGS += $(ZRTP_LIB) -lstdc++ +$(MOD)_CXXFLAGS += \ + -I$(ZRTP_PATH) \ + -I$(ZRTP_PATH)/zrtp \ + -I$(ZRTP_PATH)/srtp + +$(MOD)_CXXFLAGS += -O2 -Wall -fPIC + +include mk/mod.mk diff --git a/modules/gzrtp/session.cpp b/modules/gzrtp/session.cpp new file mode 100644 index 0000000..422bee4 --- /dev/null +++ b/modules/gzrtp/session.cpp @@ -0,0 +1,214 @@ +/** + * @file session.h GNU ZRTP: Session class implementation + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> + +#include <re.h> +#include <baresip.h> + +#include "session.h" + + +std::vector<Session *> Session::s_sessl; + + +Session::Session(const ZRTPConfig& config) + : m_start_parallel(config.start_parallel) + , m_master(NULL) + , m_encrypted(0) +{ + int newid = 1; + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if ((*it)->id() >= newid) + newid = (*it)->id() + 1; + } + + m_id = newid; + + s_sessl.push_back(this); + + debug("zrtp: New session <%d>\n", id()); +} + + +Session::~Session() +{ + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if (*it == this) { + s_sessl.erase(it); + break; + } + } + + debug("zrtp: Session <%d> is destroyed\n", id()); +} + + +Stream *Session::create_stream(const ZRTPConfig& config, + udp_sock *rtpsock, + udp_sock *rtcpsock, + uint32_t local_ssrc, + StreamMediaType media_type) +{ + int err = 0; + + Stream *st = new Stream (err, config, this, rtpsock, rtcpsock, + local_ssrc, media_type); + if (!st || err) { + delete st; + return NULL; + } + + return st; +} + + +int Session::start_stream(Stream *stream) +{ + if (stream->started()) + return 0; + + m_streams.push_back(stream); + + // Start all streams in parallel using DH mode. This is a kind of + // probing. The first stream to receive HelloACK will be the master + // stream. If disabled, only the first stream starts in DH (master) + // mode. + if (m_start_parallel) { + if (m_master && m_encrypted) + // If we already have a master in secure state, + // start in multistream mode + return stream->start(m_master); + else + // Start a new stream in DH mode + return stream->start(NULL); + } + else { + if (!m_master) { + // Start the first stream in DH mode + m_master = stream; + return stream->start(NULL); + } + else if (m_encrypted) { + // Master is in secure state; multistream + return stream->start(m_master); + } + } + + return 0; +} + + +bool Session::request_master(Stream *stream) +{ + if (!m_start_parallel) + return true; + + if (m_master) + return false; + + // This is the first stream to receive HelloACK. It will be + // used as the master for the other streams in the session. + m_master = stream; + // Stop other DH-mode streams. They will be started in the + // multistream mode after the master enters secure state. + for (std::vector<Stream *>::iterator it = m_streams.begin(); + it != m_streams.end(); ++it) { + + if (*it != m_master) { + (*it)->stop(); + } + } + + return true; +} + + +void Session::on_secure(Stream *stream) +{ + ++m_encrypted; + + if (m_encrypted == m_streams.size() && m_master) { + info("zrtp: All streams are encrypted (%s), " + "SAS is [%s] (%s)\n", + m_master->get_ciphers(), + m_master->get_sas(), + (m_master->sas_verified())? "verified" : "NOT VERIFIED"); + return; + } + + if (stream != m_master) + return; + + // Master stream has just entered secure state. Start other + // streams in the multistream mode. + + debug("zrtp: Starting other streams (%d)\n", m_streams.size() - 1); + + for (std::vector<Stream *>::iterator it = m_streams.begin(); + it != m_streams.end(); ++it) { + + if (*it != m_master) { + (*it)->start(m_master); + } + } +} + + +int Session::cmd_verify_sas(struct re_printf *pf, void *arg) +{ + return cmd_sas(true, pf, arg); +} + + +int Session::cmd_unverify_sas(struct re_printf *pf, void *arg) +{ + return cmd_sas(false, pf, arg); +} + + +int Session::cmd_sas(bool verify, struct re_printf *pf, void *arg) +{ + const struct cmd_arg *carg = (struct cmd_arg *)arg; + (void)pf; + int id = -1; + Session *sess = NULL; + + if (str_isset(carg->prm)) + id = atoi(carg->prm); + + for (std::vector<Session *>::iterator it = s_sessl.begin(); + it != s_sessl.end(); ++it) { + + if ((*it)->id() == id) { + sess = *it; + break; + } + } + + if (!sess) { + warning("zrtp: No session with id %d\n", id); + return EINVAL; + } + + if (!sess->m_master) { + warning("zrtp: No master stream for the session with id %d\n", + sess->id()); + return EFAULT; + } + + sess->m_master->verify_sas(verify); + + info("zrtp: Session <%d>: SAS [%s] is %s\n", sess->id(), + sess->m_master->get_sas(), + (sess->m_master->sas_verified())? "verified" : "NOT VERIFIED"); + + return 0; +} + diff --git a/modules/gzrtp/session.h b/modules/gzrtp/session.h new file mode 100644 index 0000000..90c12d6 --- /dev/null +++ b/modules/gzrtp/session.h @@ -0,0 +1,50 @@ +/** + * @file session.h GNU ZRTP: Session class + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __SESSION_H +#define __SESSION_H + + +#include "stream.h" + + +class Stream; +class ZRTPConfig; + +class Session { +public: + Session(const ZRTPConfig& config); + + ~Session(); + + Stream *create_stream(const ZRTPConfig& config, + udp_sock *rtpsock, + udp_sock *rtcpsock, + uint32_t local_ssrc, + StreamMediaType media_type); + + int start_stream(Stream *stream); + int id() const { return m_id; } + + bool request_master(Stream *stream); + void on_secure(Stream *stream); + + static int cmd_verify_sas(struct re_printf *pf, void *arg); + static int cmd_unverify_sas(struct re_printf *pf, void *arg); + static int cmd_sas(bool verify, struct re_printf *pf, void *arg); + +private: + static std::vector<Session *> s_sessl; + + const bool m_start_parallel; + int m_id; + std::vector<Stream *> m_streams; + Stream *m_master; + unsigned int m_encrypted; +}; + + +#endif // __SESSION_H + diff --git a/modules/gzrtp/stream.cpp b/modules/gzrtp/stream.cpp new file mode 100644 index 0000000..b367112 --- /dev/null +++ b/modules/gzrtp/stream.cpp @@ -0,0 +1,824 @@ +/** + * @file stream.cpp GNU ZRTP: Stream class implementation + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#include <stdint.h> +#include <pthread.h> + +#include <re.h> +#include <baresip.h> + +#include <libzrtpcpp/ZRtp.h> +#include <libzrtpcpp/ZrtpStateClass.h> +#include <srtp/CryptoContext.h> +#include <srtp/CryptoContextCtrl.h> +#include <srtp/SrtpHandler.h> + +#include "session.h" +#include "stream.h" + + +// A burst of SRTP/SRTCP errors enough to display a warning +// Set to 1 to display all warnings +#define SRTP_ERR_BURST_THRESHOLD 20 + + +enum { + PRESZ = 36 /* Preamble size for TURN/STUN header */ +}; + + +enum pkt_type { + PKT_TYPE_UNKNOWN = 0, + PKT_TYPE_RTP = 1, + PKT_TYPE_RTCP = 2, + PKT_TYPE_ZRTP = 4 +}; + + +static enum pkt_type get_packet_type(const struct mbuf *mb) +{ + uint8_t b, pt; + uint32_t magic; + + if (mbuf_get_left(mb) < 8) + return PKT_TYPE_UNKNOWN; + + b = mbuf_buf(mb)[0]; + + if (127 < b && b < 192) { + pt = mbuf_buf(mb)[1] & 0x7f; + if (72 <= pt && pt <= 76) + return PKT_TYPE_RTCP; + else + return PKT_TYPE_RTP; + } + else { + memcpy(&magic, &mbuf_buf(mb)[4], 4); + magic = ntohl(magic); + if (magic == ZRTP_MAGIC) + return PKT_TYPE_ZRTP; + } + + return PKT_TYPE_UNKNOWN; +} + + +ZRTPConfig::ZRTPConfig(const struct conf *conf, const char *conf_dir) +{ + zrtp.setStandardConfig(); + + str_ncpy(client_id, "baresip/gzrtp", sizeof(client_id)); + + re_snprintf(zid_filename, sizeof(zid_filename), + "%s/gzrtp.zid", conf_dir); + + start_parallel = true; + (void)conf_get_bool(conf, "zrtp_parallel", &start_parallel); +} + +SRTPStat::SRTPStat(const Stream *st, bool srtcp, uint64_t threshold) + : m_stream(st) + , m_control(srtcp) + , m_threshold(threshold) +{ + reset(); +} + + +void SRTPStat::update(int32_t ret_code, bool quiet) +{ + const char *err_msg; + uint64_t *burst; + + // SrtpHandler::unprotect/unprotectCtrl return codes + switch (ret_code) { + case 1: + ++m_ok; + m_decode_burst = 0; + m_auth_burst = 0; + m_replay_burst = 0; + return; + case 0: + ++m_decode; + burst = &m_decode_burst; + err_msg = "packet decode error"; + break; + case -1: + ++m_auth; + burst = &m_auth_burst; + err_msg = "authentication failed"; + break; + case -2: + ++m_replay; + burst = &m_replay_burst; + err_msg = "replay check failed"; + break; + default: + warning("zrtp: Unknown return code from unprotect: %d\n", + ret_code); + return; + } + + ++(*burst); + if (*burst == m_threshold) { + *burst = 0; + + if (!quiet) + warning("zrtp: Stream <%s>: %s %s, %d packets\n", + m_stream->media_name(), + (m_control)? "SRTCP" : "SRTP", + err_msg, + m_threshold); + } +} + + +void SRTPStat::reset() +{ + m_ok = 0; + m_decode = 0; m_auth = 0; m_replay = 0; + m_decode_burst = 0; m_auth_burst = 0; m_replay_burst = 0; +} + + +Stream::Stream(int& err, const ZRTPConfig& config, Session *session, + udp_sock *rtpsock, udp_sock *rtcpsock, + uint32_t local_ssrc, StreamMediaType media_type) + : m_session(session) + , m_zrtp(NULL) + , m_started(false) + , m_local_ssrc(local_ssrc) + , m_peer_ssrc(0) + , m_rtpsock(NULL) + , m_rtcpsock(NULL) + , m_uh_rtp(NULL) + , m_uh_rtcp(NULL) + , m_media_type(media_type) + , m_send_cc(NULL) + , m_recv_cc(NULL) + , m_send_cc_ctrl(NULL) + , m_recv_cc_ctrl(NULL) + , m_srtp_stat(this, false, SRTP_ERR_BURST_THRESHOLD) + , m_srtcp_stat(this, true, SRTP_ERR_BURST_THRESHOLD) +{ + err = 0; + + m_zrtp_seq = 1; // TODO: randomize + sa_init(&m_raddr, AF_INET); + tmr_init(&m_zrtp_timer); + + pthread_mutexattr_t attr; + err = pthread_mutexattr_init(&attr); + err |= pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + err |= pthread_mutex_init(&m_zrtp_mutex, &attr); + if (err) + return; + + int layer = 10; // above zero + if (rtpsock) { + m_rtpsock = (struct udp_sock *)mem_ref(rtpsock); + err |= udp_register_helper(&m_uh_rtp, rtpsock, layer, + Stream::udp_helper_send_cb, + Stream::udp_helper_recv_cb, + this); + } + if (rtcpsock && (rtcpsock != rtpsock)) { + m_rtcpsock = (struct udp_sock *)mem_ref(rtcpsock); + err |= udp_register_helper(&m_uh_rtcp, rtcpsock, layer, + Stream::udp_helper_send_cb, + Stream::udp_helper_recv_cb, + this); + } + if (err) + return; + + ZIDCache* zf = getZidCacheInstance(); + if (!zf->isOpen()) { + if (zf->open((char *)config.zid_filename) == -1) { + warning("zrtp: Couldn't open/create ZID file %s\n", + config.zid_filename); + err = ENOENT; + return; + } + } + + m_zrtp = new ZRtp((uint8_t *)zf->getZid(), this, config.client_id, + (ZrtpConfigure *)&config.zrtp, false, false); + if (!m_zrtp) { + err = ENOMEM; + return; + } + + return; +} + + +Stream::~Stream() +{ + stop(); + + delete m_zrtp; + + mem_deref(m_uh_rtp); + mem_deref(m_uh_rtcp); + mem_deref(m_rtpsock); + mem_deref(m_rtcpsock); + + pthread_mutex_destroy(&m_zrtp_mutex); + + tmr_cancel(&m_zrtp_timer); +} + + +int Stream::start(Stream *master) +{ + if (started()) + return EPERM; + + if (master) { + ZRtp *zrtp_master; + + std::string params = + master->m_zrtp->getMultiStrParams(&zrtp_master); + if (params.empty()) + return EPROTO; + + m_zrtp->setMultiStrParams(params, zrtp_master); + } + + debug("zrtp: Starting <%s> stream%s\n", media_name(), + (m_zrtp->isMultiStream())? " (multistream)" : ""); + + m_srtp_stat.reset(); + m_srtcp_stat.reset(); + m_sas.clear(); + m_ciphers.clear(); + + m_started = true; + m_zrtp->startZrtpEngine(); + + return 0; +} + + +void Stream::stop() +{ + if (!started()) + return; + + m_started = false; + + // If we got only a small amount of valid SRTP packets after ZRTP + // negotiation then assume that our peer couldn't store the RS data, + // thus make sure we have a second retained shared secret available. + // Refer to RFC 6189bis, chapter 4.6.1 50 packets are about 1 second + // of audio data + if (!m_zrtp->isMultiStream() && m_recv_cc && m_srtp_stat.ok() < 20) { + + debug("zrtp: Stream <%s>: received too few valid SRTP " + "packets (%u), storing RS2\n", + media_name(), m_srtp_stat.ok()); + + m_zrtp->setRs2Valid(); + } + + debug("zrtp: Stopping <%s> stream\n", media_name()); + + m_zrtp->stopZrtp(); + + delete m_send_cc; + m_send_cc = NULL; + + delete m_recv_cc; + m_recv_cc = NULL; + + delete m_send_cc_ctrl; + m_send_cc_ctrl = NULL; + + delete m_recv_cc_ctrl; + m_recv_cc_ctrl = NULL; + + debug("zrtp: Stream <%s> stopped\n", media_name()); +} + + +int Stream::sdp_encode(struct sdp_media *sdpm) +{ + // TODO: signaling hash + return 0; +} + + +int Stream::sdp_decode(const struct sdp_media *sdpm) +{ + if (sa_isset(sdp_media_raddr(sdpm), SA_ALL)) { + m_raddr = *sdp_media_raddr(sdpm); + } + // TODO: signaling hash + + return 0; +} + + +bool Stream::udp_helper_send_cb(int *err, struct sa *src, struct mbuf *mb, + void *arg) +{ + Stream *st = (Stream *)arg; + + if (st) + return st->udp_helper_send(err, src, mb); + + return false; +} + + +bool Stream::udp_helper_send(int *err, struct sa *src, struct mbuf *mb) +{ + if (!started()) + return false; + + enum pkt_type ptype = get_packet_type(mb); + size_t len = mbuf_get_left(mb); + size_t newlen = 0; + bool rc = false; + + if (ptype == PKT_TYPE_RTCP && m_send_cc_ctrl && len > 8) { + + int32_t extra = (mbuf_get_space(mb) > len)? + mbuf_get_space(mb) - len : 0; + + if (m_send_cc_ctrl->getTagLength() + 4 + + m_send_cc_ctrl->getMkiLength() > extra) { + warning("zrtp: Stream <%s>: No space left for SRTCP " + "packet\n", media_name()); + *err = ENOMEM; + return true; + } + + rc = SrtpHandler::protectCtrl(m_send_cc_ctrl, mbuf_buf(mb), + len, &newlen); + } + else if (ptype == PKT_TYPE_RTP && m_send_cc && len > RTP_HEADER_SIZE) { + + int32_t extra = (mbuf_get_space(mb) > len)? + mbuf_get_space(mb) - len : 0; + + if (m_send_cc->getTagLength() + + m_send_cc->getMkiLength() > extra) { + warning("zrtp: Stream <%s>: No space left for SRTP " + "packet\n", media_name()); + *err = ENOMEM; + return true; + } + + rc = SrtpHandler::protect(m_send_cc, mbuf_buf(mb), + len, &newlen); + } + else + return false; + + if (!rc) { + warning("zrtp: protect/protectCtrl failed, len: %u\n", len); + // drop + return true; + } + + if (newlen > mbuf_get_space(mb)) { + // this should never happen + error_msg("zrtp: udp_helper_send: length > space (%u > %u)\n", + newlen, mbuf_get_space(mb)); + abort(); + } + + mb->end = mb->pos + newlen; + + return false; +} + + +bool Stream::udp_helper_recv_cb(struct sa *src, struct mbuf *mb, void *arg) +{ + Stream *st = (Stream *)arg; + + if (st) + return st->udp_helper_recv(src, mb); + + return false; +} + + +bool Stream::udp_helper_recv(struct sa *src, struct mbuf *mb) +{ + if (!started()) + return false; + + enum pkt_type ptype = get_packet_type(mb); + size_t len = mbuf_get_left(mb); + size_t newlen = 0; + uint32_t rc; + + if (ptype == PKT_TYPE_RTCP && m_recv_cc_ctrl) { + + rc = SrtpHandler::unprotectCtrl(m_recv_cc_ctrl, mbuf_buf(mb), + len, &newlen); + + m_srtcp_stat.update(rc); + + if (rc != 1) + // drop + return true; + } + else if (ptype == PKT_TYPE_RTP && m_recv_cc) { + + rc = SrtpHandler::unprotect(m_recv_cc, mbuf_buf(mb), + len, &newlen, NULL); + + m_srtp_stat.update(rc); + + if (rc == 1) { + // Got a good SRTP, check state and if in WaitConfAck + // (an Initiator state) then simulate a conf2Ack, + // refer to RFC 6189, chapter 4.6, last paragraph + if (m_zrtp->inState(WaitConfAck)) + m_zrtp->conf2AckSecure(); + } + else { + // drop + return true; + } + } + else if (ptype == PKT_TYPE_ZRTP) { + return recv_zrtp(mb); + } + else + return false; + + mb->end = mb->pos + newlen; + + return false; +} + + +// <RTP> + <ext. header> + <ZRTP message type> + CRC32 +#define ZRTP_MIN_PACKET_LENGTH (RTP_HEADER_SIZE + 4 + 8 + 4) + +bool Stream::recv_zrtp(struct mbuf *mb) +{ + uint32_t crc32; + uint8_t *buf = mbuf_buf(mb); + size_t size = mbuf_get_left(mb); + + if (size < ZRTP_MIN_PACKET_LENGTH) { + warning("zrtp: incoming packet size (%d) is too small\n", + size); + return false; + } + + // check CRC + memcpy(&crc32, buf + size - 4, 4); + crc32 = ntohl(crc32); + if (!zrtpCheckCksum(buf, size - 4, crc32)) { + sendInfo(GnuZrtpCodes::Warning, + GnuZrtpCodes::WarningCRCmismatch); + return false; + } + + // store peer's SSRC for creating the CryptoContext + memcpy(&m_peer_ssrc, buf + 8, 4); + m_peer_ssrc = ntohl(m_peer_ssrc); + + m_zrtp->processZrtpMessage(buf + RTP_HEADER_SIZE, m_peer_ssrc, size); + + return true; +} + + +void Stream::verify_sas(bool verify) +{ + if (verify) + m_zrtp->SASVerified(); + else + m_zrtp->resetSASVerified(); +} + + +bool Stream::sas_verified() +{ + return m_zrtp->isSASVerified(); +} + + +// +// callbacks +// + + +int32_t Stream::sendDataZRTP(const uint8_t* data, int32_t length) +{ + struct mbuf *mb; + uint8_t *crc_buf; + uint32_t crc32; + size_t start_pos = PRESZ; + int err = 0; + + if (!sa_isset(&m_raddr, SA_ALL)) + return 0; + + mb = mbuf_alloc(start_pos + RTP_HEADER_SIZE + length); + if (!mb) + return 0; + + mbuf_set_end(mb, start_pos); + mbuf_set_pos(mb, start_pos); + crc_buf = mbuf_buf(mb); + + // write RTP header + err = mbuf_write_u8(mb, 0x10); + err |= mbuf_write_u8(mb, 0x00); + err |= mbuf_write_u16(mb, htons(m_zrtp_seq++)); + err |= mbuf_write_u32(mb, htonl(ZRTP_MAGIC)); + err |= mbuf_write_u32(mb, htonl(m_local_ssrc)); + + // copy ZRTP message data + err |= mbuf_write_mem(mb, data, length - 4); + + // compute CRC + crc32 = zrtpGenerateCksum(crc_buf, RTP_HEADER_SIZE + length - 4); + crc32 = zrtpEndCksum(crc32); + + // store CRC + err |= mbuf_write_u32(mb, htonl(crc32)); + if (err) + goto out; + + // send ZRTP packet using RTP socket + mbuf_set_pos(mb, start_pos); + err = udp_send_helper(m_rtpsock, &m_raddr, mb, m_uh_rtp); + if (err) + warning("zrtp: udp_send_helper: %m\n", err); + + out: + mem_deref(mb); + + return (err == 0); +} + + +void Stream::zrtp_timer_cb(void *arg) +{ + Stream *s = (Stream *)arg; + + s->m_zrtp->processTimeout(); +} + + +int32_t Stream::activateTimer(int32_t time) +{ + tmr_start(&m_zrtp_timer, time, &Stream::zrtp_timer_cb, this); + return 1; +} + + +int32_t Stream::cancelTimer() +{ + tmr_cancel(&m_zrtp_timer); + return 1; +} + + +void Stream::sendInfo(GnuZrtpCodes::MessageSeverity severity, int32_t subCode) +{ + print_message(severity, subCode); + + if (severity == GnuZrtpCodes::Info) { + if (subCode == GnuZrtpCodes::InfoSecureStateOn) { + m_session->on_secure(this); + } + else if (subCode == GnuZrtpCodes::InfoHelloReceived && + !m_zrtp->isMultiStream()) { + + m_session->request_master(this); + } + } +} + + +bool Stream::srtpSecretsReady(SrtpSecret_t* secrets, EnableSecurity part) +{ + CryptoContext *cc = NULL; + CryptoContextCtrl *cc_ctrl = NULL; + int cipher; + int authn; + int auth_key_len; + const uint8_t *key, *salt; + uint32_t key_len, salt_len; + + debug("zrtp: Stream <%s>: secrets are ready for %s\n", + media_name(), + (part == ForSender)? "sender" : "receiver"); + + switch (secrets->authAlgorithm) { + case Sha1: + authn = SrtpAuthenticationSha1Hmac; + auth_key_len = 20; + break; + case Skein: + authn = SrtpAuthenticationSkeinHmac; + auth_key_len = 32; + break; + default: + return false; + } + + switch (secrets->symEncAlgorithm) { + case Aes: + cipher = SrtpEncryptionAESCM; + break; + case TwoFish: + cipher = SrtpEncryptionTWOCM; + break; + default: + return false; + } + + if (part == ForSender) { + // To encrypt packets: intiator uses initiator keys, + // responder uses responder keys + if (secrets->role == Initiator) { + key = secrets->keyInitiator; + key_len = secrets->initKeyLen / 8; + salt = secrets->saltInitiator; + salt_len = secrets->initSaltLen / 8; + } + else { + key = secrets->keyResponder; + key_len = secrets->respKeyLen / 8; + salt = secrets->saltResponder; + salt_len = secrets->respSaltLen / 8; + } + } + else if (part == ForReceiver) { + // To decrypt packets: intiator uses responder keys, + // responder initiator keys + if (secrets->role == Initiator) { + key = secrets->keyResponder; + key_len = secrets->respKeyLen / 8; + salt = secrets->saltResponder; + salt_len = secrets->respSaltLen / 8; + } + else { + key = secrets->keyInitiator; + key_len = secrets->initKeyLen / 8; + salt = secrets->saltInitiator; + salt_len = secrets->initSaltLen / 8; + } + } + else { + return false; + } + + cc = new CryptoContext( + 0, // SSRC (used for lookup) + 0, // Roll-Over-Counter (ROC) + 0L, // keyderivation << 48, + cipher, // encryption algo + authn, // authtentication algo + (uint8_t *)key, // Master Key + key_len, // Master Key length + (uint8_t *)salt, // Master Salt + salt_len, // Master Salt length + key_len, // encryption keyl + auth_key_len, // authentication key len + salt_len, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + cc_ctrl = new CryptoContextCtrl( + 0, // SSRC (used for lookup) + cipher, // encryption algo + authn, // authtentication algo + (uint8_t *)key, // Master Key + key_len, // Master Key length + (uint8_t *)salt, // Master Salt + salt_len, // Master Salt length + key_len, // encryption keyl + auth_key_len, // authentication key len + salt_len, // session salt len + secrets->srtpAuthTagLen / 8); // authentication tag lenA + + if (!cc || !cc_ctrl) { + delete cc; + delete cc_ctrl; + + return false; + } + + cc->deriveSrtpKeys(0L); + cc_ctrl->deriveSrtcpKeys(); + + if (part == ForSender) { + m_send_cc = cc; + m_send_cc_ctrl = cc_ctrl; + } + else { + m_recv_cc = cc; + m_recv_cc_ctrl = cc_ctrl; + } + + return true; +} + + +void Stream::srtpSecretsOff(EnableSecurity part) +{ + debug("zrtp: Stream <%s>: secrets are off for %s\n", + media_name(), + (part == ForSender)? "sender" : "receiver"); + + if (part == ForSender) { + delete m_send_cc; + delete m_send_cc_ctrl; + m_send_cc = NULL; + m_send_cc_ctrl = NULL; + } + + if (part == ForReceiver) { + delete m_recv_cc; + delete m_recv_cc_ctrl; + m_recv_cc = NULL; + m_recv_cc_ctrl = NULL; + } +} + + +void Stream::srtpSecretsOn(std::string c, std::string s, bool verified) +{ + m_sas = s; + m_ciphers = c; + + if (s.empty()) { + info("zrtp: Stream <%s> is encrypted (%s)\n", + media_name(), c.c_str()); + } + else { + info("zrtp: Stream <%s> is encrypted (%s), " + "SAS is [%s] (%s)\n", + media_name(), c.c_str(), s.c_str(), + (verified)? "verified" : "NOT VERIFIED"); + if (!verified) + warning("zrtp: SAS is not verified, type " + "'/zrtp_verify %d' to verify\n", + m_session->id()); + } +} + + +void Stream::handleGoClear() +{ +} + + +void Stream::zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity severity, + int32_t subCode) +{ +} + + +void Stream::zrtpNotSuppOther() +{ +} + + +void Stream::synchEnter() +{ + pthread_mutex_lock(&m_zrtp_mutex); +} + + +void Stream::synchLeave() +{ + pthread_mutex_unlock(&m_zrtp_mutex); +} + + +void Stream::zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info) +{ +} + + +void Stream::zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info) +{ +} + + +void Stream::signSAS(uint8_t* sasHash) +{ +} + + +bool Stream::checkSASSignature(uint8_t* sasHash) +{ + return true; +} + diff --git a/modules/gzrtp/stream.h b/modules/gzrtp/stream.h new file mode 100644 index 0000000..4f2f987 --- /dev/null +++ b/modules/gzrtp/stream.h @@ -0,0 +1,139 @@ +/** + * @file stream.h GNU ZRTP: Stream class + * + * Copyright (C) 2010 - 2017 Creytiv.com + */ +#ifndef __STREAM_H +#define __STREAM_H + + +#include <libzrtpcpp/ZRtp.h> + + +enum StreamMediaType { + MT_UNKNOWN = 0, + MT_AUDIO, + MT_VIDEO, + MT_TEXT, + MT_APPLICATION, + MT_MESSAGE +}; + + +class ZRTPConfig { +public: + ZRTPConfig(const struct conf *conf, const char *conf_dir); +private: + friend class Stream; + friend class Session; + + ZrtpConfigure zrtp; + + char client_id[CLIENT_ID_SIZE + 1]; + char zid_filename[256]; + + bool start_parallel; +}; + + +class Stream; + +class SRTPStat { +public: + SRTPStat(const Stream *st, bool srtcp, uint64_t threshold); + void update(int32_t ret_code, bool quiet = false); + void reset(); + uint64_t ok() { return m_ok; } +private: + const Stream *m_stream; + const bool m_control; + const uint64_t m_threshold; + uint64_t m_ok, m_decode, m_auth, m_replay; + uint64_t m_decode_burst, m_auth_burst, m_replay_burst; +}; + + +class Session; +class CryptoContext; +class CryptoContextCtrl; + +class Stream : public ZrtpCallback { +public: + Stream(int& err, const ZRTPConfig& config, Session *session, + udp_sock *rtpsock, udp_sock *rtcpsock, + uint32_t local_ssrc, StreamMediaType media_type); + + virtual ~Stream(); + + int start(Stream *master); + void stop(); + bool started() { return m_started; } + + int sdp_encode(struct sdp_media *sdpm); + int sdp_decode(const struct sdp_media *sdpm); + + const char *media_name() const; + + const char *get_sas() const { return m_sas.c_str(); } + const char *get_ciphers() const { return m_ciphers.c_str(); } + bool sas_verified(); + void verify_sas(bool verify); + +private: + static void zrtp_timer_cb(void *arg); + static bool udp_helper_send_cb(int *err, struct sa *src, + struct mbuf *mb, void *arg); + static bool udp_helper_recv_cb(struct sa *src, struct mbuf *mb, + void *arg); + + bool udp_helper_send(int *err, struct sa *src, struct mbuf *mb); + bool udp_helper_recv(struct sa *src, struct mbuf *mb); + bool recv_zrtp(struct mbuf *mb); + + void print_message(GnuZrtpCodes::MessageSeverity severity, + int32_t subcode); + + Session *m_session; + ZRtp *m_zrtp; + bool m_started; + struct tmr m_zrtp_timer; + pthread_mutex_t m_zrtp_mutex; + uint16_t m_zrtp_seq; + uint32_t m_local_ssrc, m_peer_ssrc; + struct sa m_raddr; + struct udp_sock *m_rtpsock, *m_rtcpsock; + struct udp_helper *m_uh_rtp; + struct udp_helper *m_uh_rtcp; + StreamMediaType m_media_type; + CryptoContext *m_send_cc, *m_recv_cc; + CryptoContextCtrl *m_send_cc_ctrl, *m_recv_cc_ctrl; + SRTPStat m_srtp_stat, m_srtcp_stat; + std::string m_sas, m_ciphers; + +protected: + virtual int32_t sendDataZRTP(const uint8_t* data, int32_t length); + virtual int32_t activateTimer(int32_t time); + virtual int32_t cancelTimer(); + virtual void sendInfo(GnuZrtpCodes::MessageSeverity severity, + int32_t subCode); + virtual bool srtpSecretsReady(SrtpSecret_t* secrets, + EnableSecurity part); + virtual void srtpSecretsOff(EnableSecurity part); + virtual void srtpSecretsOn(std::string c, std::string s, + bool verified); + virtual void handleGoClear(); + virtual void zrtpNegotiationFailed( + GnuZrtpCodes::MessageSeverity severity, + int32_t subCode); + virtual void zrtpNotSuppOther(); + virtual void synchEnter(); + virtual void synchLeave(); + virtual void zrtpAskEnrollment(GnuZrtpCodes::InfoEnrollment info); + virtual void zrtpInformEnrollment(GnuZrtpCodes::InfoEnrollment info); + virtual void signSAS(uint8_t* sasHash); + virtual bool checkSASSignature(uint8_t* sasHash); +}; + + +#endif // __STREAM_H + |