/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ // Original author: ekr@rtfm.com #include "transportlayerdtls.h" #include #include #include #include "dtlsidentity.h" #include "keyhi.h" #include "logging.h" #include "mozilla/Telemetry.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" #include "nsCOMPtr.h" #include "nsComponentManagerUtils.h" #include "nsComponentManagerUtils.h" #include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "sslexp.h" #include "sslproto.h" #include "transportflow.h" namespace mozilla { MOZ_MTLOG_MODULE("mtransport") static PRDescIdentity transport_layer_identity = PR_INVALID_IO_LAYER; // TODO: Implement a mode for this where // the channel is not ready until confirmed externally // (e.g., after cert check). #define UNIMPLEMENTED \ MOZ_MTLOG(ML_ERROR, "Call to unimplemented function " << __FUNCTION__); \ MOZ_ASSERT(false); \ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0) #define MAX_ALPN_LENGTH 255 // We need to adapt the NSPR/libssl model to the TransportFlow model. // The former wants pull semantics and TransportFlow wants push. // // - A TransportLayerDtls assumes it is sitting on top of another // TransportLayer, which means that events come in asynchronously. // - NSS (libssl) wants to sit on top of a PRFileDesc and poll. // - The TransportLayerNSPRAdapter is a PRFileDesc containing a // FIFO. // - When TransportLayerDtls.PacketReceived() is called, we insert // the packets in the FIFO and then do a PR_Recv() on the NSS // PRFileDesc, which eventually reads off the FIFO. // // All of this stuff is assumed to happen solely in a single thread // (generally the SocketTransportService thread) void TransportLayerNSPRAdapter::PacketReceived(MediaPacket& packet) { if (enabled_) { input_.push(new MediaPacket(std::move(packet))); } } int32_t TransportLayerNSPRAdapter::Recv(void* buf, int32_t buflen) { if (input_.empty()) { PR_SetError(PR_WOULD_BLOCK_ERROR, 0); return -1; } MediaPacket* front = input_.front(); int32_t count = static_cast(front->len()); if (buflen < count) { MOZ_ASSERT(false, "Not enough buffer space to receive into"); PR_SetError(PR_BUFFER_OVERFLOW_ERROR, 0); return -1; } memcpy(buf, front->data(), count); input_.pop(); delete front; return count; } int32_t TransportLayerNSPRAdapter::Write(const void* buf, int32_t length) { if (!enabled_) { MOZ_MTLOG(ML_WARNING, "Writing to disabled transport layer"); return -1; } MediaPacket packet; // Copies. Oh well. packet.Copy(static_cast(buf), static_cast(length)); packet.SetType(MediaPacket::DTLS); TransportResult r = output_->SendPacket(packet); if (r >= 0) { return r; } if (r == TE_WOULDBLOCK) { PR_SetError(PR_WOULD_BLOCK_ERROR, 0); } else { PR_SetError(PR_IO_ERROR, 0); } return -1; } // Implementation of NSPR methods static PRStatus TransportLayerClose(PRFileDesc* f) { f->dtor(f); return PR_SUCCESS; } static int32_t TransportLayerRead(PRFileDesc* f, void* buf, int32_t length) { UNIMPLEMENTED; return -1; } static int32_t TransportLayerWrite(PRFileDesc* f, const void* buf, int32_t length) { TransportLayerNSPRAdapter* io = reinterpret_cast(f->secret); return io->Write(buf, length); } static int32_t TransportLayerAvailable(PRFileDesc* f) { UNIMPLEMENTED; return -1; } int64_t TransportLayerAvailable64(PRFileDesc* f) { UNIMPLEMENTED; return -1; } static PRStatus TransportLayerSync(PRFileDesc* f) { UNIMPLEMENTED; return PR_FAILURE; } static int32_t TransportLayerSeek(PRFileDesc* f, int32_t offset, PRSeekWhence how) { UNIMPLEMENTED; return -1; } static int64_t TransportLayerSeek64(PRFileDesc* f, int64_t offset, PRSeekWhence how) { UNIMPLEMENTED; return -1; } static PRStatus TransportLayerFileInfo(PRFileDesc* f, PRFileInfo* info) { UNIMPLEMENTED; return PR_FAILURE; } static PRStatus TransportLayerFileInfo64(PRFileDesc* f, PRFileInfo64* info) { UNIMPLEMENTED; return PR_FAILURE; } static int32_t TransportLayerWritev(PRFileDesc* f, const PRIOVec* iov, int32_t iov_size, PRIntervalTime to) { UNIMPLEMENTED; return -1; } static PRStatus TransportLayerConnect(PRFileDesc* f, const PRNetAddr* addr, PRIntervalTime to) { UNIMPLEMENTED; return PR_FAILURE; } static PRFileDesc* TransportLayerAccept(PRFileDesc* sd, PRNetAddr* addr, PRIntervalTime to) { UNIMPLEMENTED; return nullptr; } static PRStatus TransportLayerBind(PRFileDesc* f, const PRNetAddr* addr) { UNIMPLEMENTED; return PR_FAILURE; } static PRStatus TransportLayerListen(PRFileDesc* f, int32_t depth) { UNIMPLEMENTED; return PR_FAILURE; } static PRStatus TransportLayerShutdown(PRFileDesc* f, int32_t how) { // This is only called from NSS when we are the server and the client refuses // to provide a certificate. In this case, the handshake is destined for // failure, so we will just let this pass. TransportLayerNSPRAdapter* io = reinterpret_cast(f->secret); io->SetEnabled(false); return PR_SUCCESS; } // This function does not support peek, or waiting until `to` static int32_t TransportLayerRecv(PRFileDesc* f, void* buf, int32_t buflen, int32_t flags, PRIntervalTime to) { MOZ_ASSERT(flags == 0); if (flags != 0) { PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); return -1; } TransportLayerNSPRAdapter* io = reinterpret_cast(f->secret); return io->Recv(buf, buflen); } // Note: this is always nonblocking and assumes a zero timeout. static int32_t TransportLayerSend(PRFileDesc* f, const void* buf, int32_t amount, int32_t flags, PRIntervalTime to) { int32_t written = TransportLayerWrite(f, buf, amount); return written; } static int32_t TransportLayerRecvfrom(PRFileDesc* f, void* buf, int32_t amount, int32_t flags, PRNetAddr* addr, PRIntervalTime to) { UNIMPLEMENTED; return -1; } static int32_t TransportLayerSendto(PRFileDesc* f, const void* buf, int32_t amount, int32_t flags, const PRNetAddr* addr, PRIntervalTime to) { UNIMPLEMENTED; return -1; } static int16_t TransportLayerPoll(PRFileDesc* f, int16_t in_flags, int16_t* out_flags) { UNIMPLEMENTED; return -1; } static int32_t TransportLayerAcceptRead(PRFileDesc* sd, PRFileDesc** nd, PRNetAddr** raddr, void* buf, int32_t amount, PRIntervalTime t) { UNIMPLEMENTED; return -1; } static int32_t TransportLayerTransmitFile(PRFileDesc* sd, PRFileDesc* f, const void* headers, int32_t hlen, PRTransmitFileFlags flags, PRIntervalTime t) { UNIMPLEMENTED; return -1; } static PRStatus TransportLayerGetpeername(PRFileDesc* f, PRNetAddr* addr) { // TODO: Modify to return unique names for each channel // somehow, as opposed to always the same static address. The current // implementation messes up the session cache, which is why it's off // elsewhere addr->inet.family = PR_AF_INET; addr->inet.port = 0; addr->inet.ip = 0; return PR_SUCCESS; } static PRStatus TransportLayerGetsockname(PRFileDesc* f, PRNetAddr* addr) { UNIMPLEMENTED; return PR_FAILURE; } static PRStatus TransportLayerGetsockoption(PRFileDesc* f, PRSocketOptionData* opt) { switch (opt->option) { case PR_SockOpt_Nonblocking: opt->value.non_blocking = PR_TRUE; return PR_SUCCESS; default: UNIMPLEMENTED; break; } return PR_FAILURE; } // Imitate setting socket options. These are mostly noops. static PRStatus TransportLayerSetsockoption(PRFileDesc* f, const PRSocketOptionData* opt) { switch (opt->option) { case PR_SockOpt_Nonblocking: return PR_SUCCESS; case PR_SockOpt_NoDelay: return PR_SUCCESS; default: UNIMPLEMENTED; break; } return PR_FAILURE; } static int32_t TransportLayerSendfile(PRFileDesc* out, PRSendFileData* in, PRTransmitFileFlags flags, PRIntervalTime to) { UNIMPLEMENTED; return -1; } static PRStatus TransportLayerConnectContinue(PRFileDesc* f, int16_t flags) { UNIMPLEMENTED; return PR_FAILURE; } static int32_t TransportLayerReserved(PRFileDesc* f) { UNIMPLEMENTED; return -1; } static const struct PRIOMethods TransportLayerMethods = { PR_DESC_LAYERED, TransportLayerClose, TransportLayerRead, TransportLayerWrite, TransportLayerAvailable, TransportLayerAvailable64, TransportLayerSync, TransportLayerSeek, TransportLayerSeek64, TransportLayerFileInfo, TransportLayerFileInfo64, TransportLayerWritev, TransportLayerConnect, TransportLayerAccept, TransportLayerBind, TransportLayerListen, TransportLayerShutdown, TransportLayerRecv, TransportLayerSend, TransportLayerRecvfrom, TransportLayerSendto, TransportLayerPoll, TransportLayerAcceptRead, TransportLayerTransmitFile, TransportLayerGetsockname, TransportLayerGetpeername, TransportLayerReserved, TransportLayerReserved, TransportLayerGetsockoption, TransportLayerSetsockoption, TransportLayerSendfile, TransportLayerConnectContinue, TransportLayerReserved, TransportLayerReserved, TransportLayerReserved, TransportLayerReserved}; TransportLayerDtls::~TransportLayerDtls() { // Destroy the NSS instance first so it can still send out an alert before // we disable the nspr_io_adapter_. ssl_fd_ = nullptr; nspr_io_adapter_->SetEnabled(false); if (timer_) { timer_->Cancel(); } } nsresult TransportLayerDtls::InitInternal() { // Get the transport service as an event target nsresult rv; target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) { MOZ_MTLOG(ML_ERROR, "Couldn't get socket transport service"); return rv; } timer_ = NS_NewTimer(); if (!timer_) { MOZ_MTLOG(ML_ERROR, "Couldn't get timer"); return rv; } return NS_OK; } void TransportLayerDtls::WasInserted() { // Connect to the lower layers if (!Setup()) { TL_SET_STATE(TS_ERROR); } } // Set the permitted and default ALPN identifiers. // The default is here to allow for peers that don't want to negotiate ALPN // in that case, the default string will be reported from GetNegotiatedAlpn(). // Setting the default to the empty string causes the transport layer to fail // if ALPN is not negotiated. // Note: we only support Unicode strings here, which are encoded into UTF-8, // even though ALPN ostensibly allows arbitrary octet sequences. nsresult TransportLayerDtls::SetAlpn(const std::set& alpn_allowed, const std::string& alpn_default) { alpn_allowed_ = alpn_allowed; alpn_default_ = alpn_default; return NS_OK; } nsresult TransportLayerDtls::SetVerificationAllowAll() { // Defensive programming if (verification_mode_ != VERIFY_UNSET) return NS_ERROR_ALREADY_INITIALIZED; verification_mode_ = VERIFY_ALLOW_ALL; return NS_OK; } nsresult TransportLayerDtls::SetVerificationDigest(const DtlsDigest& digest) { // Defensive programming if (verification_mode_ != VERIFY_UNSET && verification_mode_ != VERIFY_DIGEST) { return NS_ERROR_ALREADY_INITIALIZED; } digests_.push_back(digest); verification_mode_ = VERIFY_DIGEST; return NS_OK; } void TransportLayerDtls::SetMinMaxVersion(Version min_version, Version max_version) { if (min_version < Version::DTLS_1_0 || min_version > Version::DTLS_1_3 || max_version < Version::DTLS_1_0 || max_version > Version::DTLS_1_3 || min_version > max_version || max_version < min_version) { return; } minVersion_ = min_version; maxVersion_ = max_version; } // These are the named groups that we will allow. static const SSLNamedGroup NamedGroupPreferences[] = { ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072}; // TODO: make sure this is called from STS. Otherwise // we have thread safety issues bool TransportLayerDtls::Setup() { CheckThread(); SECStatus rv; if (!downward_) { MOZ_MTLOG(ML_ERROR, "DTLS layer with nothing below. This is useless"); return false; } nspr_io_adapter_ = MakeUnique(downward_); if (!identity_) { MOZ_MTLOG(ML_ERROR, "Can't start DTLS without an identity"); return false; } if (verification_mode_ == VERIFY_UNSET) { MOZ_MTLOG(ML_ERROR, "Can't start DTLS without specifying a verification mode"); return false; } if (transport_layer_identity == PR_INVALID_IO_LAYER) { transport_layer_identity = PR_GetUniqueIdentity("nssstreamadapter"); } UniquePRFileDesc pr_fd( PR_CreateIOLayerStub(transport_layer_identity, &TransportLayerMethods)); MOZ_ASSERT(pr_fd != nullptr); if (!pr_fd) return false; pr_fd->secret = reinterpret_cast(nspr_io_adapter_.get()); UniquePRFileDesc ssl_fd(DTLS_ImportFD(nullptr, pr_fd.get())); MOZ_ASSERT(ssl_fd != nullptr); // This should never happen if (!ssl_fd) { return false; } Unused << pr_fd.release(); // ownership transfered to ssl_fd; if (role_ == CLIENT) { MOZ_MTLOG(ML_INFO, "Setting up DTLS as client"); rv = SSL_GetClientAuthDataHook(ssl_fd.get(), GetClientAuthDataHook, this); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set identity"); return false; } if (maxVersion_ >= Version::DTLS_1_3) { MOZ_MTLOG(ML_INFO, "Setting DTLS1.3 supported_versions workaround"); rv = SSL_SetDtls13VersionWorkaround(ssl_fd.get(), PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set DTLS1.3 workaround"); return false; } } } else { MOZ_MTLOG(ML_INFO, "Setting up DTLS as server"); // Server side rv = SSL_ConfigSecureServer(ssl_fd.get(), identity_->cert().get(), identity_->privkey().get(), identity_->auth_type()); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set identity"); return false; } UniqueCERTCertList zero_certs(CERT_NewCertList()); rv = SSL_SetTrustAnchors(ssl_fd.get(), zero_certs.get()); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set trust anchors"); return false; } // Insist on a certificate from the client rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUEST_CERTIFICATE, PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't request certificate"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUIRE_CERTIFICATE, PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't require certificate"); return false; } } SSLVersionRange version_range = {static_cast(minVersion_), static_cast(maxVersion_)}; rv = SSL_VersionRangeSet(ssl_fd.get(), &version_range); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Can't disable SSLv3"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_SESSION_TICKETS, PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable session tickets"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_CACHE, PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable session caching"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_DEFLATE, PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable deflate"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable renegotiation"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_FALSE_START, PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable false start"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_LOCKS, PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable locks"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable ECDHE key reuse"); return false; } if (!SetupCipherSuites(ssl_fd)) { return false; } rv = SSL_NamedGroupConfig(ssl_fd.get(), NamedGroupPreferences, mozilla::ArrayLength(NamedGroupPreferences)); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set named groups"); return false; } // Certificate validation rv = SSL_AuthCertificateHook(ssl_fd.get(), AuthCertificateHook, reinterpret_cast(this)); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set certificate validation hook"); return false; } if (!SetupAlpn(ssl_fd)) { return false; } // Now start the handshake rv = SSL_ResetHandshake(ssl_fd.get(), role_ == SERVER ? PR_TRUE : PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't reset handshake"); return false; } ssl_fd_ = std::move(ssl_fd); // Finally, get ready to receive data downward_->SignalStateChange.connect(this, &TransportLayerDtls::StateChange); downward_->SignalPacketReceived.connect(this, &TransportLayerDtls::PacketReceived); if (downward_->state() == TS_OPEN) { TL_SET_STATE(TS_CONNECTING); Handshake(); } return true; } bool TransportLayerDtls::SetupAlpn(UniquePRFileDesc& ssl_fd) const { if (alpn_allowed_.empty()) { return true; } SECStatus rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_NPN, PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't disable NPN"); return false; } rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_ALPN, PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't enable ALPN"); return false; } unsigned char buf[MAX_ALPN_LENGTH]; size_t offset = 0; for (const auto& tag : alpn_allowed_) { if ((offset + 1 + tag.length()) >= sizeof(buf)) { MOZ_MTLOG(ML_ERROR, "ALPN too long"); return false; } buf[offset++] = tag.length(); memcpy(buf + offset, tag.c_str(), tag.length()); offset += tag.length(); } rv = SSL_SetNextProtoNego(ssl_fd.get(), buf, offset); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN string"); return false; } return true; } // Ciphers we need to enable. These are on by default in standard firefox // builds, but can be disabled with prefs and they aren't on in our unit tests // since that uses NSS default configuration. // // Only override prefs to comply with MUST statements in the security-arch doc. // Anything outside this list is governed by the usual combination of policy // and user preferences. static const uint32_t EnabledCiphers[] = { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}; // Disable all NSS suites modes without PFS or with old and rusty ciphersuites. // Anything outside this list is governed by the usual combination of policy // and user preferences. static const uint32_t DisabledCiphers[] = { // Bug 1310061: disable all SHA384 ciphers until fixed TLS_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_DHE_DSS_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_RC4_128_SHA, TLS_ECDH_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_CAMELLIA_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_CAMELLIA_256_CBC_SHA, TLS_RSA_WITH_SEED_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_RC4_128_MD5, TLS_DHE_RSA_WITH_DES_CBC_SHA, TLS_DHE_DSS_WITH_DES_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA, TLS_ECDHE_ECDSA_WITH_NULL_SHA, TLS_ECDHE_RSA_WITH_NULL_SHA, TLS_ECDH_ECDSA_WITH_NULL_SHA, TLS_ECDH_RSA_WITH_NULL_SHA, TLS_RSA_WITH_NULL_SHA, TLS_RSA_WITH_NULL_SHA256, TLS_RSA_WITH_NULL_MD5, }; bool TransportLayerDtls::SetupCipherSuites(UniquePRFileDesc& ssl_fd) { SECStatus rv; // Set the SRTP ciphers if (!enabled_srtp_ciphers_.empty()) { rv = SSL_InstallExtensionHooks(ssl_fd.get(), ssl_use_srtp_xtn, TransportLayerDtls::WriteSrtpXtn, this, TransportLayerDtls::HandleSrtpXtn, this); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, LAYER_INFO << "unable to set SRTP extension handler"); return false; } } for (const auto& cipher : EnabledCiphers) { MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Enabling: " << cipher); rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_TRUE); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Unable to enable suite: " << cipher); return false; } } for (const auto& cipher : DisabledCiphers) { MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Disabling: " << cipher); PRBool enabled = false; rv = SSL_CipherPrefGet(ssl_fd.get(), cipher, &enabled); if (rv != SECSuccess) { MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Unable to check if suite is enabled: " << cipher); return false; } if (enabled) { rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_FALSE); if (rv != SECSuccess) { MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Unable to disable suite: " << cipher); return false; } } } return true; } nsresult TransportLayerDtls::GetCipherSuite(uint16_t* cipherSuite) const { CheckThread(); if (!cipherSuite) { MOZ_MTLOG(ML_ERROR, LAYER_INFO << "GetCipherSuite passed a nullptr"); return NS_ERROR_NULL_POINTER; } if (state_ != TS_OPEN) { return NS_ERROR_NOT_AVAILABLE; } SSLChannelInfo info; SECStatus rv = SSL_GetChannelInfo(ssl_fd_.get(), &info, sizeof(info)); if (rv != SECSuccess) { MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "GetCipherSuite can't get channel info"); return NS_ERROR_FAILURE; } *cipherSuite = info.cipherSuite; return NS_OK; } std::vector TransportLayerDtls::GetDefaultSrtpCiphers() { std::vector ciphers; ciphers.push_back(kDtlsSrtpAeadAes128Gcm); // Since we don't support DTLS 1.3 or SHA384 ciphers (see bug 1312976) // we don't really enough entropy to prefer this over 128 bit ciphers.push_back(kDtlsSrtpAeadAes256Gcm); ciphers.push_back(kDtlsSrtpAes128CmHmacSha1_80); #ifndef NIGHTLY_BUILD // To support bug 1491583 lets try to find out if we get bug reports if we no // longer offer this in Nightly builds. ciphers.push_back(kDtlsSrtpAes128CmHmacSha1_32); #endif return ciphers; } void TransportLayerDtls::StateChange(TransportLayer* layer, State state) { switch (state) { case TS_NONE: MOZ_ASSERT(false); // Can't happen break; case TS_INIT: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "State change of lower layer to INIT forbidden"); TL_SET_STATE(TS_ERROR); break; case TS_CONNECTING: MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is connecting."); break; case TS_OPEN: if (timer_) { MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is now open; starting TLS"); timer_->Cancel(); timer_->SetTarget(target_); // Async, since the ICE layer might need to send a STUN response, and we // don't want the handshake to start until that is sent. timer_->InitWithNamedFuncCallback(TimerCallback, this, 0, nsITimer::TYPE_ONE_SHOT, "TransportLayerDtls::TimerCallback"); TL_SET_STATE(TS_CONNECTING); } else { // We have already completed DTLS. Can happen if the ICE layer failed // due to a loss of network, and then recovered. TL_SET_STATE(TS_OPEN); } break; case TS_CLOSED: MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is now closed"); TL_SET_STATE(TS_CLOSED); break; case TS_ERROR: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Lower layer experienced an error"); TL_SET_STATE(TS_ERROR); break; } } void TransportLayerDtls::Handshake() { if (!timer_) { // We are done with DTLS, regardless of the state changes of lower layers return; } // Clear the retransmit timer timer_->Cancel(); MOZ_ASSERT(state_ == TS_CONNECTING); SECStatus rv = SSL_ForceHandshake(ssl_fd_.get()); if (rv == SECSuccess) { MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "****** SSL handshake completed ******"); if (!cert_ok_) { MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Certificate check never occurred"); TL_SET_STATE(TS_ERROR); return; } if (!CheckAlpn()) { // Despite connecting, the connection doesn't have a valid ALPN label. // Forcibly close the connection so that the peer isn't left hanging // (assuming the close_notify isn't dropped). ssl_fd_ = nullptr; TL_SET_STATE(TS_ERROR); return; } TL_SET_STATE(TS_OPEN); RecordTlsTelemetry(); timer_ = nullptr; } else { int32_t err = PR_GetError(); switch (err) { case SSL_ERROR_RX_MALFORMED_HANDSHAKE: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Malformed DTLS message; ignoring"); // If this were TLS (and not DTLS), this would be fatal, but // here we're required to ignore bad messages, so fall through [[fallthrough]]; case PR_WOULD_BLOCK_ERROR: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Handshake would have blocked"); PRIntervalTime timeout; rv = DTLS_GetHandshakeTimeout(ssl_fd_.get(), &timeout); if (rv == SECSuccess) { uint32_t timeout_ms = PR_IntervalToMilliseconds(timeout); MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Setting DTLS timeout to " << timeout_ms); timer_->SetTarget(target_); timer_->InitWithNamedFuncCallback( TimerCallback, this, timeout_ms, nsITimer::TYPE_ONE_SHOT, "TransportLayerDtls::TimerCallback"); } break; default: const char* err_msg = PR_ErrorToName(err); MOZ_MTLOG(ML_ERROR, LAYER_INFO << "DTLS handshake error " << err << " (" << err_msg << ")"); TL_SET_STATE(TS_ERROR); break; } } } // Checks if ALPN was negotiated correctly and returns false if it wasn't. // After this returns successfully, alpn_ will be set to the negotiated // protocol. bool TransportLayerDtls::CheckAlpn() { if (alpn_allowed_.empty()) { return true; } SSLNextProtoState alpnState; char chosenAlpn[MAX_ALPN_LENGTH]; unsigned int chosenAlpnLen; SECStatus rv = SSL_GetNextProto(ssl_fd_.get(), &alpnState, reinterpret_cast(chosenAlpn), &chosenAlpnLen, sizeof(chosenAlpn)); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, LAYER_INFO << "ALPN error"); return false; } switch (alpnState) { case SSL_NEXT_PROTO_SELECTED: case SSL_NEXT_PROTO_NEGOTIATED: break; // OK case SSL_NEXT_PROTO_NO_SUPPORT: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "ALPN not negotiated, " << (alpn_default_.empty() ? "failing" : "selecting default")); alpn_ = alpn_default_; return !alpn_.empty(); case SSL_NEXT_PROTO_NO_OVERLAP: // This only happens if there is a custom NPN/ALPN callback installed and // that callback doesn't properly handle ALPN. MOZ_MTLOG(ML_ERROR, LAYER_INFO << "error in ALPN selection callback"); return false; case SSL_NEXT_PROTO_EARLY_VALUE: MOZ_CRASH("Unexpected 0-RTT ALPN value"); return false; } // Warning: NSS won't null terminate the ALPN string for us. std::string chosen(chosenAlpn, chosenAlpnLen); MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Selected ALPN string: " << chosen); if (alpn_allowed_.find(chosen) == alpn_allowed_.end()) { // Maybe our peer chose a protocol we didn't offer (when we are client), or // something is seriously wrong. std::ostringstream ss; for (auto i = alpn_allowed_.begin(); i != alpn_allowed_.end(); ++i) { ss << (i == alpn_allowed_.begin() ? " '" : ", '") << *i << "'"; } MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Bad ALPN string: '" << chosen << "'; permitted:" << ss.str()); return false; } alpn_ = chosen; return true; } void TransportLayerDtls::PacketReceived(TransportLayer* layer, MediaPacket& packet) { CheckThread(); MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << packet.len() << ")"); if (state_ != TS_CONNECTING && state_ != TS_OPEN) { MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Discarding packet in inappropriate state"); return; } if (!packet.data()) { // Something ate this, probably the SRTP layer return; } if (packet.type() != MediaPacket::DTLS) { return; } nspr_io_adapter_->PacketReceived(packet); GetDecryptedPackets(); } void TransportLayerDtls::GetDecryptedPackets() { // If we're still connecting, try to handshake if (state_ == TS_CONNECTING) { Handshake(); } // Now try a recv if we're open, since there might be data left if (state_ == TS_OPEN) { int32_t rv; // One packet might contain several DTLS packets do { // nICEr uses a 9216 bytes buffer to allow support for jumbo frames // Can we peek to get a better idea of the actual size? static const size_t kBufferSize = 9216; auto buffer = MakeUnique(kBufferSize); rv = PR_Recv(ssl_fd_.get(), buffer.get(), kBufferSize, 0, PR_INTERVAL_NO_WAIT); if (rv > 0) { // We have data MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Read " << rv << " bytes from NSS"); MediaPacket packet; packet.SetType(MediaPacket::SCTP); packet.Take(std::move(buffer), static_cast(rv)); SignalPacketReceived(this, packet); } else if (rv == 0) { TL_SET_STATE(TS_CLOSED); } else { int32_t err = PR_GetError(); if (err == PR_WOULD_BLOCK_ERROR) { // This gets ignored MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Receive would have blocked"); } else { MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err); TL_SET_STATE(TS_ERROR); } } } while (rv > 0); } } void TransportLayerDtls::SetState(State state, const char* file, unsigned line) { if (timer_) { switch (state) { case TS_NONE: case TS_INIT: MOZ_ASSERT(false); break; case TS_CONNECTING: break; case TS_OPEN: case TS_CLOSED: case TS_ERROR: timer_->Cancel(); break; } } TransportLayer::SetState(state, file, line); } TransportResult TransportLayerDtls::SendPacket(MediaPacket& packet) { CheckThread(); if (state_ != TS_OPEN) { MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Can't call SendPacket() in state " << state_); return TE_ERROR; } int32_t rv = PR_Send(ssl_fd_.get(), packet.data(), packet.len(), 0, PR_INTERVAL_NO_WAIT); if (rv > 0) { // We have data MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Wrote " << rv << " bytes to SSL Layer"); return rv; } if (rv == 0) { TL_SET_STATE(TS_CLOSED); return 0; } int32_t err = PR_GetError(); if (err == PR_WOULD_BLOCK_ERROR) { // This gets ignored MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Send would have blocked"); return TE_WOULDBLOCK; } MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err); TL_SET_STATE(TS_ERROR); return TE_ERROR; } SECStatus TransportLayerDtls::GetClientAuthDataHook( void* arg, PRFileDesc* fd, CERTDistNames* caNames, CERTCertificate** pRetCert, SECKEYPrivateKey** pRetKey) { MOZ_MTLOG(ML_DEBUG, "Server requested client auth"); TransportLayerDtls* stream = reinterpret_cast(arg); stream->CheckThread(); if (!stream->identity_) { MOZ_MTLOG(ML_ERROR, "No identity available"); PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0); return SECFailure; } *pRetCert = CERT_DupCertificate(stream->identity_->cert().get()); if (!*pRetCert) { PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); return SECFailure; } *pRetKey = SECKEY_CopyPrivateKey(stream->identity_->privkey().get()); if (!*pRetKey) { CERT_DestroyCertificate(*pRetCert); *pRetCert = nullptr; PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); return SECFailure; } return SECSuccess; } nsresult TransportLayerDtls::SetSrtpCiphers( const std::vector& ciphers) { enabled_srtp_ciphers_ = std::move(ciphers); return NS_OK; } nsresult TransportLayerDtls::GetSrtpCipher(uint16_t* cipher) const { CheckThread(); if (srtp_cipher_ == 0) { return NS_ERROR_NOT_AVAILABLE; } *cipher = srtp_cipher_; return NS_OK; } static uint8_t* WriteUint16(uint8_t* cursor, uint16_t v) { *cursor++ = v >> 8; *cursor++ = v & 0xff; return cursor; } static SSLHandshakeType SrtpXtnServerMessage(PRFileDesc* fd) { SSLPreliminaryChannelInfo preinfo; SECStatus rv = SSL_GetPreliminaryChannelInfo(fd, &preinfo, sizeof(preinfo)); if (rv != SECSuccess) { MOZ_ASSERT(false, "Can't get version info"); return ssl_hs_client_hello; } return (preinfo.protocolVersion >= SSL_LIBRARY_VERSION_TLS_1_3) ? ssl_hs_encrypted_extensions : ssl_hs_server_hello; } /* static */ PRBool TransportLayerDtls::WriteSrtpXtn(PRFileDesc* fd, SSLHandshakeType message, uint8_t* data, unsigned int* len, unsigned int max_len, void* arg) { auto self = reinterpret_cast(arg); // ClientHello: send all supported versions. if (message == ssl_hs_client_hello) { MOZ_ASSERT(self->role_ == CLIENT); MOZ_ASSERT(self->enabled_srtp_ciphers_.size(), "Haven't enabled SRTP"); // We will take 2 octets for each cipher, plus a 2 octet length and 1 octet // for the length of the empty MKI. if (max_len < self->enabled_srtp_ciphers_.size() * 2 + 3) { MOZ_ASSERT(false, "Not enough space to send SRTP extension"); return false; } uint8_t* cursor = WriteUint16(data, self->enabled_srtp_ciphers_.size() * 2); for (auto cs : self->enabled_srtp_ciphers_) { cursor = WriteUint16(cursor, cs); } *cursor++ = 0; // MKI is empty *len = cursor - data; return true; } if (message == SrtpXtnServerMessage(fd)) { MOZ_ASSERT(self->role_ == SERVER); if (!self->srtp_cipher_) { // Not negotiated. Definitely bad, but the connection can fail later. return false; } if (max_len < 5) { MOZ_ASSERT(false, "Not enough space to send SRTP extension"); return false; } uint8_t* cursor = WriteUint16(data, 2); // Length = 2. cursor = WriteUint16(cursor, self->srtp_cipher_); *cursor++ = 0; // No MKI *len = cursor - data; return true; } return false; } class TlsParser { public: TlsParser(const uint8_t* data, size_t len) : cursor_(data), remaining_(len) {} bool error() const { return error_; } size_t remaining() const { return remaining_; } template ::value>::type> void Read(T* v, size_t sz = sizeof(T)) { MOZ_ASSERT(sz <= sizeof(T), "Type is too small to hold the value requested"); if (remaining_ < sz) { error_ = true; return; } T result = 0; for (size_t i = 0; i < sz; ++i) { result = (result << 8) | *cursor_++; remaining_--; } *v = result; } template ::value>::type> void ReadVector(std::vector* v, size_t w) { MOZ_ASSERT(v->empty(), "vector needs to be empty"); uint32_t len; Read(&len, w); if (error_ || len % sizeof(T) != 0 || len > remaining_) { error_ = true; return; } size_t count = len / sizeof(T); v->reserve(count); for (T i = 0; !error_ && i < count; ++i) { T item; Read(&item); if (!error_) { v->push_back(item); } } } void Skip(size_t n) { if (remaining_ < n) { error_ = true; } else { cursor_ += n; remaining_ -= n; } } size_t SkipVector(size_t w) { uint32_t len = 0; Read(&len, w); Skip(len); return len; } private: const uint8_t* cursor_; size_t remaining_; bool error_ = false; }; /* static */ SECStatus TransportLayerDtls::HandleSrtpXtn( PRFileDesc* fd, SSLHandshakeType message, const uint8_t* data, unsigned int len, SSLAlertDescription* alert, void* arg) { static const uint8_t kTlsAlertHandshakeFailure = 40; static const uint8_t kTlsAlertIllegalParameter = 47; static const uint8_t kTlsAlertDecodeError = 50; static const uint8_t kTlsAlertUnsupportedExtension = 110; auto self = reinterpret_cast(arg); // Parse the extension. TlsParser parser(data, len); std::vector advertised; parser.ReadVector(&advertised, 2); size_t mki_len = parser.SkipVector(1); if (parser.error() || parser.remaining() > 0) { *alert = kTlsAlertDecodeError; return SECFailure; } if (message == ssl_hs_client_hello) { MOZ_ASSERT(self->role_ == SERVER); if (self->enabled_srtp_ciphers_.empty()) { // We don't have SRTP enabled, which is probably bad, but no sense in // having the handshake fail at this point, let the client decide if this // is a problem. return SECSuccess; } for (auto supported : self->enabled_srtp_ciphers_) { auto it = std::find(advertised.begin(), advertised.end(), supported); if (it != advertised.end()) { self->srtp_cipher_ = supported; return SECSuccess; } } // No common cipher. *alert = kTlsAlertHandshakeFailure; return SECFailure; } if (message == SrtpXtnServerMessage(fd)) { MOZ_ASSERT(self->role_ == CLIENT); if (advertised.size() != 1 || mki_len > 0) { *alert = kTlsAlertIllegalParameter; return SECFailure; } self->srtp_cipher_ = advertised[0]; return SECSuccess; } *alert = kTlsAlertUnsupportedExtension; return SECFailure; } nsresult TransportLayerDtls::ExportKeyingMaterial(const std::string& label, bool use_context, const std::string& context, unsigned char* out, unsigned int outlen) { CheckThread(); if (state_ != TS_OPEN) { MOZ_ASSERT(false, "Transport must be open for ExportKeyingMaterial"); return NS_ERROR_NOT_AVAILABLE; } SECStatus rv = SSL_ExportKeyingMaterial( ssl_fd_.get(), label.c_str(), label.size(), use_context, reinterpret_cast(context.c_str()), context.size(), out, outlen); if (rv != SECSuccess) { MOZ_MTLOG(ML_ERROR, "Couldn't export SSL keying material"); return NS_ERROR_FAILURE; } return NS_OK; } SECStatus TransportLayerDtls::AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig, PRBool isServer) { TransportLayerDtls* stream = reinterpret_cast(arg); stream->CheckThread(); return stream->AuthCertificateHook(fd, checksig, isServer); } SECStatus TransportLayerDtls::CheckDigest( const DtlsDigest& digest, UniqueCERTCertificate& peer_cert) const { DtlsDigest computed_digest(digest.algorithm_); MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Checking digest, algorithm=" << digest.algorithm_); nsresult res = DtlsIdentity::ComputeFingerprint(peer_cert, &computed_digest); if (NS_FAILED(res)) { MOZ_MTLOG(ML_ERROR, "Could not compute peer fingerprint for digest " << digest.algorithm_); // Go to end PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0); return SECFailure; } if (computed_digest != digest) { MOZ_MTLOG(ML_ERROR, "Digest does not match"); PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0); return SECFailure; } return SECSuccess; } SECStatus TransportLayerDtls::AuthCertificateHook(PRFileDesc* fd, PRBool checksig, PRBool isServer) { CheckThread(); UniqueCERTCertificate peer_cert(SSL_PeerCertificate(fd)); // We are not set up to take this being called multiple // times. Change this if we ever add renegotiation. MOZ_ASSERT(!auth_hook_called_); if (auth_hook_called_) { PR_SetError(PR_UNKNOWN_ERROR, 0); return SECFailure; } auth_hook_called_ = true; MOZ_ASSERT(verification_mode_ != VERIFY_UNSET); switch (verification_mode_) { case VERIFY_UNSET: // Break out to error exit PR_SetError(PR_UNKNOWN_ERROR, 0); break; case VERIFY_ALLOW_ALL: cert_ok_ = true; return SECSuccess; case VERIFY_DIGEST: { MOZ_ASSERT(digests_.size() != 0); // Check all the provided digests // Checking functions call PR_SetError() SECStatus rv = SECFailure; for (auto digest : digests_) { rv = CheckDigest(digest, peer_cert); // Matches a digest, we are good to go if (rv == SECSuccess) { cert_ok_ = true; return SECSuccess; } } } break; default: MOZ_CRASH(); // Can't happen } return SECFailure; } void TransportLayerDtls::TimerCallback(nsITimer* timer, void* arg) { TransportLayerDtls* dtls = reinterpret_cast(arg); MOZ_MTLOG(ML_DEBUG, "DTLS timer expired"); dtls->Handshake(); } void TransportLayerDtls::RecordTlsTelemetry() { MOZ_ASSERT(state_ == TS_OPEN); SSLChannelInfo info; SECStatus ss = SSL_GetChannelInfo(ssl_fd_.get(), &info, sizeof(info)); if (ss != SECSuccess) { MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "RecordTlsTelemetry failed to get channel info"); return; } auto protocol_label = mozilla::Telemetry::LABELS_WEBRTC_DTLS_PROTOCOL_VERSION::Unknown; switch (info.protocolVersion) { case SSL_LIBRARY_VERSION_TLS_1_1: protocol_label = Telemetry::LABELS_WEBRTC_DTLS_PROTOCOL_VERSION::Dtls_version_1_0; break; case SSL_LIBRARY_VERSION_TLS_1_2: protocol_label = Telemetry::LABELS_WEBRTC_DTLS_PROTOCOL_VERSION::Dtls_version_1_2; break; case SSL_LIBRARY_VERSION_TLS_1_3: protocol_label = Telemetry::LABELS_WEBRTC_DTLS_PROTOCOL_VERSION::Dtls_version_1_3; break; } Telemetry::AccumulateCategorical(protocol_label); uint16_t telemetry_cipher = 0; switch (info.cipherSuite) { /* Old DHE ciphers: candidates for removal, see bug 1227519 */ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: telemetry_cipher = 1; break; case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: telemetry_cipher = 2; break; /* Current ciphers */ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: telemetry_cipher = 3; break; case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: telemetry_cipher = 4; break; case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: telemetry_cipher = 5; break; case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: telemetry_cipher = 6; break; case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: telemetry_cipher = 7; break; case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: telemetry_cipher = 8; break; case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: telemetry_cipher = 9; break; case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: telemetry_cipher = 10; break; /* TLS 1.3 ciphers */ case TLS_AES_128_GCM_SHA256: telemetry_cipher = 11; break; case TLS_CHACHA20_POLY1305_SHA256: telemetry_cipher = 12; break; case TLS_AES_256_GCM_SHA384: telemetry_cipher = 13; break; } Telemetry::Accumulate(Telemetry::WEBRTC_DTLS_CIPHER, telemetry_cipher); uint16_t cipher; nsresult rv = GetSrtpCipher(&cipher); if (NS_FAILED(rv)) { MOZ_MTLOG(ML_DEBUG, "No SRTP cipher suite"); return; } auto cipher_label = mozilla::Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Unknown; switch (cipher) { case kDtlsSrtpAes128CmHmacSha1_80: cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Aes128CmHmacSha1_80; break; case kDtlsSrtpAes128CmHmacSha1_32: cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Aes128CmHmacSha1_32; break; case kDtlsSrtpAeadAes128Gcm: cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::AeadAes128Gcm; break; case kDtlsSrtpAeadAes256Gcm: cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::AeadAes256Gcm; break; } Telemetry::AccumulateCategorical(cipher_label); } } // namespace mozilla