diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h index dd2eacdd67..6ae46812d6 100644 --- a/api/stats/rtcstats_objects.h +++ b/api/stats/rtcstats_objects.h @@ -600,6 +600,9 @@ class RTC_EXPORT RTCTransportStats final : public RTCStats { RTCStatsMember selected_candidate_pair_id; RTCStatsMember local_certificate_id; RTCStatsMember remote_certificate_id; + RTCStatsMember tls_version; + RTCStatsMember dtls_cipher; + RTCStatsMember srtp_cipher; RTCStatsMember selected_candidate_pair_changes; }; diff --git a/p2p/base/dtls_transport.cc b/p2p/base/dtls_transport.cc index 3af5fd870d..acd5765f59 100644 --- a/p2p/base/dtls_transport.cc +++ b/p2p/base/dtls_transport.cc @@ -380,6 +380,14 @@ bool DtlsTransport::GetSrtpCryptoSuite(int* cipher) { return dtls_->GetDtlsSrtpCryptoSuite(cipher); } +bool DtlsTransport::GetSslVersionBytes(int* version) const { + if (dtls_state() != DTLS_TRANSPORT_CONNECTED) { + return false; + } + + return dtls_->GetSslVersionBytes(version); +} + // Called from upper layers to send a media packet. int DtlsTransport::SendPacket(const char* data, size_t size, diff --git a/p2p/base/dtls_transport.h b/p2p/base/dtls_transport.h index bf3e056bea..89156a15d1 100644 --- a/p2p/base/dtls_transport.h +++ b/p2p/base/dtls_transport.h @@ -142,6 +142,8 @@ class DtlsTransport : public DtlsTransportInternal { bool SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) override; + // Find out which TLS version was negotiated + bool GetSslVersionBytes(int* version) const override; // Find out which DTLS-SRTP cipher was negotiated bool GetSrtpCryptoSuite(int* cipher) override; diff --git a/p2p/base/dtls_transport_internal.h b/p2p/base/dtls_transport_internal.h index 07a669af2e..4c35d7371f 100644 --- a/p2p/base/dtls_transport_internal.h +++ b/p2p/base/dtls_transport_internal.h @@ -74,6 +74,8 @@ class DtlsTransportInternal : public rtc::PacketTransportInternal { virtual bool SetDtlsRole(rtc::SSLRole role) = 0; + // Finds out which TLS/DTLS version is running. + virtual bool GetSslVersionBytes(int* version) const = 0; // Finds out which DTLS-SRTP cipher was negotiated. // TODO(zhihuang): Remove this once all dependencies implement this. virtual bool GetSrtpCryptoSuite(int* cipher) = 0; diff --git a/p2p/base/fake_dtls_transport.h b/p2p/base/fake_dtls_transport.h index c26cae9c37..cf6fd784b1 100644 --- a/p2p/base/fake_dtls_transport.h +++ b/p2p/base/fake_dtls_transport.h @@ -168,6 +168,13 @@ class FakeDtlsTransport : public DtlsTransportInternal { remote_cert_ = cert; } bool IsDtlsActive() const override { return do_dtls_; } + bool GetSslVersionBytes(int* version) const override { + if (!do_dtls_) { + return false; + } + *version = 0x0102; + return true; + } bool GetSrtpCryptoSuite(int* crypto_suite) override { if (!do_dtls_) { return false; diff --git a/p2p/base/no_op_dtls_transport.cc b/p2p/base/no_op_dtls_transport.cc index 8981158707..0ce03b930c 100644 --- a/p2p/base/no_op_dtls_transport.cc +++ b/p2p/base/no_op_dtls_transport.cc @@ -66,6 +66,9 @@ bool NoOpDtlsTransport::GetDtlsRole(rtc::SSLRole* role) const { bool NoOpDtlsTransport::SetDtlsRole(rtc::SSLRole role) { return false; } +bool NoOpDtlsTransport::GetSslVersionBytes(int* version) const { + return false; +} bool NoOpDtlsTransport::GetSrtpCryptoSuite(int* cipher) { return false; } diff --git a/p2p/base/no_op_dtls_transport.h b/p2p/base/no_op_dtls_transport.h index 7111b29988..f8829dbfa9 100644 --- a/p2p/base/no_op_dtls_transport.h +++ b/p2p/base/no_op_dtls_transport.h @@ -64,6 +64,7 @@ class NoOpDtlsTransport : public DtlsTransportInternal { bool IsDtlsActive() const override; bool GetDtlsRole(rtc::SSLRole* role) const override; bool SetDtlsRole(rtc::SSLRole role) override; + bool GetSslVersionBytes(int* version) const override; bool GetSrtpCryptoSuite(int* cipher) override; bool GetSslCipherSuite(int* cipher) override; rtc::scoped_refptr GetLocalCertificate() const override; diff --git a/pc/jsep_transport.cc b/pc/jsep_transport.cc index ca44ec8b65..13618c7a6c 100644 --- a/pc/jsep_transport.cc +++ b/pc/jsep_transport.cc @@ -772,6 +772,7 @@ bool JsepTransport::GetTransportStats(DtlsTransportInternal* dtls_transport, } else { substats.component = ICE_CANDIDATE_COMPONENT_RTP; } + dtls_transport->GetSslVersionBytes(&substats.ssl_version_bytes); dtls_transport->GetSrtpCryptoSuite(&substats.srtp_crypto_suite); dtls_transport->GetSslCipherSuite(&substats.ssl_cipher_suite); substats.dtls_state = dtls_transport->dtls_state(); diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc index ab12c65772..481e25541d 100644 --- a/pc/rtc_stats_collector.cc +++ b/pc/rtc_stats_collector.cc @@ -1721,6 +1721,26 @@ void RTCStatsCollector::ProduceTransportStats_n( transport_stats->local_certificate_id = local_certificate_id; if (!remote_certificate_id.empty()) transport_stats->remote_certificate_id = remote_certificate_id; + // Crypto information + if (channel_stats.ssl_version_bytes) { + char bytes[5]; + snprintf(bytes, sizeof(bytes), "%04X", channel_stats.ssl_version_bytes); + transport_stats->tls_version = bytes; + } + if (channel_stats.ssl_cipher_suite != rtc::TLS_NULL_WITH_NULL_NULL && + rtc::SSLStreamAdapter::SslCipherSuiteToName( + channel_stats.ssl_cipher_suite) + .length()) { + transport_stats->dtls_cipher = + rtc::SSLStreamAdapter::SslCipherSuiteToName( + channel_stats.ssl_cipher_suite); + } + if (channel_stats.srtp_crypto_suite != rtc::SRTP_INVALID_CRYPTO_SUITE && + rtc::SrtpCryptoSuiteToName(channel_stats.srtp_crypto_suite) + .length()) { + transport_stats->srtp_cipher = + rtc::SrtpCryptoSuiteToName(channel_stats.srtp_crypto_suite); + } report->AddStats(std::move(transport_stats)); } } diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc index ce2d54e82e..95c2a9b863 100644 --- a/pc/rtc_stats_collector_unittest.cc +++ b/pc/rtc_stats_collector_unittest.cc @@ -2202,6 +2202,68 @@ TEST_F(RTCStatsCollectorTest, CollectRTCTransportStats) { report->Get(expected_rtcp_transport.id())->cast_to()); } +TEST_F(RTCStatsCollectorTest, CollectRTCTransportStatsWithCrypto) { + const char kTransportName[] = "transport"; + + pc_->AddVoiceChannel("audio", kTransportName); + + std::unique_ptr rtp_local_candidate = + CreateFakeCandidate("42.42.42.42", 42, "protocol", rtc::ADAPTER_TYPE_WIFI, + cricket::LOCAL_PORT_TYPE, 42); + std::unique_ptr rtp_remote_candidate = + CreateFakeCandidate("42.42.42.42", 42, "protocol", + rtc::ADAPTER_TYPE_UNKNOWN, cricket::LOCAL_PORT_TYPE, + 42); + std::unique_ptr rtcp_local_candidate = + CreateFakeCandidate("42.42.42.42", 42, "protocol", rtc::ADAPTER_TYPE_WIFI, + cricket::LOCAL_PORT_TYPE, 42); + std::unique_ptr rtcp_remote_candidate = + CreateFakeCandidate("42.42.42.42", 42, "protocol", + rtc::ADAPTER_TYPE_UNKNOWN, cricket::LOCAL_PORT_TYPE, + 42); + + cricket::ConnectionInfo rtp_connection_info; + rtp_connection_info.best_connection = false; + rtp_connection_info.local_candidate = *rtp_local_candidate.get(); + rtp_connection_info.remote_candidate = *rtp_remote_candidate.get(); + rtp_connection_info.sent_total_bytes = 42; + rtp_connection_info.recv_total_bytes = 1337; + cricket::TransportChannelStats rtp_transport_channel_stats; + rtp_transport_channel_stats.component = cricket::ICE_CANDIDATE_COMPONENT_RTP; + rtp_transport_channel_stats.ice_transport_stats.connection_infos.push_back( + rtp_connection_info); + // The state must be connected in order for crypto parameters to show up. + rtp_transport_channel_stats.dtls_state = cricket::DTLS_TRANSPORT_CONNECTED; + rtp_transport_channel_stats.ice_transport_stats + .selected_candidate_pair_changes = 1; + rtp_transport_channel_stats.ssl_version_bytes = 0x0203; + // 0x2F is TLS_RSA_WITH_AES_128_CBC_SHA according to IANA + rtp_transport_channel_stats.ssl_cipher_suite = 0x2F; + rtp_transport_channel_stats.srtp_crypto_suite = rtc::SRTP_AES128_CM_SHA1_80; + pc_->SetTransportStats(kTransportName, {rtp_transport_channel_stats}); + + // Get stats + rtc::scoped_refptr report = stats_->GetStatsReport(); + + RTCTransportStats expected_rtp_transport( + "RTCTransport_transport_" + + rtc::ToString(cricket::ICE_CANDIDATE_COMPONENT_RTP), + report->timestamp_us()); + expected_rtp_transport.bytes_sent = 42; + expected_rtp_transport.bytes_received = 1337; + expected_rtp_transport.dtls_state = RTCDtlsTransportState::kConnected; + expected_rtp_transport.selected_candidate_pair_changes = 1; + // Crypto parameters + expected_rtp_transport.tls_version = "0203"; + expected_rtp_transport.dtls_cipher = "TLS_RSA_WITH_AES_128_CBC_SHA"; + expected_rtp_transport.srtp_cipher = "AES_CM_128_HMAC_SHA1_80"; + + ASSERT_TRUE(report->Get(expected_rtp_transport.id())); + EXPECT_EQ( + expected_rtp_transport, + report->Get(expected_rtp_transport.id())->cast_to()); +} + TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) { cricket::VoiceMediaInfo voice_media_info; diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc index 9000ff95f5..31258a2af4 100644 --- a/pc/rtc_stats_integrationtest.cc +++ b/pc/rtc_stats_integrationtest.cc @@ -973,6 +973,9 @@ class RTCStatsReportVerifier { RTCCertificateStats::kType); verifier.TestMemberIsIDReference(transport.remote_certificate_id, RTCCertificateStats::kType); + verifier.TestMemberIsDefined(transport.tls_version); + verifier.TestMemberIsDefined(transport.dtls_cipher); + verifier.TestMemberIsDefined(transport.srtp_cipher); verifier.TestMemberIsPositive( transport.selected_candidate_pair_changes); return verifier.ExpectAllMembersSuccessfullyTested(); diff --git a/pc/transport_stats.h b/pc/transport_stats.h index 4f6ce2a22a..7cb95f4ad2 100644 --- a/pc/transport_stats.h +++ b/pc/transport_stats.h @@ -27,6 +27,7 @@ struct TransportChannelStats { ~TransportChannelStats(); int component = 0; + int ssl_version_bytes = 0; int srtp_crypto_suite = rtc::SRTP_INVALID_CRYPTO_SUITE; int ssl_cipher_suite = rtc::TLS_NULL_WITH_NULL_NULL; DtlsTransportState dtls_state = DTLS_TRANSPORT_NEW; diff --git a/rtc_base/openssl_stream_adapter.cc b/rtc_base/openssl_stream_adapter.cc index 7e7fae3db7..28e8106e77 100644 --- a/rtc_base/openssl_stream_adapter.cc +++ b/rtc_base/openssl_stream_adapter.cc @@ -373,9 +373,9 @@ bool OpenSSLStreamAdapter::GetSslCipherSuite(int* cipher_suite) { return true; } -int OpenSSLStreamAdapter::GetSslVersion() const { +SSLProtocolVersion OpenSSLStreamAdapter::GetSslVersion() const { if (state_ != SSL_CONNECTED) { - return -1; + return SSL_PROTOCOL_NOT_GIVEN; } int ssl_version = SSL_version(ssl_); @@ -395,7 +395,15 @@ int OpenSSLStreamAdapter::GetSslVersion() const { } } - return -1; + return SSL_PROTOCOL_NOT_GIVEN; +} + +bool OpenSSLStreamAdapter::GetSslVersionBytes(int* version) const { + if (state_ != SSL_CONNECTED) { + return false; + } + *version = SSL_version(ssl_); + return true; } // Key Extractor interface diff --git a/rtc_base/openssl_stream_adapter.h b/rtc_base/openssl_stream_adapter.h index 5d99e3227e..dfe57a441e 100644 --- a/rtc_base/openssl_stream_adapter.h +++ b/rtc_base/openssl_stream_adapter.h @@ -96,8 +96,8 @@ class OpenSSLStreamAdapter final : public SSLStreamAdapter { bool GetSslCipherSuite(int* cipher) override; - int GetSslVersion() const override; - + SSLProtocolVersion GetSslVersion() const override; + bool GetSslVersionBytes(int* version) const override; // Key Extractor interface bool ExportKeyingMaterial(const std::string& label, const uint8_t* context, diff --git a/rtc_base/ssl_stream_adapter.h b/rtc_base/ssl_stream_adapter.h index 04d0fc5dd4..484657ebaf 100644 --- a/rtc_base/ssl_stream_adapter.h +++ b/rtc_base/ssl_stream_adapter.h @@ -91,7 +91,8 @@ bool IsGcmCryptoSuiteName(const std::string& crypto_suite); enum SSLRole { SSL_CLIENT, SSL_SERVER }; enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS }; enum SSLProtocolVersion { - SSL_PROTOCOL_TLS_10, + SSL_PROTOCOL_NOT_GIVEN = -1, + SSL_PROTOCOL_TLS_10 = 0, SSL_PROTOCOL_TLS_11, SSL_PROTOCOL_TLS_12, SSL_PROTOCOL_DTLS_10 = SSL_PROTOCOL_TLS_11, @@ -187,7 +188,12 @@ class SSLStreamAdapter : public StreamAdapterInterface { // connection (e.g. 0x2F for "TLS_RSA_WITH_AES_128_CBC_SHA"). virtual bool GetSslCipherSuite(int* cipher_suite); - virtual int GetSslVersion() const = 0; + // Retrieves the enum value for SSL version. + // Will return -1 until the version has been negotiated. + virtual SSLProtocolVersion GetSslVersion() const = 0; + // Retrieves the 2-byte version from the TLS protocol. + // Will return false until the version has been negotiated. + virtual bool GetSslVersionBytes(int* version) const = 0; // Key Exporter interface from RFC 5705 // Arguments are: diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc index b1a1a238c8..1037077e0c 100644 --- a/stats/rtcstats_objects.cc +++ b/stats/rtcstats_objects.cc @@ -883,6 +883,9 @@ WEBRTC_RTCSTATS_IMPL(RTCTransportStats, RTCStats, "transport", &selected_candidate_pair_id, &local_certificate_id, &remote_certificate_id, + &tls_version, + &dtls_cipher, + &srtp_cipher, &selected_candidate_pair_changes) // clang-format on @@ -899,6 +902,9 @@ RTCTransportStats::RTCTransportStats(std::string&& id, int64_t timestamp_us) selected_candidate_pair_id("selectedCandidatePairId"), local_certificate_id("localCertificateId"), remote_certificate_id("remoteCertificateId"), + tls_version("tlsVersion"), + dtls_cipher("dtlsCipher"), + srtp_cipher("srtpCipher"), selected_candidate_pair_changes("selectedCandidatePairChanges") {} RTCTransportStats::RTCTransportStats(const RTCTransportStats& other) @@ -910,6 +916,9 @@ RTCTransportStats::RTCTransportStats(const RTCTransportStats& other) selected_candidate_pair_id(other.selected_candidate_pair_id), local_certificate_id(other.local_certificate_id), remote_certificate_id(other.remote_certificate_id), + tls_version(other.tls_version), + dtls_cipher(other.dtls_cipher), + srtp_cipher(other.srtp_cipher), selected_candidate_pair_changes(other.selected_candidate_pair_changes) {} RTCTransportStats::~RTCTransportStats() {}