summary refs log tree commit diff
path: root/crypto/src/tls/TlsServerProtocol.cs
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2021-07-12 15:15:36 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2021-07-12 15:15:36 +0700
commit68c795fe81277f73aeb90d8ad4c6f4305f32c906 (patch)
tree59643344aafef91bbd4c4a3a7973deba3d837a00 /crypto/src/tls/TlsServerProtocol.cs
parentTLS test tweaks (diff)
downloadBouncyCastle.NET-ed25519-68c795fe81277f73aeb90d8ad4c6f4305f32c906.tar.xz
Port of new TLS API from bc-java
Diffstat (limited to 'crypto/src/tls/TlsServerProtocol.cs')
-rw-r--r--crypto/src/tls/TlsServerProtocol.cs1471
1 files changed, 1471 insertions, 0 deletions
diff --git a/crypto/src/tls/TlsServerProtocol.cs b/crypto/src/tls/TlsServerProtocol.cs
new file mode 100644
index 000000000..a7e0e0120
--- /dev/null
+++ b/crypto/src/tls/TlsServerProtocol.cs
@@ -0,0 +1,1471 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsServerProtocol
+        : TlsProtocol
+    {
+        protected TlsServer m_tlsServer = null;
+        internal TlsServerContextImpl m_tlsServerContext = null;
+
+        protected int[] m_offeredCipherSuites = null;
+        protected TlsKeyExchange m_keyExchange = null;
+        protected CertificateRequest m_certificateRequest = null;
+
+        /// <summary>Constructor for non-blocking mode.</summary>
+        /// <remarks>
+        /// When data is received, use <see cref="TlsProtocol.OfferInput(byte[])"/> to provide the received ciphertext,
+        /// then use <see cref="TlsProtocol.ReadInput(byte[],int,int)"/> to read the corresponding cleartext.<br/><br/>
+        /// Similarly, when data needs to be sent, use <see cref="TlsProtocol.WriteApplicationData(byte[],int,int)"/>
+        /// to provide the cleartext, then use <see cref="TlsProtocol.ReadOutput(byte[],int,int)"/> to get the
+        /// corresponding ciphertext.
+        /// </remarks>
+        public TlsServerProtocol()
+            : base()
+        {
+        }
+
+        /// <summary>Constructor for blocking mode.</summary>
+        /// <param name="stream">The <see cref="Stream"/> of data to/from the server.</param>
+        public TlsServerProtocol(Stream stream)
+            : base(stream)
+        {
+        }
+
+        /// <summary>Constructor for blocking mode.</summary>
+        /// <param name="input">The <see cref="Stream"/> of data from the server.</param>
+        /// <param name="output">The <see cref="Stream"/> of data to the server.</param>
+        public TlsServerProtocol(Stream input, Stream output)
+            : base(input, output)
+        {
+        }
+
+        /// <summary>Receives a TLS handshake in the role of server.</summary>
+        /// <remarks>
+        /// In blocking mode, this will not return until the handshake is complete. In non-blocking mode, use
+        /// <see cref="TlsPeer.NotifyHandshakeComplete"/> to receive a callback when the handshake is complete.
+        /// </remarks>
+        /// <param name="tlsServer">The <see cref="TlsServer"/> to use for the handshake.</param>
+        /// <exception cref="IOException">If in blocking mode and handshake was not successful.</exception>
+        public void Accept(TlsServer tlsServer)
+        {
+            if (tlsServer == null)
+                throw new ArgumentNullException("tlsServer");
+            if (m_tlsServer != null)
+                throw new InvalidOperationException("'Accept' can only be called once");
+
+            this.m_tlsServer = tlsServer;
+            this.m_tlsServerContext = new TlsServerContextImpl(tlsServer.Crypto);
+
+            tlsServer.Init(m_tlsServerContext);
+            tlsServer.NotifyCloseHandle(this);
+
+            BeginHandshake();
+
+            if (m_blocking)
+            {
+                BlockForHandshake();
+            }
+        }
+
+        protected override void CleanupHandshake()
+        {
+            base.CleanupHandshake();
+
+            this.m_offeredCipherSuites = null;
+            this.m_keyExchange = null;
+            this.m_certificateRequest = null;
+        }
+
+        protected virtual bool ExpectCertificateVerifyMessage()
+        {
+            if (null == m_certificateRequest)
+                return false;
+
+            Certificate clientCertificate = m_tlsServerContext.SecurityParameters.PeerCertificate;
+
+            return null != clientCertificate && !clientCertificate.IsEmpty
+                && (null == m_keyExchange || m_keyExchange.RequiresCertificateVerify);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello Generate13HelloRetryRequest(ClientHello clientHello)
+        {
+            // TODO[tls13] In future there might be other reasons for a HelloRetryRequest.
+            if (m_retryGroup < 0)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+            ProtocolVersion serverVersion = securityParameters.NegotiatedVersion;
+
+            IDictionary serverHelloExtensions = Platform.CreateHashtable();
+            TlsExtensionsUtilities.AddSupportedVersionsExtensionServer(serverHelloExtensions, serverVersion);
+            if (m_retryGroup >= 0)
+            {
+                TlsExtensionsUtilities.AddKeyShareHelloRetryRequest(serverHelloExtensions, m_retryGroup);
+            }
+            if (null != m_retryCookie)
+            {
+                TlsExtensionsUtilities.AddCookieExtension(serverHelloExtensions, m_retryCookie);
+            }
+
+            TlsUtilities.CheckExtensionData13(serverHelloExtensions, HandshakeType.hello_retry_request,
+                AlertDescription.internal_error);
+
+            return new ServerHello(clientHello.SessionID, securityParameters.CipherSuite, serverHelloExtensions);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello Generate13ServerHello(ClientHello clientHello, bool afterHelloRetryRequest)
+        {
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+
+            byte[] legacy_session_id = clientHello.SessionID;
+
+            IDictionary clientHelloExtensions = clientHello.Extensions;
+            if (null == clientHelloExtensions)
+                throw new TlsFatalAlert(AlertDescription.missing_extension);
+
+
+            ProtocolVersion serverVersion = securityParameters.NegotiatedVersion;
+            TlsCrypto crypto = m_tlsServerContext.Crypto;
+
+            IList clientShares = TlsExtensionsUtilities.GetKeyShareClientHello(clientHelloExtensions);
+            KeyShareEntry clientShare = null;
+
+            if (afterHelloRetryRequest)
+            {
+                if (m_retryGroup < 0)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                /*
+                 * TODO[tls13] Confirm fields in the ClientHello haven't changed
+                 * 
+                 * RFC 8446 4.1.2 [..] when the server has responded to its ClientHello with a
+                 * HelloRetryRequest [..] the client MUST send the same ClientHello without
+                 * modification, except as follows: [key_share, early_data, cookie, pre_shared_key,
+                 * padding].
+                 */
+
+                byte[] cookie = TlsExtensionsUtilities.GetCookieExtension(clientHelloExtensions);
+                if (!Arrays.AreEqual(m_retryCookie, cookie))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                this.m_retryCookie = null;
+
+                clientShare = TlsUtilities.SelectKeyShare(clientShares, m_retryGroup);
+                if (null == clientShare)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+            else
+            {
+                this.m_clientExtensions = clientHelloExtensions;
+
+                securityParameters.m_secureRenegotiation = false;
+
+                // NOTE: Validates the padding extension data, if present
+                TlsExtensionsUtilities.GetPaddingExtension(clientHelloExtensions);
+
+                securityParameters.m_clientServerNames = TlsExtensionsUtilities
+                    .GetServerNameExtensionClient(clientHelloExtensions);
+
+                TlsUtilities.EstablishClientSigAlgs(securityParameters, clientHelloExtensions);
+
+                /*
+                 * RFC 8446 4.2.3. If a server is authenticating via a certificate and the client has
+                 * not sent a "signature_algorithms" extension, then the server MUST abort the handshake
+                 * with a "missing_extension" alert.
+                 */
+                // TODO[tls13] Revisit this check if we add support for PSK-only key exchange.
+                if (null == securityParameters.ClientSigAlgs)
+                    throw new TlsFatalAlert(AlertDescription.missing_extension);
+
+                m_tlsServer.ProcessClientExtensions(clientHelloExtensions);
+
+                /*
+                 * NOTE: Currently no server support for session resumption
+                 * 
+                 * If adding support, ensure securityParameters.tlsUnique is set to the localVerifyData, but
+                 * ONLY when extended_master_secret has been negotiated (otherwise NULL).
+                 */
+                {
+                    // TODO[tls13] Resumption/PSK
+
+                    this.m_tlsSession = TlsUtilities.ImportSession(TlsUtilities.EmptyBytes, null);
+                    this.m_sessionParameters = null;
+                    this.m_sessionMasterSecret = null;
+                }
+
+                securityParameters.m_sessionID = m_tlsSession.SessionID;
+
+                m_tlsServer.NotifySession(m_tlsSession);
+
+                TlsUtilities.NegotiatedVersionTlsServer(m_tlsServerContext);
+
+                {
+                    securityParameters.m_serverRandom = CreateRandomBlock(false, m_tlsServerContext);
+
+                    if (!serverVersion.Equals(ProtocolVersion.GetLatestTls(m_tlsServer.GetProtocolVersions())))
+                    {
+                        TlsUtilities.WriteDowngradeMarker(serverVersion, securityParameters.ServerRandom);
+                    }
+                }
+
+                {
+                    int cipherSuite = m_tlsServer.GetSelectedCipherSuite();
+
+                    if (!TlsUtilities.IsValidCipherSuiteSelection(m_offeredCipherSuites, cipherSuite) ||
+                        !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, serverVersion))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                    }
+
+                    TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+                }
+
+                int[] clientSupportedGroups = securityParameters.ClientSupportedGroups;
+                int[] serverSupportedGroups = securityParameters.ServerSupportedGroups;
+
+                clientShare = TlsUtilities.SelectKeyShare(crypto, serverVersion, clientShares, clientSupportedGroups,
+                    serverSupportedGroups);
+
+                if (null == clientShare)
+                {
+                    this.m_retryGroup = TlsUtilities.SelectKeyShareGroup(crypto, serverVersion, clientSupportedGroups,
+                        serverSupportedGroups);
+                    if (m_retryGroup < 0)
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                    this.m_retryCookie = m_tlsServerContext.NonceGenerator.GenerateNonce(16);
+
+                    return Generate13HelloRetryRequest(clientHello);
+                }
+
+                if (clientShare.NamedGroup != serverSupportedGroups[0])
+                {
+                    /*
+                     * TODO[tls13] RFC 8446 4.2.7. As of TLS 1.3, servers are permitted to send the
+                     * "supported_groups" extension to the client. Clients MUST NOT act upon any
+                     * information found in "supported_groups" prior to successful completion of the
+                     * handshake but MAY use the information learned from a successfully completed
+                     * handshake to change what groups they use in their "key_share" extension in
+                     * subsequent connections. If the server has a group it prefers to the ones in the
+                     * "key_share" extension but is still willing to accept the ClientHello, it SHOULD
+                     * send "supported_groups" to update the client's view of its preferences; this
+                     * extension SHOULD contain all groups the server supports, regardless of whether
+                     * they are currently supported by the client.
+                     */
+                }
+            }
+
+
+            IDictionary serverHelloExtensions = Platform.CreateHashtable();
+            IDictionary serverEncryptedExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                m_tlsServer.GetServerExtensions());
+
+            m_tlsServer.GetServerExtensionsForConnection(serverEncryptedExtensions);
+
+            ProtocolVersion serverLegacyVersion = ProtocolVersion.TLSv12;
+            TlsExtensionsUtilities.AddSupportedVersionsExtensionServer(serverHelloExtensions, serverVersion);
+
+            /*
+             * RFC 8446 Appendix D. Because TLS 1.3 always hashes in the transcript up to the server
+             * Finished, implementations which support both TLS 1.3 and earlier versions SHOULD indicate
+             * the use of the Extended Master Secret extension in their APIs whenever TLS 1.3 is used.
+             */
+            securityParameters.m_extendedMasterSecret = true;
+
+            /*
+             * RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
+             * contents of this extension are irrelevant, and only the values in the new handshake
+             * messages are considered.
+             */
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(
+                serverEncryptedExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            if (serverEncryptedExtensions.Count > 0)
+            {
+                securityParameters.m_maxFragmentLength = ProcessMaxFragmentLengthExtension(clientHelloExtensions,
+                    serverEncryptedExtensions, AlertDescription.internal_error);
+            }
+
+            securityParameters.m_encryptThenMac = false;
+            securityParameters.m_truncatedHmac = false;
+
+            /*
+             * TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions.
+             * 
+             * OCSP information is carried in an extension for a CertificateEntry.
+             */
+            securityParameters.m_statusRequestVersion = clientHelloExtensions.Contains(ExtensionType.status_request)
+                ? 1 : 0;
+
+            this.m_expectSessionTicket = false;
+
+            {
+                int namedGroup = clientShare.NamedGroup;
+
+                TlsAgreement agreement;
+                if (NamedGroup.RefersToASpecificCurve(namedGroup))
+                {
+                    agreement = crypto.CreateECDomain(new TlsECConfig(namedGroup)).CreateECDH();
+                }
+                else if (NamedGroup.RefersToASpecificFiniteField(namedGroup))
+                {
+                    agreement = crypto.CreateDHDomain(new TlsDHConfig(namedGroup, true)).CreateDH();
+                }
+                else
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                byte[] key_exchange = agreement.GenerateEphemeral();
+                KeyShareEntry serverShare = new KeyShareEntry(namedGroup, key_exchange);
+                TlsExtensionsUtilities.AddKeyShareServerHello(serverHelloExtensions, serverShare);
+
+                agreement.ReceivePeerValue(clientShare.KeyExchange);
+                securityParameters.m_sharedSecret = agreement.CalculateSecret();
+                TlsUtilities.Establish13PhaseSecrets(m_tlsServerContext);
+            }
+
+            this.m_serverExtensions = serverEncryptedExtensions;
+
+            ApplyMaxFragmentLengthExtension(securityParameters.MaxFragmentLength);
+
+            TlsUtilities.CheckExtensionData13(serverHelloExtensions, HandshakeType.server_hello,
+                AlertDescription.internal_error);
+
+            return new ServerHello(serverLegacyVersion, securityParameters.ServerRandom, legacy_session_id,
+                securityParameters.CipherSuite, serverHelloExtensions);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello GenerateServerHello(ClientHello clientHello)
+        {
+            ProtocolVersion clientLegacyVersion = clientHello.Version;
+            if (!clientLegacyVersion.IsTls)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.m_offeredCipherSuites = clientHello.CipherSuites;
+
+
+ 
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+            m_tlsServerContext.SetClientSupportedVersions(
+                TlsExtensionsUtilities.GetSupportedVersionsExtensionClient(clientHello.Extensions));
+
+            ProtocolVersion clientVersion = clientLegacyVersion;
+            if (null == m_tlsServerContext.ClientSupportedVersions)
+            {
+                if (clientVersion.IsLaterVersionOf(ProtocolVersion.TLSv12))
+                {
+                    clientVersion = ProtocolVersion.TLSv12;
+                }
+
+                m_tlsServerContext.SetClientSupportedVersions(clientVersion.DownTo(ProtocolVersion.SSLv3));
+            }
+            else
+            {
+                clientVersion = ProtocolVersion.GetLatestTls(m_tlsServerContext.ClientSupportedVersions);
+            }
+
+            // Set the legacy_record_version to use for early alerts 
+            m_recordStream.SetWriteVersion(clientVersion);
+
+            if (!ProtocolVersion.SERVER_EARLIEST_SUPPORTED_TLS.IsEqualOrEarlierVersionOf(clientVersion))
+                throw new TlsFatalAlert(AlertDescription.protocol_version);
+
+            // NOT renegotiating
+            {
+                m_tlsServerContext.SetClientVersion(clientVersion);
+            }
+
+            m_tlsServer.NotifyClientVersion(m_tlsServerContext.ClientVersion);
+
+            securityParameters.m_clientRandom = clientHello.Random;
+
+            m_tlsServer.NotifyFallback(Arrays.Contains(m_offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV));
+
+            m_tlsServer.NotifyOfferedCipherSuites(m_offeredCipherSuites);
+
+            // TODO[tls13] Negotiate cipher suite first?
+
+            ProtocolVersion serverVersion;
+
+            // NOT renegotiating
+            {
+                serverVersion = m_tlsServer.GetServerVersion();
+                if (!ProtocolVersion.Contains(m_tlsServerContext.ClientSupportedVersions, serverVersion))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                securityParameters.m_negotiatedVersion = serverVersion;
+            }
+
+            securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                clientHello.Extensions);
+            securityParameters.m_serverSupportedGroups = m_tlsServer.GetSupportedGroups();
+
+            if (ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(serverVersion))
+            {
+                // See RFC 8446 D.4.
+                m_recordStream.SetIgnoreChangeCipherSpec(true);
+
+                m_recordStream.SetWriteVersion(ProtocolVersion.TLSv12);
+
+                return Generate13ServerHello(clientHello, false);
+            }
+
+            m_recordStream.SetWriteVersion(serverVersion);
+
+            this.m_clientExtensions = clientHello.Extensions;
+
+            byte[] clientRenegExtData = TlsUtilities.GetExtensionData(m_clientExtensions, ExtensionType.renegotiation_info);
+
+            // NOT renegotiating
+            {
+                /*
+                 * RFC 5746 3.6. Server Behavior: Initial Handshake (both full and session-resumption)
+                 */
+
+                /*
+                 * 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(m_offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+                {
+                    securityParameters.m_secureRenegotiation = true;
+                }
+
+                /*
+                 * The server MUST check if the "renegotiation_info" extension is included in the
+                 * ClientHello.
+                 */
+                if (clientRenegExtData != 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.
+                     */
+                    securityParameters.m_secureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(clientRenegExtData,
+                        CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+            }
+
+            m_tlsServer.NotifySecureRenegotiation(securityParameters.IsSecureRenegotiation);
+
+            bool offeredExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(
+                m_clientExtensions);
+
+            if (m_clientExtensions != null)
+            {
+                // NOTE: Validates the padding extension data, if present
+                TlsExtensionsUtilities.GetPaddingExtension(m_clientExtensions);
+
+                securityParameters.m_clientServerNames = TlsExtensionsUtilities.GetServerNameExtensionClient(
+                    m_clientExtensions);
+
+                /*
+                 * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior
+                 * to 1.2. Clients MUST NOT offer it if they are offering prior versions.
+                 */
+                if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(clientVersion))
+                {
+                    TlsUtilities.EstablishClientSigAlgs(securityParameters, m_clientExtensions);
+                }
+
+                securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                    m_clientExtensions);
+
+                m_tlsServer.ProcessClientExtensions(m_clientExtensions);
+            }
+
+            this.m_resumedSession = EstablishSession(m_tlsServer.GetSessionToResume(clientHello.SessionID));
+
+            if (!m_resumedSession)
+            {
+                byte[] newSessionID = m_tlsServer.GetNewSessionID();
+                if (null == newSessionID)
+                {
+                    newSessionID = TlsUtilities.EmptyBytes;
+                }
+
+                this.m_tlsSession = TlsUtilities.ImportSession(newSessionID, null);
+                this.m_sessionParameters = null;
+                this.m_sessionMasterSecret = null;
+            }
+
+            securityParameters.m_sessionID = m_tlsSession.SessionID;
+
+            m_tlsServer.NotifySession(m_tlsSession);
+
+            TlsUtilities.NegotiatedVersionTlsServer(m_tlsServerContext);
+
+            {
+                bool useGmtUnixTime = m_tlsServer.ShouldUseGmtUnixTime();
+
+                securityParameters.m_serverRandom = CreateRandomBlock(useGmtUnixTime, m_tlsServerContext);
+
+                if (!serverVersion.Equals(ProtocolVersion.GetLatestTls(m_tlsServer.GetProtocolVersions())))
+                {
+                    TlsUtilities.WriteDowngradeMarker(serverVersion, securityParameters.ServerRandom);
+                }
+            }
+
+            {
+                int cipherSuite = m_resumedSession
+                    ?   m_sessionParameters.CipherSuite
+                    :   m_tlsServer.GetSelectedCipherSuite();
+
+                if (!TlsUtilities.IsValidCipherSuiteSelection(m_offeredCipherSuites, cipherSuite) ||
+                    !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, serverVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+            }
+
+            m_tlsServerContext.SetRsaPreMasterSecretVersion(clientLegacyVersion);
+
+            {
+                IDictionary sessionServerExtensions = m_resumedSession
+                    ?   m_sessionParameters.ReadServerExtensions()
+                    :   m_tlsServer.GetServerExtensions();
+
+                this.m_serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(sessionServerExtensions);
+            }
+
+            m_tlsServer.GetServerExtensionsForConnection(m_serverExtensions);
+
+            // NOT renegotiating
+            {
+                /*
+                 * RFC 5746 3.6. Server Behavior: Initial Handshake (both full and session-resumption)
+                 */
+                if (securityParameters.IsSecureRenegotiation)
+                {
+                    byte[] serverRenegExtData = TlsUtilities.GetExtensionData(m_serverExtensions,
+                        ExtensionType.renegotiation_info);
+                    bool noRenegExt = (null == serverRenegExtData);
+
+                    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.m_serverExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(
+                            TlsUtilities.EmptyBytes);
+                    }
+                }
+            }
+
+            /*
+             * RFC 7627 4. Clients and servers SHOULD NOT accept handshakes that do not use the extended
+             * master secret [..]. (and see 5.2, 5.3)
+             */
+            if (m_resumedSession)
+            {
+                if (!m_sessionParameters.IsExtendedMasterSecret)
+                {
+                    /*
+                     * TODO[resumption] ProvTlsServer currently only resumes EMS sessions. Revisit this
+                     * in relation to 'tlsServer.allowLegacyResumption()'.
+                     */
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                if (!offeredExtendedMasterSecret)
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                securityParameters.m_extendedMasterSecret = true;
+
+                TlsExtensionsUtilities.AddExtendedMasterSecretExtension(m_serverExtensions);
+            }
+            else
+            {
+                securityParameters.m_extendedMasterSecret = offeredExtendedMasterSecret && !serverVersion.IsSsl
+                    && m_tlsServer.ShouldUseExtendedMasterSecret();
+
+                if (securityParameters.IsExtendedMasterSecret)
+                {
+                    TlsExtensionsUtilities.AddExtendedMasterSecretExtension(m_serverExtensions);
+                }
+                else if (m_tlsServer.RequiresExtendedMasterSecret())
+                {
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+            }
+
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(m_serverExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            if (m_serverExtensions.Count > 0)
+            {
+                securityParameters.m_encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(
+                    m_serverExtensions);
+
+                securityParameters.m_maxFragmentLength = ProcessMaxFragmentLengthExtension(m_clientExtensions,
+                    m_serverExtensions, AlertDescription.internal_error);
+
+                securityParameters.m_truncatedHmac = TlsExtensionsUtilities.HasTruncatedHmacExtension(
+                    m_serverExtensions);
+
+                if (!m_resumedSession)
+                {
+                    if (TlsUtilities.HasExpectedEmptyExtensionData(m_serverExtensions, ExtensionType.status_request_v2,
+                        AlertDescription.internal_error))
+                    {
+                        securityParameters.m_statusRequestVersion = 2;
+                    }
+                    else if (TlsUtilities.HasExpectedEmptyExtensionData(m_serverExtensions, ExtensionType.status_request,
+                        AlertDescription.internal_error))
+                    {
+                        securityParameters.m_statusRequestVersion = 1;
+                    }
+
+                    this.m_expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(m_serverExtensions,
+                        ExtensionType.session_ticket, AlertDescription.internal_error);
+                }
+            }
+
+            ApplyMaxFragmentLengthExtension(securityParameters.MaxFragmentLength);
+
+            return new ServerHello(serverVersion, securityParameters.ServerRandom, m_tlsSession.SessionID,
+                securityParameters.CipherSuite, m_serverExtensions);
+        }
+
+        protected override TlsContext Context
+        {
+            get { return m_tlsServerContext; }
+        }
+
+        internal override AbstractTlsContext ContextAdmin
+        {
+            get { return m_tlsServerContext; }
+        }
+
+        protected override TlsPeer Peer
+        {
+            get { return m_tlsServer; }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Handle13HandshakeMessage(short type, HandshakeMessageInput buf)
+        {
+            if (!IsTlsV13ConnectionState())
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_resumedSession)
+            {
+                /*
+                 * TODO[tls13] Abbreviated handshakes (PSK resumption)
+                 * 
+                 * NOTE: No CertificateRequest, Certificate, CertificateVerify messages, but client
+                 * might now send EndOfEarlyData after receiving server Finished message.
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            switch (type)
+            {
+            case HandshakeType.certificate:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_FINISHED:
+                {
+                    Receive13ClientCertificate(buf);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_verify:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_CERTIFICATE:
+                {
+                    Receive13ClientCertificateVerify(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.client_hello:
+            {
+                switch (m_connectionState)
+                {
+                case CS_START:
+                {
+                    // NOTE: Legacy handler should be dispatching initial ClientHello.
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+                case CS_SERVER_HELLO_RETRY_REQUEST:
+                {
+                    ClientHello clientHelloRetry = ReceiveClientHelloMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_HELLO_RETRY;
+
+                    ServerHello serverHello = Generate13ServerHello(clientHelloRetry, true);
+                    SendServerHelloMessage(serverHello);
+                    this.m_connectionState = CS_SERVER_HELLO;
+
+                    Send13ServerHelloCoda(serverHello, true);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_FINISHED:
+                case CS_CLIENT_CERTIFICATE:
+                case CS_CLIENT_CERTIFICATE_VERIFY:
+                {
+                    if (m_connectionState == CS_SERVER_FINISHED)
+                    {
+                        Skip13ClientCertificate();
+                    }
+                    if (m_connectionState != CS_CLIENT_CERTIFICATE_VERIFY)
+                    {
+                        Skip13ClientCertificateVerify();
+                    }
+
+                    Receive13ClientFinished(buf);
+                    this.m_connectionState = CS_CLIENT_FINISHED;
+
+                    // See RFC 8446 D.4.
+                    m_recordStream.SetIgnoreChangeCipherSpec(false);
+
+                    // NOTE: Completes the switch to application-data phase (server entered after CS_SERVER_FINISHED).
+                    m_recordStream.EnablePendingCipherRead(false);
+
+                    CompleteHandshake();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.key_update:
+            {
+                Receive13KeyUpdate(buf);
+                break;
+            }
+
+            case HandshakeType.certificate_request:
+            case HandshakeType.certificate_status:
+            case HandshakeType.certificate_url:
+            case HandshakeType.client_key_exchange:
+            case HandshakeType.encrypted_extensions:
+            case HandshakeType.end_of_early_data:
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.message_hash:
+            case HandshakeType.new_session_ticket:
+            case HandshakeType.server_hello:
+            case HandshakeType.server_hello_done:
+            case HandshakeType.server_key_exchange:
+            case HandshakeType.supplemental_data:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected override void HandleHandshakeMessage(short type, HandshakeMessageInput buf)
+        {
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+            if (m_connectionState > CS_CLIENT_HELLO
+                && TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+            {
+                Handle13HandshakeMessage(type, buf);
+                return;
+            }
+
+            if (!IsLegacyConnectionState())
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_resumedSession)
+            {
+                if (type != HandshakeType.finished || m_connectionState != CS_SERVER_FINISHED)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                ProcessFinishedMessage(buf);
+                this.m_connectionState = CS_CLIENT_FINISHED;
+
+                CompleteHandshake();
+                return;
+            }
+
+            switch (type)
+            {
+            case HandshakeType.client_hello:
+            {
+                if (IsApplicationDataReady)
+                {
+                    RefuseRenegotiation();
+                    break;
+                }
+
+                switch (m_connectionState)
+                {
+                case CS_END:
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+                case CS_START:
+                {
+                    ClientHello clientHello = ReceiveClientHelloMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_HELLO;
+
+                    ServerHello serverHello = GenerateServerHello(clientHello);
+                    m_handshakeHash.NotifyPrfDetermined();
+
+                    if (TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+                    {
+                        if (serverHello.IsHelloRetryRequest())
+                        {
+                            TlsUtilities.AdjustTranscriptForRetry(m_handshakeHash);
+                            SendServerHelloMessage(serverHello);
+                            this.m_connectionState = CS_SERVER_HELLO_RETRY_REQUEST;
+
+                            // See RFC 8446 D.4.
+                            SendChangeCipherSpecMessage();
+                        }
+                        else
+                        {
+                            SendServerHelloMessage(serverHello);
+                            this.m_connectionState = CS_SERVER_HELLO;
+
+                            // See RFC 8446 D.4.
+                            SendChangeCipherSpecMessage();
+
+                            Send13ServerHelloCoda(serverHello, false);
+                        }
+                        break;
+                    }
+
+                    SendServerHelloMessage(serverHello);
+                    this.m_connectionState = CS_SERVER_HELLO;
+
+                    if (m_resumedSession)
+                    {
+                        securityParameters.m_masterSecret = m_sessionMasterSecret;
+                        m_recordStream.SetPendingCipher(TlsUtilities.InitCipher(m_tlsServerContext));
+
+                        SendChangeCipherSpec();
+                        SendFinishedMessage();
+                        this.m_connectionState = CS_SERVER_FINISHED;
+                        break;
+                    }
+
+                    IList serverSupplementalData = m_tlsServer.GetServerSupplementalData();
+                    if (serverSupplementalData != null)
+                    {
+                        SendSupplementalDataMessage(serverSupplementalData);
+                        this.m_connectionState = CS_SERVER_SUPPLEMENTAL_DATA;
+                    }
+
+                    this.m_keyExchange = TlsUtilities.InitKeyExchangeServer(m_tlsServerContext, m_tlsServer);
+
+                    TlsCredentials serverCredentials = TlsUtilities.EstablishServerCredentials(m_tlsServer);
+
+                    // Server certificate
+                    {
+                        Certificate serverCertificate = null;
+
+                        MemoryStream endPointHash = new MemoryStream();
+                        if (null == serverCredentials)
+                        {
+                            m_keyExchange.SkipServerCredentials();
+                        }
+                        else
+                        {
+                            m_keyExchange.ProcessServerCredentials(serverCredentials);
+
+                            serverCertificate = serverCredentials.Certificate;
+                            SendCertificateMessage(serverCertificate, endPointHash);
+                            this.m_connectionState = CS_SERVER_CERTIFICATE;
+                        }
+
+                        securityParameters.m_tlsServerEndPoint = endPointHash.ToArray();
+
+                        // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes
+                        // CertificateStatus
+                        if (null == serverCertificate || serverCertificate.IsEmpty)
+                        {
+                            securityParameters.m_statusRequestVersion = 0;
+                        }
+                    }
+
+                    if (securityParameters.StatusRequestVersion > 0)
+                    {
+                        CertificateStatus certificateStatus = m_tlsServer.GetCertificateStatus();
+                        if (certificateStatus != null)
+                        {
+                            SendCertificateStatusMessage(certificateStatus);
+                            this.m_connectionState = CS_SERVER_CERTIFICATE_STATUS;
+                        }
+                    }
+
+                    byte[] serverKeyExchange = m_keyExchange.GenerateServerKeyExchange();
+                    if (serverKeyExchange != null)
+                    {
+                        SendServerKeyExchangeMessage(serverKeyExchange);
+                        this.m_connectionState = CS_SERVER_KEY_EXCHANGE;
+                    }
+
+                    if (null != serverCredentials)
+                    {
+                        this.m_certificateRequest = m_tlsServer.GetCertificateRequest();
+
+                        if (null == m_certificateRequest)
+                        {
+                            /*
+                             * For static agreement key exchanges, CertificateRequest is required since
+                             * the client Certificate message is mandatory but can only be sent if the
+                             * server requests it.
+                             */
+                            if (!m_keyExchange.RequiresCertificateVerify)
+                                throw new TlsFatalAlert(AlertDescription.internal_error);
+                        }
+                        else
+                        {
+                            if (TlsUtilities.IsTlsV12(m_tlsServerContext)
+                                != (m_certificateRequest.SupportedSignatureAlgorithms != null))
+                            {
+                                throw new TlsFatalAlert(AlertDescription.internal_error);
+                            }
+
+                            this.m_certificateRequest = TlsUtilities.ValidateCertificateRequest(m_certificateRequest,
+                                m_keyExchange);
+
+                            TlsUtilities.EstablishServerSigAlgs(securityParameters, m_certificateRequest);
+
+                            TlsUtilities.TrackHashAlgorithms(m_handshakeHash, securityParameters.ServerSigAlgs);
+
+                            SendCertificateRequestMessage(m_certificateRequest);
+                            this.m_connectionState = CS_SERVER_CERTIFICATE_REQUEST;
+                        }
+                    }
+
+                    SendServerHelloDoneMessage();
+                    this.m_connectionState = CS_SERVER_HELLO_DONE;
+
+                    bool forceBuffering = false;
+                    TlsUtilities.SealHandshakeHash(m_tlsServerContext, m_handshakeHash, forceBuffering);
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.supplemental_data:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                {
+                    m_tlsServer.ProcessClientSupplementalData(ReadSupplementalDataMessage(buf));
+                    this.m_connectionState = CS_CLIENT_SUPPLEMENTAL_DATA;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                {
+                    if (m_connectionState != CS_CLIENT_SUPPLEMENTAL_DATA)
+                    {
+                        m_tlsServer.ProcessClientSupplementalData(null);
+                    }
+
+                    if (m_certificateRequest == null)
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    ReceiveCertificateMessage(buf);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.client_key_exchange:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                case CS_CLIENT_CERTIFICATE:
+                {
+                    if (m_connectionState == CS_SERVER_HELLO_DONE)
+                    {
+                        m_tlsServer.ProcessClientSupplementalData(null);
+                    }
+                    if (m_connectionState != CS_CLIENT_CERTIFICATE)
+                    {
+                        if (null == m_certificateRequest)
+                        {
+                            m_keyExchange.SkipClientCredentials();
+                        }
+                        else if (TlsUtilities.IsTlsV12(m_tlsServerContext))
+                        {
+                            /*
+                             * 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(m_tlsServerContext))
+                        {
+                            /*
+                             * 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.
+                             */
+                            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                        }
+                        else
+                        {
+                            NotifyClientCertificate(Certificate.EmptyChain);
+                        }
+                    }
+
+                    ReceiveClientKeyExchangeMessage(buf);
+                    this.m_connectionState = CS_CLIENT_KEY_EXCHANGE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_verify:
+            {
+                switch (m_connectionState)
+                {
+                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);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_KEY_EXCHANGE:
+                case CS_CLIENT_CERTIFICATE_VERIFY:
+                {
+                    if (m_connectionState != CS_CLIENT_CERTIFICATE_VERIFY)
+                    {
+                        if (ExpectCertificateVerifyMessage())
+                            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    ProcessFinishedMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_FINISHED;
+
+                    if (m_expectSessionTicket)
+                    {
+                        SendNewSessionTicketMessage(m_tlsServer.GetNewSessionTicket());
+                        this.m_connectionState = CS_SERVER_SESSION_TICKET;
+                    }
+
+                    SendChangeCipherSpec();
+                    SendFinishedMessage();
+                    this.m_connectionState = CS_SERVER_FINISHED;
+
+                    CompleteHandshake();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+
+            case HandshakeType.certificate_request:
+            case HandshakeType.certificate_status:
+            case HandshakeType.certificate_url:
+            case HandshakeType.encrypted_extensions:
+            case HandshakeType.end_of_early_data:
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.key_update:
+            case HandshakeType.message_hash:
+            case HandshakeType.new_session_ticket:
+            case HandshakeType.server_hello:
+            case HandshakeType.server_hello_done:
+            case HandshakeType.server_key_exchange:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected override void HandleAlertWarningMessage(short alertDescription)
+        {
+            /*
+             * 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 (AlertDescription.no_certificate == alertDescription && null != m_certificateRequest
+                && TlsUtilities.IsSsl(m_tlsServerContext))
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                {
+                    if (m_connectionState != CS_CLIENT_SUPPLEMENTAL_DATA)
+                    {
+                        m_tlsServer.ProcessClientSupplementalData(null);
+                    }
+
+                    NotifyClientCertificate(Certificate.EmptyChain);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    return;
+                }
+                }
+            }
+
+            base.HandleAlertWarningMessage(alertDescription);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            if (null == m_certificateRequest)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsUtilities.ProcessClientCertificate(m_tlsServerContext, clientCertificate, m_keyExchange, m_tlsServer);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ClientCertificate(MemoryStream buf)
+        {
+            // TODO[tls13] This currently just duplicates 'receiveCertificateMessage'
+
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(m_tlsServer.GetMaxCertificateChainLength());
+
+            Certificate clientCertificate = Certificate.Parse(options, m_tlsServerContext, buf, null);
+
+            AssertEmpty(buf);
+
+            NotifyClientCertificate(clientCertificate);
+        }
+
+        /// <exception cref="IOException"/>
+        protected void Receive13ClientCertificateVerify(MemoryStream buf)
+        {
+            Certificate clientCertificate = m_tlsServerContext.SecurityParameters.PeerCertificate;
+            if (null == clientCertificate || clientCertificate.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            // TODO[tls13] Actual structure is 'CertificateVerify' in RFC 8446, consider adding for clarity
+            DigitallySigned certificateVerify = DigitallySigned.Parse(m_tlsServerContext, buf);
+
+            AssertEmpty(buf);
+
+            TlsUtilities.Verify13CertificateVerifyClient(m_tlsServerContext, m_certificateRequest, certificateVerify,
+                m_handshakeHash);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ClientFinished(MemoryStream buf)
+        {
+            Process13FinishedMessage(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveCertificateMessage(MemoryStream buf)
+        {
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(m_tlsServer.GetMaxCertificateChainLength());
+
+            Certificate clientCertificate = Certificate.Parse(options, m_tlsServerContext, buf, null);
+
+            AssertEmpty(buf);
+
+            NotifyClientCertificate(clientCertificate);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveCertificateVerifyMessage(MemoryStream buf)
+        {
+            DigitallySigned certificateVerify = DigitallySigned.Parse(m_tlsServerContext, buf);
+
+            AssertEmpty(buf);
+
+            TlsUtilities.VerifyCertificateVerifyClient(m_tlsServerContext, m_certificateRequest, certificateVerify,
+                m_handshakeHash);
+
+            this.m_handshakeHash = m_handshakeHash.StopTracking();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ClientHello ReceiveClientHelloMessage(MemoryStream buf)
+        {
+            return ClientHello.Parse(buf, null);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveClientKeyExchangeMessage(MemoryStream buf)
+        {
+            m_keyExchange.ProcessClientKeyExchange(buf);
+
+            AssertEmpty(buf);
+
+            bool isSsl = TlsUtilities.IsSsl(m_tlsServerContext);
+            if (isSsl)
+            {
+                // NOTE: For SSLv3 (only), master_secret needed to calculate session hash
+                EstablishMasterSecret(m_tlsServerContext, m_keyExchange);
+            }
+
+            m_tlsServerContext.SecurityParameters.m_sessionHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            if (!isSsl)
+            {
+                // NOTE: For (D)TLS, session hash potentially needed for extended_master_secret
+                EstablishMasterSecret(m_tlsServerContext, m_keyExchange);
+            }
+
+            m_recordStream.SetPendingCipher(TlsUtilities.InitCipher(m_tlsServerContext));
+
+            if (!ExpectCertificateVerifyMessage())
+            {
+                this.m_handshakeHash = m_handshakeHash.StopTracking();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13EncryptedExtensionsMessage(IDictionary serverExtensions)
+        {
+            // TODO[tls13] Avoid extra copy; use placeholder to write opaque-16 data directly to message buffer
+
+            byte[] extBytes = WriteExtensionsData(serverExtensions);
+
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.encrypted_extensions);
+            TlsUtilities.WriteOpaque16(extBytes, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13ServerHelloCoda(ServerHello serverHello, bool afterHelloRetryRequest)
+        {
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+            byte[] serverHelloTranscriptHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            TlsUtilities.Establish13PhaseHandshake(m_tlsServerContext, serverHelloTranscriptHash, m_recordStream);
+
+            m_recordStream.EnablePendingCipherWrite();
+            m_recordStream.EnablePendingCipherRead(true);
+
+            Send13EncryptedExtensionsMessage(m_serverExtensions);
+            this.m_connectionState = CS_SERVER_ENCRYPTED_EXTENSIONS;
+
+            // CertificateRequest
+            {
+                this.m_certificateRequest = m_tlsServer.GetCertificateRequest();
+                if (null != m_certificateRequest)
+                {
+                    if (!m_certificateRequest.HasCertificateRequestContext(TlsUtilities.EmptyBytes))
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                    TlsUtilities.EstablishServerSigAlgs(securityParameters, m_certificateRequest);
+
+                    SendCertificateRequestMessage(m_certificateRequest);
+                    this.m_connectionState = CS_SERVER_CERTIFICATE_REQUEST;
+                }
+            }
+
+            /*
+             * TODO[tls13] For PSK-only key exchange, there's no Certificate message.
+             */
+
+            TlsCredentialedSigner serverCredentials = TlsUtilities.Establish13ServerCredentials(m_tlsServer);
+            if (null == serverCredentials)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            // Certificate
+            {
+                /*
+                 * TODO[tls13] Note that we are expecting the TlsServer implementation to take care of
+                 * e.g. adding optional "status_request" extension to each CertificateEntry.
+                 */
+                /*
+                 * No CertificateStatus message is sent; TLS 1.3 uses per-CertificateEntry
+                 * "status_request" extension instead.
+                 */
+
+                Certificate serverCertificate = serverCredentials.Certificate;
+                Send13CertificateMessage(serverCertificate);
+                securityParameters.m_tlsServerEndPoint = null;
+                this.m_connectionState = CS_SERVER_CERTIFICATE;
+            }
+
+            // CertificateVerify
+            {
+                DigitallySigned certificateVerify = TlsUtilities.Generate13CertificateVerify(m_tlsServerContext,
+                    serverCredentials, m_handshakeHash);
+                Send13CertificateVerifyMessage(certificateVerify);
+                this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+            }
+
+            // Finished
+            {
+                Send13FinishedMessage();
+                this.m_connectionState = CS_SERVER_FINISHED;
+            }
+
+            byte[] serverFinishedTranscriptHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            TlsUtilities.Establish13PhaseApplication(m_tlsServerContext, serverFinishedTranscriptHash, m_recordStream);
+
+            m_recordStream.EnablePendingCipherWrite();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendCertificateRequestMessage(CertificateRequest certificateRequest)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_request);
+            certificateRequest.Encode(m_tlsServerContext, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendCertificateStatusMessage(CertificateStatus certificateStatus)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_status);
+            // TODO[tls13] Ensure this cannot happen for (D)TLS1.3+
+            certificateStatus.Encode(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendHelloRequestMessage()
+        {
+            HandshakeMessageOutput.Send(this, HandshakeType.hello_request, TlsUtilities.EmptyBytes);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendNewSessionTicketMessage(NewSessionTicket newSessionTicket)
+        {
+            if (newSessionTicket == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.new_session_ticket);
+            newSessionTicket.Encode(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendServerHelloDoneMessage()
+        {
+            HandshakeMessageOutput.Send(this, HandshakeType.server_hello_done, TlsUtilities.EmptyBytes);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendServerHelloMessage(ServerHello serverHello)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.server_hello);
+            serverHello.Encode(m_tlsServerContext, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendServerKeyExchangeMessage(byte[] serverKeyExchange)
+        {
+            HandshakeMessageOutput.Send(this, HandshakeType.server_key_exchange, serverKeyExchange);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Skip13ClientCertificate()
+        {
+            if (null != m_certificateRequest)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Skip13ClientCertificateVerify()
+        {
+            if (ExpectCertificateVerifyMessage())
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+}