summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2014-08-25 10:31:54 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2014-08-25 10:31:54 +0700
commit24f37b51eafe7cbbf42b633be133ecca3abcb46e (patch)
tree6ee7123e825d546ec7c53225620eb3d962b20c9f
parentFix NAnt build issue (diff)
downloadBouncyCastle.NET-ed25519-24f37b51eafe7cbbf42b633be133ecca3abcb46e.tar.xz
Port TLS server impl/test from Java API
-rw-r--r--crypto/crypto.csproj15
-rw-r--r--crypto/src/crypto/tls/TlsProtocol.cs4
-rw-r--r--crypto/src/crypto/tls/TlsServerProtocol.cs744
-rw-r--r--crypto/test/src/crypto/tls/test/MockTlsServer.cs137
-rw-r--r--crypto/test/src/crypto/tls/test/TlsServerTest.cs78
5 files changed, 976 insertions, 2 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index cb4710669..d65021a68 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -4874,6 +4874,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsServerProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsSession.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -10916,11 +10921,21 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\crypto\tls\test\MockTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\crypto\tls\test\TlsClientTest.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\crypto\tls\test\TlsServerTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\crypto\tls\test\TlsTestUtilities.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs
index 2e2ef9214..8ba156952 100644
--- a/crypto/src/crypto/tls/TlsProtocol.cs
+++ b/crypto/src/crypto/tls/TlsProtocol.cs
@@ -533,7 +533,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
         }
 
-        protected void WriteHandshakeMessage(byte[] buf, int off, int len)
+        protected virtual void WriteHandshakeMessage(byte[] buf, int off, int len)
         {
             while (len > 0)
             {
@@ -698,7 +698,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             message.WriteToRecordStream(this);
         }
 
-        protected byte[] CreateVerifyData(bool isServer)
+        protected virtual byte[] CreateVerifyData(bool isServer)
         {
             TlsContext context = Context;
             string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished;
diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs
new file mode 100644
index 000000000..589ede802
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsServerProtocol.cs
@@ -0,0 +1,744 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class TlsServerProtocol
+        :   TlsProtocol
+    {
+        protected TlsServer mTlsServer = null;
+        internal TlsServerContextImpl mTlsServerContext = null;
+
+        protected TlsKeyExchange mKeyExchange = null;
+        protected TlsCredentials mServerCredentials = null;
+        protected CertificateRequest mCertificateRequest = null;
+
+        protected short mClientCertificateType = -1;
+        protected TlsHandshakeHash mPrepareFinishHash = null;
+
+        public TlsServerProtocol(Stream stream, SecureRandom secureRandom)
+            :   base(stream, secureRandom)
+        {
+        }
+
+        public TlsServerProtocol(Stream input, Stream output, SecureRandom secureRandom)
+            :   base(input, output, secureRandom)
+        {
+        }
+
+        /**
+         * Receives a TLS handshake in the role of server
+         *
+         * @param mTlsServer
+         * @throws IOException If handshake was not successful.
+         */
+        public virtual void Accept(TlsServer tlsServer)
+        {
+            if (tlsServer == null)
+                throw new ArgumentNullException("tlsServer");
+            if (this.mTlsServer != null)
+                throw new InvalidOperationException("'Accept' can only be called once");
+
+            this.mTlsServer = tlsServer;
+
+            this.mSecurityParameters = new SecurityParameters();
+            this.mSecurityParameters.entity = ConnectionEnd.server;
+
+            this.mTlsServerContext = new TlsServerContextImpl(mSecureRandom, mSecurityParameters);
+
+            this.mSecurityParameters.serverRandom = CreateRandomBlock(tlsServer.ShouldUseGmtUnixTime(),
+                mTlsServerContext.NonceRandomGenerator);
+
+            this.mTlsServer.Init(mTlsServerContext);
+            this.mRecordStream.Init(mTlsServerContext);
+
+            this.mRecordStream.SetRestrictReadVersion(false);
+
+            CompleteHandshake();
+        }
+
+        protected override void CleanupHandshake()
+        {
+            base.CleanupHandshake();
+        
+            this.mKeyExchange = null;
+            this.mServerCredentials = null;
+            this.mCertificateRequest = null;
+            this.mPrepareFinishHash = null;
+        }
+
+        protected override TlsContext Context
+        {
+            get { return mTlsServerContext; }
+        }
+
+        internal override AbstractTlsContext ContextAdmin
+        {
+            get { return mTlsServerContext; }
+        }
+
+        protected override TlsPeer Peer
+        {
+            get { return mTlsServer; }
+        }
+
+        protected override void HandleHandshakeMessage(byte type, byte[] data)
+        {
+            MemoryStream buf = new MemoryStream(data);
+
+            switch (type)
+            {
+            case HandshakeType.client_hello:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_START:
+                {
+                    ReceiveClientHelloMessage(buf);
+                    this.mConnectionState = CS_CLIENT_HELLO;
+
+                    SendServerHelloMessage();
+                    this.mConnectionState = CS_SERVER_HELLO;
+
+                    IList serverSupplementalData = mTlsServer.GetServerSupplementalData();
+                    if (serverSupplementalData != null)
+                    {
+                        SendSupplementalDataMessage(serverSupplementalData);
+                    }
+                    this.mConnectionState = CS_SERVER_SUPPLEMENTAL_DATA;
+
+                    this.mKeyExchange = mTlsServer.GetKeyExchange();
+                    this.mKeyExchange.Init(Context);
+
+                    this.mServerCredentials = mTlsServer.GetCredentials();
+
+                    Certificate serverCertificate = null;
+
+                    if (this.mServerCredentials == null)
+                    {
+                        this.mKeyExchange.SkipServerCredentials();
+                    }
+                    else
+                    {
+                        this.mKeyExchange.ProcessServerCredentials(this.mServerCredentials);
+
+                        serverCertificate = this.mServerCredentials.Certificate;
+                        SendCertificateMessage(serverCertificate);
+                    }
+                    this.mConnectionState = CS_SERVER_CERTIFICATE;
+
+                    // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+                    if (serverCertificate == null || serverCertificate.IsEmpty)
+                    {
+                        this.mAllowCertificateStatus = false;
+                    }
+
+                    if (this.mAllowCertificateStatus)
+                    {
+                        CertificateStatus certificateStatus = mTlsServer.GetCertificateStatus();
+                        if (certificateStatus != null)
+                        {
+                            SendCertificateStatusMessage(certificateStatus);
+                        }
+                    }
+
+                    this.mConnectionState = CS_CERTIFICATE_STATUS;
+
+                    byte[] serverKeyExchange = this.mKeyExchange.GenerateServerKeyExchange();
+                    if (serverKeyExchange != null)
+                    {
+                        SendServerKeyExchangeMessage(serverKeyExchange);
+                    }
+                    this.mConnectionState = CS_SERVER_KEY_EXCHANGE;
+
+                    if (this.mServerCredentials != null)
+                    {
+                        this.mCertificateRequest = mTlsServer.GetCertificateRequest();
+                        if (this.mCertificateRequest != null)
+                        {
+                            this.mKeyExchange.ValidateCertificateRequest(mCertificateRequest);
+
+                            SendCertificateRequestMessage(mCertificateRequest);
+
+                            TlsUtilities.TrackHashAlgorithms(this.mRecordStream.HandshakeHash,
+                                this.mCertificateRequest.SupportedSignatureAlgorithms);
+                        }
+                    }
+                    this.mConnectionState = CS_CERTIFICATE_REQUEST;
+
+                    SendServerHelloDoneMessage();
+                    this.mConnectionState = CS_SERVER_HELLO_DONE;
+
+                    this.mRecordStream.HandshakeHash.SealHashAlgorithms();
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.supplemental_data:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                {
+                    mTlsServer.ProcessClientSupplementalData(ReadSupplementalDataMessage(buf));
+                    this.mConnectionState = CS_CLIENT_SUPPLEMENTAL_DATA;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                {
+                    if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA)
+                    {
+                        mTlsServer.ProcessClientSupplementalData(null);
+                    }
+
+                    if (this.mCertificateRequest == null)
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    ReceiveCertificateMessage(buf);
+                    this.mConnectionState = CS_CLIENT_CERTIFICATE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.client_key_exchange:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                case CS_CLIENT_CERTIFICATE:
+                {
+                    if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA)
+                    {
+                        mTlsServer.ProcessClientSupplementalData(null);
+                    }
+
+                    if (mConnectionState < CS_CLIENT_CERTIFICATE)
+                    {
+                        if (this.mCertificateRequest == null)
+                        {
+                            this.mKeyExchange.SkipClientCredentials();
+                        }
+                        else
+                        {
+                            if (TlsUtilities.IsTlsV12(Context))
+                            {
+                                /*
+                                 * RFC 5246 If no suitable certificate is available, the client MUST Send a
+                                 * certificate message containing no certificates.
+                                 * 
+                                 * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                                 */
+                                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                            }
+                            else if (TlsUtilities.IsSsl(Context))
+                            {
+                                if (this.mPeerCertificate == null)
+                                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                            }
+                            else
+                            {
+                                NotifyClientCertificate(Certificate.EmptyChain);
+                            }
+                        }
+                    }
+
+                    ReceiveClientKeyExchangeMessage(buf);
+                    this.mConnectionState = CS_CLIENT_KEY_EXCHANGE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_verify:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_CLIENT_KEY_EXCHANGE:
+                {
+                    /*
+                     * RFC 5246 7.4.8 This message is only sent following a client certificate that has
+                     * signing capability (i.e., all certificates except those containing fixed
+                     * Diffie-Hellman parameters).
+                     */
+                    if (!ExpectCertificateVerifyMessage())
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    ReceiveCertificateVerifyMessage(buf);
+                    this.mConnectionState = CS_CERTIFICATE_VERIFY;
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_CLIENT_KEY_EXCHANGE:
+                case CS_CERTIFICATE_VERIFY:
+                {
+                    if (mConnectionState < CS_CERTIFICATE_VERIFY && ExpectCertificateVerifyMessage())
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    ProcessFinishedMessage(buf);
+                    this.mConnectionState = CS_CLIENT_FINISHED;
+
+                    if (this.mExpectSessionTicket)
+                    {
+                        SendNewSessionTicketMessage(mTlsServer.GetNewSessionTicket());
+                        SendChangeCipherSpecMessage();
+                    }
+                    this.mConnectionState = CS_SERVER_SESSION_TICKET;
+
+                    SendFinishedMessage();
+                    this.mConnectionState = CS_SERVER_FINISHED;
+                    this.mConnectionState = CS_END;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.server_hello:
+            case HandshakeType.server_key_exchange:
+            case HandshakeType.certificate_request:
+            case HandshakeType.server_hello_done:
+            case HandshakeType.session_ticket:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected override void HandleWarningMessage(byte description)
+        {
+            switch (description)
+            {
+            case AlertDescription.no_certificate:
+            {
+                /*
+                 * SSL 3.0 If the server has sent a certificate request Message, the client must Send
+                 * either the certificate message or a no_certificate alert.
+                 */
+                if (TlsUtilities.IsSsl(Context) && mCertificateRequest != null)
+                {
+                    NotifyClientCertificate(Certificate.EmptyChain);
+                }
+                break;
+            }
+            default:
+            {
+                base.HandleWarningMessage(description);
+                break;
+            }
+            }
+        }
+
+        protected virtual void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            if (mCertificateRequest == null)
+                throw new InvalidOperationException();
+            if (mPeerCertificate != null)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            this.mPeerCertificate = clientCertificate;
+
+            if (clientCertificate.IsEmpty)
+            {
+                this.mKeyExchange.SkipClientCredentials();
+            }
+            else
+            {
+
+                /*
+                 * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request
+                 * message was non-empty, one of the certificates in the certificate chain SHOULD be
+                 * issued by one of the listed CAs.
+                 */
+
+                this.mClientCertificateType = TlsUtilities.GetClientCertificateType(clientCertificate,
+                    this.mServerCredentials.Certificate);
+
+                this.mKeyExchange.ProcessClientCertificate(clientCertificate);
+            }
+
+            /*
+             * RFC 5246 7.4.6. If the client does not Send any certificates, the server MAY at its
+             * discretion either continue the handshake without client authentication, or respond with a
+             * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+             * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+             * discretion either continue the handshake (considering the client unauthenticated) or Send
+             * a fatal alert.
+             */
+            this.mTlsServer.NotifyClientCertificate(clientCertificate);
+        }
+
+        protected virtual void ReceiveCertificateMessage(MemoryStream buf)
+        {
+            Certificate clientCertificate = Certificate.Parse(buf);
+
+            AssertEmpty(buf);
+
+            NotifyClientCertificate(clientCertificate);
+        }
+
+        protected virtual void ReceiveCertificateVerifyMessage(MemoryStream buf)
+        {
+            DigitallySigned clientCertificateVerify = DigitallySigned.Parse(Context, buf);
+
+            AssertEmpty(buf);
+
+            // Verify the CertificateVerify message contains a correct signature.
+            try
+            {
+                byte[] certificateVerifyHash;
+                if (TlsUtilities.IsTlsV12(Context))
+                {
+                    certificateVerifyHash = mPrepareFinishHash.GetFinalHash(clientCertificateVerify.Algorithm.Hash);
+                }
+                else
+                {
+                    certificateVerifyHash = TlsProtocol.GetCurrentPrfHash(Context, mPrepareFinishHash, null);
+                }
+
+                X509CertificateStructure x509Cert = mPeerCertificate.GetCertificateAt(0);
+                SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo;
+                AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo);
+
+                TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)mClientCertificateType);
+                tlsSigner.Init(Context);
+                if (!tlsSigner.VerifyRawSignature(clientCertificateVerify.Algorithm,
+                    clientCertificateVerify.Signature, publicKey, certificateVerifyHash))
+                {
+                    throw new TlsFatalAlert(AlertDescription.decrypt_error);
+                }
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error, e);
+            }
+        }
+
+        protected virtual void ReceiveClientHelloMessage(MemoryStream buf)
+        {
+            ProtocolVersion client_version = TlsUtilities.ReadVersion(buf);
+            if (client_version.IsDtls)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            byte[] client_random = TlsUtilities.ReadFully(32, buf);
+
+            /*
+             * TODO RFC 5077 3.4. If a ticket is presented by the client, the server MUST NOT attempt to
+             * use the Session ID in the ClientHello for stateful session resumption.
+             */
+            byte[] sessionID = TlsUtilities.ReadOpaque8(buf);
+            if (sessionID.Length > 32)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            /*
+             * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session
+             * resumption request), this vector MUST include at least the cipher_suite from that
+             * session.
+             */
+            int cipher_suites_length = TlsUtilities.ReadUint16(buf);
+            if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            this.mOfferedCipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, buf);
+
+            /*
+             * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session
+             * resumption request), it MUST include the compression_method from that session.
+             */
+            int compression_methods_length = TlsUtilities.ReadUint8(buf);
+            if (compression_methods_length < 1)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.mOfferedCompressionMethods = TlsUtilities.ReadUint8Array(compression_methods_length, buf);
+
+            /*
+             * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+             * extensions appearing in the client hello, and Send a server hello containing no
+             * extensions.
+             */
+            this.mClientExtensions = ReadExtensions(buf);
+
+            ContextAdmin.SetClientVersion(client_version);
+
+            mTlsServer.NotifyClientVersion(client_version);
+
+            mSecurityParameters.clientRandom = client_random;
+
+            mTlsServer.NotifyOfferedCipherSuites(mOfferedCipherSuites);
+            mTlsServer.NotifyOfferedCompressionMethods(mOfferedCompressionMethods);
+
+            /*
+             * RFC 5746 3.6. Server Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+                 * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+                 * ClientHello. Including both is NOT RECOMMENDED.
+                 */
+
+                /*
+                 * When a ClientHello is received, the server MUST check if it includes the
+                 * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag
+                 * to TRUE.
+                 */
+                if (Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+                {
+                    this.mSecureRenegotiation = true;
+                }
+
+                /*
+                 * The server MUST check if the "renegotiation_info" extension is included in the
+                 * ClientHello.
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, ExtensionType.renegotiation_info);
+                if (renegExtData != null)
+                {
+                    /*
+                     * If the extension is present, set secure_renegotiation flag to TRUE. The
+                     * server MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake.
+                     */
+                    this.mSecureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(renegExtData, CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+            }
+
+            mTlsServer.NotifySecureRenegotiation(this.mSecureRenegotiation);
+
+            if (mClientExtensions != null)
+            {
+                mTlsServer.ProcessClientExtensions(mClientExtensions);
+            }
+        }
+
+        protected virtual void ReceiveClientKeyExchangeMessage(MemoryStream buf)
+        {
+            mKeyExchange.ProcessClientKeyExchange(buf);
+
+            AssertEmpty(buf);
+
+            EstablishMasterSecret(Context, mKeyExchange);
+            mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher());
+
+            this.mPrepareFinishHash = mRecordStream.PrepareToFinish();
+
+            if (!mExpectSessionTicket)
+            {
+                SendChangeCipherSpecMessage();
+            }
+        }
+
+        protected virtual void SendCertificateRequestMessage(CertificateRequest certificateRequest)
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_request);
+
+            certificateRequest.Encode(message);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendCertificateStatusMessage(CertificateStatus certificateStatus)
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_status);
+
+            certificateStatus.Encode(message);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendNewSessionTicketMessage(NewSessionTicket newSessionTicket)
+        {
+            if (newSessionTicket == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.session_ticket);
+
+            newSessionTicket.Encode(message);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendServerHelloMessage()
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.server_hello);
+
+            ProtocolVersion server_version = mTlsServer.GetServerVersion();
+            if (!server_version.IsEqualOrEarlierVersionOf(Context.ClientVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            mRecordStream.ReadVersion = server_version;
+            mRecordStream.SetWriteVersion(server_version);
+            mRecordStream.SetRestrictReadVersion(true);
+            ContextAdmin.SetServerVersion(server_version);
+
+            TlsUtilities.WriteVersion(server_version, message);
+
+            message.Write(this.mSecurityParameters.serverRandom);
+
+            /*
+             * The server may return an empty session_id to indicate that the session will not be cached
+             * and therefore cannot be resumed.
+             */
+            TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, message);
+
+            int selectedCipherSuite = mTlsServer.GetSelectedCipherSuite();
+            if (!Arrays.Contains(mOfferedCipherSuites, selectedCipherSuite)
+                || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+                || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+                || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+            mSecurityParameters.cipherSuite = selectedCipherSuite;
+
+            byte selectedCompressionMethod = mTlsServer.GetSelectedCompressionMethod();
+            if (!Arrays.Contains(mOfferedCompressionMethods, selectedCompressionMethod))
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+            mSecurityParameters.compressionAlgorithm = selectedCompressionMethod;
+
+            TlsUtilities.WriteUint16(selectedCipherSuite, message);
+            TlsUtilities.WriteUint8(selectedCompressionMethod, message);
+
+            this.mServerExtensions = mTlsServer.GetServerExtensions();
+
+            /*
+             * RFC 5746 3.6. Server Behavior: Initial Handshake
+             */
+            if (this.mSecureRenegotiation)
+            {
+                byte[] renegExtData = TlsUtilities.GetExtensionData(this.mServerExtensions, ExtensionType.renegotiation_info);
+                bool noRenegExt = (null == renegExtData);
+
+                if (noRenegExt)
+                {
+                    /*
+                     * Note that Sending a "renegotiation_info" extension in response to a ClientHello
+                     * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+                     * Section 7.4.1.4, on the server Sending unsolicited extensions and is only allowed
+                     * because the client is signaling its willingness to receive the extension via the
+                     * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                     */
+
+                    /*
+                     * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+                     * "renegotiation_info" extension in the ServerHello message.
+                     */
+                    this.mServerExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(mServerExtensions);
+                    this.mServerExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(TlsUtilities.EmptyBytes);
+                }
+            }
+
+            /*
+             * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+             * extensions appearing in the client hello, and Send a server hello containing no
+             * extensions.
+             */
+
+            if (this.mServerExtensions != null)
+            {
+                this.mSecurityParameters.encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(mServerExtensions);
+
+                this.mSecurityParameters.maxFragmentLength = ProcessMaxFragmentLengthExtension(mClientExtensions,
+                    mServerExtensions, AlertDescription.internal_error);
+
+                this.mSecurityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(mServerExtensions);
+
+                /*
+                 * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in
+                 * a session resumption handshake.
+                 */
+                this.mAllowCertificateStatus = !mResumedSession
+                    && TlsUtilities.HasExpectedEmptyExtensionData(mServerExtensions, ExtensionType.status_request,
+                        AlertDescription.internal_error);
+
+                this.mExpectSessionTicket = !mResumedSession
+                    && TlsUtilities.HasExpectedEmptyExtensionData(mServerExtensions, ExtensionType.session_ticket,
+                        AlertDescription.internal_error);
+
+                WriteExtensions(message, this.mServerExtensions);
+            }
+
+            if (mSecurityParameters.maxFragmentLength >= 0)
+            {
+                int plainTextLimit = 1 << (8 + mSecurityParameters.maxFragmentLength);
+                mRecordStream.SetPlaintextLimit(plainTextLimit);
+            }
+
+            mSecurityParameters.prfAlgorithm = GetPrfAlgorithm(Context, mSecurityParameters.CipherSuite);
+
+            /*
+             * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has
+             * a verify_data_length equal to 12. This includes all existing cipher suites.
+             */
+            mSecurityParameters.verifyDataLength = 12;
+
+            message.WriteToRecordStream(this);
+
+            this.mRecordStream.NotifyHelloComplete();
+        }
+
+        protected virtual void SendServerHelloDoneMessage()
+        {
+            byte[] message = new byte[4];
+            TlsUtilities.WriteUint8(HandshakeType.server_hello_done, message, 0);
+            TlsUtilities.WriteUint24(0, message, 1);
+
+            WriteHandshakeMessage(message, 0, message.Length);
+        }
+
+        protected virtual void SendServerKeyExchangeMessage(byte[] serverKeyExchange)
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.server_key_exchange, serverKeyExchange.Length);
+
+            message.Write(serverKeyExchange);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual bool ExpectCertificateVerifyMessage()
+        {
+            return mClientCertificateType >= 0 && TlsUtilities.HasSigningCapability((byte)mClientCertificateType);
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/tls/test/MockTlsServer.cs b/crypto/test/src/crypto/tls/test/MockTlsServer.cs
new file mode 100644
index 000000000..54a645d96
--- /dev/null
+++ b/crypto/test/src/crypto/tls/test/MockTlsServer.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls.Tests
+{
+    internal class MockTlsServer
+        :   DefaultTlsServer
+    {
+        public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+                + ")");
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(byte alertLevel, byte alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
+                + alertDescription + ")");
+        }
+
+        protected override int[] GetCipherSuites()
+        {
+            return Arrays.Concatenate(base.GetCipherSuites(),
+                new int[]
+                {
+                    CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+                    CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1,
+                    CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1,
+                    CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1,
+                    CipherSuite.TLS_RSA_WITH_SALSA20_SHA1,
+                });
+        }
+
+        protected override ProtocolVersion MaximumVersion
+        {
+            get { return ProtocolVersion.TLSv12; }
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            IList serverSigAlgs = null;
+
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(mServerVersion))
+            {
+                byte[] hashAlgorithms = new byte[]{ HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256,
+                    HashAlgorithm.sha224, HashAlgorithm.sha1 };
+                byte[] signatureAlgorithms = new byte[]{ SignatureAlgorithm.rsa };
+
+                serverSigAlgs = new ArrayList();
+                for (int i = 0; i < hashAlgorithms.Length; ++i)
+                {
+                    for (int j = 0; j < signatureAlgorithms.Length; ++j)
+                    {
+                        serverSigAlgs.Add(new SignatureAndHashAlgorithm(hashAlgorithms[i],
+                            signatureAlgorithms[j]));
+                    }
+                }
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            certificateAuthorities.Add(TlsTestUtilities.LoadCertificateResource("x509-ca.pem").Subject);
+
+            return new CertificateRequest(new byte[]{ ClientCertificateType.rsa_sign }, serverSigAlgs, certificateAuthorities);
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            X509CertificateStructure[] chain = clientCertificate.GetCertificateList();
+            Console.WriteLine("TLS server received client certificate chain of length " + chain.Length);
+            for (int i = 0; i != chain.Length; i++)
+            {
+                X509CertificateStructure entry = chain[i];
+                // TODO Create fingerprint based on certificate signature algorithm digest
+                Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                    + entry.Subject + ")");
+            }
+        }
+
+        protected override TlsEncryptionCredentials GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(mContext, new string[]{"x509-server.pem", "x509-ca.pem"},
+                "x509-server-key.pem");
+        }
+
+        protected override TlsSignerCredentials GetRsaSignerCredentials()
+        {
+            /*
+             * TODO Note that this code fails to provide default value for the client supported
+             * algorithms if it wasn't sent.
+             */
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = null;
+            IList sigAlgs = mSupportedSignatureAlgorithms;
+            if (sigAlgs != null)
+            {
+                foreach (SignatureAndHashAlgorithm sigAlg in sigAlgs)
+                {
+                    if (sigAlg.Signature == SignatureAlgorithm.rsa)
+                    {
+                        signatureAndHashAlgorithm = sigAlg;
+                        break;
+                    }
+                }
+
+                if (signatureAndHashAlgorithm == null)
+                {
+                    return null;
+                }
+            }
+
+            return TlsTestUtilities.LoadSignerCredentials(mContext, new string[]{"x509-server.pem", "x509-ca.pem"},
+                "x509-server-key.pem", signatureAndHashAlgorithm);
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/tls/test/TlsServerTest.cs b/crypto/test/src/crypto/tls/test/TlsServerTest.cs
new file mode 100644
index 000000000..77adf22f7
--- /dev/null
+++ b/crypto/test/src/crypto/tls/test/TlsServerTest.cs
@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Crypto.Tls.Tests
+{
+    /**
+     * A simple test designed to conduct a TLS handshake with an external TLS client.
+     * <p/>
+     * Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in
+     * this package (under 'src/test/resources') for help configuring an external TLS client.
+     */
+    public class TlsServerTest
+    {
+        private static readonly SecureRandom secureRandom = new SecureRandom();
+
+        public static void Main(string[] args)
+        {
+            int port = 5556;
+
+            TcpListener ss = new TcpListener(IPAddress.Any, port);
+            ss.Start();
+            Stream stdout = Console.OpenStandardOutput();
+            while (true)
+            {
+                TcpClient s = ss.AcceptTcpClient();
+                Console.WriteLine("--------------------------------------------------------------------------------");
+                Console.WriteLine("Accepted " + s);
+                ServerThread st = new ServerThread(s, stdout);
+                Thread t = new Thread(new ThreadStart(st.Run));
+                t.Start();
+            }
+        }
+
+        internal class ServerThread
+        {
+            private readonly TcpClient s;
+            private readonly Stream stdout;
+
+            internal ServerThread(TcpClient s, Stream stdout)
+            {
+                this.s = s;
+                this.stdout = stdout;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockTlsServer server = new MockTlsServer();
+                    TlsServerProtocol serverProtocol = new TlsServerProtocol(s.GetStream(), secureRandom);
+                    serverProtocol.Accept(server);
+                    Stream log = new TeeOutputStream(serverProtocol.Stream, stdout);
+                    Streams.PipeAll(serverProtocol.Stream, log);
+                    serverProtocol.Close();
+                }
+                finally
+                {
+                    try
+                    {
+                        s.Close();
+                    }
+                    catch (IOException)
+                    {
+                    }
+                    finally
+                    {
+                    }
+                }
+            }
+        }
+    }
+}