Bug 242448 - Add a basic scriptable TLS server. r=honzab

This commit is contained in:
J. Ryan Stinnett 2014-09-19 17:25:00 +02:00
parent b051c1fc40
commit 497d1586b4
13 changed files with 999 additions and 24 deletions

View File

@ -108,6 +108,7 @@ XPIDL_SOURCES += [
'nsIThreadRetargetableRequest.idl',
'nsIThreadRetargetableStreamListener.idl',
'nsITimedChannel.idl',
'nsITLSServerSocket.idl',
'nsITraceableChannel.idl',
'nsITransport.idl',
'nsIUDPSocket.idl',

View File

@ -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);
};

View File

@ -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

View File

@ -0,0 +1,81 @@
/* 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 "nsString.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

View File

@ -77,6 +77,7 @@ UNIFIED_SOURCES += [
'RedirectChannelRegistrar.cpp',
'StreamingProtocolService.cpp',
'Tickler.cpp',
'TLSServerSocket.cpp',
]
# These files cannot be built in unified mode because they force NSPR logging.

View File

@ -45,8 +45,8 @@ PostEvent(nsServerSocket *s, nsServerSocketFunc func)
//-----------------------------------------------------------------------------
nsServerSocket::nsServerSocket()
: mLock("nsServerSocket.mLock")
, mFD(nullptr)
: mFD(nullptr)
, mLock("nsServerSocket.mLock")
, mAttached(false)
, mKeepWhenOffline(false)
{
@ -154,6 +154,25 @@ nsServerSocket::TryAttach()
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
//-----------------------------------------------------------------------------
@ -185,25 +204,14 @@ nsServerSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
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
@ -328,6 +336,7 @@ NS_IMETHODIMP
nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
{
NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
//
// configure listening socket...
@ -373,12 +382,18 @@ nsServerSocket::InitWithAddress(const PRNetAddr *aAddr, int32_t aBackLog)
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;
}
@ -509,6 +524,13 @@ nsServerSocket::AsyncListen(nsIServerSocketListener *aListener)
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);
}

View File

@ -6,12 +6,18 @@
#ifndef nsServerSocket_h__
#define nsServerSocket_h__
#include "prio.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
{
@ -29,20 +35,26 @@ public:
virtual uint64_t ByteCountReceived() { return 0; }
nsServerSocket();
private:
virtual ~nsServerSocket();
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;

View File

@ -942,6 +942,15 @@ nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr)
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)
{

View File

@ -131,6 +131,12 @@ public:
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.

View File

@ -335,6 +335,17 @@
{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"

View File

@ -75,6 +75,10 @@ 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)
@ -665,6 +669,7 @@ 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);
@ -804,6 +809,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
{ &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 },
@ -950,6 +956,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
{ 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 },

View File

@ -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);
});

View File

@ -299,3 +299,6 @@ run-if = os == "win"
[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"