When SDES is used, pass pre-shared key to media transport.
This allows to use secure, end to end communication if SDES cryptos are passed. MediaTransport can use a derived key to secure its own communication. Bug: webrtc:9719 Change-Id: If1a20b136b3b4af0cb24f10b52fc5ce1eb31daa2 Reviewed-on: https://webrtc-review.googlesource.com/c/108504 Commit-Queue: Peter Slatala <psla@webrtc.org> Reviewed-by: Seth Hampson <shampson@webrtc.org> Reviewed-by: Qingsi Wang <qingsi@webrtc.org> Reviewed-by: Benjamin Wright <benwright@webrtc.org> Reviewed-by: Bjorn Mellem <mellem@webrtc.org> Cr-Commit-Position: refs/heads/master@{#25452}
This commit is contained in:

committed by
Commit Bot

parent
71822866c6
commit
9f9562592f
@ -48,6 +48,8 @@ struct MediaTransportSettings final {
|
|||||||
bool is_caller;
|
bool is_caller;
|
||||||
|
|
||||||
// Must be set if a pre-shared key is used for the call.
|
// Must be set if a pre-shared key is used for the call.
|
||||||
|
// TODO(bugs.webrtc.org/9944): This should become zero buffer in the distant
|
||||||
|
// future.
|
||||||
absl::optional<std::string> pre_shared_key;
|
absl::optional<std::string> pre_shared_key;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#define API_TEST_FAKE_MEDIA_TRANSPORT_H_
|
#define API_TEST_FAKE_MEDIA_TRANSPORT_H_
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "absl/memory/memory.h"
|
#include "absl/memory/memory.h"
|
||||||
@ -25,7 +26,8 @@ namespace webrtc {
|
|||||||
// could unit test audio / video integration.
|
// could unit test audio / video integration.
|
||||||
class FakeMediaTransport : public MediaTransportInterface {
|
class FakeMediaTransport : public MediaTransportInterface {
|
||||||
public:
|
public:
|
||||||
explicit FakeMediaTransport(bool is_caller) : is_caller_(is_caller) {}
|
explicit FakeMediaTransport(const MediaTransportSettings& settings)
|
||||||
|
: settings_(settings) {}
|
||||||
~FakeMediaTransport() = default;
|
~FakeMediaTransport() = default;
|
||||||
|
|
||||||
RTCError SendAudioFrame(uint64_t channel_id,
|
RTCError SendAudioFrame(uint64_t channel_id,
|
||||||
@ -46,14 +48,17 @@ class FakeMediaTransport : public MediaTransportInterface {
|
|||||||
void SetReceiveAudioSink(MediaTransportAudioSinkInterface* sink) override {}
|
void SetReceiveAudioSink(MediaTransportAudioSinkInterface* sink) override {}
|
||||||
void SetReceiveVideoSink(MediaTransportVideoSinkInterface* sink) override {}
|
void SetReceiveVideoSink(MediaTransportVideoSinkInterface* sink) override {}
|
||||||
|
|
||||||
// Returns true if fake media trasport was created as a caller.
|
// Returns true if fake media transport was created as a caller.
|
||||||
bool is_caller() const { return is_caller_; }
|
bool is_caller() const { return settings_.is_caller; }
|
||||||
|
absl::optional<std::string> pre_shared_key() const {
|
||||||
|
return settings_.pre_shared_key;
|
||||||
|
}
|
||||||
|
|
||||||
void SetTargetTransferRateObserver(
|
void SetTargetTransferRateObserver(
|
||||||
webrtc::TargetTransferRateObserver* observer) override {}
|
webrtc::TargetTransferRateObserver* observer) override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const bool is_caller_;
|
const MediaTransportSettings settings_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fake media transport factory creates fake media transport.
|
// Fake media transport factory creates fake media transport.
|
||||||
@ -66,9 +71,17 @@ class FakeMediaTransportFactory : public MediaTransportFactory {
|
|||||||
rtc::PacketTransportInternal* packet_transport,
|
rtc::PacketTransportInternal* packet_transport,
|
||||||
rtc::Thread* network_thread,
|
rtc::Thread* network_thread,
|
||||||
bool is_caller) override {
|
bool is_caller) override {
|
||||||
std::unique_ptr<MediaTransportInterface> media_transport =
|
MediaTransportSettings settings;
|
||||||
absl::make_unique<FakeMediaTransport>(is_caller);
|
settings.is_caller = is_caller;
|
||||||
|
return CreateMediaTransport(packet_transport, network_thread, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
RTCErrorOr<std::unique_ptr<MediaTransportInterface>> CreateMediaTransport(
|
||||||
|
rtc::PacketTransportInternal* packet_transport,
|
||||||
|
rtc::Thread* network_thread,
|
||||||
|
const MediaTransportSettings& settings) override {
|
||||||
|
std::unique_ptr<MediaTransportInterface> media_transport =
|
||||||
|
absl::make_unique<FakeMediaTransport>(settings);
|
||||||
return std::move(media_transport);
|
return std::move(media_transport);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "p2p/base/port.h"
|
#include "p2p/base/port.h"
|
||||||
|
#include "pc/srtpfilter.h"
|
||||||
#include "rtc_base/bind.h"
|
#include "rtc_base/bind.h"
|
||||||
#include "rtc_base/checks.h"
|
#include "rtc_base/checks.h"
|
||||||
|
#include "rtc_base/key_derivation.h"
|
||||||
#include "rtc_base/thread.h"
|
#include "rtc_base/thread.h"
|
||||||
|
|
||||||
using webrtc::SdpType;
|
using webrtc::SdpType;
|
||||||
@ -940,17 +942,83 @@ RTCError JsepTransportController::MaybeCreateJsepTransport(
|
|||||||
CreateDtlsTransport(content_info.name, /*rtcp =*/true);
|
CreateDtlsTransport(content_info.name, /*rtcp =*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
absl::optional<cricket::CryptoParams> selected_crypto_for_media_transport;
|
||||||
|
if (content_info.media_description() &&
|
||||||
|
!content_info.media_description()->cryptos().empty()) {
|
||||||
|
// Order of cryptos is deterministic (rfc4568, 5.1.1), so we just select the
|
||||||
|
// first one (in fact the first one should be the most preferred one.) We
|
||||||
|
// ignore the HMAC size, as media transport crypto settings currently don't
|
||||||
|
// expose HMAC size, nor crypto protocol for that matter.
|
||||||
|
selected_crypto_for_media_transport =
|
||||||
|
content_info.media_description()->cryptos()[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (config_.media_transport_factory != nullptr) {
|
if (config_.media_transport_factory != nullptr) {
|
||||||
|
if (!selected_crypto_for_media_transport.has_value()) {
|
||||||
|
RTC_LOG(LS_WARNING) << "a=cryto line was not found in the offer. Most "
|
||||||
|
"likely you did not enable SDES. "
|
||||||
|
"Make sure to pass config.enable_dtls_srtp=false "
|
||||||
|
"to RTCConfiguration. "
|
||||||
|
"Cannot continue with media transport. Falling "
|
||||||
|
"back to RTP. is_local="
|
||||||
|
<< local;
|
||||||
|
|
||||||
|
// Remove media_transport_factory from config, because we don't want to
|
||||||
|
// use it on the subsequent call (for the other side of the offer).
|
||||||
|
config_.media_transport_factory = nullptr;
|
||||||
|
} else {
|
||||||
|
// Note that we ignore here lifetime and length.
|
||||||
|
// In fact we take those bits (inline, lifetime and length) and keep it as
|
||||||
|
// part of key derivation.
|
||||||
|
//
|
||||||
|
// Technically, we are also not following rfc4568, which requires us to
|
||||||
|
// send and answer with the key that we chose. In practice, for media
|
||||||
|
// transport, the current approach should be sufficient (we take the key
|
||||||
|
// that sender offered, and caller assumes we will use it. We are not
|
||||||
|
// signaling back that we indeed used it.)
|
||||||
|
std::unique_ptr<rtc::KeyDerivation> key_derivation =
|
||||||
|
rtc::KeyDerivation::Create(rtc::KeyDerivationAlgorithm::HKDF_SHA256);
|
||||||
|
const std::string label = "MediaTransportLabel";
|
||||||
|
constexpr int kDerivedKeyByteSize = 32;
|
||||||
|
|
||||||
|
int key_len, salt_len;
|
||||||
|
if (!rtc::GetSrtpKeyAndSaltLengths(
|
||||||
|
rtc::SrtpCryptoSuiteFromName(
|
||||||
|
selected_crypto_for_media_transport.value().cipher_suite),
|
||||||
|
&key_len, &salt_len)) {
|
||||||
|
RTC_CHECK(false) << "Cannot set up secure media transport";
|
||||||
|
}
|
||||||
|
rtc::ZeroOnFreeBuffer<uint8_t> raw_key(key_len + salt_len);
|
||||||
|
|
||||||
|
cricket::SrtpFilter::ParseKeyParams(
|
||||||
|
selected_crypto_for_media_transport.value().key_params,
|
||||||
|
raw_key.data(), raw_key.size());
|
||||||
|
absl::optional<rtc::ZeroOnFreeBuffer<uint8_t>> key =
|
||||||
|
key_derivation->DeriveKey(
|
||||||
|
raw_key,
|
||||||
|
/*salt=*/nullptr,
|
||||||
|
rtc::ArrayView<const uint8_t>(
|
||||||
|
reinterpret_cast<const uint8_t*>(label.data()), label.size()),
|
||||||
|
kDerivedKeyByteSize);
|
||||||
|
|
||||||
|
// We want to crash the app if we don't have a key, and not silently fall
|
||||||
|
// back to the unsecure communication.
|
||||||
|
RTC_CHECK(key.has_value());
|
||||||
|
MediaTransportSettings settings;
|
||||||
|
settings.is_caller = local;
|
||||||
|
settings.pre_shared_key =
|
||||||
|
std::string(reinterpret_cast<const char*>(key.value().data()),
|
||||||
|
key.value().size());
|
||||||
auto media_transport_result =
|
auto media_transport_result =
|
||||||
config_.media_transport_factory->CreateMediaTransport(
|
config_.media_transport_factory->CreateMediaTransport(
|
||||||
rtp_dtls_transport->ice_transport(), network_thread_,
|
rtp_dtls_transport->ice_transport(), network_thread_, settings);
|
||||||
/*is_caller=*/local);
|
|
||||||
|
|
||||||
// TODO(sukhanov): Proper error handling.
|
// TODO(sukhanov): Proper error handling.
|
||||||
RTC_CHECK(media_transport_result.ok());
|
RTC_CHECK(media_transport_result.ok());
|
||||||
|
|
||||||
media_transport = std::move(media_transport_result.value());
|
media_transport = std::move(media_transport_result.value());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(sukhanov): Do not create RTP/RTCP transports if media transport is
|
// TODO(sukhanov): Do not create RTP/RTCP transports if media transport is
|
||||||
// used.
|
// used.
|
||||||
|
@ -41,6 +41,20 @@ static const char kDataMid1[] = "data1";
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Media transport factory requires crypto settings to be present in order to
|
||||||
|
// create media transport.
|
||||||
|
void AddCryptoSettings(cricket::SessionDescription* description) {
|
||||||
|
for (auto& content : description->contents()) {
|
||||||
|
content.media_description()->AddCrypto(cricket::CryptoParams(
|
||||||
|
/*t=*/0, std::string(rtc::CS_AES_CM_128_HMAC_SHA1_80),
|
||||||
|
"inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class FakeTransportFactory : public cricket::TransportFactoryInterface {
|
class FakeTransportFactory : public cricket::TransportFactoryInterface {
|
||||||
public:
|
public:
|
||||||
std::unique_ptr<cricket::IceTransportInternal> CreateIceTransport(
|
std::unique_ptr<cricket::IceTransportInternal> CreateIceTransport(
|
||||||
@ -380,6 +394,8 @@ TEST_F(JsepTransportControllerTest, GetMediaTransportInCaller) {
|
|||||||
config.media_transport_factory = &fake_media_transport_factory;
|
config.media_transport_factory = &fake_media_transport_factory;
|
||||||
CreateJsepTransportController(config);
|
CreateJsepTransportController(config);
|
||||||
auto description = CreateSessionDescriptionWithoutBundle();
|
auto description = CreateSessionDescriptionWithoutBundle();
|
||||||
|
AddCryptoSettings(description.get());
|
||||||
|
|
||||||
EXPECT_TRUE(transport_controller_
|
EXPECT_TRUE(transport_controller_
|
||||||
->SetLocalDescription(SdpType::kOffer, description.get())
|
->SetLocalDescription(SdpType::kOffer, description.get())
|
||||||
.ok());
|
.ok());
|
||||||
@ -391,6 +407,7 @@ TEST_F(JsepTransportControllerTest, GetMediaTransportInCaller) {
|
|||||||
|
|
||||||
// After SetLocalDescription, media transport should be created as caller.
|
// After SetLocalDescription, media transport should be created as caller.
|
||||||
EXPECT_TRUE(media_transport->is_caller());
|
EXPECT_TRUE(media_transport->is_caller());
|
||||||
|
EXPECT_TRUE(media_transport->pre_shared_key().has_value());
|
||||||
|
|
||||||
// Return nullptr for non-existing mids.
|
// Return nullptr for non-existing mids.
|
||||||
EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
|
EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
|
||||||
@ -404,6 +421,7 @@ TEST_F(JsepTransportControllerTest, GetMediaTransportInCallee) {
|
|||||||
config.media_transport_factory = &fake_media_transport_factory;
|
config.media_transport_factory = &fake_media_transport_factory;
|
||||||
CreateJsepTransportController(config);
|
CreateJsepTransportController(config);
|
||||||
auto description = CreateSessionDescriptionWithoutBundle();
|
auto description = CreateSessionDescriptionWithoutBundle();
|
||||||
|
AddCryptoSettings(description.get());
|
||||||
EXPECT_TRUE(transport_controller_
|
EXPECT_TRUE(transport_controller_
|
||||||
->SetRemoteDescription(SdpType::kOffer, description.get())
|
->SetRemoteDescription(SdpType::kOffer, description.get())
|
||||||
.ok());
|
.ok());
|
||||||
@ -415,11 +433,70 @@ TEST_F(JsepTransportControllerTest, GetMediaTransportInCallee) {
|
|||||||
|
|
||||||
// After SetRemoteDescription, media transport should be created as callee.
|
// After SetRemoteDescription, media transport should be created as callee.
|
||||||
EXPECT_FALSE(media_transport->is_caller());
|
EXPECT_FALSE(media_transport->is_caller());
|
||||||
|
EXPECT_TRUE(media_transport->pre_shared_key().has_value());
|
||||||
|
|
||||||
// Return nullptr for non-existing mids.
|
// Return nullptr for non-existing mids.
|
||||||
EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
|
EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(JsepTransportControllerTest, GetMediaTransportIsNotSetIfNoSdes) {
|
||||||
|
FakeMediaTransportFactory fake_media_transport_factory;
|
||||||
|
JsepTransportController::Config config;
|
||||||
|
|
||||||
|
config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
|
||||||
|
config.media_transport_factory = &fake_media_transport_factory;
|
||||||
|
CreateJsepTransportController(config);
|
||||||
|
auto description = CreateSessionDescriptionWithoutBundle();
|
||||||
|
EXPECT_TRUE(transport_controller_
|
||||||
|
->SetRemoteDescription(SdpType::kOffer, description.get())
|
||||||
|
.ok());
|
||||||
|
|
||||||
|
EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kAudioMid1));
|
||||||
|
|
||||||
|
// Even if we set local description with crypto now (after the remote offer
|
||||||
|
// was set), media transport won't be provided.
|
||||||
|
auto description2 = CreateSessionDescriptionWithoutBundle();
|
||||||
|
AddCryptoSettings(description2.get());
|
||||||
|
EXPECT_TRUE(transport_controller_
|
||||||
|
->SetLocalDescription(SdpType::kAnswer, description2.get())
|
||||||
|
.ok());
|
||||||
|
|
||||||
|
EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kAudioMid1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(JsepTransportControllerTest,
|
||||||
|
AfterSettingAnswerTheSameMediaTransportIsReturned) {
|
||||||
|
FakeMediaTransportFactory fake_media_transport_factory;
|
||||||
|
JsepTransportController::Config config;
|
||||||
|
|
||||||
|
config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
|
||||||
|
config.media_transport_factory = &fake_media_transport_factory;
|
||||||
|
CreateJsepTransportController(config);
|
||||||
|
auto description = CreateSessionDescriptionWithoutBundle();
|
||||||
|
AddCryptoSettings(description.get());
|
||||||
|
EXPECT_TRUE(transport_controller_
|
||||||
|
->SetRemoteDescription(SdpType::kOffer, description.get())
|
||||||
|
.ok());
|
||||||
|
|
||||||
|
FakeMediaTransport* media_transport = static_cast<FakeMediaTransport*>(
|
||||||
|
transport_controller_->GetMediaTransport(kAudioMid1));
|
||||||
|
EXPECT_NE(nullptr, media_transport);
|
||||||
|
EXPECT_TRUE(media_transport->pre_shared_key().has_value());
|
||||||
|
|
||||||
|
// Even if we set local description with crypto now (after the remote offer
|
||||||
|
// was set), media transport won't be provided.
|
||||||
|
auto description2 = CreateSessionDescriptionWithoutBundle();
|
||||||
|
AddCryptoSettings(description2.get());
|
||||||
|
|
||||||
|
RTCError result = transport_controller_->SetLocalDescription(
|
||||||
|
SdpType::kAnswer, description2.get());
|
||||||
|
EXPECT_TRUE(result.ok()) << result.message();
|
||||||
|
|
||||||
|
// Media transport did not change.
|
||||||
|
EXPECT_EQ(media_transport,
|
||||||
|
transport_controller_->GetMediaTransport(kAudioMid1));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(JsepTransportControllerTest, SetIceConfig) {
|
TEST_F(JsepTransportControllerTest, SetIceConfig) {
|
||||||
CreateJsepTransportController(JsepTransportController::Config());
|
CreateJsepTransportController(JsepTransportController::Config());
|
||||||
auto description = CreateSessionDescriptionWithoutBundle();
|
auto description = CreateSessionDescriptionWithoutBundle();
|
||||||
|
@ -1092,6 +1092,9 @@ TEST_P(PeerConnectionMediaTest, MediaTransportPropagatedToVoiceEngine) {
|
|||||||
// Setup PeerConnection to use media transport.
|
// Setup PeerConnection to use media transport.
|
||||||
config.use_media_transport = true;
|
config.use_media_transport = true;
|
||||||
|
|
||||||
|
// Force SDES.
|
||||||
|
config.enable_dtls_srtp = false;
|
||||||
|
|
||||||
auto caller = CreatePeerConnectionWithAudioVideo(config);
|
auto caller = CreatePeerConnectionWithAudioVideo(config);
|
||||||
auto callee = CreatePeerConnectionWithAudioVideo(config);
|
auto callee = CreatePeerConnectionWithAudioVideo(config);
|
||||||
|
|
||||||
|
@ -78,6 +78,10 @@ class SrtpFilter {
|
|||||||
|
|
||||||
bool ResetParams();
|
bool ResetParams();
|
||||||
|
|
||||||
|
static bool ParseKeyParams(const std::string& params,
|
||||||
|
uint8_t* key,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
absl::optional<int> send_cipher_suite() { return send_cipher_suite_; }
|
absl::optional<int> send_cipher_suite() { return send_cipher_suite_; }
|
||||||
absl::optional<int> recv_cipher_suite() { return recv_cipher_suite_; }
|
absl::optional<int> recv_cipher_suite() { return recv_cipher_suite_; }
|
||||||
|
|
||||||
@ -104,10 +108,6 @@ class SrtpFilter {
|
|||||||
|
|
||||||
bool ApplyRecvParams(const CryptoParams& recv_params);
|
bool ApplyRecvParams(const CryptoParams& recv_params);
|
||||||
|
|
||||||
static bool ParseKeyParams(const std::string& params,
|
|
||||||
uint8_t* key,
|
|
||||||
size_t len);
|
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
ST_INIT, // SRTP filter unused.
|
ST_INIT, // SRTP filter unused.
|
||||||
ST_SENTOFFER, // Offer with SRTP parameters sent.
|
ST_SENTOFFER, // Offer with SRTP parameters sent.
|
||||||
|
Reference in New Issue
Block a user