
The existing methods SetEncrypedHeaderExtensionIds in SrtpTransport and SrtpSession are removed because those methods could be confusing. When these methods are called the head extension IDs are not actually updated and the user need to call SetRtpParams again to make that happen. The existing setter just caches the new IDs. To make it less confusing, the SetEncryptedHeaderExtensionIds is removed and the new extension IDs will be set immediately when setting the crypto params. For SDES, the crypto params and the header extension IDs will be set at the same time. For DTLS, the new header extensions are cached in BaseChannel and will be set when the DTLS handshake is completed. Another major change is that when doing DTLS-SRTP, the encrypted header extension IDs will be updated only when they are changed. Bug: webrtc:7013 Change-Id: Ib70d4797456ae5ecb61b3dfff15c7e3e7ede89bd Reviewed-on: https://webrtc-review.googlesource.com/15860 Commit-Queue: Zhi Huang <zhihuang@webrtc.org> Reviewed-by: Peter Thatcher <pthatcher@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20639}
424 lines
13 KiB
C++
424 lines
13 KiB
C++
/*
|
|
* Copyright 2017 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 "pc/srtpsession.h"
|
|
|
|
#include "media/base/rtputils.h"
|
|
#include "pc/externalhmac.h"
|
|
#include "rtc_base/criticalsection.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/sslstreamadapter.h"
|
|
#include "third_party/libsrtp/include/srtp.h"
|
|
#include "third_party/libsrtp/include/srtp_priv.h"
|
|
|
|
namespace cricket {
|
|
|
|
SrtpSession::SrtpSession() {}
|
|
|
|
SrtpSession::~SrtpSession() {
|
|
if (session_) {
|
|
srtp_set_user_data(session_, nullptr);
|
|
srtp_dealloc(session_);
|
|
}
|
|
if (inited_) {
|
|
DecrementLibsrtpUsageCountAndMaybeDeinit();
|
|
}
|
|
}
|
|
|
|
bool SrtpSession::SetSend(int cs,
|
|
const uint8_t* key,
|
|
size_t len,
|
|
const std::vector<int>& extension_ids) {
|
|
return SetKey(ssrc_any_outbound, cs, key, len, extension_ids);
|
|
}
|
|
|
|
bool SrtpSession::UpdateSend(int cs,
|
|
const uint8_t* key,
|
|
size_t len,
|
|
const std::vector<int>& extension_ids) {
|
|
return UpdateKey(ssrc_any_outbound, cs, key, len, extension_ids);
|
|
}
|
|
|
|
bool SrtpSession::SetRecv(int cs,
|
|
const uint8_t* key,
|
|
size_t len,
|
|
const std::vector<int>& extension_ids) {
|
|
return SetKey(ssrc_any_inbound, cs, key, len, extension_ids);
|
|
}
|
|
|
|
bool SrtpSession::UpdateRecv(int cs,
|
|
const uint8_t* key,
|
|
size_t len,
|
|
const std::vector<int>& extension_ids) {
|
|
return UpdateKey(ssrc_any_inbound, cs, key, len, extension_ids);
|
|
}
|
|
|
|
bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (!session_) {
|
|
RTC_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) {
|
|
RTC_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) {
|
|
RTC_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_) {
|
|
RTC_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) {
|
|
RTC_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) {
|
|
RTC_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_) {
|
|
RTC_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) {
|
|
RTC_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_) {
|
|
RTC_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) {
|
|
RTC_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 (srtp_context && srtp_context->session_keys &&
|
|
srtp_context->session_keys->rtp_auth) {
|
|
external_hmac = reinterpret_cast<ExternalHmacContext*>(
|
|
srtp_context->session_keys->rtp_auth->state);
|
|
}
|
|
|
|
if (!external_hmac) {
|
|
RTC_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,
|
|
const std::vector<int>& extension_ids) {
|
|
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 {
|
|
RTC_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.
|
|
RTC_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)) {
|
|
RTC_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 (!extension_ids.empty()) {
|
|
policy.enc_xtn_hdr = const_cast<int*>(&extension_ids[0]);
|
|
policy.enc_xtn_hdr_count = static_cast<int>(extension_ids.size());
|
|
}
|
|
policy.next = nullptr;
|
|
|
|
if (!session_) {
|
|
int err = srtp_create(&session_, &policy);
|
|
if (err != srtp_err_status_ok) {
|
|
session_ = nullptr;
|
|
RTC_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) {
|
|
RTC_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,
|
|
const std::vector<int>& extension_ids) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (session_) {
|
|
RTC_LOG(LS_ERROR) << "Failed to create SRTP session: "
|
|
<< "SRTP session already created";
|
|
return false;
|
|
}
|
|
|
|
// This is the first time we need to actually interact with libsrtp, so
|
|
// initialize it if needed.
|
|
if (IncrementLibsrtpUsageCountAndMaybeInit()) {
|
|
inited_ = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return DoSetKey(type, cs, key, len, extension_ids);
|
|
}
|
|
|
|
bool SrtpSession::UpdateKey(int type,
|
|
int cs,
|
|
const uint8_t* key,
|
|
size_t len,
|
|
const std::vector<int>& extension_ids) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
if (!session_) {
|
|
RTC_LOG(LS_ERROR) << "Failed to update non-existing SRTP session";
|
|
return false;
|
|
}
|
|
|
|
return DoSetKey(type, cs, key, len, extension_ids);
|
|
}
|
|
|
|
int g_libsrtp_usage_count = 0;
|
|
rtc::GlobalLockPod g_libsrtp_lock;
|
|
|
|
// static
|
|
bool SrtpSession::IncrementLibsrtpUsageCountAndMaybeInit() {
|
|
rtc::GlobalLockScope ls(&g_libsrtp_lock);
|
|
|
|
RTC_DCHECK_GE(g_libsrtp_usage_count, 0);
|
|
if (g_libsrtp_usage_count == 0) {
|
|
int err;
|
|
err = srtp_init();
|
|
if (err != srtp_err_status_ok) {
|
|
RTC_LOG(LS_ERROR) << "Failed to init SRTP, err=" << err;
|
|
return false;
|
|
}
|
|
|
|
err = srtp_install_event_handler(&SrtpSession::HandleEventThunk);
|
|
if (err != srtp_err_status_ok) {
|
|
RTC_LOG(LS_ERROR) << "Failed to install SRTP event handler, err=" << err;
|
|
return false;
|
|
}
|
|
|
|
err = external_crypto_init();
|
|
if (err != srtp_err_status_ok) {
|
|
RTC_LOG(LS_ERROR) << "Failed to initialize fake auth, err=" << err;
|
|
return false;
|
|
}
|
|
}
|
|
++g_libsrtp_usage_count;
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
void SrtpSession::DecrementLibsrtpUsageCountAndMaybeDeinit() {
|
|
rtc::GlobalLockScope ls(&g_libsrtp_lock);
|
|
|
|
RTC_DCHECK_GE(g_libsrtp_usage_count, 1);
|
|
if (--g_libsrtp_usage_count == 0) {
|
|
int err = srtp_shutdown();
|
|
if (err) {
|
|
RTC_LOG(LS_ERROR) << "srtp_shutdown failed. err=" << err;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SrtpSession::HandleEvent(const srtp_event_data_t* ev) {
|
|
RTC_DCHECK(thread_checker_.CalledOnValidThread());
|
|
switch (ev->event) {
|
|
case event_ssrc_collision:
|
|
RTC_LOG(LS_INFO) << "SRTP event: SSRC collision";
|
|
break;
|
|
case event_key_soft_limit:
|
|
RTC_LOG(LS_INFO) << "SRTP event: reached soft key usage limit";
|
|
break;
|
|
case event_key_hard_limit:
|
|
RTC_LOG(LS_INFO) << "SRTP event: reached hard key usage limit";
|
|
break;
|
|
case event_packet_index_limit:
|
|
RTC_LOG(LS_INFO)
|
|
<< "SRTP event: reached hard packet limit (2^48 packets)";
|
|
break;
|
|
default:
|
|
RTC_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
|