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/gzrtp/stream.cpp | |
parent | 4c21ac818a9fed61d02b9f0966645b18265166bb (diff) |
modules/gzrtp: new module using GNU ZRTP C++ library (#314)
Diffstat (limited to 'modules/gzrtp/stream.cpp')
-rw-r--r-- | modules/gzrtp/stream.cpp | 824 |
1 files changed, 824 insertions, 0 deletions
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; +} + |