Re-land: Add API to get negotiated SSL ciphers
This CL adds an API to the SSL stream adapters and transport channels to get the SSL cipher that was negotiated with the remote peer.
The previously approved CL https://webrtc-codereview.appspot.com/26009004/ was reverted in https://webrtc-codereview.appspot.com/40689004/ due to compilation issues while rolling into Chromium.
As the new method has landed in Chromium in https://crrev.com/bc321c76ace6e1d5a03440e554ccb207159802ec, this should be safe to land here now.
BUG=3976
R=pthatcher@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/37209004
git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@8343 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/base/nssstreamadapter.cc b/base/nssstreamadapter.cc
index 7152eda..1286bf9 100644
--- a/base/nssstreamadapter.cc
+++ b/base/nssstreamadapter.cc
@@ -66,6 +66,10 @@
};
#endif
+// Default cipher used between NSS stream adapters.
+// This needs to be updated when the default of the SSL library changes.
+static const char kDefaultSslCipher[] = "TLS_RSA_WITH_AES_128_CBC_SHA";
+
// Implementation of NSPR methods
static PRStatus StreamClose(PRFileDesc *socket) {
@@ -866,6 +870,27 @@
return SECSuccess;
}
+bool NSSStreamAdapter::GetSslCipher(std::string* cipher) {
+ ASSERT(state_ == SSL_CONNECTED);
+ if (state_ != SSL_CONNECTED)
+ return false;
+
+ SSLChannelInfo channel_info;
+ SECStatus rv = SSL_GetChannelInfo(ssl_fd_, &channel_info,
+ sizeof(channel_info));
+ if (rv == SECFailure)
+ return false;
+
+ SSLCipherSuiteInfo ciphersuite_info;
+ rv = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, &ciphersuite_info,
+ sizeof(ciphersuite_info));
+ if (rv == SECFailure)
+ return false;
+
+ *cipher = ciphersuite_info.cipherSuiteName;
+ return true;
+}
+
// RFC 5705 Key Exporter
bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label,
const uint8* context,
@@ -1011,6 +1036,10 @@
return true;
}
+std::string NSSStreamAdapter::GetDefaultSslCipher() {
+ return kDefaultSslCipher;
+}
+
} // namespace rtc
#endif // HAVE_NSS_SSL_H
diff --git a/base/nssstreamadapter.h b/base/nssstreamadapter.h
index 210a479..afa2eb6 100644
--- a/base/nssstreamadapter.h
+++ b/base/nssstreamadapter.h
@@ -61,6 +61,8 @@
size_t* written, int* error);
void OnMessage(Message *msg);
+ virtual bool GetSslCipher(std::string* cipher);
+
// Key Extractor interface
virtual bool ExportKeyingMaterial(const std::string& label,
const uint8* context,
@@ -77,6 +79,7 @@
static bool HaveDtls();
static bool HaveDtlsSrtp();
static bool HaveExporter();
+ static std::string GetDefaultSslCipher();
protected:
// Override SSLStreamAdapter
diff --git a/base/opensslstreamadapter.cc b/base/opensslstreamadapter.cc
index c18295f..0f82d28 100644
--- a/base/opensslstreamadapter.cc
+++ b/base/opensslstreamadapter.cc
@@ -20,6 +20,7 @@
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/rand.h>
+#include <openssl/tls1.h>
#include <openssl/x509v3.h>
#include <vector>
@@ -56,6 +57,99 @@
};
#endif
+// Cipher name table. Maps internal OpenSSL cipher ids to the RFC name.
+struct SslCipherMapEntry {
+ uint32_t openssl_id;
+ const char* rfc_name;
+};
+
+#define DEFINE_CIPHER_ENTRY_SSL3(name) {SSL3_CK_##name, "TLS_"#name}
+#define DEFINE_CIPHER_ENTRY_TLS1(name) {TLS1_CK_##name, "TLS_"#name}
+
+// There currently is no method available to get a RFC-compliant name for a
+// cipher suite from BoringSSL, so we need to define the mapping manually here.
+// This should go away once BoringSSL supports "SSL_CIPHER_standard_name"
+// (as available in OpenSSL if compiled with tracing enabled) or a similar
+// method.
+static const SslCipherMapEntry kSslCipherMap[] = {
+ // TLS v1.0 ciphersuites from RFC2246.
+ DEFINE_CIPHER_ENTRY_SSL3(RSA_RC4_128_SHA),
+ {SSL3_CK_RSA_DES_192_CBC3_SHA,
+ "TLS_RSA_WITH_3DES_EDE_CBC_SHA"},
+
+ // AES ciphersuites from RFC3268.
+ {TLS1_CK_RSA_WITH_AES_128_SHA,
+ "TLS_RSA_WITH_AES_128_CBC_SHA"},
+ {TLS1_CK_DHE_RSA_WITH_AES_128_SHA,
+ "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"},
+ {TLS1_CK_RSA_WITH_AES_256_SHA,
+ "TLS_RSA_WITH_AES_256_CBC_SHA"},
+ {TLS1_CK_DHE_RSA_WITH_AES_256_SHA,
+ "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"},
+
+ // ECC ciphersuites from RFC4492.
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_ECDSA_WITH_RC4_128_SHA),
+ {TLS1_CK_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA,
+ "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"},
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_ECDSA_WITH_AES_128_CBC_SHA),
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_ECDSA_WITH_AES_256_CBC_SHA),
+
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_RSA_WITH_RC4_128_SHA),
+ {TLS1_CK_ECDHE_RSA_WITH_DES_192_CBC3_SHA,
+ "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"},
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_RSA_WITH_AES_128_CBC_SHA),
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_RSA_WITH_AES_256_CBC_SHA),
+
+ // TLS v1.2 ciphersuites.
+ {TLS1_CK_RSA_WITH_AES_128_SHA256,
+ "TLS_RSA_WITH_AES_128_CBC_SHA256"},
+ {TLS1_CK_RSA_WITH_AES_256_SHA256,
+ "TLS_RSA_WITH_AES_256_CBC_SHA256"},
+ {TLS1_CK_DHE_RSA_WITH_AES_128_SHA256,
+ "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"},
+ {TLS1_CK_DHE_RSA_WITH_AES_256_SHA256,
+ "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"},
+
+ // TLS v1.2 GCM ciphersuites from RFC5288.
+ DEFINE_CIPHER_ENTRY_TLS1(RSA_WITH_AES_128_GCM_SHA256),
+ DEFINE_CIPHER_ENTRY_TLS1(RSA_WITH_AES_256_GCM_SHA384),
+ DEFINE_CIPHER_ENTRY_TLS1(DHE_RSA_WITH_AES_128_GCM_SHA256),
+ DEFINE_CIPHER_ENTRY_TLS1(DHE_RSA_WITH_AES_256_GCM_SHA384),
+ DEFINE_CIPHER_ENTRY_TLS1(DH_RSA_WITH_AES_128_GCM_SHA256),
+ DEFINE_CIPHER_ENTRY_TLS1(DH_RSA_WITH_AES_256_GCM_SHA384),
+
+ // ECDH HMAC based ciphersuites from RFC5289.
+ {TLS1_CK_ECDHE_ECDSA_WITH_AES_128_SHA256,
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"},
+ {TLS1_CK_ECDHE_ECDSA_WITH_AES_256_SHA384,
+ "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"},
+ {TLS1_CK_ECDHE_RSA_WITH_AES_128_SHA256,
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"},
+ {TLS1_CK_ECDHE_RSA_WITH_AES_256_SHA384,
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"},
+
+ // ECDH GCM based ciphersuites from RFC5289.
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_ECDSA_WITH_AES_128_GCM_SHA256),
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_ECDSA_WITH_AES_256_GCM_SHA384),
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_RSA_WITH_AES_128_GCM_SHA256),
+ DEFINE_CIPHER_ENTRY_TLS1(ECDHE_RSA_WITH_AES_256_GCM_SHA384),
+
+#ifdef OPENSSL_IS_BORINGSSL
+ {TLS1_CK_ECDHE_RSA_CHACHA20_POLY1305,
+ "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"},
+ {TLS1_CK_ECDHE_ECDSA_CHACHA20_POLY1305,
+ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"},
+ {TLS1_CK_DHE_RSA_CHACHA20_POLY1305,
+ "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256"},
+#endif
+
+ {0, NULL}
+};
+
+// Default cipher used between OpenSSL/BoringSSL stream adapters.
+// This needs to be updated when the default of the SSL library changes.
+static const char kDefaultSslCipher[] = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA";
+
//////////////////////////////////////////////////////////////////////
// StreamBIO
//////////////////////////////////////////////////////////////////////
@@ -222,6 +316,36 @@
return true;
}
+const char* OpenSSLStreamAdapter::GetRfcSslCipherName(
+ const SSL_CIPHER* cipher) {
+ ASSERT(cipher != NULL);
+ for (const SslCipherMapEntry* entry = kSslCipherMap; entry->rfc_name;
+ ++entry) {
+ if (cipher->id == entry->openssl_id) {
+ return entry->rfc_name;
+ }
+ }
+ return NULL;
+}
+
+bool OpenSSLStreamAdapter::GetSslCipher(std::string* cipher) {
+ if (state_ != SSL_CONNECTED)
+ return false;
+
+ const SSL_CIPHER* current_cipher = SSL_get_current_cipher(ssl_);
+ if (current_cipher == NULL) {
+ return false;
+ }
+
+ const char* cipher_name = GetRfcSslCipherName(current_cipher);
+ if (cipher_name == NULL) {
+ return false;
+ }
+
+ *cipher = cipher_name;
+ return true;
+}
+
// Key Extractor interface
bool OpenSSLStreamAdapter::ExportKeyingMaterial(const std::string& label,
const uint8* context,
@@ -877,6 +1001,10 @@
#endif
}
+std::string OpenSSLStreamAdapter::GetDefaultSslCipher() {
+ return kDefaultSslCipher;
+}
+
} // namespace rtc
#endif // HAVE_OPENSSL_SSL_H
diff --git a/base/opensslstreamadapter.h b/base/opensslstreamadapter.h
index 9506217..a9d98fd 100644
--- a/base/opensslstreamadapter.h
+++ b/base/opensslstreamadapter.h
@@ -20,6 +20,7 @@
typedef struct ssl_st SSL;
typedef struct ssl_ctx_st SSL_CTX;
+typedef struct ssl_cipher_st SSL_CIPHER;
typedef struct x509_store_ctx_st X509_STORE_CTX;
namespace rtc {
@@ -81,6 +82,11 @@
virtual void Close();
virtual StreamState GetState() const;
+ // Return the RFC (5246, 3268, etc.) cipher name for an OpenSSL cipher.
+ static const char* GetRfcSslCipherName(const SSL_CIPHER* cipher);
+
+ virtual bool GetSslCipher(std::string* cipher);
+
// Key Extractor interface
virtual bool ExportKeyingMaterial(const std::string& label,
const uint8* context,
@@ -98,6 +104,7 @@
static bool HaveDtls();
static bool HaveDtlsSrtp();
static bool HaveExporter();
+ static std::string GetDefaultSslCipher();
protected:
virtual void OnEvent(StreamInterface* stream, int events, int err);
diff --git a/base/sslstreamadapter.cc b/base/sslstreamadapter.cc
index 44df2ee..513ae8c 100644
--- a/base/sslstreamadapter.cc
+++ b/base/sslstreamadapter.cc
@@ -50,6 +50,9 @@
bool SSLStreamAdapter::HaveDtls() { return false; }
bool SSLStreamAdapter::HaveDtlsSrtp() { return false; }
bool SSLStreamAdapter::HaveExporter() { return false; }
+std::string SSLStreamAdapter::GetDefaultSslCipher() {
+ return std::string();
+}
#elif SSL_USE_OPENSSL
bool SSLStreamAdapter::HaveDtls() {
return OpenSSLStreamAdapter::HaveDtls();
@@ -60,6 +63,9 @@
bool SSLStreamAdapter::HaveExporter() {
return OpenSSLStreamAdapter::HaveExporter();
}
+std::string SSLStreamAdapter::GetDefaultSslCipher() {
+ return OpenSSLStreamAdapter::GetDefaultSslCipher();
+}
#elif SSL_USE_NSS
bool SSLStreamAdapter::HaveDtls() {
return NSSStreamAdapter::HaveDtls();
@@ -70,6 +76,9 @@
bool SSLStreamAdapter::HaveExporter() {
return NSSStreamAdapter::HaveExporter();
}
+std::string SSLStreamAdapter::GetDefaultSslCipher() {
+ return NSSStreamAdapter::GetDefaultSslCipher();
+}
#endif // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS
///////////////////////////////////////////////////////////////////////////////
diff --git a/base/sslstreamadapter.h b/base/sslstreamadapter.h
index ea966c5..c940ecb 100644
--- a/base/sslstreamadapter.h
+++ b/base/sslstreamadapter.h
@@ -119,6 +119,12 @@
// chain. The returned certificate is owned by the caller.
virtual bool GetPeerCertificate(SSLCertificate** cert) const = 0;
+ // Retrieves the name of the cipher suite used for the connection
+ // (e.g. "TLS_RSA_WITH_AES_128_CBC_SHA").
+ virtual bool GetSslCipher(std::string* cipher) {
+ return false;
+ }
+
// Key Exporter interface from RFC 5705
// Arguments are:
// label -- the exporter label.
@@ -155,6 +161,10 @@
static bool HaveDtlsSrtp();
static bool HaveExporter();
+ // Returns the default Ssl cipher used between streams of this class.
+ // This is used by the unit tests.
+ static std::string GetDefaultSslCipher();
+
private:
// If true, the server certificate need not match the configured
// server_name, and in fact missing certificate authority and other
diff --git a/base/sslstreamadapter_unittest.cc b/base/sslstreamadapter_unittest.cc
index b9d477d..677be35 100644
--- a/base/sslstreamadapter_unittest.cc
+++ b/base/sslstreamadapter_unittest.cc
@@ -388,6 +388,13 @@
return server_ssl_->GetPeerCertificate(cert);
}
+ bool GetSslCipher(bool client, std::string *retval) {
+ if (client)
+ return client_ssl_->GetSslCipher(retval);
+ else
+ return server_ssl_->GetSslCipher(retval);
+ }
+
bool ExportKeyingMaterial(const char *label,
const unsigned char *context,
size_t context_len,
@@ -939,3 +946,17 @@
rtc::SSLCertChain* server_peer_chain;
ASSERT_FALSE(server_peer_cert->GetChain(&server_peer_chain));
}
+
+// Test getting the used DTLS ciphers.
+TEST_F(SSLStreamAdapterTestDTLS, TestGetSslCipher) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ TestHandshake();
+
+ std::string client_cipher;
+ ASSERT_TRUE(GetSslCipher(true, &client_cipher));
+ std::string server_cipher;
+ ASSERT_TRUE(GetSslCipher(false, &server_cipher));
+
+ ASSERT_EQ(client_cipher, server_cipher);
+ ASSERT_EQ(rtc::SSLStreamAdapter::GetDefaultSslCipher(), client_cipher);
+}
diff --git a/p2p/base/dtlstransportchannel.cc b/p2p/base/dtlstransportchannel.cc
index 9cceca3..956c52a 100644
--- a/p2p/base/dtlstransportchannel.cc
+++ b/p2p/base/dtlstransportchannel.cc
@@ -186,6 +186,14 @@
return true;
}
+bool DtlsTransportChannelWrapper::GetSslCipher(std::string* cipher) {
+ if (dtls_state_ != STATE_OPEN) {
+ return false;
+ }
+
+ return dtls_->GetSslCipher(cipher);
+}
+
bool DtlsTransportChannelWrapper::SetRemoteFingerprint(
const std::string& digest_alg,
const uint8* digest,
diff --git a/p2p/base/dtlstransportchannel.h b/p2p/base/dtlstransportchannel.h
index 4c9c879..03a916b 100644
--- a/p2p/base/dtlstransportchannel.h
+++ b/p2p/base/dtlstransportchannel.h
@@ -150,6 +150,9 @@
virtual bool GetSslRole(rtc::SSLRole* role) const;
virtual bool SetSslRole(rtc::SSLRole role);
+ // Find out which DTLS cipher was negotiated
+ virtual bool GetSslCipher(std::string* cipher);
+
// Once DTLS has been established, this method retrieves the certificate in
// use by the remote peer, for use in external identity verification.
virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const;
diff --git a/p2p/base/dtlstransportchannel_unittest.cc b/p2p/base/dtlstransportchannel_unittest.cc
index 52f8c1e..f3086bb 100644
--- a/p2p/base/dtlstransportchannel_unittest.cc
+++ b/p2p/base/dtlstransportchannel_unittest.cc
@@ -213,6 +213,22 @@
}
}
+ void CheckSsl(const std::string& expected_cipher) {
+ for (std::vector<cricket::DtlsTransportChannelWrapper*>::iterator it =
+ channels_.begin(); it != channels_.end(); ++it) {
+ std::string cipher;
+
+ bool rv = (*it)->GetSslCipher(&cipher);
+ if (negotiated_dtls_ && !expected_cipher.empty()) {
+ ASSERT_TRUE(rv);
+
+ ASSERT_EQ(cipher, expected_cipher);
+ } else {
+ ASSERT_FALSE(rv);
+ }
+ }
+ }
+
void SendPackets(size_t channel, size_t size, size_t count, bool srtp) {
ASSERT(channel < channels_.size());
rtc::scoped_ptr<char[]> packet(new char[size]);
@@ -433,6 +449,8 @@
client1_.CheckSrtp("");
client2_.CheckSrtp("");
}
+ client1_.CheckSsl(rtc::SSLStreamAdapter::GetDefaultSslCipher());
+ client2_.CheckSsl(rtc::SSLStreamAdapter::GetDefaultSslCipher());
return true;
}
diff --git a/p2p/base/fakesession.h b/p2p/base/fakesession.h
index 13eceef..6f43b65 100644
--- a/p2p/base/fakesession.h
+++ b/p2p/base/fakesession.h
@@ -246,6 +246,10 @@
return false;
}
+ virtual bool GetSslCipher(std::string* cipher) {
+ return false;
+ }
+
virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const {
if (!identity_)
return false;
diff --git a/p2p/base/p2ptransportchannel.h b/p2p/base/p2ptransportchannel.h
index f8756dc..cd852d0 100644
--- a/p2p/base/p2ptransportchannel.h
+++ b/p2p/base/p2ptransportchannel.h
@@ -109,11 +109,16 @@
return false;
}
- // Find out which DTLS-SRTP cipher was negotiated
+ // Find out which DTLS-SRTP cipher was negotiated.
virtual bool GetSrtpCipher(std::string* cipher) {
return false;
}
+ // Find out which DTLS cipher was negotiated.
+ virtual bool GetSslCipher(std::string* cipher) {
+ return false;
+ }
+
// Returns false because the channel is not encrypted by default.
virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const {
return false;
diff --git a/p2p/base/rawtransportchannel.h b/p2p/base/rawtransportchannel.h
index a4d9ce0..3455325 100644
--- a/p2p/base/rawtransportchannel.h
+++ b/p2p/base/rawtransportchannel.h
@@ -114,11 +114,16 @@
return false;
}
- // Find out which DTLS-SRTP cipher was negotiated
+ // Find out which DTLS-SRTP cipher was negotiated.
virtual bool GetSrtpCipher(std::string* cipher) {
return false;
}
+ // Find out which DTLS cipher was negotiated.
+ virtual bool GetSslCipher(std::string* cipher) {
+ return false;
+ }
+
// Returns false because the channel is not DTLS.
virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const {
return false;
diff --git a/p2p/base/transport.cc b/p2p/base/transport.cc
index b03e2d5..0569cc0 100644
--- a/p2p/base/transport.cc
+++ b/p2p/base/transport.cc
@@ -454,9 +454,12 @@
for (ChannelMap::iterator iter = channels_.begin();
iter != channels_.end();
++iter) {
+ ChannelMapEntry& entry = iter->second;
TransportChannelStats substats;
- substats.component = iter->second->component();
- if (!iter->second->GetStats(&substats.connection_infos)) {
+ substats.component = entry->component();
+ entry->GetSrtpCipher(&substats.srtp_cipher);
+ entry->GetSslCipher(&substats.ssl_cipher);
+ if (!entry->GetStats(&substats.connection_infos)) {
return false;
}
stats->channel_stats.push_back(substats);
diff --git a/p2p/base/transport.h b/p2p/base/transport.h
index abead7f..32c6bb3 100644
--- a/p2p/base/transport.h
+++ b/p2p/base/transport.h
@@ -106,6 +106,8 @@
struct TransportChannelStats {
int component;
ConnectionInfos connection_infos;
+ std::string srtp_cipher;
+ std::string ssl_cipher;
};
// Information about all the channels of a transport.
diff --git a/p2p/base/transportchannel.h b/p2p/base/transportchannel.h
index d26a185..3d32b63 100644
--- a/p2p/base/transportchannel.h
+++ b/p2p/base/transportchannel.h
@@ -100,9 +100,12 @@
// Sets up the ciphers to use for DTLS-SRTP.
virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) = 0;
- // Finds out which DTLS-SRTP cipher was negotiated
+ // Finds out which DTLS-SRTP cipher was negotiated.
virtual bool GetSrtpCipher(std::string* cipher) = 0;
+ // Finds out which DTLS cipher was negotiated.
+ virtual bool GetSslCipher(std::string* cipher) = 0;
+
// Gets a copy of the local SSL identity, owned by the caller.
virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const = 0;
diff --git a/p2p/base/transportchannelproxy.cc b/p2p/base/transportchannelproxy.cc
index a8535fa..a278d96 100644
--- a/p2p/base/transportchannelproxy.cc
+++ b/p2p/base/transportchannelproxy.cc
@@ -186,6 +186,14 @@
return impl_->GetSrtpCipher(cipher);
}
+bool TransportChannelProxy::GetSslCipher(std::string* cipher) {
+ ASSERT(rtc::Thread::Current() == worker_thread_);
+ if (!impl_) {
+ return false;
+ }
+ return impl_->GetSslCipher(cipher);
+}
+
bool TransportChannelProxy::GetLocalIdentity(
rtc::SSLIdentity** identity) const {
ASSERT(rtc::Thread::Current() == worker_thread_);
diff --git a/p2p/base/transportchannelproxy.h b/p2p/base/transportchannelproxy.h
index 46803f7..1987002 100644
--- a/p2p/base/transportchannelproxy.h
+++ b/p2p/base/transportchannelproxy.h
@@ -61,6 +61,7 @@
virtual bool SetSslRole(rtc::SSLRole role);
virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers);
virtual bool GetSrtpCipher(std::string* cipher);
+ virtual bool GetSslCipher(std::string* cipher);
virtual bool GetLocalIdentity(rtc::SSLIdentity** identity) const;
virtual bool GetRemoteCertificate(rtc::SSLCertificate** cert) const;
virtual bool ExportKeyingMaterial(const std::string& label,