3 * Copyright 2004--2008, Google Inc.
4 * Copyright 2004--2011, RTFM, Inc.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
20 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #endif // HAVE_CONFIG_H
37 #include "talk/base/nssstreamadapter.h"
45 #ifdef NSS_SSL_RELATIVE_PATH
50 #include "net/third_party/nss/ssl/ssl.h"
51 #include "net/third_party/nss/ssl/sslerr.h"
52 #include "net/third_party/nss/ssl/sslproto.h"
55 #include "talk/base/nssidentity.h"
56 #include "talk/base/thread.h"
60 PRDescIdentity NSSStreamAdapter::nspr_layer_identity = PR_INVALID_IO_LAYER;
62 #define UNIMPLEMENTED \
63 PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); \
65 << "Call to unimplemented function "<< __FUNCTION__; ASSERT(false)
67 #ifdef SRTP_AES128_CM_HMAC_SHA1_80
68 #define HAVE_DTLS_SRTP
72 // SRTP cipher suite table
73 struct SrtpCipherMapEntry {
74 const char* external_name;
78 // This isn't elegant, but it's better than an external reference
79 static const SrtpCipherMapEntry kSrtpCipherMap[] = {
80 {"AES_CM_128_HMAC_SHA1_80", SRTP_AES128_CM_HMAC_SHA1_80 },
81 {"AES_CM_128_HMAC_SHA1_32", SRTP_AES128_CM_HMAC_SHA1_32 },
87 // Implementation of NSPR methods
88 static PRStatus StreamClose(PRFileDesc *socket) {
93 static PRInt32 StreamRead(PRFileDesc *socket, void *buf, PRInt32 length) {
94 StreamInterface *stream = reinterpret_cast<StreamInterface *>(socket->secret);
97 StreamResult result = stream->Read(buf, length, &read, &error);
98 if (result == SR_SUCCESS) {
102 if (result == SR_EOS) {
106 if (result == SR_BLOCK) {
107 PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
111 PR_SetError(PR_UNKNOWN_ERROR, error);
115 static PRInt32 StreamWrite(PRFileDesc *socket, const void *buf,
117 StreamInterface *stream = reinterpret_cast<StreamInterface *>(socket->secret);
120 StreamResult result = stream->Write(buf, length, &written, &error);
121 if (result == SR_SUCCESS) {
125 if (result == SR_BLOCK) {
127 "NSSStreamAdapter: write to underlying transport would block";
128 PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
132 LOG(LS_ERROR) << "Write error";
133 PR_SetError(PR_UNKNOWN_ERROR, error);
137 static PRInt32 StreamAvailable(PRFileDesc *socket) {
142 PRInt64 StreamAvailable64(PRFileDesc *socket) {
147 static PRStatus StreamSync(PRFileDesc *socket) {
152 static PROffset32 StreamSeek(PRFileDesc *socket, PROffset32 offset,
158 static PROffset64 StreamSeek64(PRFileDesc *socket, PROffset64 offset,
164 static PRStatus StreamFileInfo(PRFileDesc *socket, PRFileInfo *info) {
169 static PRStatus StreamFileInfo64(PRFileDesc *socket, PRFileInfo64 *info) {
174 static PRInt32 StreamWritev(PRFileDesc *socket, const PRIOVec *iov,
175 PRInt32 iov_size, PRIntervalTime timeout) {
180 static PRStatus StreamConnect(PRFileDesc *socket, const PRNetAddr *addr,
181 PRIntervalTime timeout) {
186 static PRFileDesc *StreamAccept(PRFileDesc *sd, PRNetAddr *addr,
187 PRIntervalTime timeout) {
192 static PRStatus StreamBind(PRFileDesc *socket, const PRNetAddr *addr) {
197 static PRStatus StreamListen(PRFileDesc *socket, PRIntn depth) {
202 static PRStatus StreamShutdown(PRFileDesc *socket, PRIntn how) {
207 // Note: this is always nonblocking and ignores the timeout.
208 // TODO(ekr@rtfm.com): In future verify that the socket is
209 // actually in non-blocking mode.
210 // This function does not support peek.
211 static PRInt32 StreamRecv(PRFileDesc *socket, void *buf, PRInt32 amount,
212 PRIntn flags, PRIntervalTime to) {
216 PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
220 return StreamRead(socket, buf, amount);
223 // Note: this is always nonblocking and assumes a zero timeout.
224 // This function does not support peek.
225 static PRInt32 StreamSend(PRFileDesc *socket, const void *buf,
226 PRInt32 amount, PRIntn flags,
230 return StreamWrite(socket, buf, amount);
233 static PRInt32 StreamRecvfrom(PRFileDesc *socket, void *buf,
234 PRInt32 amount, PRIntn flags,
235 PRNetAddr *addr, PRIntervalTime to) {
240 static PRInt32 StreamSendto(PRFileDesc *socket, const void *buf,
241 PRInt32 amount, PRIntn flags,
242 const PRNetAddr *addr, PRIntervalTime to) {
247 static PRInt16 StreamPoll(PRFileDesc *socket, PRInt16 in_flags,
248 PRInt16 *out_flags) {
253 static PRInt32 StreamAcceptRead(PRFileDesc *sd, PRFileDesc **nd,
255 void *buf, PRInt32 amount, PRIntervalTime t) {
260 static PRInt32 StreamTransmitFile(PRFileDesc *sd, PRFileDesc *socket,
261 const void *headers, PRInt32 hlen,
262 PRTransmitFileFlags flags, PRIntervalTime t) {
267 static PRStatus StreamGetPeerName(PRFileDesc *socket, PRNetAddr *addr) {
268 // TODO(ekr@rtfm.com): Modify to return unique names for each channel
269 // somehow, as opposed to always the same static address. The current
270 // implementation messes up the session cache, which is why it's off
272 addr->inet.family = PR_AF_INET;
279 static PRStatus StreamGetSockName(PRFileDesc *socket, PRNetAddr *addr) {
284 static PRStatus StreamGetSockOption(PRFileDesc *socket, PRSocketOptionData *opt) {
285 switch (opt->option) {
286 case PR_SockOpt_Nonblocking:
287 opt->value.non_blocking = PR_TRUE;
297 // Imitate setting socket options. These are mostly noops.
298 static PRStatus StreamSetSockOption(PRFileDesc *socket,
299 const PRSocketOptionData *opt) {
300 switch (opt->option) {
301 case PR_SockOpt_Nonblocking:
303 case PR_SockOpt_NoDelay:
313 static PRInt32 StreamSendfile(PRFileDesc *out, PRSendFileData *in,
314 PRTransmitFileFlags flags, PRIntervalTime to) {
319 static PRStatus StreamConnectContinue(PRFileDesc *socket, PRInt16 flags) {
324 static PRIntn StreamReserved(PRFileDesc *socket) {
329 static const struct PRIOMethods nss_methods = {
361 StreamConnectContinue,
368 NSSStreamAdapter::NSSStreamAdapter(StreamInterface *stream)
369 : SSLStreamAdapterHelper(stream),
374 bool NSSStreamAdapter::Init() {
375 if (nspr_layer_identity == PR_INVALID_IO_LAYER) {
376 nspr_layer_identity = PR_GetUniqueIdentity("nssstreamadapter");
378 PRFileDesc *pr_fd = PR_CreateIOLayerStub(nspr_layer_identity, &nss_methods);
381 pr_fd->secret = reinterpret_cast<PRFilePrivate *>(stream());
384 if (ssl_mode_ == SSL_MODE_DTLS) {
385 ssl_fd = DTLS_ImportFD(NULL, pr_fd);
387 ssl_fd = SSL_ImportFD(NULL, pr_fd);
389 ASSERT(ssl_fd != NULL); // This should never happen
397 rv = SSL_OptionSet(ssl_fd, SSL_SECURITY, PR_TRUE);
398 if (rv != SECSuccess) {
399 LOG(LS_ERROR) << "Error enabling security on SSL Socket";
404 rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
405 if (rv != SECSuccess) {
406 LOG(LS_ERROR) << "Error disabling SSL2";
411 // TODO(ekr@rtfm.com): restore this when I have the caching
413 rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE);
414 if (rv != SECSuccess) {
415 LOG(LS_ERROR) << "Error disabling cache";
419 // Disable session tickets.
420 rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
421 if (rv != SECSuccess) {
422 LOG(LS_ERROR) << "Error enabling tickets";
426 // Disable renegotiation.
427 rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION,
428 SSL_RENEGOTIATE_NEVER);
429 if (rv != SECSuccess) {
430 LOG(LS_ERROR) << "Error disabling renegotiation";
434 // Disable false start.
435 rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE);
436 if (rv != SECSuccess) {
437 LOG(LS_ERROR) << "Error disabling false start";
446 NSSStreamAdapter::~NSSStreamAdapter() {
452 int NSSStreamAdapter::BeginSSL() {
456 Error("Init", -1, false);
460 ASSERT(state_ == SSL_CONNECTING);
461 // The underlying stream has been opened. If we are in peer-to-peer mode
462 // then a peer certificate must have been specified by now.
463 ASSERT(!ssl_server_name_.empty() ||
464 peer_certificate_.get() != NULL ||
465 !peer_certificate_digest_algorithm_.empty());
466 LOG(LS_INFO) << "BeginSSL: "
467 << (!ssl_server_name_.empty() ? ssl_server_name_ :
470 if (role_ == SSL_CLIENT) {
471 LOG(LS_INFO) << "BeginSSL: as client";
473 rv = SSL_GetClientAuthDataHook(ssl_fd_, GetClientAuthDataHook,
475 if (rv != SECSuccess) {
476 Error("BeginSSL", -1, false);
480 LOG(LS_INFO) << "BeginSSL: as server";
481 NSSIdentity *identity;
483 if (identity_.get()) {
484 identity = static_cast<NSSIdentity *>(identity_.get());
486 LOG(LS_ERROR) << "Can't be an SSL server without an identity";
487 Error("BeginSSL", -1, false);
490 rv = SSL_ConfigSecureServer(ssl_fd_, identity->certificate().certificate(),
491 identity->keypair()->privkey(),
493 if (rv != SECSuccess) {
494 Error("BeginSSL", -1, false);
498 // Insist on a certificate from the client
499 rv = SSL_OptionSet(ssl_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE);
500 if (rv != SECSuccess) {
501 Error("BeginSSL", -1, false);
505 rv = SSL_OptionSet(ssl_fd_, SSL_REQUIRE_CERTIFICATE, PR_TRUE);
506 if (rv != SECSuccess) {
507 Error("BeginSSL", -1, false);
512 // Set the version range.
513 SSLVersionRange vrange;
514 vrange.min = (ssl_mode_ == SSL_MODE_DTLS) ?
515 SSL_LIBRARY_VERSION_TLS_1_1 :
516 SSL_LIBRARY_VERSION_TLS_1_0;
517 vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
519 rv = SSL_VersionRangeSet(ssl_fd_, &vrange);
520 if (rv != SECSuccess) {
521 Error("BeginSSL", -1, false);
526 #ifdef HAVE_DTLS_SRTP
527 if (!srtp_ciphers_.empty()) {
528 rv = SSL_SetSRTPCiphers(ssl_fd_, &srtp_ciphers_[0], srtp_ciphers_.size());
529 if (rv != SECSuccess) {
530 Error("BeginSSL", -1, false);
536 // Certificate validation
537 rv = SSL_AuthCertificateHook(ssl_fd_, AuthCertificateHook, this);
538 if (rv != SECSuccess) {
539 Error("BeginSSL", -1, false);
543 // Now start the handshake
544 rv = SSL_ResetHandshake(ssl_fd_, role_ == SSL_SERVER ? PR_TRUE : PR_FALSE);
545 if (rv != SECSuccess) {
546 Error("BeginSSL", -1, false);
550 return ContinueSSL();
553 int NSSStreamAdapter::ContinueSSL() {
554 LOG(LS_INFO) << "ContinueSSL";
555 ASSERT(state_ == SSL_CONNECTING);
557 // Clear the DTLS timer
558 Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT);
560 SECStatus rv = SSL_ForceHandshake(ssl_fd_);
562 if (rv == SECSuccess) {
563 LOG(LS_INFO) << "Handshake complete";
567 Error("ContinueSSL", -1, true);
571 state_ = SSL_CONNECTED;
572 StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
576 PRInt32 err = PR_GetError();
578 case SSL_ERROR_RX_MALFORMED_HANDSHAKE:
579 if (ssl_mode_ != SSL_MODE_DTLS) {
580 Error("ContinueSSL", -1, true);
583 LOG(LS_INFO) << "Malformed DTLS message. Ignoring.";
586 case PR_WOULD_BLOCK_ERROR:
587 LOG(LS_INFO) << "Would have blocked";
588 if (ssl_mode_ == SSL_MODE_DTLS) {
589 PRIntervalTime timeout;
591 SECStatus rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout);
592 if (rv == SECSuccess) {
593 LOG(LS_INFO) << "Timeout is " << timeout << " ms";
594 Thread::Current()->PostDelayed(PR_IntervalToMilliseconds(timeout),
595 this, MSG_DTLS_TIMEOUT, 0);
601 LOG(LS_INFO) << "Error " << err;
605 Error("ContinueSSL", -1, true);
609 void NSSStreamAdapter::Cleanup() {
610 if (state_ != SSL_ERROR) {
620 peer_certificate_.reset();
622 Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT);
625 StreamResult NSSStreamAdapter::Read(void* data, size_t data_len,
626 size_t* read, int* error) {
627 // SSL_CONNECTED sanity check.
643 *error = ssl_error_code_;
647 PRInt32 rv = PR_Read(ssl_fd_, data, data_len);
655 PRInt32 err = PR_GetError();
658 case PR_WOULD_BLOCK_ERROR:
661 Error("Read", -1, false);
662 *error = err; // libjingle semantics are that this is impl-specific
673 StreamResult NSSStreamAdapter::Write(const void* data, size_t data_len,
674 size_t* written, int* error) {
675 // SSL_CONNECTED sanity check.
689 *error = ssl_error_code_;
693 PRInt32 rv = PR_Write(ssl_fd_, data, data_len);
697 PRInt32 err = PR_GetError();
700 case PR_WOULD_BLOCK_ERROR:
703 Error("Write", -1, false);
704 *error = err; // libjingle semantics are that this is impl-specific
715 void NSSStreamAdapter::OnEvent(StreamInterface* stream, int events,
717 int events_to_signal = 0;
718 int signal_error = 0;
719 ASSERT(stream == this->stream());
720 if ((events & SE_OPEN)) {
721 LOG(LS_INFO) << "NSSStreamAdapter::OnEvent SE_OPEN";
722 if (state_ != SSL_WAIT) {
723 ASSERT(state_ == SSL_NONE);
724 events_to_signal |= SE_OPEN;
726 state_ = SSL_CONNECTING;
727 if (int err = BeginSSL()) {
728 Error("BeginSSL", err, true);
733 if ((events & (SE_READ|SE_WRITE))) {
734 LOG(LS_INFO) << "NSSStreamAdapter::OnEvent"
735 << ((events & SE_READ) ? " SE_READ" : "")
736 << ((events & SE_WRITE) ? " SE_WRITE" : "");
737 if (state_ == SSL_NONE) {
738 events_to_signal |= events & (SE_READ|SE_WRITE);
739 } else if (state_ == SSL_CONNECTING) {
740 if (int err = ContinueSSL()) {
741 Error("ContinueSSL", err, true);
744 } else if (state_ == SSL_CONNECTED) {
745 if (events & SE_WRITE) {
746 LOG(LS_INFO) << " -- onStreamWriteable";
747 events_to_signal |= SE_WRITE;
749 if (events & SE_READ) {
750 LOG(LS_INFO) << " -- onStreamReadable";
751 events_to_signal |= SE_READ;
755 if ((events & SE_CLOSE)) {
756 LOG(LS_INFO) << "NSSStreamAdapter::OnEvent(SE_CLOSE, " << err << ")";
758 events_to_signal |= SE_CLOSE;
759 // SE_CLOSE is the only event that uses the final parameter to OnEvent().
760 ASSERT(signal_error == 0);
763 if (events_to_signal)
764 StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
767 void NSSStreamAdapter::OnMessage(Message* msg) {
768 // Process our own messages and then pass others to the superclass
769 if (MSG_DTLS_TIMEOUT == msg->message_id) {
770 LOG(LS_INFO) << "DTLS timeout expired";
773 StreamInterface::OnMessage(msg);
777 // Certificate verification callback. Called to check any certificate
778 SECStatus NSSStreamAdapter::AuthCertificateHook(void *arg,
782 LOG(LS_INFO) << "NSSStreamAdapter::AuthCertificateHook";
783 NSSCertificate peer_cert(SSL_PeerCertificate(fd));
786 // TODO(ekr@rtfm.com): Should we be enforcing self-signed like
787 // the OpenSSL version?
788 NSSStreamAdapter *stream = reinterpret_cast<NSSStreamAdapter *>(arg);
790 if (stream->peer_certificate_.get()) {
791 LOG(LS_INFO) << "Checking against specified certificate";
793 // The peer certificate was specified
794 if (reinterpret_cast<NSSCertificate *>(stream->peer_certificate_.get())->
795 Equals(&peer_cert)) {
796 LOG(LS_INFO) << "Accepted peer certificate";
799 } else if (!stream->peer_certificate_digest_algorithm_.empty()) {
800 LOG(LS_INFO) << "Checking against specified digest";
801 // The peer certificate digest was specified
802 unsigned char digest[64]; // Maximum size
803 std::size_t digest_length;
805 if (!peer_cert.ComputeDigest(
806 stream->peer_certificate_digest_algorithm_,
807 digest, sizeof(digest), &digest_length)) {
808 LOG(LS_ERROR) << "Digest computation failed";
810 Buffer computed_digest(digest, digest_length);
811 if (computed_digest == stream->peer_certificate_digest_value_) {
812 LOG(LS_INFO) << "Accepted peer certificate";
817 // Other modes, but we haven't implemented yet
818 // TODO(ekr@rtfm.com): Implement real certificate validation
823 stream->cert_ok_ = true;
825 // Record the peer's certificate chain.
826 CERTCertList* cert_list = SSL_PeerCertificateChain(fd);
827 ASSERT(cert_list != NULL);
829 stream->peer_certificate_.reset(new NSSCertificate(cert_list));
830 CERT_DestroyCertList(cert_list);
834 if (!ok && stream->ignore_bad_cert()) {
835 LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
836 stream->cert_ok_ = true;
840 PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
845 SECStatus NSSStreamAdapter::GetClientAuthDataHook(void *arg, PRFileDesc *fd,
846 CERTDistNames *caNames,
847 CERTCertificate **pRetCert,
848 SECKEYPrivateKey **pRetKey) {
849 LOG(LS_INFO) << "Client cert requested";
850 NSSStreamAdapter *stream = reinterpret_cast<NSSStreamAdapter *>(arg);
852 if (!stream->identity_.get()) {
853 LOG(LS_ERROR) << "No identity available";
857 NSSIdentity *identity = static_cast<NSSIdentity *>(stream->identity_.get());
858 // Destroyed internally by NSS
859 *pRetCert = CERT_DupCertificate(identity->certificate().certificate());
860 *pRetKey = SECKEY_CopyPrivateKey(identity->keypair()->privkey());
865 // RFC 5705 Key Exporter
866 bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label,
867 const uint8* context,
872 SECStatus rv = SSL_ExportKeyingMaterial(ssl_fd_,
873 label.c_str(), label.size(),
875 context, context_len,
878 return rv == SECSuccess;
881 bool NSSStreamAdapter::SetDtlsSrtpCiphers(
882 const std::vector<std::string>& ciphers) {
883 #ifdef HAVE_DTLS_SRTP
884 std::vector<PRUint16> internal_ciphers;
885 if (state_ != SSL_NONE)
888 for (std::vector<std::string>::const_iterator cipher = ciphers.begin();
889 cipher != ciphers.end(); ++cipher) {
891 for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; entry->cipher_id;
893 if (*cipher == entry->external_name) {
895 internal_ciphers.push_back(entry->cipher_id);
901 LOG(LS_ERROR) << "Could not find cipher: " << *cipher;
906 if (internal_ciphers.empty())
909 srtp_ciphers_ = internal_ciphers;
917 bool NSSStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) {
918 #ifdef HAVE_DTLS_SRTP
919 ASSERT(state_ == SSL_CONNECTED);
920 if (state_ != SSL_CONNECTED)
923 PRUint16 selected_cipher;
925 SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, &selected_cipher);
926 if (rv == SECFailure)
929 for (const SrtpCipherMapEntry *entry = kSrtpCipherMap;
930 entry->cipher_id; ++entry) {
931 if (selected_cipher == entry->cipher_id) {
932 *cipher = entry->external_name;
937 ASSERT(false); // This should never happen
943 bool NSSContext::initialized;
944 NSSContext *NSSContext::global_nss_context;
946 // Static initialization and shutdown
947 NSSContext *NSSContext::Instance() {
948 if (!global_nss_context) {
949 NSSContext *new_ctx = new NSSContext();
951 if (!(new_ctx->slot_ = PK11_GetInternalSlot())) {
956 global_nss_context = new_ctx;
960 return global_nss_context;
965 bool NSSContext::InitializeSSL(VerificationCallback callback) {
971 rv = NSS_NoDB_Init(NULL);
972 if (rv != SECSuccess) {
973 LOG(LS_ERROR) << "Couldn't initialize NSS error=" <<
978 NSS_SetDomesticPolicy();
986 bool NSSContext::InitializeSSLThread() {
991 bool NSSContext::CleanupSSL() {
996 bool NSSStreamAdapter::HaveDtls() {
1000 bool NSSStreamAdapter::HaveDtlsSrtp() {
1001 #ifdef HAVE_DTLS_SRTP
1008 bool NSSStreamAdapter::HaveExporter() {
1012 } // namespace talk_base
1014 #endif // HAVE_NSS_SSL_H