michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Original author: ekr@rtfm.com michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "logging.h" michael@0: #include "ssl.h" michael@0: #include "sslerr.h" michael@0: #include "sslproto.h" michael@0: #include "keyhi.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsIEventTarget.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: #include "dtlsidentity.h" michael@0: #include "transportflow.h" michael@0: #include "transportlayerdtls.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: MOZ_MTLOG_MODULE("mtransport") michael@0: michael@0: static PRDescIdentity transport_layer_identity = PR_INVALID_IO_LAYER; michael@0: michael@0: // TODO: Implement a mode for this where michael@0: // the channel is not ready until confirmed externally michael@0: // (e.g., after cert check). michael@0: michael@0: #define UNIMPLEMENTED \ michael@0: MOZ_MTLOG(ML_ERROR, \ michael@0: "Call to unimplemented function "<< __FUNCTION__); \ michael@0: MOZ_ASSERT(false); \ michael@0: PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0) michael@0: michael@0: michael@0: // We need to adapt the NSPR/libssl model to the TransportFlow model. michael@0: // The former wants pull semantics and TransportFlow wants push. michael@0: // michael@0: // - A TransportLayerDtls assumes it is sitting on top of another michael@0: // TransportLayer, which means that events come in asynchronously. michael@0: // - NSS (libssl) wants to sit on top of a PRFileDesc and poll. michael@0: // - The TransportLayerNSPRAdapter is a PRFileDesc containing a michael@0: // FIFO. michael@0: // - When TransportLayerDtls.PacketReceived() is called, we insert michael@0: // the packets in the FIFO and then do a PR_Recv() on the NSS michael@0: // PRFileDesc, which eventually reads off the FIFO. michael@0: // michael@0: // All of this stuff is assumed to happen solely in a single thread michael@0: // (generally the SocketTransportService thread) michael@0: struct Packet { michael@0: Packet() : data_(nullptr), len_(0), offset_(0) {} michael@0: michael@0: void Assign(const void *data, int32_t len) { michael@0: data_ = new uint8_t[len]; michael@0: memcpy(data_, data, len); michael@0: len_ = len; michael@0: } michael@0: michael@0: ScopedDeleteArray data_; michael@0: int32_t len_; michael@0: int32_t offset_; michael@0: }; michael@0: michael@0: void TransportLayerNSPRAdapter::PacketReceived(const void *data, int32_t len) { michael@0: input_.push(new Packet()); michael@0: input_.back()->Assign(data, len); michael@0: } michael@0: michael@0: int32_t TransportLayerNSPRAdapter::Read(void *data, int32_t len) { michael@0: if (input_.empty()) { michael@0: PR_SetError(PR_WOULD_BLOCK_ERROR, 0); michael@0: return TE_WOULDBLOCK; michael@0: } michael@0: michael@0: Packet* front = input_.front(); michael@0: int32_t to_read = std::min(len, front->len_ - front->offset_); michael@0: memcpy(data, front->data_, to_read); michael@0: front->offset_ += to_read; michael@0: michael@0: if (front->offset_ == front->len_) { michael@0: input_.pop(); michael@0: delete front; michael@0: } michael@0: michael@0: return to_read; michael@0: } michael@0: michael@0: int32_t TransportLayerNSPRAdapter::Write(const void *buf, int32_t length) { michael@0: TransportResult r = output_->SendPacket( michael@0: static_cast(buf), length); michael@0: if (r >= 0) { michael@0: return r; michael@0: } michael@0: michael@0: if (r == TE_WOULDBLOCK) { michael@0: PR_SetError(PR_WOULD_BLOCK_ERROR, 0); michael@0: } else { michael@0: PR_SetError(PR_IO_ERROR, 0); michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: michael@0: // Implementation of NSPR methods michael@0: static PRStatus TransportLayerClose(PRFileDesc *f) { michael@0: f->secret = nullptr; michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: static int32_t TransportLayerRead(PRFileDesc *f, void *buf, int32_t length) { michael@0: TransportLayerNSPRAdapter *io = reinterpret_cast(f->secret); michael@0: return io->Read(buf, length); michael@0: } michael@0: michael@0: static int32_t TransportLayerWrite(PRFileDesc *f, const void *buf, int32_t length) { michael@0: TransportLayerNSPRAdapter *io = reinterpret_cast(f->secret); michael@0: return io->Write(buf, length); michael@0: } michael@0: michael@0: static int32_t TransportLayerAvailable(PRFileDesc *f) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: int64_t TransportLayerAvailable64(PRFileDesc *f) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static PRStatus TransportLayerSync(PRFileDesc *f) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static int32_t TransportLayerSeek(PRFileDesc *f, int32_t offset, michael@0: PRSeekWhence how) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static int64_t TransportLayerSeek64(PRFileDesc *f, int64_t offset, michael@0: PRSeekWhence how) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static PRStatus TransportLayerFileInfo(PRFileDesc *f, PRFileInfo *info) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static PRStatus TransportLayerFileInfo64(PRFileDesc *f, PRFileInfo64 *info) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static int32_t TransportLayerWritev(PRFileDesc *f, const PRIOVec *iov, michael@0: int32_t iov_size, PRIntervalTime to) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static PRStatus TransportLayerConnect(PRFileDesc *f, const PRNetAddr *addr, michael@0: PRIntervalTime to) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static PRFileDesc *TransportLayerAccept(PRFileDesc *sd, PRNetAddr *addr, michael@0: PRIntervalTime to) { michael@0: UNIMPLEMENTED; michael@0: return nullptr; michael@0: } michael@0: michael@0: static PRStatus TransportLayerBind(PRFileDesc *f, const PRNetAddr *addr) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static PRStatus TransportLayerListen(PRFileDesc *f, int32_t depth) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static PRStatus TransportLayerShutdown(PRFileDesc *f, int32_t how) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: // This function does not support peek. michael@0: static int32_t TransportLayerRecv(PRFileDesc *f, void *buf, int32_t amount, michael@0: int32_t flags, PRIntervalTime to) { michael@0: MOZ_ASSERT(flags == 0); michael@0: if (flags != 0) { michael@0: PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: return TransportLayerRead(f, buf, amount); michael@0: } michael@0: michael@0: // Note: this is always nonblocking and assumes a zero timeout. michael@0: static int32_t TransportLayerSend(PRFileDesc *f, const void *buf, int32_t amount, michael@0: int32_t flags, PRIntervalTime to) { michael@0: int32_t written = TransportLayerWrite(f, buf, amount); michael@0: return written; michael@0: } michael@0: michael@0: static int32_t TransportLayerRecvfrom(PRFileDesc *f, void *buf, int32_t amount, michael@0: int32_t flags, PRNetAddr *addr, PRIntervalTime to) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static int32_t TransportLayerSendto(PRFileDesc *f, const void *buf, int32_t amount, michael@0: int32_t flags, const PRNetAddr *addr, PRIntervalTime to) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static int16_t TransportLayerPoll(PRFileDesc *f, int16_t in_flags, int16_t *out_flags) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static int32_t TransportLayerAcceptRead(PRFileDesc *sd, PRFileDesc **nd, michael@0: PRNetAddr **raddr, michael@0: void *buf, int32_t amount, PRIntervalTime t) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static int32_t TransportLayerTransmitFile(PRFileDesc *sd, PRFileDesc *f, michael@0: const void *headers, int32_t hlen, michael@0: PRTransmitFileFlags flags, PRIntervalTime t) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static PRStatus TransportLayerGetpeername(PRFileDesc *f, PRNetAddr *addr) { michael@0: // TODO: Modify to return unique names for each channel michael@0: // somehow, as opposed to always the same static address. The current michael@0: // implementation messes up the session cache, which is why it's off michael@0: // elsewhere michael@0: addr->inet.family = PR_AF_INET; michael@0: addr->inet.port = 0; michael@0: addr->inet.ip = 0; michael@0: michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: static PRStatus TransportLayerGetsockname(PRFileDesc *f, PRNetAddr *addr) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static PRStatus TransportLayerGetsockoption(PRFileDesc *f, PRSocketOptionData *opt) { michael@0: switch (opt->option) { michael@0: case PR_SockOpt_Nonblocking: michael@0: opt->value.non_blocking = PR_TRUE; michael@0: return PR_SUCCESS; michael@0: default: michael@0: UNIMPLEMENTED; michael@0: break; michael@0: } michael@0: michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: // Imitate setting socket options. These are mostly noops. michael@0: static PRStatus TransportLayerSetsockoption(PRFileDesc *f, michael@0: const PRSocketOptionData *opt) { michael@0: switch (opt->option) { michael@0: case PR_SockOpt_Nonblocking: michael@0: return PR_SUCCESS; michael@0: case PR_SockOpt_NoDelay: michael@0: return PR_SUCCESS; michael@0: default: michael@0: UNIMPLEMENTED; michael@0: break; michael@0: } michael@0: michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static int32_t TransportLayerSendfile(PRFileDesc *out, PRSendFileData *in, michael@0: PRTransmitFileFlags flags, PRIntervalTime to) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static PRStatus TransportLayerConnectContinue(PRFileDesc *f, int16_t flags) { michael@0: UNIMPLEMENTED; michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static int32_t TransportLayerReserved(PRFileDesc *f) { michael@0: UNIMPLEMENTED; michael@0: return -1; michael@0: } michael@0: michael@0: static const struct PRIOMethods TransportLayerMethods = { michael@0: PR_DESC_LAYERED, michael@0: TransportLayerClose, michael@0: TransportLayerRead, michael@0: TransportLayerWrite, michael@0: TransportLayerAvailable, michael@0: TransportLayerAvailable64, michael@0: TransportLayerSync, michael@0: TransportLayerSeek, michael@0: TransportLayerSeek64, michael@0: TransportLayerFileInfo, michael@0: TransportLayerFileInfo64, michael@0: TransportLayerWritev, michael@0: TransportLayerConnect, michael@0: TransportLayerAccept, michael@0: TransportLayerBind, michael@0: TransportLayerListen, michael@0: TransportLayerShutdown, michael@0: TransportLayerRecv, michael@0: TransportLayerSend, michael@0: TransportLayerRecvfrom, michael@0: TransportLayerSendto, michael@0: TransportLayerPoll, michael@0: TransportLayerAcceptRead, michael@0: TransportLayerTransmitFile, michael@0: TransportLayerGetsockname, michael@0: TransportLayerGetpeername, michael@0: TransportLayerReserved, michael@0: TransportLayerReserved, michael@0: TransportLayerGetsockoption, michael@0: TransportLayerSetsockoption, michael@0: TransportLayerSendfile, michael@0: TransportLayerConnectContinue, michael@0: TransportLayerReserved, michael@0: TransportLayerReserved, michael@0: TransportLayerReserved, michael@0: TransportLayerReserved michael@0: }; michael@0: michael@0: TransportLayerDtls::~TransportLayerDtls() { michael@0: if (timer_) { michael@0: timer_->Cancel(); michael@0: } michael@0: } michael@0: michael@0: nsresult TransportLayerDtls::InitInternal() { michael@0: // Get the transport service as an event target michael@0: nsresult rv; michael@0: target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't get socket transport service"); michael@0: return rv; michael@0: } michael@0: michael@0: timer_ = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't get timer"); michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void TransportLayerDtls::WasInserted() { michael@0: // Connect to the lower layers michael@0: if (!Setup()) { michael@0: TL_SET_STATE(TS_ERROR); michael@0: } michael@0: } michael@0: michael@0: michael@0: nsresult TransportLayerDtls::SetVerificationAllowAll() { michael@0: // Defensive programming michael@0: if (verification_mode_ != VERIFY_UNSET) michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: michael@0: verification_mode_ = VERIFY_ALLOW_ALL; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TransportLayerDtls::SetVerificationDigest(const std::string digest_algorithm, michael@0: const unsigned char *digest_value, michael@0: size_t digest_len) { michael@0: // Defensive programming michael@0: if (verification_mode_ != VERIFY_UNSET && michael@0: verification_mode_ != VERIFY_DIGEST) { michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: } michael@0: michael@0: // Note that we do not sanity check these values for length. michael@0: // We merely ensure they will fit into the buffer. michael@0: // TODO: is there a Data construct we could use? michael@0: if (digest_len > kMaxDigestLength) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: digests_.push_back(new VerificationDigest( michael@0: digest_algorithm, digest_value, digest_len)); michael@0: michael@0: verification_mode_ = VERIFY_DIGEST; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // TODO: make sure this is called from STS. Otherwise michael@0: // we have thread safety issues michael@0: bool TransportLayerDtls::Setup() { michael@0: CheckThread(); michael@0: SECStatus rv; michael@0: michael@0: if (!downward_) { michael@0: MOZ_MTLOG(ML_ERROR, "DTLS layer with nothing below. This is useless"); michael@0: return false; michael@0: } michael@0: nspr_io_adapter_ = new TransportLayerNSPRAdapter(downward_); michael@0: michael@0: if (!identity_) { michael@0: MOZ_MTLOG(ML_ERROR, "Can't start DTLS without an identity"); michael@0: return false; michael@0: } michael@0: michael@0: if (verification_mode_ == VERIFY_UNSET) { michael@0: MOZ_MTLOG(ML_ERROR, michael@0: "Can't start DTLS without specifying a verification mode"); michael@0: return false; michael@0: } michael@0: michael@0: if (transport_layer_identity == PR_INVALID_IO_LAYER) { michael@0: transport_layer_identity = PR_GetUniqueIdentity("nssstreamadapter"); michael@0: } michael@0: michael@0: ScopedPRFileDesc pr_fd(PR_CreateIOLayerStub(transport_layer_identity, michael@0: &TransportLayerMethods)); michael@0: MOZ_ASSERT(pr_fd != nullptr); michael@0: if (!pr_fd) michael@0: return false; michael@0: pr_fd->secret = reinterpret_cast(nspr_io_adapter_.get()); michael@0: michael@0: ScopedPRFileDesc ssl_fd; michael@0: if (mode_ == DGRAM) { michael@0: ssl_fd = DTLS_ImportFD(nullptr, pr_fd); michael@0: } else { michael@0: ssl_fd = SSL_ImportFD(nullptr, pr_fd); michael@0: } michael@0: michael@0: MOZ_ASSERT(ssl_fd != nullptr); // This should never happen michael@0: if (!ssl_fd) { michael@0: return false; michael@0: } michael@0: michael@0: pr_fd.forget(); // ownership transfered to ssl_fd; michael@0: michael@0: if (role_ == CLIENT) { michael@0: MOZ_MTLOG(ML_DEBUG, "Setting up DTLS as client"); michael@0: rv = SSL_GetClientAuthDataHook(ssl_fd, GetClientAuthDataHook, michael@0: this); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't set identity"); michael@0: return false; michael@0: } michael@0: } else { michael@0: MOZ_MTLOG(ML_DEBUG, "Setting up DTLS as server"); michael@0: // Server side michael@0: rv = SSL_ConfigSecureServer(ssl_fd, identity_->cert(), michael@0: identity_->privkey(), michael@0: kt_rsa); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't set identity"); michael@0: return false; michael@0: } michael@0: michael@0: // Insist on a certificate from the client michael@0: rv = SSL_OptionSet(ssl_fd, SSL_REQUEST_CERTIFICATE, PR_TRUE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't request certificate"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_REQUIRE_CERTIFICATE, PR_TRUE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't require certificate"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Require TLS 1.1. Perhaps some day in the future we will allow michael@0: // TLS 1.0 for stream modes. michael@0: SSLVersionRange version_range = { michael@0: SSL_LIBRARY_VERSION_TLS_1_1, michael@0: SSL_LIBRARY_VERSION_TLS_1_1 michael@0: }; michael@0: michael@0: rv = SSL_VersionRangeSet(ssl_fd, &version_range); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Can't disable SSLv3"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't disable session tickets"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't disable session caching"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_DEFLATE, PR_FALSE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't disable deflate"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't disable renegotiation"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't disable false start"); michael@0: return false; michael@0: } michael@0: michael@0: rv = SSL_OptionSet(ssl_fd, SSL_NO_LOCKS, PR_TRUE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't disable locks"); michael@0: return false; michael@0: } michael@0: michael@0: // Set the SRTP ciphers michael@0: if (srtp_ciphers_.size()) { michael@0: // Note: std::vector is guaranteed to contiguous michael@0: rv = SSL_SetSRTPCiphers(ssl_fd, &srtp_ciphers_[0], michael@0: srtp_ciphers_.size()); michael@0: michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't set SRTP cipher suite"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Certificate validation michael@0: rv = SSL_AuthCertificateHook(ssl_fd, AuthCertificateHook, michael@0: reinterpret_cast(this)); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't set certificate validation hook"); michael@0: return false; michael@0: } michael@0: michael@0: // Now start the handshake michael@0: rv = SSL_ResetHandshake(ssl_fd, role_ == SERVER ? PR_TRUE : PR_FALSE); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't reset handshake"); michael@0: return false; michael@0: } michael@0: ssl_fd_ = ssl_fd.forget(); michael@0: michael@0: // Finally, get ready to receive data michael@0: downward_->SignalStateChange.connect(this, &TransportLayerDtls::StateChange); michael@0: downward_->SignalPacketReceived.connect(this, &TransportLayerDtls::PacketReceived); michael@0: michael@0: if (downward_->state() == TS_OPEN) { michael@0: Handshake(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: void TransportLayerDtls::StateChange(TransportLayer *layer, State state) { michael@0: if (state <= state_) { michael@0: MOZ_MTLOG(ML_ERROR, "Lower layer state is going backwards from ours"); michael@0: TL_SET_STATE(TS_ERROR); michael@0: return; michael@0: } michael@0: michael@0: switch (state) { michael@0: case TS_NONE: michael@0: MOZ_ASSERT(false); // Can't happen michael@0: break; michael@0: michael@0: case TS_INIT: michael@0: MOZ_MTLOG(ML_ERROR, michael@0: LAYER_INFO << "State change of lower layer to INIT forbidden"); michael@0: TL_SET_STATE(TS_ERROR); michael@0: break; michael@0: michael@0: case TS_CONNECTING: michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Lower lower is connecting."); michael@0: break; michael@0: michael@0: case TS_OPEN: michael@0: MOZ_MTLOG(ML_ERROR, michael@0: LAYER_INFO << "Lower lower is now open; starting TLS"); michael@0: Handshake(); michael@0: break; michael@0: michael@0: case TS_CLOSED: michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Lower lower is now closed"); michael@0: TL_SET_STATE(TS_CLOSED); michael@0: break; michael@0: michael@0: case TS_ERROR: michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Lower lower experienced an error"); michael@0: TL_SET_STATE(TS_ERROR); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void TransportLayerDtls::Handshake() { michael@0: TL_SET_STATE(TS_CONNECTING); michael@0: michael@0: // Clear the retransmit timer michael@0: timer_->Cancel(); michael@0: michael@0: SECStatus rv = SSL_ForceHandshake(ssl_fd_); michael@0: michael@0: if (rv == SECSuccess) { michael@0: MOZ_MTLOG(ML_NOTICE, michael@0: LAYER_INFO << "****** SSL handshake completed ******"); michael@0: if (!cert_ok_) { michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Certificate check never occurred"); michael@0: TL_SET_STATE(TS_ERROR); michael@0: return; michael@0: } michael@0: TL_SET_STATE(TS_OPEN); michael@0: } else { michael@0: int32_t err = PR_GetError(); michael@0: switch(err) { michael@0: case SSL_ERROR_RX_MALFORMED_HANDSHAKE: michael@0: if (mode_ != DGRAM) { michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Malformed TLS message"); michael@0: TL_SET_STATE(TS_ERROR); michael@0: } else { michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Malformed DTLS message; ignoring"); michael@0: } michael@0: // Fall through michael@0: case PR_WOULD_BLOCK_ERROR: michael@0: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Would have blocked"); michael@0: if (mode_ == DGRAM) { michael@0: PRIntervalTime timeout; michael@0: rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout); michael@0: if (rv == SECSuccess) { michael@0: uint32_t timeout_ms = PR_IntervalToMilliseconds(timeout); michael@0: michael@0: MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Setting DTLS timeout to " << michael@0: timeout_ms); michael@0: timer_->SetTarget(target_); michael@0: timer_->InitWithFuncCallback(TimerCallback, michael@0: this, timeout_ms, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "SSL handshake error "<< err); michael@0: TL_SET_STATE(TS_ERROR); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void TransportLayerDtls::PacketReceived(TransportLayer* layer, michael@0: const unsigned char *data, michael@0: size_t len) { michael@0: CheckThread(); michael@0: MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << len << ")"); michael@0: michael@0: if (state_ != TS_CONNECTING && state_ != TS_OPEN) { michael@0: MOZ_MTLOG(ML_DEBUG, michael@0: LAYER_INFO << "Discarding packet in inappropriate state"); michael@0: return; michael@0: } michael@0: michael@0: nspr_io_adapter_->PacketReceived(data, len); michael@0: michael@0: // If we're still connecting, try to handshake michael@0: if (state_ == TS_CONNECTING) { michael@0: Handshake(); michael@0: } michael@0: michael@0: // Now try a recv if we're open, since there might be data left michael@0: if (state_ == TS_OPEN) { michael@0: unsigned char buf[2000]; michael@0: michael@0: int32_t rv = PR_Recv(ssl_fd_, buf, sizeof(buf), 0, PR_INTERVAL_NO_WAIT); michael@0: if (rv > 0) { michael@0: // We have data michael@0: MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Read " << rv << " bytes from NSS"); michael@0: SignalPacketReceived(this, buf, rv); michael@0: } else if (rv == 0) { michael@0: TL_SET_STATE(TS_CLOSED); michael@0: } else { michael@0: int32_t err = PR_GetError(); michael@0: michael@0: if (err == PR_WOULD_BLOCK_ERROR) { michael@0: // This gets ignored michael@0: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Would have blocked"); michael@0: } else { michael@0: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err); michael@0: TL_SET_STATE(TS_ERROR); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: TransportResult TransportLayerDtls::SendPacket(const unsigned char *data, michael@0: size_t len) { michael@0: CheckThread(); michael@0: if (state_ != TS_OPEN) { michael@0: MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Can't call SendPacket() in state " michael@0: << state_); michael@0: return TE_ERROR; michael@0: } michael@0: michael@0: int32_t rv = PR_Send(ssl_fd_, data, len, 0, PR_INTERVAL_NO_WAIT); michael@0: michael@0: if (rv > 0) { michael@0: // We have data michael@0: MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Wrote " << rv << " bytes to SSL Layer"); michael@0: return rv; michael@0: } michael@0: michael@0: if (rv == 0) { michael@0: TL_SET_STATE(TS_CLOSED); michael@0: return 0; michael@0: } michael@0: michael@0: int32_t err = PR_GetError(); michael@0: michael@0: if (err == PR_WOULD_BLOCK_ERROR) { michael@0: // This gets ignored michael@0: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Would have blocked"); michael@0: return TE_WOULDBLOCK; michael@0: } michael@0: michael@0: MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err); michael@0: TL_SET_STATE(TS_ERROR); michael@0: return TE_ERROR; michael@0: } michael@0: michael@0: SECStatus TransportLayerDtls::GetClientAuthDataHook(void *arg, PRFileDesc *fd, michael@0: CERTDistNames *caNames, michael@0: CERTCertificate **pRetCert, michael@0: SECKEYPrivateKey **pRetKey) { michael@0: MOZ_MTLOG(ML_DEBUG, "Server requested client auth"); michael@0: michael@0: TransportLayerDtls *stream = reinterpret_cast(arg); michael@0: stream->CheckThread(); michael@0: michael@0: if (!stream->identity_) { michael@0: MOZ_MTLOG(ML_ERROR, "No identity available"); michael@0: PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: *pRetCert = CERT_DupCertificate(stream->identity_->cert()); michael@0: if (!*pRetCert) { michael@0: PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: *pRetKey = SECKEY_CopyPrivateKey(stream->identity_->privkey()); michael@0: if (!*pRetKey) { michael@0: CERT_DestroyCertificate(*pRetCert); michael@0: *pRetCert = nullptr; michael@0: PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: nsresult TransportLayerDtls::SetSrtpCiphers(std::vector ciphers) { michael@0: // TODO: We should check these michael@0: srtp_ciphers_ = ciphers; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TransportLayerDtls::GetSrtpCipher(uint16_t *cipher) { michael@0: CheckThread(); michael@0: SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, cipher); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_DEBUG, "No SRTP cipher negotiated"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TransportLayerDtls::ExportKeyingMaterial(const std::string& label, michael@0: bool use_context, michael@0: const std::string& context, michael@0: unsigned char *out, michael@0: unsigned int outlen) { michael@0: CheckThread(); michael@0: SECStatus rv = SSL_ExportKeyingMaterial(ssl_fd_, michael@0: label.c_str(), michael@0: label.size(), michael@0: use_context, michael@0: reinterpret_cast( michael@0: context.c_str()), michael@0: context.size(), michael@0: out, michael@0: outlen); michael@0: if (rv != SECSuccess) { michael@0: MOZ_MTLOG(ML_ERROR, "Couldn't export SSL keying material"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: SECStatus TransportLayerDtls::AuthCertificateHook(void *arg, michael@0: PRFileDesc *fd, michael@0: PRBool checksig, michael@0: PRBool isServer) { michael@0: TransportLayerDtls *stream = reinterpret_cast(arg); michael@0: stream->CheckThread(); michael@0: return stream->AuthCertificateHook(fd, checksig, isServer); michael@0: } michael@0: michael@0: SECStatus michael@0: TransportLayerDtls::CheckDigest(const RefPtr& michael@0: digest, michael@0: CERTCertificate *peer_cert) { michael@0: unsigned char computed_digest[kMaxDigestLength]; michael@0: size_t computed_digest_len; michael@0: michael@0: MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Checking digest, algorithm=" michael@0: << digest->algorithm_); michael@0: nsresult res = michael@0: DtlsIdentity::ComputeFingerprint(peer_cert, michael@0: digest->algorithm_, michael@0: computed_digest, michael@0: sizeof(computed_digest), michael@0: &computed_digest_len); michael@0: if (NS_FAILED(res)) { michael@0: MOZ_MTLOG(ML_ERROR, "Could not compute peer fingerprint for digest " << michael@0: digest->algorithm_); michael@0: // Go to end michael@0: PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (computed_digest_len != digest->len_) { michael@0: MOZ_MTLOG(ML_ERROR, "Digest is wrong length " << digest->len_ << michael@0: " should be " << computed_digest_len << " for algorithm " << michael@0: digest->algorithm_); michael@0: PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (memcmp(digest->value_, computed_digest, computed_digest_len) != 0) { michael@0: MOZ_MTLOG(ML_ERROR, "Digest does not match"); michael@0: PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: michael@0: SECStatus TransportLayerDtls::AuthCertificateHook(PRFileDesc *fd, michael@0: PRBool checksig, michael@0: PRBool isServer) { michael@0: CheckThread(); michael@0: ScopedCERTCertificate peer_cert; michael@0: peer_cert = SSL_PeerCertificate(fd); michael@0: michael@0: michael@0: // We are not set up to take this being called multiple michael@0: // times. Change this if we ever add renegotiation. michael@0: MOZ_ASSERT(!auth_hook_called_); michael@0: if (auth_hook_called_) { michael@0: PR_SetError(PR_UNKNOWN_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: auth_hook_called_ = true; michael@0: michael@0: MOZ_ASSERT(verification_mode_ != VERIFY_UNSET); michael@0: MOZ_ASSERT(peer_cert_ == nullptr); michael@0: michael@0: switch (verification_mode_) { michael@0: case VERIFY_UNSET: michael@0: // Break out to error exit michael@0: PR_SetError(PR_UNKNOWN_ERROR, 0); michael@0: break; michael@0: michael@0: case VERIFY_ALLOW_ALL: michael@0: peer_cert_ = peer_cert.forget(); michael@0: cert_ok_ = true; michael@0: return SECSuccess; michael@0: michael@0: case VERIFY_DIGEST: michael@0: { michael@0: MOZ_ASSERT(digests_.size() != 0); michael@0: // Check all the provided digests michael@0: michael@0: // Checking functions call PR_SetError() michael@0: SECStatus rv = SECFailure; michael@0: for (size_t i = 0; i < digests_.size(); i++) { michael@0: RefPtr digest = digests_[i]; michael@0: rv = CheckDigest(digest, peer_cert); michael@0: michael@0: if (rv != SECSuccess) michael@0: break; michael@0: } michael@0: michael@0: if (rv == SECSuccess) { michael@0: // Matches all digests, we are good to go michael@0: cert_ok_ = true; michael@0: peer_cert = peer_cert.forget(); michael@0: return SECSuccess; michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: MOZ_CRASH(); // Can't happen michael@0: } michael@0: michael@0: return SECFailure; michael@0: } michael@0: michael@0: void TransportLayerDtls::TimerCallback(nsITimer *timer, void *arg) { michael@0: TransportLayerDtls *dtls = reinterpret_cast(arg); michael@0: michael@0: MOZ_MTLOG(ML_DEBUG, "DTLS timer expired"); michael@0: michael@0: dtls->Handshake(); michael@0: } michael@0: michael@0: } // close namespace