| /* |
| * 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/dtls_srtp_transport.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "api/dtls_transport_interface.h" |
| #include "api/field_trials_view.h" |
| #include "p2p/base/packet_transport_internal.h" |
| #include "p2p/dtls/dtls_transport_internal.h" |
| #include "pc/srtp_transport.h" |
| #include "rtc_base/buffer.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/ssl_stream_adapter.h" |
| |
| namespace webrtc { |
| namespace { |
| void ValidateAndLogTransport(DtlsTransportInternal* rtp_dtls_transport, |
| DtlsTransportInternal* old_rtcp_dtls_transport, |
| DtlsTransportInternal* rtcp_dtls_transport, |
| bool is_srtp_active) { |
| if (rtcp_dtls_transport && rtcp_dtls_transport != old_rtcp_dtls_transport) { |
| // This would only be possible if using BUNDLE but not rtcp-mux, which isn't |
| // allowed according to the BUNDLE spec. |
| RTC_CHECK(!is_srtp_active) |
| << "Setting RTCP for DTLS/SRTP after the DTLS is active " |
| "should never happen."; |
| } |
| if (rtcp_dtls_transport && rtp_dtls_transport) { |
| RTC_DCHECK_EQ(rtcp_dtls_transport->transport_name(), |
| rtp_dtls_transport->transport_name()); |
| } |
| if (rtcp_dtls_transport) { |
| RTC_LOG(LS_INFO) << "Setting RTCP Transport on " |
| << rtcp_dtls_transport->transport_name() << " transport " |
| << rtcp_dtls_transport; |
| } |
| if (rtp_dtls_transport) { |
| RTC_LOG(LS_INFO) << "Setting RTP Transport on " |
| << rtp_dtls_transport->transport_name() << " transport " |
| << rtp_dtls_transport; |
| } |
| } |
| } // namespace |
| |
| DtlsSrtpTransport::DtlsSrtpTransport(bool rtcp_mux_enabled, |
| const FieldTrialsView& field_trials) |
| : SrtpTransport(rtcp_mux_enabled, field_trials) {} |
| |
| void DtlsSrtpTransport::SetDtlsTransports(DtlsTransportInternal* rtp_dtls, |
| DtlsTransportInternal* rtcp_dtls) { |
| ValidateAndLogTransport(rtp_dtls, rtcp_dtls_transport(), rtcp_dtls, |
| IsSrtpActive()); |
| |
| bool rtp_changed = MaybeUnsubscribe(rtp_dtls_transport(), rtp_dtls); |
| bool rtcp_changed = MaybeUnsubscribe(rtcp_dtls_transport(), rtcp_dtls); |
| |
| // Now pass the RTP transport to RtpTransport. |
| SetRtpPacketTransport(rtp_dtls); |
| SetRtcpPacketTransport(rtcp_dtls); |
| |
| if (rtp_changed) { |
| SetupDtlsTransport(rtp_dtls, /*is_rtcp=*/false); |
| } |
| if (rtcp_changed) { |
| SetupDtlsTransport(rtcp_dtls, /*is_rtcp=*/true); |
| } |
| } |
| |
| void DtlsSrtpTransport::SetDtlsTransportsOwned( |
| std::unique_ptr<DtlsTransportInternal> rtp_dtls, |
| std::unique_ptr<DtlsTransportInternal> rtcp_dtls) { |
| ValidateAndLogTransport(rtp_dtls.get(), rtcp_dtls_transport(), |
| rtcp_dtls.get(), IsSrtpActive()); |
| |
| bool rtp_changed = MaybeUnsubscribe(rtp_dtls_transport(), rtp_dtls.get()); |
| bool rtcp_changed = MaybeUnsubscribe(rtcp_dtls_transport(), rtcp_dtls.get()); |
| |
| // Pass the RTP transport to RtpTransport and ownership of |
| // rtcp_dtls_transport. |
| SetRtpPacketTransportOwned(std::move(rtp_dtls)); |
| SetRtcpPacketTransportOwned(std::move(rtcp_dtls)); |
| |
| if (rtp_changed) { |
| SetupDtlsTransport(rtp_dtls_transport(), /*is_rtcp=*/false); |
| } |
| |
| if (rtcp_changed) { |
| SetupDtlsTransport(rtcp_dtls_transport(), /*is_rtcp=*/true); |
| } |
| } |
| |
| void DtlsSrtpTransport::SetRtcpMuxEnabled(bool enable) { |
| SrtpTransport::SetRtcpMuxEnabled(enable); |
| if (enable) { |
| MaybeSetupDtlsSrtp(); |
| } |
| } |
| |
| void DtlsSrtpTransport::UpdateSendEncryptedHeaderExtensionIds( |
| const std::vector<int>& send_extension_ids) { |
| if (send_extension_ids_ == send_extension_ids) { |
| return; |
| } |
| send_extension_ids_.emplace(send_extension_ids); |
| if (DtlsHandshakeCompleted()) { |
| // Reset the crypto parameters to update the send extension IDs. |
| SetupRtpDtlsSrtp(); |
| } |
| } |
| |
| void DtlsSrtpTransport::UpdateRecvEncryptedHeaderExtensionIds( |
| const std::vector<int>& recv_extension_ids) { |
| if (recv_extension_ids_ == recv_extension_ids) { |
| return; |
| } |
| recv_extension_ids_.emplace(recv_extension_ids); |
| if (DtlsHandshakeCompleted()) { |
| // Reset the crypto parameters to update the receive extension IDs. |
| SetupRtpDtlsSrtp(); |
| } |
| } |
| |
| bool DtlsSrtpTransport::IsDtlsActive() { |
| auto rtcp_dtls_transport_ptr = |
| rtcp_mux_enabled() ? nullptr : rtcp_dtls_transport(); |
| return rtp_dtls_transport() && rtp_dtls_transport()->IsDtlsActive() && |
| (!rtcp_dtls_transport_ptr || rtcp_dtls_transport_ptr->IsDtlsActive()); |
| } |
| |
| bool DtlsSrtpTransport::IsDtlsConnected() { |
| auto rtcp_dtls_transport_ptr = |
| rtcp_mux_enabled() ? nullptr : rtcp_dtls_transport(); |
| return rtp_dtls_transport() && |
| rtp_dtls_transport()->dtls_state() == DtlsTransportState::kConnected && |
| (!rtcp_dtls_transport_ptr || rtcp_dtls_transport_ptr->dtls_state() == |
| DtlsTransportState::kConnected); |
| } |
| |
| bool DtlsSrtpTransport::IsDtlsWritable() { |
| auto rtcp_packet_transport = |
| rtcp_mux_enabled() ? nullptr : rtcp_dtls_transport(); |
| return rtp_dtls_transport() && rtp_dtls_transport()->writable() && |
| (!rtcp_packet_transport || rtcp_packet_transport->writable()); |
| } |
| |
| bool DtlsSrtpTransport::DtlsHandshakeCompleted() { |
| return IsDtlsActive() && IsDtlsConnected(); |
| } |
| |
| void DtlsSrtpTransport::MaybeSetupDtlsSrtp() { |
| if (IsSrtpActive() || !IsDtlsWritable()) { |
| return; |
| } |
| |
| SetupRtpDtlsSrtp(); |
| |
| if (!rtcp_mux_enabled() && rtcp_dtls_transport()) { |
| SetupRtcpDtlsSrtp(); |
| } |
| } |
| |
| void DtlsSrtpTransport::SetupRtpDtlsSrtp() { |
| // Use an empty encrypted header extension ID vector if not set. This could |
| // happen when the DTLS handshake is completed before processing the |
| // Offer/Answer which contains the encrypted header extension IDs. |
| std::vector<int> send_extension_ids; |
| std::vector<int> recv_extension_ids; |
| if (send_extension_ids_) { |
| send_extension_ids = *send_extension_ids_; |
| } |
| if (recv_extension_ids_) { |
| recv_extension_ids = *recv_extension_ids_; |
| } |
| |
| int selected_crypto_suite; |
| ZeroOnFreeBuffer<uint8_t> send_key; |
| ZeroOnFreeBuffer<uint8_t> recv_key; |
| |
| if (!ExtractParams(rtp_dtls_transport(), &selected_crypto_suite, &send_key, |
| &recv_key) || |
| !SetRtpParams(selected_crypto_suite, send_key, send_extension_ids, |
| selected_crypto_suite, recv_key, recv_extension_ids)) { |
| RTC_LOG(LS_WARNING) << "DTLS-SRTP key installation for RTP failed"; |
| } |
| } |
| |
| void DtlsSrtpTransport::SetupRtcpDtlsSrtp() { |
| // Return if the DTLS-SRTP is active because the encrypted header extension |
| // IDs don't need to be updated for RTCP and the crypto params don't need to |
| // be reset. |
| if (IsSrtpActive()) { |
| return; |
| } |
| |
| std::vector<int> send_extension_ids; |
| std::vector<int> recv_extension_ids; |
| if (send_extension_ids_) { |
| send_extension_ids = *send_extension_ids_; |
| } |
| if (recv_extension_ids_) { |
| recv_extension_ids = *recv_extension_ids_; |
| } |
| |
| int selected_crypto_suite; |
| ZeroOnFreeBuffer<uint8_t> rtcp_send_key; |
| ZeroOnFreeBuffer<uint8_t> rtcp_recv_key; |
| if (!ExtractParams(rtcp_dtls_transport(), &selected_crypto_suite, |
| &rtcp_send_key, &rtcp_recv_key) || |
| !SetRtcpParams(selected_crypto_suite, rtcp_send_key, send_extension_ids, |
| selected_crypto_suite, rtcp_recv_key, |
| recv_extension_ids)) { |
| RTC_LOG(LS_WARNING) << "DTLS-SRTP key installation for RTCP failed"; |
| } |
| } |
| |
| bool DtlsSrtpTransport::ExtractParams(DtlsTransportInternal* dtls_transport, |
| int* selected_crypto_suite, |
| ZeroOnFreeBuffer<uint8_t>* send_key, |
| ZeroOnFreeBuffer<uint8_t>* recv_key) { |
| if (!dtls_transport || !dtls_transport->IsDtlsActive()) { |
| return false; |
| } |
| |
| if (!dtls_transport->GetSrtpCryptoSuite(selected_crypto_suite)) { |
| RTC_LOG(LS_ERROR) << "No DTLS-SRTP selected crypto suite"; |
| return false; |
| } |
| |
| RTC_LOG(LS_INFO) << "Extracting keys from transport: " |
| << dtls_transport->transport_name(); |
| |
| int key_len; |
| int salt_len; |
| if (!GetSrtpKeyAndSaltLengths((*selected_crypto_suite), &key_len, |
| &salt_len)) { |
| RTC_LOG(LS_ERROR) << "Unknown DTLS-SRTP crypto suite" |
| << selected_crypto_suite; |
| return false; |
| } |
| |
| // OK, we're now doing DTLS (RFC 5764) |
| ZeroOnFreeBuffer<uint8_t> dtls_buffer(key_len * 2 + salt_len * 2); |
| |
| // RFC 5705 exporter using the RFC 5764 parameters |
| if (!dtls_transport->ExportSrtpKeyingMaterial(dtls_buffer)) { |
| RTC_LOG(LS_ERROR) << "DTLS-SRTP key export failed"; |
| RTC_DCHECK_NOTREACHED(); // This should never happen |
| return false; |
| } |
| |
| // Sync up the keys with the DTLS-SRTP interface |
| // https://datatracker.ietf.org/doc/html/rfc5764#section-4.2 |
| // The keying material is in the format: |
| // client_write_key|server_write_key|client_write_salt|server_write_salt |
| ZeroOnFreeBuffer<uint8_t> client_write_key(&dtls_buffer[0], key_len, |
| key_len + salt_len); |
| ZeroOnFreeBuffer<uint8_t> server_write_key(&dtls_buffer[key_len], key_len, |
| key_len + salt_len); |
| client_write_key.AppendData(&dtls_buffer[key_len + key_len], salt_len); |
| server_write_key.AppendData(&dtls_buffer[key_len + key_len + salt_len], |
| salt_len); |
| |
| SSLRole role; |
| if (!dtls_transport->GetDtlsRole(&role)) { |
| RTC_LOG(LS_WARNING) << "Failed to get the DTLS role."; |
| return false; |
| } |
| |
| if (role == SSL_SERVER) { |
| *send_key = std::move(server_write_key); |
| *recv_key = std::move(client_write_key); |
| } else { |
| *send_key = std::move(client_write_key); |
| *recv_key = std::move(server_write_key); |
| } |
| return true; |
| } |
| |
| void DtlsSrtpTransport::SetupDtlsTransport( |
| DtlsTransportInternal* dtls_transport, |
| bool is_rtcp) { |
| if (dtls_transport) { |
| dtls_transport->SubscribeDtlsTransportState( |
| this, |
| [this](DtlsTransportInternal* transport, DtlsTransportState state) { |
| OnDtlsState(transport, state); |
| }); |
| // Set the initial state. |
| OnDtlsState(dtls_transport, dtls_transport->dtls_state()); |
| } else { |
| // When the transport is removed, we usually reset the SRTP parameters. |
| // However, if the RTCP transport is removed because we are enabling RTCP |
| // muxing, we should not reset the parameters because the SRTP session |
| // will be maintained by the RTP transport. |
| if (is_rtcp && rtcp_mux_enabled()) { |
| return; |
| } |
| OnDtlsState(nullptr, DtlsTransportState::kNew); |
| } |
| } |
| |
| bool DtlsSrtpTransport::MaybeUnsubscribe(DtlsTransportInternal* old_transport, |
| DtlsTransportInternal* new_transport) { |
| if (old_transport && old_transport != new_transport) { |
| old_transport->UnsubscribeDtlsTransportState(this); |
| } |
| return old_transport != new_transport; |
| } |
| |
| void DtlsSrtpTransport::OnDtlsState(DtlsTransportInternal* transport, |
| DtlsTransportState state) { |
| RTC_DCHECK(transport == rtp_dtls_transport() || |
| transport == rtcp_dtls_transport()); |
| |
| if (on_dtls_state_change_) { |
| on_dtls_state_change_(); |
| } |
| |
| if (state != DtlsTransportState::kConnected) { |
| ResetParams(); |
| } |
| |
| MaybeSetupDtlsSrtp(); |
| } |
| |
| void DtlsSrtpTransport::OnWritableState( |
| PacketTransportInternal* packet_transport) { |
| MaybeSetupDtlsSrtp(); |
| } |
| |
| void DtlsSrtpTransport::SetOnDtlsStateChange( |
| absl::AnyInvocable<void()> callback) { |
| on_dtls_state_change_ = std::move(callback); |
| } |
| } // namespace webrtc |