blob: a7634757d6b537d3f4b72fab31f3bac93ce4c142 [file] [log] [blame]
/*
* Copyright 2009 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/pc/srtpfilter.h"
#include <string.h>
#include <algorithm>
#include "third_party/libsrtp/include/srtp.h"
#include "third_party/libsrtp/include/srtp_priv.h"
#include "webrtc/media/base/rtputils.h"
#include "webrtc/pc/externalhmac.h"
#include "webrtc/rtc_base/base64.h"
#include "webrtc/rtc_base/buffer.h"
#include "webrtc/rtc_base/byteorder.h"
#include "webrtc/rtc_base/checks.h"
#include "webrtc/rtc_base/logging.h"
#include "webrtc/rtc_base/sslstreamadapter.h"
#include "webrtc/rtc_base/stringencode.h"
#include "webrtc/rtc_base/timeutils.h"
namespace cricket {
// NOTE: This is called from ChannelManager D'tor.
void ShutdownSrtp() {
// If srtp_dealloc is not executed then this will clear all existing sessions.
// This should be called when application is shutting down.
SrtpSession::Terminate();
}
SrtpFilter::SrtpFilter() {
}
SrtpFilter::~SrtpFilter() {
}
bool SrtpFilter::IsActive() const {
return state_ >= ST_ACTIVE;
}
bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
ContentSource source) {
if (!ExpectOffer(source)) {
LOG(LS_ERROR) << "Wrong state to update SRTP offer";
return false;
}
return StoreParams(offer_params, source);
}
bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
ContentSource source) {
return DoSetAnswer(answer_params, source, true);
}
bool SrtpFilter::SetProvisionalAnswer(
const std::vector<CryptoParams>& answer_params,
ContentSource source) {
return DoSetAnswer(answer_params, source, false);
}
bool SrtpFilter::SetRtpParams(int send_cs,
const uint8_t* send_key,
int send_key_len,
int recv_cs,
const uint8_t* recv_key,
int recv_key_len) {
if (IsActive()) {
LOG(LS_ERROR) << "Tried to set SRTP Params when filter already active";
return false;
}
CreateSrtpSessions();
send_session_->SetEncryptedHeaderExtensionIds(
send_encrypted_header_extension_ids_);
if (!send_session_->SetSend(send_cs, send_key, send_key_len)) {
return false;
}
recv_session_->SetEncryptedHeaderExtensionIds(
recv_encrypted_header_extension_ids_);
if (!recv_session_->SetRecv(recv_cs, recv_key, recv_key_len)) {
return false;
}
state_ = ST_ACTIVE;
LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
<< " send cipher_suite " << send_cs
<< " recv cipher_suite " << recv_cs;
return true;
}
bool SrtpFilter::UpdateRtpParams(int send_cs,
const uint8_t* send_key,
int send_key_len,
int recv_cs,
const uint8_t* recv_key,
int recv_key_len) {
if (!IsActive()) {
LOG(LS_ERROR) << "Tried to update SRTP Params when filter is not active";
return false;
}
send_session_->SetEncryptedHeaderExtensionIds(
send_encrypted_header_extension_ids_);
if (!send_session_->UpdateSend(send_cs, send_key, send_key_len)) {
return false;
}
recv_session_->SetEncryptedHeaderExtensionIds(
recv_encrypted_header_extension_ids_);
if (!recv_session_->UpdateRecv(recv_cs, recv_key, recv_key_len)) {
return false;
}
LOG(LS_INFO) << "SRTP updated with negotiated parameters:"
<< " send cipher_suite " << send_cs
<< " recv cipher_suite " << recv_cs;
return true;
}
// This function is provided separately because DTLS-SRTP behaves
// differently in RTP/RTCP mux and non-mux modes.
//
// - In the non-muxed case, RTP and RTCP are keyed with different
// keys (from different DTLS handshakes), and so we need a new
// SrtpSession.
// - In the muxed case, they are keyed with the same keys, so
// this function is not needed
bool SrtpFilter::SetRtcpParams(int send_cs,
const uint8_t* send_key,
int send_key_len,
int recv_cs,
const uint8_t* recv_key,
int recv_key_len) {
// This can only be called once, but can be safely called after
// SetRtpParams
if (send_rtcp_session_ || recv_rtcp_session_) {
LOG(LS_ERROR) << "Tried to set SRTCP Params when filter already active";
return false;
}
send_rtcp_session_.reset(new SrtpSession());
if (!send_rtcp_session_->SetRecv(send_cs, send_key, send_key_len)) {
return false;
}
recv_rtcp_session_.reset(new SrtpSession());
if (!recv_rtcp_session_->SetRecv(recv_cs, recv_key, recv_key_len)) {
return false;
}
LOG(LS_INFO) << "SRTCP activated with negotiated parameters:"
<< " send cipher_suite " << send_cs
<< " recv cipher_suite " << recv_cs;
return true;
}
bool SrtpFilter::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to ProtectRtp: SRTP not active";
return false;
}
RTC_CHECK(send_session_);
return send_session_->ProtectRtp(p, in_len, max_len, out_len);
}
bool SrtpFilter::ProtectRtp(void* p,
int in_len,
int max_len,
int* out_len,
int64_t* index) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to ProtectRtp: SRTP not active";
return false;
}
RTC_CHECK(send_session_);
return send_session_->ProtectRtp(p, in_len, max_len, out_len, index);
}
bool SrtpFilter::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to ProtectRtcp: SRTP not active";
return false;
}
if (send_rtcp_session_) {
return send_rtcp_session_->ProtectRtcp(p, in_len, max_len, out_len);
} else {
RTC_CHECK(send_session_);
return send_session_->ProtectRtcp(p, in_len, max_len, out_len);
}
}
bool SrtpFilter::UnprotectRtp(void* p, int in_len, int* out_len) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to UnprotectRtp: SRTP not active";
return false;
}
RTC_CHECK(recv_session_);
return recv_session_->UnprotectRtp(p, in_len, out_len);
}
bool SrtpFilter::UnprotectRtcp(void* p, int in_len, int* out_len) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to UnprotectRtcp: SRTP not active";
return false;
}
if (recv_rtcp_session_) {
return recv_rtcp_session_->UnprotectRtcp(p, in_len, out_len);
} else {
RTC_CHECK(recv_session_);
return recv_session_->UnprotectRtcp(p, in_len, out_len);
}
}
bool SrtpFilter::GetRtpAuthParams(uint8_t** key, int* key_len, int* tag_len) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to GetRtpAuthParams: SRTP not active";
return false;
}
RTC_CHECK(send_session_);
return send_session_->GetRtpAuthParams(key, key_len, tag_len);
}
bool SrtpFilter::GetSrtpOverhead(int* srtp_overhead) const {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to GetSrtpOverhead: SRTP not active";
return false;
}
RTC_CHECK(send_session_);
*srtp_overhead = send_session_->GetSrtpOverhead();
return true;
}
void SrtpFilter::EnableExternalAuth() {
RTC_DCHECK(!IsActive());
external_auth_enabled_ = true;
}
bool SrtpFilter::IsExternalAuthEnabled() const {
return external_auth_enabled_;
}
bool SrtpFilter::IsExternalAuthActive() const {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to check IsExternalAuthActive: SRTP not active";
return false;
}
RTC_CHECK(send_session_);
return send_session_->IsExternalAuthActive();
}
void SrtpFilter::SetEncryptedHeaderExtensionIds(ContentSource source,
const std::vector<int>& extension_ids) {
if (source == CS_LOCAL) {
recv_encrypted_header_extension_ids_ = extension_ids;
} else {
send_encrypted_header_extension_ids_ = extension_ids;
}
}
bool SrtpFilter::ExpectOffer(ContentSource source) {
return ((state_ == ST_INIT) ||
(state_ == ST_ACTIVE) ||
(state_ == ST_SENTOFFER && source == CS_LOCAL) ||
(state_ == ST_SENTUPDATEDOFFER && source == CS_LOCAL) ||
(state_ == ST_RECEIVEDOFFER && source == CS_REMOTE) ||
(state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_REMOTE));
}
bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
ContentSource source) {
offer_params_ = params;
if (state_ == ST_INIT) {
state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
} else if (state_ == ST_ACTIVE) {
state_ =
(source == CS_LOCAL) ? ST_SENTUPDATEDOFFER : ST_RECEIVEDUPDATEDOFFER;
}
return true;
}
bool SrtpFilter::ExpectAnswer(ContentSource source) {
return ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
(state_ == ST_RECEIVEDOFFER && source == CS_LOCAL) ||
(state_ == ST_SENTUPDATEDOFFER && source == CS_REMOTE) ||
(state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_LOCAL) ||
(state_ == ST_SENTPRANSWER_NO_CRYPTO && source == CS_LOCAL) ||
(state_ == ST_SENTPRANSWER && source == CS_LOCAL) ||
(state_ == ST_RECEIVEDPRANSWER_NO_CRYPTO && source == CS_REMOTE) ||
(state_ == ST_RECEIVEDPRANSWER && source == CS_REMOTE));
}
bool SrtpFilter::DoSetAnswer(const std::vector<CryptoParams>& answer_params,
ContentSource source,
bool final) {
if (!ExpectAnswer(source)) {
LOG(LS_ERROR) << "Invalid state for SRTP answer";
return false;
}
// If the answer doesn't requests crypto complete the negotiation of an
// unencrypted session.
// Otherwise, finalize the parameters and apply them.
if (answer_params.empty()) {
if (final) {
return ResetParams();
} else {
// Need to wait for the final answer to decide if
// we should go to Active state.
state_ = (source == CS_LOCAL) ? ST_SENTPRANSWER_NO_CRYPTO :
ST_RECEIVEDPRANSWER_NO_CRYPTO;
return true;
}
}
CryptoParams selected_params;
if (!NegotiateParams(answer_params, &selected_params))
return false;
const CryptoParams& send_params =
(source == CS_REMOTE) ? selected_params : answer_params[0];
const CryptoParams& recv_params =
(source == CS_REMOTE) ? answer_params[0] : selected_params;
if (!ApplyParams(send_params, recv_params)) {
return false;
}
if (final) {
offer_params_.clear();
state_ = ST_ACTIVE;
} else {
state_ =
(source == CS_LOCAL) ? ST_SENTPRANSWER : ST_RECEIVEDPRANSWER;
}
return true;
}
void SrtpFilter::CreateSrtpSessions() {
send_session_.reset(new SrtpSession());
applied_send_params_ = CryptoParams();
recv_session_.reset(new SrtpSession());
applied_recv_params_ = CryptoParams();
if (external_auth_enabled_) {
send_session_->EnableExternalAuth();
}
}
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(jiayl): Split this method to apply send and receive CryptoParams
// independently, so that we can skip one method when either send or receive
// CryptoParams is unchanged.
if (applied_send_params_.cipher_suite == send_params.cipher_suite &&
applied_send_params_.key_params == send_params.key_params &&
applied_recv_params_.cipher_suite == recv_params.cipher_suite &&
applied_recv_params_.key_params == recv_params.key_params) {
LOG(LS_INFO) << "Applying the same SRTP parameters again. No-op.";
// We do not want to reset the ROC if the keys are the same. So just return.
return true;
}
int send_suite = rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite);
int recv_suite = rtc::SrtpCryptoSuiteFromName(recv_params.cipher_suite);
if (send_suite == rtc::SRTP_INVALID_CRYPTO_SUITE ||
recv_suite == rtc::SRTP_INVALID_CRYPTO_SUITE) {
LOG(LS_WARNING) << "Unknown crypto suite(s) received:"
<< " send cipher_suite " << send_params.cipher_suite
<< " recv cipher_suite " << recv_params.cipher_suite;
return false;
}
int send_key_len, send_salt_len;
int recv_key_len, recv_salt_len;
if (!rtc::GetSrtpKeyAndSaltLengths(send_suite, &send_key_len,
&send_salt_len) ||
!rtc::GetSrtpKeyAndSaltLengths(recv_suite, &recv_key_len,
&recv_salt_len)) {
LOG(LS_WARNING) << "Could not get lengths for crypto suite(s):"
<< " send cipher_suite " << send_params.cipher_suite
<< " recv cipher_suite " << recv_params.cipher_suite;
return false;
}
// TODO(juberti): Zero these buffers after use.
bool ret;
rtc::Buffer send_key(send_key_len + send_salt_len);
rtc::Buffer recv_key(recv_key_len + recv_salt_len);
ret = (ParseKeyParams(send_params.key_params, send_key.data(),
send_key.size()) &&
ParseKeyParams(recv_params.key_params, recv_key.data(),
recv_key.size()));
if (ret) {
CreateSrtpSessions();
send_session_->SetEncryptedHeaderExtensionIds(
send_encrypted_header_extension_ids_);
recv_session_->SetEncryptedHeaderExtensionIds(
recv_encrypted_header_extension_ids_);
ret = (send_session_->SetSend(
rtc::SrtpCryptoSuiteFromName(send_params.cipher_suite),
send_key.data(), send_key.size()) &&
recv_session_->SetRecv(
rtc::SrtpCryptoSuiteFromName(recv_params.cipher_suite),
recv_key.data(), recv_key.size()));
}
if (ret) {
LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
<< " send cipher_suite " << send_params.cipher_suite
<< " recv cipher_suite " << recv_params.cipher_suite;
applied_send_params_ = send_params;
applied_recv_params_ = recv_params;
} else {
LOG(LS_WARNING) << "Failed to apply negotiated SRTP parameters";
}
return ret;
}
bool SrtpFilter::ResetParams() {
offer_params_.clear();
state_ = ST_INIT;
send_session_ = nullptr;
recv_session_ = nullptr;
send_rtcp_session_ = nullptr;
recv_rtcp_session_ = nullptr;
LOG(LS_INFO) << "SRTP reset to init state";
return true;
}
bool SrtpFilter::ParseKeyParams(const std::string& key_params,
uint8_t* key,
size_t 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 (!rtc::Base64::Decode(key_b64, rtc::Base64::DO_STRICT,
&key_str, nullptr) || key_str.size() != len) {
return false;
}
memcpy(key, key_str.c_str(), len);
return true;
}
///////////////////////////////////////////////////////////////////////////////
// SrtpSession
bool SrtpSession::inited_ = false;
// This lock protects SrtpSession::inited_.
rtc::GlobalLockPod SrtpSession::lock_;
SrtpSession::SrtpSession() {}
SrtpSession::~SrtpSession() {
if (session_) {
srtp_set_user_data(session_, nullptr);
srtp_dealloc(session_);
}
}
bool SrtpSession::SetSend(int cs, const uint8_t* key, size_t len) {
return SetKey(ssrc_any_outbound, cs, key, len);
}
bool SrtpSession::UpdateSend(int cs, const uint8_t* key, size_t len) {
return UpdateKey(ssrc_any_outbound, cs, key, len);
}
bool SrtpSession::SetRecv(int cs, const uint8_t* key, size_t len) {
return SetKey(ssrc_any_inbound, cs, key, len);
}
bool SrtpSession::UpdateRecv(int cs, const uint8_t* key, size_t len) {
return UpdateKey(ssrc_any_inbound, cs, key, len);
}
bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!session_) {
LOG(LS_WARNING) << "Failed to protect SRTP packet: no SRTP Session";
return false;
}
int need_len = in_len + rtp_auth_tag_len_; // NOLINT
if (max_len < need_len) {
LOG(LS_WARNING) << "Failed to protect SRTP packet: The buffer length "
<< max_len << " is less than the needed " << need_len;
return false;
}
*out_len = in_len;
int err = srtp_protect(session_, p, out_len);
int seq_num;
GetRtpSeqNum(p, in_len, &seq_num);
if (err != srtp_err_status_ok) {
LOG(LS_WARNING) << "Failed to protect SRTP packet, seqnum="
<< seq_num << ", err=" << err << ", last seqnum="
<< last_send_seq_num_;
return false;
}
last_send_seq_num_ = seq_num;
return true;
}
bool SrtpSession::ProtectRtp(void* p,
int in_len,
int max_len,
int* out_len,
int64_t* index) {
if (!ProtectRtp(p, in_len, max_len, out_len)) {
return false;
}
return (index) ? GetSendStreamPacketIndex(p, in_len, index) : true;
}
bool SrtpSession::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!session_) {
LOG(LS_WARNING) << "Failed to protect SRTCP packet: no SRTP Session";
return false;
}
int need_len = in_len + sizeof(uint32_t) + rtcp_auth_tag_len_; // NOLINT
if (max_len < need_len) {
LOG(LS_WARNING) << "Failed to protect SRTCP packet: The buffer length "
<< max_len << " is less than the needed " << need_len;
return false;
}
*out_len = in_len;
int err = srtp_protect_rtcp(session_, p, out_len);
if (err != srtp_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) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!session_) {
LOG(LS_WARNING) << "Failed to unprotect SRTP packet: no SRTP Session";
return false;
}
*out_len = in_len;
int err = srtp_unprotect(session_, p, out_len);
if (err != srtp_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) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!session_) {
LOG(LS_WARNING) << "Failed to unprotect SRTCP packet: no SRTP Session";
return false;
}
*out_len = in_len;
int err = srtp_unprotect_rtcp(session_, p, out_len);
if (err != srtp_err_status_ok) {
LOG(LS_WARNING) << "Failed to unprotect SRTCP packet, err=" << err;
return false;
}
return true;
}
bool SrtpSession::GetRtpAuthParams(uint8_t** key, int* key_len, int* tag_len) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
RTC_DCHECK(IsExternalAuthActive());
if (!IsExternalAuthActive()) {
return false;
}
ExternalHmacContext* external_hmac = nullptr;
// stream_template will be the reference context for other streams.
// Let's use it for getting the keys.
srtp_stream_ctx_t* srtp_context = session_->stream_template;
#if defined(SRTP_MAX_MKI_LEN)
// libsrtp 2.1.0
if (srtp_context && srtp_context->session_keys &&
srtp_context->session_keys->rtp_auth) {
external_hmac = reinterpret_cast<ExternalHmacContext*>(
srtp_context->session_keys->rtp_auth->state);
}
#else
// libsrtp 2.0.0
// TODO(jbauch): Remove after switching to libsrtp 2.1.0
if (srtp_context && srtp_context->rtp_auth) {
external_hmac = reinterpret_cast<ExternalHmacContext*>(
srtp_context->rtp_auth->state);
}
#endif
if (!external_hmac) {
LOG(LS_ERROR) << "Failed to get auth keys from libsrtp!.";
return false;
}
*key = external_hmac->key;
*key_len = external_hmac->key_length;
*tag_len = rtp_auth_tag_len_;
return true;
}
int SrtpSession::GetSrtpOverhead() const {
return rtp_auth_tag_len_;
}
void SrtpSession::EnableExternalAuth() {
RTC_DCHECK(!session_);
external_auth_enabled_ = true;
}
bool SrtpSession::IsExternalAuthEnabled() const {
return external_auth_enabled_;
}
bool SrtpSession::IsExternalAuthActive() const {
return external_auth_active_;
}
bool SrtpSession::GetSendStreamPacketIndex(void* p,
int in_len,
int64_t* index) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
srtp_hdr_t* hdr = reinterpret_cast<srtp_hdr_t*>(p);
srtp_stream_ctx_t* stream = srtp_get_stream(session_, hdr->ssrc);
if (!stream) {
return false;
}
// Shift packet index, put into network byte order
*index = static_cast<int64_t>(
rtc::NetworkToHost64(
srtp_rdbx_get_packet_index(&stream->rtp_rdbx) << 16));
return true;
}
bool SrtpSession::DoSetKey(int type, int cs, const uint8_t* key, size_t len) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
srtp_policy_t policy;
memset(&policy, 0, sizeof(policy));
if (cs == rtc::SRTP_AES128_CM_SHA1_80) {
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
} else if (cs == rtc::SRTP_AES128_CM_SHA1_32) {
// RTP HMAC is shortened to 32 bits, but RTCP remains 80 bits.
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
} else if (cs == rtc::SRTP_AEAD_AES_128_GCM) {
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp);
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp);
} else if (cs == rtc::SRTP_AEAD_AES_256_GCM) {
srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp);
srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp);
} else {
LOG(LS_WARNING) << "Failed to " << (session_ ? "update" : "create")
<< " SRTP session: unsupported cipher_suite " << cs;
return false;
}
int expected_key_len;
int expected_salt_len;
if (!rtc::GetSrtpKeyAndSaltLengths(cs, &expected_key_len,
&expected_salt_len)) {
// This should never happen.
LOG(LS_WARNING) << "Failed to " << (session_ ? "update" : "create")
<< " SRTP session: unsupported cipher_suite without length information"
<< cs;
return false;
}
if (!key ||
len != static_cast<size_t>(expected_key_len + expected_salt_len)) {
LOG(LS_WARNING) << "Failed to " << (session_ ? "update" : "create")
<< " SRTP session: invalid key";
return false;
}
policy.ssrc.type = static_cast<srtp_ssrc_type_t>(type);
policy.ssrc.value = 0;
policy.key = const_cast<uint8_t*>(key);
// TODO(astor) parse window size from WSH session-param
policy.window_size = 1024;
policy.allow_repeat_tx = 1;
// If external authentication option is enabled, supply custom auth module
// id EXTERNAL_HMAC_SHA1 in the policy structure.
// We want to set this option only for rtp packets.
// By default policy structure is initialized to HMAC_SHA1.
// Enable external HMAC authentication only for outgoing streams and only
// for cipher suites that support it (i.e. only non-GCM cipher suites).
if (type == ssrc_any_outbound && IsExternalAuthEnabled() &&
!rtc::IsGcmCryptoSuite(cs)) {
policy.rtp.auth_type = EXTERNAL_HMAC_SHA1;
}
if (!encrypted_header_extension_ids_.empty()) {
policy.enc_xtn_hdr = const_cast<int*>(&encrypted_header_extension_ids_[0]);
policy.enc_xtn_hdr_count =
static_cast<int>(encrypted_header_extension_ids_.size());
}
policy.next = nullptr;
if (!session_) {
int err = srtp_create(&session_, &policy);
if (err != srtp_err_status_ok) {
session_ = nullptr;
LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err;
return false;
}
srtp_set_user_data(session_, this);
} else {
int err = srtp_update(session_, &policy);
if (err != srtp_err_status_ok) {
LOG(LS_ERROR) << "Failed to update SRTP session, err=" << err;
return false;
}
}
rtp_auth_tag_len_ = policy.rtp.auth_tag_len;
rtcp_auth_tag_len_ = policy.rtcp.auth_tag_len;
external_auth_active_ = (policy.rtp.auth_type == EXTERNAL_HMAC_SHA1);
return true;
}
bool SrtpSession::SetKey(int type, int cs, const uint8_t* key, size_t len) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (session_) {
LOG(LS_ERROR) << "Failed to create SRTP session: "
<< "SRTP session already created";
return false;
}
if (!Init()) {
return false;
}
return DoSetKey(type, cs, key, len);
}
bool SrtpSession::UpdateKey(int type, int cs, const uint8_t* key, size_t len) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
if (!session_) {
LOG(LS_ERROR) << "Failed to update non-existing SRTP session";
return false;
}
return DoSetKey(type, cs, key, len);
}
void SrtpSession::SetEncryptedHeaderExtensionIds(
const std::vector<int>& encrypted_header_extension_ids) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
encrypted_header_extension_ids_ = encrypted_header_extension_ids;
}
bool SrtpSession::Init() {
rtc::GlobalLockScope ls(&lock_);
if (!inited_) {
int err;
err = srtp_init();
if (err != srtp_err_status_ok) {
LOG(LS_ERROR) << "Failed to init SRTP, err=" << err;
return false;
}
err = srtp_install_event_handler(&SrtpSession::HandleEventThunk);
if (err != srtp_err_status_ok) {
LOG(LS_ERROR) << "Failed to install SRTP event handler, err=" << err;
return false;
}
err = external_crypto_init();
if (err != srtp_err_status_ok) {
LOG(LS_ERROR) << "Failed to initialize fake auth, err=" << err;
return false;
}
inited_ = true;
}
return true;
}
void SrtpSession::Terminate() {
rtc::GlobalLockScope ls(&lock_);
if (inited_) {
int err = srtp_shutdown();
if (err) {
LOG(LS_ERROR) << "srtp_shutdown failed. err=" << err;
return;
}
inited_ = false;
}
}
void SrtpSession::HandleEvent(const srtp_event_data_t* ev) {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
switch (ev->event) {
case event_ssrc_collision:
LOG(LS_INFO) << "SRTP event: SSRC collision";
break;
case event_key_soft_limit:
LOG(LS_INFO) << "SRTP event: reached soft key usage limit";
break;
case event_key_hard_limit:
LOG(LS_INFO) << "SRTP event: reached hard key usage limit";
break;
case event_packet_index_limit:
LOG(LS_INFO) << "SRTP event: reached hard packet limit (2^48 packets)";
break;
default:
LOG(LS_INFO) << "SRTP event: unknown " << ev->event;
break;
}
}
void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
// Callback will be executed from same thread that calls the "srtp_protect"
// and "srtp_unprotect" functions.
SrtpSession* session = static_cast<SrtpSession*>(
srtp_get_user_data(ev->session));
if (session) {
session->HandleEvent(ev);
}
}
} // namespace cricket