blob: f8d2dd4007baaa677cbb3ebf8422eb6b56595ac4 [file] [log] [blame]
/*
* libjingle
* Copyright 2009, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#undef HAVE_CONFIG_H // talk's config.h conflicts with the one included by the
// libsrtp headers. Don't use it.
#include "talk/session/phone/srtpfilter.h"
#include <algorithm>
#include <cstring>
#include "talk/base/base64.h"
#include "talk/base/logging.h"
// TODO: For the XCode build, we force SRTP (b/2500074)
#if defined(OSX) && !defined(HAVE_SRTP)
#define HAVE_SRTP 1
#endif
// Enable this line to turn on SRTP debugging
// #define SRTP_DEBUG
#ifdef HAVE_SRTP
#ifdef SRTP_RELATIVE_PATH
#include "srtp.h" // NOLINT
#else
#include "third_party/libsrtp/include/srtp.h"
#endif // SRTP_RELATIVE_PATH
#ifdef _DEBUG
extern "C" debug_module_t mod_srtp;
#endif
#else
// SrtpFilter needs that constant.
#define SRTP_MASTER_KEY_LEN 30
#endif // HAVE_SRTP
namespace cricket {
const std::string& CS_DEFAULT = CS_AES_CM_128_HMAC_SHA1_80;
const std::string CS_AES_CM_128_HMAC_SHA1_80 = "AES_CM_128_HMAC_SHA1_80";
const std::string CS_AES_CM_128_HMAC_SHA1_32 = "AES_CM_128_HMAC_SHA1_32";
const int SRTP_MASTER_KEY_BASE64_LEN = SRTP_MASTER_KEY_LEN * 4 / 3;
SrtpFilter::SrtpFilter() : state_(ST_INIT) {
}
SrtpFilter::~SrtpFilter() {
}
bool SrtpFilter::IsActive() const {
return (state_ == ST_ACTIVE);
}
bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
ContentSource source) {
bool ret = false;
if (state_ == ST_INIT) {
ret = StoreParams(offer_params, source);
} else {
LOG(LS_ERROR) << "Invalid state for SRTP offer";
}
return ret;
}
bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
ContentSource source) {
bool ret = false;
if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
(state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
// If the answer requests crypto, finalize the parameters and apply them.
// Otherwise, complete the negotiation of a unencrypted session.
if (!answer_params.empty()) {
CryptoParams selected_params;
ret = NegotiateParams(answer_params, &selected_params);
if (ret) {
if (state_ == ST_SENTOFFER) {
ret = ApplyParams(selected_params, answer_params[0]);
} else { // ST_RECEIVEDOFFER
ret = ApplyParams(answer_params[0], selected_params);
}
}
} else {
ret = ResetParams();
}
} else {
LOG(LS_ERROR) << "Invalid state for SRTP answer";
}
return ret;
}
bool SrtpFilter::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
if (!IsActive()) {
return false;
}
return send_session_.ProtectRtp(p, in_len, max_len, out_len);
}
bool SrtpFilter::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
if (!IsActive()) {
return false;
}
return send_session_.ProtectRtcp(p, in_len, max_len, out_len);
}
bool SrtpFilter::UnprotectRtp(void* p, int in_len, int* out_len) {
if (!IsActive()) {
return false;
}
return recv_session_.UnprotectRtp(p, in_len, out_len);
}
bool SrtpFilter::UnprotectRtcp(void* p, int in_len, int* out_len) {
if (!IsActive()) {
return false;
}
return recv_session_.UnprotectRtcp(p, in_len, out_len);
}
bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
ContentSource source) {
offer_params_ = params;
state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
return true;
}
bool SrtpFilter::NegotiateParams(const std::vector<CryptoParams>& answer_params,
CryptoParams* selected_params) {
// We're processing an accept. We should have exactly one set of params,
// unless the offer didn't mention crypto, in which case we shouldn't be here.
bool ret = (answer_params.size() == 1U && !offer_params_.empty());
if (ret) {
// We should find a match between the answer params and the offered params.
std::vector<CryptoParams>::const_iterator it;
for (it = offer_params_.begin(); it != offer_params_.end(); ++it) {
if (answer_params[0].Matches(*it)) {
break;
}
}
if (it != offer_params_.end()) {
*selected_params = *it;
} else {
ret = false;
}
}
if (!ret) {
LOG(LS_WARNING) << "Invalid parameters in SRTP answer";
}
return ret;
}
bool SrtpFilter::ApplyParams(const CryptoParams& send_params,
const CryptoParams& recv_params) {
// TODO: Zero these buffers after use.
bool ret;
uint8 send_key[SRTP_MASTER_KEY_LEN], recv_key[SRTP_MASTER_KEY_LEN];
ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) &&
ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key)));
if (ret) {
ret = (send_session_.SetSend(send_params.cipher_suite,
send_key, sizeof(send_key)) &&
recv_session_.SetRecv(recv_params.cipher_suite,
recv_key, sizeof(recv_key)));
}
if (ret) {
offer_params_.clear();
state_ = ST_ACTIVE;
} else {
LOG(LS_WARNING) << "Failed to apply negotiated SRTP parameters";
}
return ret;
}
bool SrtpFilter::ResetParams() {
offer_params_.clear();
state_ = ST_INIT;
return true;
}
bool SrtpFilter::ParseKeyParams(const std::string& key_params,
uint8* key, int len) {
// example key_params: "inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2"
// Fail if key-method is wrong.
if (key_params.find("inline:") != 0) {
return false;
}
// Fail if base64 decode fails, or the key is the wrong size.
std::string key_b64(key_params.substr(7)), key_str;
if (!talk_base::Base64::Decode(key_b64, talk_base::Base64::DO_STRICT,
&key_str, NULL) ||
static_cast<int>(key_str.size()) != len) {
return false;
}
memcpy(key, key_str.c_str(), len);
return true;
}
///////////////////////////////////////////////////////////////////////////////
// SrtpSession
#ifdef HAVE_SRTP
bool SrtpSession::inited_ = false;
std::list<SrtpSession*> SrtpSession::sessions_;
SrtpSession::SrtpSession()
: session_(NULL), rtp_auth_tag_len_(0), rtcp_auth_tag_len_(0) {
sessions_.push_back(this);
}
SrtpSession::~SrtpSession() {
sessions_.erase(std::find(sessions_.begin(), sessions_.end(), this));
if (session_) {
srtp_dealloc(session_);
}
}
bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
return SetKey(ssrc_any_outbound, cs, key, len);
}
bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
return SetKey(ssrc_any_inbound, cs, key, len);
}
bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
if (!session_)
return false;
int need_len = in_len + rtp_auth_tag_len_; // NOLINT
if (max_len < need_len)
return false;
*out_len = in_len;
int err = srtp_protect(session_, p, out_len);
if (err != err_status_ok) {
LOG(LS_WARNING) << "Failed to protect SRTP packet, err=" << err;
return false;
}
return true;
}
bool SrtpSession::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
if (!session_)
return false;
int need_len = in_len + sizeof(uint32) + rtcp_auth_tag_len_; // NOLINT
if (max_len < need_len)
return false;
*out_len = in_len;
int err = srtp_protect_rtcp(session_, p, out_len);
if (err != err_status_ok) {
LOG(LS_WARNING) << "Failed to protect SRTCP packet, err=" << err;
return false;
}
return true;
}
bool SrtpSession::UnprotectRtp(void* p, int in_len, int* out_len) {
if (!session_)
return false;
*out_len = in_len;
int err = srtp_unprotect(session_, p, out_len);
if (err != err_status_ok) {
LOG(LS_WARNING) << "Failed to unprotect SRTP packet, err=" << err;
return false;
}
return true;
}
bool SrtpSession::UnprotectRtcp(void* p, int in_len, int* out_len) {
if (!session_)
return false;
*out_len = in_len;
int err = srtp_unprotect_rtcp(session_, p, out_len);
if (err != err_status_ok) {
LOG(LS_WARNING) << "Failed to unprotect SRTCP packet, err=" << err;
return false;
}
return true;
}
bool SrtpSession::SetKey(int type, const std::string& cs,
const uint8* key, int len) {
if (session_) {
return false;
}
if (!Init()) {
return false;
}
srtp_policy_t policy;
memset(&policy, 0, sizeof(policy));
if (cs == CS_AES_CM_128_HMAC_SHA1_80) {
crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
} else if (cs == CS_AES_CM_128_HMAC_SHA1_32) {
crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp); // rtp is 32,
crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); // rtcp still 80
} else {
return false;
}
if (!key || len != SRTP_MASTER_KEY_LEN) {
return false;
}
policy.ssrc.type = static_cast<ssrc_type_t>(type);
policy.ssrc.value = 0;
policy.key = const_cast<uint8*>(key);
// TODO parse window size from WSH session-param
policy.window_size = 1024;
policy.allow_repeat_tx = 1;
policy.next = NULL;
int err = srtp_create(&session_, &policy);
if (err != err_status_ok) {
LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err;
return false;
}
rtp_auth_tag_len_ = policy.rtp.auth_tag_len;
rtcp_auth_tag_len_ = policy.rtcp.auth_tag_len;
return true;
}
bool SrtpSession::Init() {
if (!inited_) {
int err;
#ifdef DEBUG_SRTP
debug_on(mod_srtp);
#endif
err = srtp_init();
if (err != err_status_ok) {
LOG(LS_ERROR) << "Failed to init SRTP, err=" << err;
return false;
}
err = srtp_install_event_handler(&SrtpSession::HandleEventThunk);
if (err != err_status_ok) {
LOG(LS_ERROR) << "Failed to install SRTP event handler, err=" << err;
return false;
}
inited_ = true;
}
return true;
}
void SrtpSession::HandleEvent(const srtp_event_data_t* ev) {
// TODO: Do something about events.
}
void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
for (std::list<SrtpSession*>::iterator it = sessions_.begin();
it != sessions_.end(); ++it) {
if ((*it)->session_ == ev->session) {
(*it)->HandleEvent(ev);
break;
}
}
}
#else // !HAVE_SRTP
namespace {
bool SrtpNotAvailable(const char *func) {
LOG(LS_ERROR) << func << ": SRTP is not available on your system.";
return false;
}
} // anonymous namespace
SrtpSession::SrtpSession() {
LOG(WARNING) << "SRTP implementation is missing.";
}
SrtpSession::~SrtpSession() {
}
bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
return SrtpNotAvailable(__FUNCTION__);
}
bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
return SrtpNotAvailable(__FUNCTION__);
}
bool SrtpSession::ProtectRtp(void* data, int in_len, int max_len,
int* out_len) {
return SrtpNotAvailable(__FUNCTION__);
}
bool SrtpSession::ProtectRtcp(void* data, int in_len, int max_len,
int* out_len) {
return SrtpNotAvailable(__FUNCTION__);
}
bool SrtpSession::UnprotectRtp(void* data, int in_len, int* out_len) {
return SrtpNotAvailable(__FUNCTION__);
}
bool SrtpSession::UnprotectRtcp(void* data, int in_len, int* out_len) {
return SrtpNotAvailable(__FUNCTION__);
}
#endif // HAVE_SRTP
} // namespace cricket