Bug 242448 - Add a basic scriptable TLS server. r=honzab
☠☠ backed out by 36a3cc973ef8 ☠ ☠
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 18 Sep 2014 08:13:00 +0200
changeset 206192 23400439c1da8dab2478d4f11bac93c2b796235e
parent 206136 f1fd44eebac78525a4055757386d54ac7bbf4d70
child 206193 0c15fcd00145a3f74cf8c1ed151baa1778a93243
push id27516
push userryanvm@gmail.com
push dateFri, 19 Sep 2014 17:54:48 +0000
treeherdermozilla-central@b00bdb144e06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs242448
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 242448 - Add a basic scriptable TLS server. r=honzab
netwerk/base/public/moz.build
netwerk/base/public/nsITLSServerSocket.idl
netwerk/base/src/TLSServerSocket.cpp
netwerk/base/src/TLSServerSocket.h
netwerk/base/src/moz.build
netwerk/base/src/nsServerSocket.cpp
netwerk/base/src/nsServerSocket.h
netwerk/base/src/nsSocketTransport2.cpp
netwerk/base/src/nsSocketTransport2.h
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/test/unit/test_tls_server.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/base/public/moz.build
+++ b/netwerk/base/public/moz.build
@@ -103,16 +103,17 @@ XPIDL_SOURCES += [
     'nsIStreamListenerTee.idl',
     'nsIStreamLoader.idl',
     'nsIStreamTransportService.idl',
     'nsISyncStreamListener.idl',
     'nsISystemProxySettings.idl',
     'nsIThreadRetargetableRequest.idl',
     'nsIThreadRetargetableStreamListener.idl',
     'nsITimedChannel.idl',
+    'nsITLSServerSocket.idl',
     'nsITraceableChannel.idl',
     'nsITransport.idl',
     'nsIUDPSocket.idl',
     'nsIUDPSocketFilter.idl',
     'nsIUnicharStreamLoader.idl',
     'nsIUploadChannel.idl',
     'nsIUploadChannel2.idl',
     'nsIURI.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsITLSServerSocket.idl
@@ -0,0 +1,179 @@
+/* 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/. */
+
+#include "nsIServerSocket.idl"
+
+interface nsIX509Cert;
+interface nsITLSServerSecurityObserver;
+interface nsISocketTransport;
+
+[scriptable, uuid(2e025b6c-96ba-4781-85fb-d1cf1a653207)]
+interface nsITLSServerSocket : nsIServerSocket
+{
+  /**
+   * serverCert
+   *
+   * The server's certificate that is presented to the client during the TLS
+   * handshake.  This is required to be set before calling |asyncListen|.
+   */
+  attribute nsIX509Cert serverCert;
+
+  /**
+   * setSessionCache
+   *
+   * Whether the server should use a session cache.  Defaults to true.  This
+   * should be set before calling |asyncListen| if you wish to change the
+   * default.
+   */
+  void setSessionCache(in boolean aSessionCache);
+
+  /**
+   * setSessionTickets
+   *
+   * Whether the server should support session tickets.  Defaults to true.  This
+   * should be set before calling |asyncListen| if you wish to change the
+   * default.
+   */
+  void setSessionTickets(in boolean aSessionTickets);
+
+  /**
+   * Values for setRequestClientCertificate
+   */
+  // Never request
+  const unsigned long REQUEST_NEVER           = 0;
+  // Request (but do not require) during the first handshake only
+  const unsigned long REQUEST_FIRST_HANDSHAKE = 1;
+  // Request (but do not require) during each handshake
+  const unsigned long REQUEST_ALWAYS          = 2;
+  // Require during the first handshake only
+  const unsigned long REQUIRE_FIRST_HANDSHAKE = 3;
+  // Require during each handshake
+  const unsigned long REQUIRE_ALWAYS          = 4;
+
+  /**
+   * setRequestClientCertificate
+   *
+   * Whether the server should request and/or require a client auth certificate
+   * from the client.  Defaults to REQUEST_NEVER.  See the possible options
+   * above.  This should be set before calling |asyncListen| if you wish to
+   * change the default.
+   */
+  void setRequestClientCertificate(in unsigned long aRequestClientCert);
+};
+
+/**
+ * Security summary for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server.
+ *
+ * This is accessible through the security info object on the transport, which
+ * will be an instance of |nsITLSServerConnectionInfo| (see below).
+ *
+ * The values of these attributes are available once the |onHandshakeDone|
+ * method of the security observer has been called (see
+ * |nsITLSServerSecurityObserver| below).
+ */
+[scriptable, uuid(19668ea4-e5ad-4182-9698-7e890d48f327)]
+interface nsITLSClientStatus : nsISupports
+{
+  /**
+   * peerCert
+   *
+   * The client's certificate, if one was requested via |requestCertificate|
+   * above and supplied by the client.
+   */
+  readonly attribute nsIX509Cert peerCert;
+
+  /**
+   * Values for tlsVersionUsed, as defined by TLS
+   */
+  const short SSL_VERSION_3   = 0x0300;
+  const short TLS_VERSION_1   = 0x0301;
+  const short TLS_VERSION_1_1 = 0x0302;
+  const short TLS_VERSION_1_2 = 0x0303;
+  const short TLS_VERSION_UNKNOWN = -1;
+
+  /**
+   * tlsVersionUsed
+   *
+   * The version of TLS used by the connection.  See values above.
+   */
+  readonly attribute short tlsVersionUsed;
+
+  /**
+   * cipherName
+   *
+   * Name of the cipher suite used, such as
+   * "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".
+   * See security/nss/lib/ssl/sslinfo.c for the possible values.
+   */
+  readonly attribute ACString cipherName;
+
+  /**
+   * keyLength
+   *
+   * The "effective" key size of the symmetric key in bits.
+   */
+  readonly attribute unsigned long keyLength;
+
+  /**
+   * macLength
+   *
+   * The size of the MAC in bits.
+   */
+  readonly attribute unsigned long macLength;
+};
+
+/**
+ * Connection info for a given TLS client connection being handled by a
+ * |nsITLSServerSocket| server.  This object is thread-safe.
+ *
+ * This is exposed as the security info object on the transport, so it can be
+ * accessed via |transport.securityInfo|.
+ *
+ * This interface is available by the time the |onSocketAttached| is called,
+ * which is the first time the TLS server consumer is notified of a new client.
+ */
+[scriptable, uuid(8a93f5d5-eddd-4c62-a4bd-bfd297653184)]
+interface nsITLSServerConnectionInfo : nsISupports
+{
+  /**
+   * setSecurityObserver
+   *
+   * Set the security observer to be notified when the TLS handshake has
+   * completed.
+   */
+  void setSecurityObserver(in nsITLSServerSecurityObserver observer);
+
+  /**
+   * serverSocket
+   *
+   * The nsITLSServerSocket instance that accepted this client connection.
+   */
+  readonly attribute nsITLSServerSocket serverSocket;
+
+  /**
+   * status
+   *
+   * Security summary for this TLS client connection.  Note that the values of
+   * this interface are not available until the TLS handshake has completed.
+   * See |nsITLSClientStatus| above for more details.
+   */
+  readonly attribute nsITLSClientStatus status;
+};
+
+[scriptable, uuid(1f62e1ae-e546-4a38-8917-d428472ed736)]
+interface nsITLSServerSecurityObserver : nsISupports
+{
+  /**
+   * onHandsakeDone
+   *
+   * This method is called once the TLS handshake is completed.  This takes
+   * place after |onSocketAccepted| has been called, which typically opens the
+   * streams to keep things moving along. It's important to be aware that the
+   * handshake has not completed at the point that |onSocketAccepted| is called,
+   * so no security verification can be done until this method is called.
+   */
+  void onHandshakeDone(in nsITLSServerSocket aServer,
+                       in nsITLSClientStatus aStatus);
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/TLSServerSocket.cpp
@@ -0,0 +1,476 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* 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/. */
+
+#include "TLSServerSocket.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIServerSocket.h"
+#include "nsITimer.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "ssl.h"
+
+extern PRThread *gSocketThread;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket
+//-----------------------------------------------------------------------------
+
+TLSServerSocket::TLSServerSocket()
+  : mServerCert(nullptr)
+{
+}
+
+TLSServerSocket::~TLSServerSocket()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(TLSServerSocket,
+                            nsServerSocket,
+                            nsITLSServerSocket)
+
+nsresult
+TLSServerSocket::SetSocketDefaults()
+{
+  // Set TLS options on the listening socket
+  mFD = SSL_ImportFD(nullptr, mFD);
+  if (NS_WARN_IF(!mFD)) {
+    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+  }
+
+  SSL_OptionSet(mFD, SSL_SECURITY, true);
+  SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_CLIENT, false);
+  SSL_OptionSet(mFD, SSL_HANDSHAKE_AS_SERVER, true);
+
+  // We don't currently notify the server API consumer of renegotiation events
+  // (to revalidate peer certs, etc.), so disable it for now.
+  SSL_OptionSet(mFD, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_NEVER);
+
+  SetSessionCache(true);
+  SetSessionTickets(true);
+  SetRequestClientCertificate(REQUEST_NEVER);
+
+  return NS_OK;
+}
+
+void
+TLSServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+                                       const NetAddr& aClientAddr)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  nsresult rv;
+
+  nsRefPtr<nsSocketTransport> trans = new nsSocketTransport;
+  if (NS_WARN_IF(!trans)) {
+    mCondition = NS_ERROR_OUT_OF_MEMORY;
+    return;
+  }
+
+  nsRefPtr<TLSServerConnectionInfo> info = new TLSServerConnectionInfo();
+  info->mServerSocket = this;
+  info->mTransport = trans;
+  nsCOMPtr<nsISupports> infoSupports =
+    NS_ISUPPORTS_CAST(nsITLSServerConnectionInfo*, info);
+  rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, infoSupports);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mCondition = rv;
+    return;
+  }
+
+  // Override the default peer certificate validation, so that server consumers
+  // can make their own choice after the handshake completes.
+  SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr);
+  // Once the TLS handshake has completed, the server consumer is notified and
+  // has access to various TLS state details.
+  // It's safe to pass info here because the socket transport holds it as
+  // |mSecInfo| which keeps it alive for the lifetime of the socket.
+  SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback,
+                        info);
+
+  // Notify the consumer of the new client so it can manage the streams.
+  // Security details aren't known yet.  The security observer will be notified
+  // later when they are ready.
+  nsCOMPtr<nsIServerSocket> serverSocket =
+    do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this));
+  mListener->OnSocketAccepted(serverSocket, trans);
+}
+
+nsresult
+TLSServerSocket::OnSocketListen()
+{
+  if (NS_WARN_IF(!mServerCert)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  ScopedCERTCertificate cert(mServerCert->GetCert());
+  if (NS_WARN_IF(!cert)) {
+    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+  }
+
+  ScopedSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert, nullptr));
+  if (NS_WARN_IF(!key)) {
+    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+  }
+
+  SSLKEAType certKEA = NSS_FindCertKEAType(cert);
+
+  nsresult rv = MapSECStatus(SSL_ConfigSecureServer(mFD, cert, key, certKEA));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+// static
+SECStatus
+TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checksig,
+                                     PRBool isServer)
+{
+  // Allow any client cert here, server consumer code can decide whether it's
+  // okay after being notified of the new client socket.
+  return SECSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket::nsITLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TLSServerSocket::GetServerCert(nsIX509Cert** aCert)
+{
+  if (NS_WARN_IF(!aCert)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aCert = mServerCert;
+  NS_IF_ADDREF(*aCert);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetServerCert(nsIX509Cert* aCert)
+{
+  // If AsyncListen was already called (and set mListener), it's too late to set
+  // this.
+  if (NS_WARN_IF(mListener)) {
+    return NS_ERROR_IN_PROGRESS;
+  }
+  mServerCert = aCert;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionCache(bool aEnabled)
+{
+  // If AsyncListen was already called (and set mListener), it's too late to set
+  // this.
+  if (NS_WARN_IF(mListener)) {
+    return NS_ERROR_IN_PROGRESS;
+  }
+  SSL_OptionSet(mFD, SSL_NO_CACHE, !aEnabled);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetSessionTickets(bool aEnabled)
+{
+  // If AsyncListen was already called (and set mListener), it's too late to set
+  // this.
+  if (NS_WARN_IF(mListener)) {
+    return NS_ERROR_IN_PROGRESS;
+  }
+  SSL_OptionSet(mFD, SSL_ENABLE_SESSION_TICKETS, aEnabled);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetRequestClientCertificate(uint32_t aMode)
+{
+  // If AsyncListen was already called (and set mListener), it's too late to set
+  // this.
+  if (NS_WARN_IF(mListener)) {
+    return NS_ERROR_IN_PROGRESS;
+  }
+  SSL_OptionSet(mFD, SSL_REQUEST_CERTIFICATE, aMode != REQUEST_NEVER);
+
+  switch (aMode) {
+    case REQUEST_ALWAYS:
+      SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR);
+      break;
+    case REQUIRE_FIRST_HANDSHAKE:
+      SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_FIRST_HANDSHAKE);
+      break;
+    case REQUIRE_ALWAYS:
+      SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS);
+      break;
+    default:
+      SSL_OptionSet(mFD, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER);
+  }
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerConnectionInfo
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class TLSServerSecurityObserverProxy MOZ_FINAL : public nsITLSServerSecurityObserver
+{
+  ~TLSServerSecurityObserverProxy() {}
+
+public:
+  explicit TLSServerSecurityObserverProxy(nsITLSServerSecurityObserver* aListener)
+    : mListener(new nsMainThreadPtrHolder<nsITLSServerSecurityObserver>(aListener))
+  { }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+  class OnHandshakeDoneRunnable : public nsRunnable
+  {
+  public:
+    OnHandshakeDoneRunnable(const nsMainThreadPtrHandle<nsITLSServerSecurityObserver>& aListener,
+                            nsITLSServerSocket* aServer,
+                            nsITLSClientStatus* aStatus)
+      : mListener(aListener)
+      , mServer(aServer)
+      , mStatus(aStatus)
+    { }
+
+    NS_DECL_NSIRUNNABLE
+
+  private:
+    nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+    nsCOMPtr<nsITLSServerSocket> mServer;
+    nsCOMPtr<nsITLSClientStatus> mStatus;
+  };
+
+private:
+  nsMainThreadPtrHandle<nsITLSServerSecurityObserver> mListener;
+};
+
+NS_IMPL_ISUPPORTS(TLSServerSecurityObserverProxy,
+                  nsITLSServerSecurityObserver)
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDone(nsITLSServerSocket* aServer,
+                                                nsITLSClientStatus* aStatus)
+{
+  nsRefPtr<OnHandshakeDoneRunnable> r =
+    new OnHandshakeDoneRunnable(mListener, aServer, aStatus);
+  return NS_DispatchToMainThread(r);
+}
+
+NS_IMETHODIMP
+TLSServerSecurityObserverProxy::OnHandshakeDoneRunnable::Run()
+{
+  mListener->OnHandshakeDone(mServer, mStatus);
+  return NS_OK;
+}
+
+} // anonymous namespace
+
+NS_IMPL_ISUPPORTS(TLSServerConnectionInfo,
+                  nsITLSServerConnectionInfo,
+                  nsITLSClientStatus)
+
+TLSServerConnectionInfo::TLSServerConnectionInfo()
+  : mServerSocket(nullptr)
+  , mTransport(nullptr)
+  , mPeerCert(nullptr)
+  , mTlsVersionUsed(TLS_VERSION_UNKNOWN)
+  , mKeyLength(0)
+  , mMacLength(0)
+  , mLock("TLSServerConnectionInfo.mLock")
+  , mSecurityObserver(nullptr)
+{
+}
+
+TLSServerConnectionInfo::~TLSServerConnectionInfo()
+{
+  if (!mSecurityObserver) {
+    return;
+  }
+
+  nsITLSServerSecurityObserver* observer;
+  {
+    MutexAutoLock lock(mLock);
+    mSecurityObserver.forget(&observer);
+  }
+
+  if (observer) {
+    nsCOMPtr<nsIThread> mainThread;
+    NS_GetMainThread(getter_AddRefs(mainThread));
+    NS_ProxyRelease(mainThread, observer);
+  }
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::SetSecurityObserver(nsITLSServerSecurityObserver* aObserver)
+{
+  {
+    MutexAutoLock lock(mLock);
+    mSecurityObserver = new TLSServerSecurityObserverProxy(aObserver);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetServerSocket(nsITLSServerSocket** aSocket)
+{
+  if (NS_WARN_IF(!aSocket)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aSocket = mServerSocket;
+  NS_IF_ADDREF(*aSocket);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus)
+{
+  if (NS_WARN_IF(!aStatus)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aStatus = this;
+  NS_IF_ADDREF(*aStatus);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert)
+{
+  if (NS_WARN_IF(!aCert)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aCert = mPeerCert;
+  NS_IF_ADDREF(*aCert);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed)
+{
+  if (NS_WARN_IF(!aTlsVersionUsed)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aTlsVersionUsed = mTlsVersionUsed;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName)
+{
+  aCipherName.Assign(mCipherName);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength)
+{
+  if (NS_WARN_IF(!aKeyLength)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aKeyLength = mKeyLength;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength)
+{
+  if (NS_WARN_IF(!aMacLength)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aMacLength = mMacLength;
+  return NS_OK;
+}
+
+// static
+void
+TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg)
+{
+  nsRefPtr<TLSServerConnectionInfo> info =
+    static_cast<TLSServerConnectionInfo*>(aArg);
+  nsISocketTransport* transport = info->mTransport;
+  // No longer needed outside this function, so clear the weak ref
+  info->mTransport = nullptr;
+  nsresult rv = info->HandshakeCallback(aFD);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    transport->Close(rv);
+  }
+}
+
+nsresult
+TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD)
+{
+  nsresult rv;
+
+  ScopedCERTCertificate clientCert(SSL_PeerCertificate(aFD));
+  if (clientCert) {
+    nsCOMPtr<nsIX509CertDB> certDB =
+      do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    nsCOMPtr<nsIX509Cert> clientCertPSM;
+    rv = certDB->ConstructX509(reinterpret_cast<char*>(clientCert->derCert.data),
+                               clientCert->derCert.len,
+                               getter_AddRefs(clientCertPSM));
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    mPeerCert = clientCertPSM;
+  }
+
+  SSLChannelInfo channelInfo;
+  rv = MapSECStatus(SSL_GetChannelInfo(aFD, &channelInfo, sizeof(channelInfo)));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  mTlsVersionUsed = channelInfo.protocolVersion;
+
+  SSLCipherSuiteInfo cipherInfo;
+  rv = MapSECStatus(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+                                           sizeof(cipherInfo)));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  mCipherName.Assign(cipherInfo.cipherSuiteName);
+  mKeyLength = cipherInfo.effectiveKeyBits;
+  mMacLength = cipherInfo.macBits;
+
+  if (!mSecurityObserver) {
+    return NS_OK;
+  }
+
+  // Notify consumer code that handshake is complete
+  nsCOMPtr<nsITLSServerSecurityObserver> observer;
+  {
+    MutexAutoLock lock(mLock);
+    mSecurityObserver.swap(observer);
+  }
+  nsCOMPtr<nsITLSServerSocket> serverSocket;
+  GetServerSocket(getter_AddRefs(serverSocket));
+  observer->OnHandshakeDone(serverSocket, this);
+
+  return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/TLSServerSocket.h
@@ -0,0 +1,80 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_net_TLSServerSocket_h
+#define mozilla_net_TLSServerSocket_h
+
+#include "nsAutoPtr.h"
+#include "nsITLSServerSocket.h"
+#include "nsServerSocket.h"
+#include "mozilla/Mutex.h"
+#include "seccomon.h"
+
+namespace mozilla {
+namespace net {
+
+class TLSServerSocket : public nsServerSocket
+                      , public nsITLSServerSocket
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_FORWARD_NSISERVERSOCKET(nsServerSocket::)
+  NS_DECL_NSITLSSERVERSOCKET
+
+  // Override methods from nsServerSocket
+  virtual void CreateClientTransport(PRFileDesc* clientFD,
+                                     const NetAddr& clientAddr) MOZ_OVERRIDE;
+  virtual nsresult SetSocketDefaults() MOZ_OVERRIDE;
+  virtual nsresult OnSocketListen() MOZ_OVERRIDE;
+
+  TLSServerSocket();
+
+private:
+  virtual ~TLSServerSocket();
+
+  static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+                                       PRBool checksig, PRBool isServer);
+
+  nsCOMPtr<nsIX509Cert>                  mServerCert;
+};
+
+class TLSServerConnectionInfo : public nsITLSServerConnectionInfo
+                              , public nsITLSClientStatus
+{
+  friend class TLSServerSocket;
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITLSSERVERCONNECTIONINFO
+  NS_DECL_NSITLSCLIENTSTATUS
+
+  TLSServerConnectionInfo();
+
+private:
+  virtual ~TLSServerConnectionInfo();
+
+  static void HandshakeCallback(PRFileDesc* aFD, void* aArg);
+  nsresult HandshakeCallback(PRFileDesc* aFD);
+
+  nsRefPtr<TLSServerSocket>              mServerSocket;
+  // Weak ref to the transport, to avoid cycles since the transport holds a
+  // reference to the TLSServerConnectionInfo object.  This is not handed out to
+  // anyone, and is only used in HandshakeCallback to close the transport in
+  // case of an error.  After this, it's set to nullptr.
+  nsISocketTransport*                    mTransport;
+  nsCOMPtr<nsIX509Cert>                  mPeerCert;
+  int16_t                                mTlsVersionUsed;
+  nsCString                              mCipherName;
+  uint32_t                               mKeyLength;
+  uint32_t                               mMacLength;
+  // lock protects access to mSecurityObserver
+  mozilla::Mutex                         mLock;
+  nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSServerSocket_h
--- a/netwerk/base/src/moz.build
+++ b/netwerk/base/src/moz.build
@@ -72,16 +72,17 @@ UNIFIED_SOURCES += [
     'nsURIChecker.cpp',
     'nsURLHelper.cpp',
     'nsURLParsers.cpp',
     'Predictor.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
     'StreamingProtocolService.cpp',
     'Tickler.cpp',
+    'TLSServerSocket.cpp',
 ]
 
 # These files cannot be built in unified mode because they force NSPR logging.
 SOURCES += [
     'nsAsyncRedirectVerifyHelper.cpp',
     'nsSocketTransport2.cpp',
     'nsSocketTransportService2.cpp',
 ]
--- a/netwerk/base/src/nsServerSocket.cpp
+++ b/netwerk/base/src/nsServerSocket.cpp
@@ -40,18 +40,18 @@ PostEvent(nsServerSocket *s, nsServerSoc
   return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
 }
 
 //-----------------------------------------------------------------------------
 // nsServerSocket
 //-----------------------------------------------------------------------------
 
 nsServerSocket::nsServerSocket()
-  : mLock("nsServerSocket.mLock")
-  , mFD(nullptr)
+  : mFD(nullptr)
+  , mLock("nsServerSocket.mLock")
   , mAttached(false)
   , mKeepWhenOffline(false)
 {
   // we want to be able to access the STS directly, and it may not have been
   // constructed yet.  the STS constructor sets gSocketTransportService.
   if (!gSocketTransportService)
   {
     // This call can fail if we're offline, for example.
@@ -149,16 +149,35 @@ nsServerSocket::TryAttach()
 
   //
   // now, configure our poll flags for listening...
   //
   mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
   return NS_OK;
 }
 
+void
+nsServerSocket::CreateClientTransport(PRFileDesc* aClientFD,
+                                      const NetAddr& aClientAddr)
+{
+  nsRefPtr<nsSocketTransport> trans = new nsSocketTransport;
+  if (NS_WARN_IF(!trans)) {
+    mCondition = NS_ERROR_OUT_OF_MEMORY;
+    return;
+  }
+
+  nsresult rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mCondition = rv;
+    return;
+  }
+
+  mListener->OnSocketAccepted(this, trans);
+}
+
 //-----------------------------------------------------------------------------
 // nsServerSocket::nsASocketHandler
 //-----------------------------------------------------------------------------
 
 void
 nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
 {
   NS_ASSERTION(NS_SUCCEEDED(mCondition), "oops");
@@ -180,35 +199,24 @@ nsServerSocket::OnSocketReady(PRFileDesc
   // 'accept' system call), so we can't distinguish between named,
   // unnamed, and abstract peer addresses. Clear prClientAddr first, so
   // that the path will at least be reliably empty for unnamed and
   // abstract addresses, and not garbage when the peer is unnamed.
   memset(&prClientAddr, 0, sizeof(prClientAddr));
 
   clientFD = PR_Accept(mFD, &prClientAddr, PR_INTERVAL_NO_WAIT);
   PRNetAddrToNetAddr(&prClientAddr, &clientAddr);
-  if (!clientFD)
-  {
+  if (!clientFD) {
     NS_WARNING("PR_Accept failed");
     mCondition = NS_ERROR_UNEXPECTED;
+    return;
   }
-  else
-  {
-    nsRefPtr<nsSocketTransport> trans = new nsSocketTransport;
-    if (!trans)
-      mCondition = NS_ERROR_OUT_OF_MEMORY;
-    else
-    {
-      nsresult rv = trans->InitWithConnectedSocket(clientFD, &clientAddr);
-      if (NS_FAILED(rv))
-        mCondition = rv;
-      else
-        mListener->OnSocketAccepted(this, trans);
-    }
-  }
+
+  // Accept succeeded, create socket transport and notify consumer
+  CreateClientTransport(clientFD, clientAddr);
 }
 
 void
 nsServerSocket::OnSocketDetached(PRFileDesc *fd)
 {
   // force a failure condition if none set; maybe the STS is shutting down :-/
   if (NS_SUCCEEDED(mCondition))
     mCondition = NS_ERROR_ABORT;
@@ -323,16 +331,17 @@ nsServerSocket::InitSpecialConnection(in
   mKeepWhenOffline = ((aFlags & nsIServerSocket::KeepWhenOffline) != 0);
   return InitWithAddress(&addr, aBackLog);
 }
 
 NS_IMETHODIMP
 nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
 {
   NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+  nsresult rv;
 
   //
   // configure listening socket...
   //
 
   mFD = PR_OpenTCPSocket(aAddr->raw.family);
   if (!mFD)
   {
@@ -368,22 +377,28 @@ nsServerSocket::InitWithAddress(const PR
   // get the resulting socket address, which may be different than what
   // we passed to bind.
   if (PR_GetSockName(mFD, &mAddr) != PR_SUCCESS)
   {
     NS_WARNING("cannot get socket name");
     goto fail;
   }
 
+  // Set any additional socket defaults needed by child classes
+  rv = SetSocketDefaults();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    goto fail;
+  }
+
   // wait until AsyncListen is called before polling the socket for
   // client connections.
   return NS_OK;
 
 fail:
-  nsresult rv = ErrorAccordingToNSPR(PR_GetError());
+  rv = ErrorAccordingToNSPR(PR_GetError());
   Close();
   return rv;
 }
 
 NS_IMETHODIMP
 nsServerSocket::Close()
 {
   {
@@ -504,16 +519,23 @@ nsServerSocket::AsyncListen(nsIServerSoc
   // ensuring mFD implies ensuring mLock
   NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
   NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
   {
     MutexAutoLock lock(mLock);
     mListener = new ServerSocketListenerProxy(aListener);
     mListenerTarget = NS_GetCurrentThread();
   }
+
+  // Child classes may need to do additional setup just before listening begins
+  nsresult rv = OnSocketListen();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   return PostEvent(this, &nsServerSocket::OnMsgAttach);
 }
 
 NS_IMETHODIMP
 nsServerSocket::GetPort(int32_t *aResult)
 {
   // no need to enter the lock here
   uint16_t port;
--- a/netwerk/base/src/nsServerSocket.h
+++ b/netwerk/base/src/nsServerSocket.h
@@ -7,16 +7,21 @@
 #define nsServerSocket_h__
 
 #include "nsASocketHandler.h"
 #include "nsIServerSocket.h"
 #include "mozilla/Mutex.h"
 
 //-----------------------------------------------------------------------------
 
+class nsIEventTarget;
+namespace mozilla { namespace net {
+union NetAddr;
+}} // namespace mozilla::net
+
 class nsServerSocket : public nsASocketHandler
                      , public nsIServerSocket
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSISERVERSOCKET
 
   // nsASocketHandler methods:
@@ -24,30 +29,36 @@ public:
   virtual void OnSocketDetached(PRFileDesc *fd);
   virtual void IsLocal(bool *aIsLocal);
   virtual void KeepWhenOffline(bool *aKeepWhenOffline);
 
   virtual uint64_t ByteCountSent() { return 0; }
   virtual uint64_t ByteCountReceived() { return 0; }
   nsServerSocket();
 
-private:
+  virtual void CreateClientTransport(PRFileDesc* clientFD,
+                                     const mozilla::net::NetAddr& clientAddr);
+  virtual nsresult SetSocketDefaults() { return NS_OK; }
+  virtual nsresult OnSocketListen() { return NS_OK; }
+
+protected:
   virtual ~nsServerSocket();
+  PRFileDesc*                       mFD;
+  nsCOMPtr<nsIServerSocketListener> mListener;
 
+private:
   void OnMsgClose();
   void OnMsgAttach();
-  
+
   // try attaching our socket (mFD) to the STS's poll list.
   nsresult TryAttach();
 
   // lock protects access to mListener; so it is not cleared while being used.
   mozilla::Mutex                    mLock;
-  PRFileDesc                       *mFD;
   PRNetAddr                         mAddr;
-  nsCOMPtr<nsIServerSocketListener> mListener;
   nsCOMPtr<nsIEventTarget>          mListenerTarget;
   bool                              mAttached;
   bool                              mKeepWhenOffline;
 };
 
 //-----------------------------------------------------------------------------
 
 #endif // nsServerSocket_h__
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -938,16 +938,25 @@ nsSocketTransport::InitWithConnectedSock
     SOCKET_LOG(("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
         this, mHost.get(), mPort));
 
     // jump to InitiateSocket to get ourselves attached to the STS poll list.
     return PostEvent(MSG_RETRY_INIT_SOCKET);
 }
 
 nsresult
+nsSocketTransport::InitWithConnectedSocket(PRFileDesc* aFD,
+                                           const NetAddr* aAddr,
+                                           nsISupports* aSecInfo)
+{
+    mSecInfo = aSecInfo;
+    return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult
 nsSocketTransport::PostEvent(uint32_t type, nsresult status, nsISupports *param)
 {
     SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%x param=%p]\n",
         this, type, status, param));
 
     nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, type, status, param);
     if (!event)
         return NS_ERROR_OUT_OF_MEMORY;
--- a/netwerk/base/src/nsSocketTransport2.h
+++ b/netwerk/base/src/nsSocketTransport2.h
@@ -126,16 +126,22 @@ public:
                   const nsACString &host, uint16_t port,
                   nsIProxyInfo *proxyInfo);
 
     // this method instructs the socket transport to use an already connected
     // socket with the given address.
     nsresult InitWithConnectedSocket(PRFileDesc *socketFD,
                                      const mozilla::net::NetAddr *addr);
 
+    // this method instructs the socket transport to use an already connected
+    // socket with the given address, and additionally supplies security info.
+    nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD,
+                                     const mozilla::net::NetAddr* aAddr,
+                                     nsISupports* aSecInfo);
+
     // This method instructs the socket transport to open a socket
     // connected to the given Unix domain address. We can only create
     // unlayered, simple, stream sockets.
     nsresult InitWithFilename(const char *filename);
 
     // nsASocketHandler methods:
     void OnSocketReady(PRFileDesc *, int16_t outFlags);
     void OnSocketDetached(PRFileDesc *);
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -330,16 +330,27 @@
 #define NS_SERVERSOCKET_CID                          \
 { /* 2ec62893-3b35-48fa-ab1d-5e68a9f45f08 */         \
     0x2ec62893,                                      \
     0x3b35,                                          \
     0x48fa,                                          \
     {0xab, 0x1d, 0x5e, 0x68, 0xa9, 0xf4, 0x5f, 0x08} \
 }
 
+// component implementing nsITLSServerSocket
+#define NS_TLSSERVERSOCKET_CONTRACTID \
+    "@mozilla.org/network/tls-server-socket;1"
+#define NS_TLSSERVERSOCKET_CID                       \
+{ /* 1813cbb4-c98e-4622-8c7d-839167f3f272 */         \
+    0x1813cbb4,                                      \
+    0xc98e,                                          \
+    0x4622,                                          \
+    {0x8c, 0x7d, 0x83, 0x91, 0x67, 0xf3, 0xf2, 0x72} \
+}
+
 // component implementing nsIUDPSocket
 #define NS_UDPSOCKET_CONTRACTID \
     "@mozilla.org/network/udp-socket;1"
 #define NS_UDPSOCKET_CID                             \
 { /* c9f74572-7b8e-4fec-bb4a-03c0d3021bd6 */         \
     0xc9f74572,                                      \
     0x7b8e,                                          \
     0x4fec,                                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -70,16 +70,20 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSt
 #include "nsSocketTransportService2.h"
 #undef LOG
 #undef LOG_ENABLED
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSocketTransportService, Init)
 
 #include "nsServerSocket.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsServerSocket)
 
+#include "TLSServerSocket.h"
+typedef mozilla::net::TLSServerSocket TLSServerSocket;
+NS_GENERIC_FACTORY_CONSTRUCTOR(TLSServerSocket)
+
 #include "nsUDPSocket.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUDPSocket)
 
 #include "nsUDPSocketProvider.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUDPSocketProvider)
 
 #include "nsAsyncStreamCopier.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAsyncStreamCopier)
@@ -660,16 +664,17 @@ static void nsNetShutdown()
     delete gDataSniffers;
     gDataSniffers = nullptr;
 }
 
 NS_DEFINE_NAMED_CID(NS_IOSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_STREAMTRANSPORTSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_SOCKETTRANSPORTSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_SERVERSOCKET_CID);
+NS_DEFINE_NAMED_CID(NS_TLSSERVERSOCKET_CID);
 NS_DEFINE_NAMED_CID(NS_UDPSOCKET_CID);
 NS_DEFINE_NAMED_CID(NS_SOCKETPROVIDERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_DNSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_IDNSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EFFECTIVETLDSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_SIMPLEURI_CID);
 NS_DEFINE_NAMED_CID(NS_SIMPLENESTEDURI_CID);
 NS_DEFINE_NAMED_CID(NS_ASYNCSTREAMCOPIER_CID);
@@ -799,16 +804,17 @@ NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELRE
 NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID);
 
 static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
     { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
     { &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor },
     { &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor },
     { &kNS_SERVERSOCKET_CID, false, nullptr, nsServerSocketConstructor },
+    { &kNS_TLSSERVERSOCKET_CID, false, nullptr, TLSServerSocketConstructor },
     { &kNS_UDPSOCKET_CID, false, nullptr, nsUDPSocketConstructor },
     { &kNS_SOCKETPROVIDERSERVICE_CID, false, nullptr, nsSocketProviderService::Create },
     { &kNS_DNSSERVICE_CID, false, nullptr, nsIDNSServiceConstructor },
     { &kNS_IDNSERVICE_CID, false, nullptr, nsIDNServiceConstructor },
     { &kNS_EFFECTIVETLDSERVICE_CID, false, nullptr, nsEffectiveTLDServiceConstructor },
     { &kNS_SIMPLEURI_CID, false, nullptr, nsSimpleURIConstructor },
     { &kNS_SIMPLENESTEDURI_CID, false, nullptr, nsSimpleNestedURIConstructor },
     { &kNS_ASYNCSTREAMCOPIER_CID, false, nullptr, nsAsyncStreamCopierConstructor },
@@ -945,16 +951,17 @@ static const mozilla::Module::CIDEntry k
 };
 
 static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
     { NS_IOSERVICE_CONTRACTID, &kNS_IOSERVICE_CID },
     { NS_NETUTIL_CONTRACTID, &kNS_IOSERVICE_CID },
     { NS_STREAMTRANSPORTSERVICE_CONTRACTID, &kNS_STREAMTRANSPORTSERVICE_CID },
     { NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID },
     { NS_SERVERSOCKET_CONTRACTID, &kNS_SERVERSOCKET_CID },
+    { NS_TLSSERVERSOCKET_CONTRACTID, &kNS_TLSSERVERSOCKET_CID },
     { NS_UDPSOCKET_CONTRACTID, &kNS_UDPSOCKET_CID },
     { NS_SOCKETPROVIDERSERVICE_CONTRACTID, &kNS_SOCKETPROVIDERSERVICE_CID },
     { NS_DNSSERVICE_CONTRACTID, &kNS_DNSSERVICE_CID },
     { NS_IDNSERVICE_CONTRACTID, &kNS_IDNSERVICE_CID },
     { NS_EFFECTIVETLDSERVICE_CONTRACTID, &kNS_EFFECTIVETLDSERVICE_CID },
     { NS_SIMPLEURI_CONTRACTID, &kNS_SIMPLEURI_CID },
     { NS_ASYNCSTREAMCOPIER_CONTRACTID, &kNS_ASYNCSTREAMCOPIER_CID },
     { NS_INPUTSTREAMPUMP_CONTRACTID, &kNS_INPUTSTREAMPUMP_CID },
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Need profile dir to store the key / cert
+do_get_profile();
+// Ensure PSM is initialized
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+  Cu.import("resource://gre/modules/Promise.jsm", {});
+const certService = Cc["@mozilla.org/security/local-cert-service;1"]
+                    .getService(Ci.nsILocalCertService);
+const certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                            .getService(Ci.nsICertOverrideService);
+const socketTransportService =
+  Cc["@mozilla.org/network/socket-transport-service;1"]
+  .getService(Ci.nsISocketTransportService);
+
+function run_test() {
+  run_next_test();
+}
+
+function getCert() {
+  let deferred = promise.defer();
+  certService.getOrCreateCert("tls-test", {
+    handleCert: function(c, rv) {
+      if (rv) {
+        deferred.reject(rv);
+        return;
+      }
+      deferred.resolve(c);
+    }
+  });
+  return deferred.promise;
+}
+
+function startServer(cert) {
+  let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+                  .createInstance(Ci.nsITLSServerSocket);
+  tlsServer.init(-1, true, -1);
+  tlsServer.serverCert = cert;
+
+  let input, output;
+
+  let listener = {
+    onSocketAccepted: function(socket, transport) {
+      do_print("Accept TLS client connection");
+      let connectionInfo = transport.securityInfo
+                           .QueryInterface(Ci.nsITLSServerConnectionInfo);
+      connectionInfo.setSecurityObserver(listener);
+      input = transport.openInputStream(0, 0, 0);
+      output = transport.openOutputStream(0, 0, 0);
+    },
+    onHandshakeDone: function(socket, status) {
+      do_print("TLS handshake done");
+      ok(!!status.peerCert, "Has peer cert");
+      ok(status.peerCert.equals(cert), "Peer cert matches expected cert");
+
+      equal(status.tlsVersionUsed, Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+            "Using TLS 1.2");
+      equal(status.cipherName, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+            "Using expected cipher");
+      equal(status.keyLength, 128, "Using 128-bit key");
+      equal(status.macLength, 128, "Using 128-bit MAC");
+
+      input.asyncWait({
+        onInputStreamReady: function(input) {
+          NetUtil.asyncCopy(input, output);
+        }
+      }, 0, 0, Services.tm.currentThread);
+    },
+    onStopListening: function() {}
+  };
+
+  tlsServer.setSessionCache(false);
+  tlsServer.setSessionTickets(false);
+  tlsServer.setRequestClientCertificate(Ci.nsITLSServerSocket.REQUIRE_ALWAYS);
+
+  tlsServer.asyncListen(listener);
+
+  return tlsServer.port;
+}
+
+function storeCertOverride(port, cert) {
+  let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                     Ci.nsICertOverrideService.ERROR_MISMATCH;
+  certOverrideService.rememberValidityOverride("127.0.0.1", port, cert,
+                                               overrideBits, true);
+}
+
+function startClient(port, cert) {
+  let transport =
+    socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
+  let input;
+  let output;
+
+  let inputDeferred = promise.defer();
+  let outputDeferred = promise.defer();
+
+  let handler = {
+
+    onTransportStatus: function(transport, status) {
+      if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+        output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+      }
+    },
+
+    onInputStreamReady: function(input) {
+      try {
+        let data = NetUtil.readInputStreamToString(input, input.available());
+        equal(data, "HELLO", "Echoed data received");
+        input.close();
+        output.close();
+        inputDeferred.resolve();
+      } catch (e) {
+        let SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
+        let SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
+        let errorCode = -1 * (e.result & 0xFFFF);
+        if (errorCode == SEC_ERROR_UNKNOWN_ISSUER) {
+          do_print("Client doesn't like server cert");
+        }
+        inputDeferred.reject(e);
+      }
+    },
+
+    onOutputStreamReady: function(output) {
+      try {
+        // Set the cert we want to avoid any cert UI prompts
+        let clientSecInfo = transport.securityInfo;
+        let tlsControl = clientSecInfo.QueryInterface(Ci.nsISSLSocketControl);
+        tlsControl.clientCert = cert;
+
+        output.write("HELLO", 5);
+        do_print("Output to server written");
+        outputDeferred.resolve();
+        input = transport.openInputStream(0, 0, 0);
+        input.asyncWait(handler, 0, 0, Services.tm.currentThread);
+      } catch (e) {
+        let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
+        let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
+        let errorCode = -1 * (e.result & 0xFFFF);
+        if (errorCode == SSL_ERROR_BAD_CERT_ALERT) {
+          do_print("Server doesn't like client cert");
+        }
+        outputDeferred.reject(e);
+      }
+    }
+
+  };
+
+  transport.setEventSink(handler, Services.tm.currentThread);
+  output = transport.openOutputStream(0, 0, 0);
+
+  return promise.all([inputDeferred.promise, outputDeferred.promise]);
+}
+
+add_task(function*() {
+  let cert = yield getCert();
+  ok(!!cert, "Got self-signed cert");
+  let port = startServer(cert);
+  storeCertOverride(port, cert);
+  yield startClient(port, cert);
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -294,8 +294,11 @@ skip-if = os == "android"
 # the wild.
 skip-if = os == "android"
 [test_signature_extraction.js]
 run-if = os == "win"
 [test_udp_multicast.js]
 [test_redirect_history.js]
 [test_reply_without_content_type.js]
 [test_websocket_offline.js]
+[test_tls_server.js]
+# The local cert service used by this test is not currently shipped on Android
+skip-if = os == "android"