예제 #1
0
        private void ProcessHandshake()
        {
            bool read;

            do
            {
                read = false;

                /*
                 * We need the first 4 bytes, they contain type and length of
                 * the message.
                 */
                if (handshakeQueue.Available >= 4)
                {
                    byte[] beginning = new byte[4];
                    handshakeQueue.Read(beginning, 0, 4, 0);
                    MemoryStream  bis  = new MemoryStream(beginning, false);
                    HandshakeType type = (HandshakeType)TlsUtilities.ReadUint8(bis);
                    int           len  = TlsUtilities.ReadUint24(bis);

                    /*
                     * Check if we have enough bytes in the buffer to read
                     * the full message.
                     */
                    if (handshakeQueue.Available >= (len + 4))
                    {
                        /*
                         * Read the message.
                         */
                        byte[] buf = new byte[len];
                        handshakeQueue.Read(buf, 0, len, 4);
                        handshakeQueue.RemoveData(len + 4);

                        /*
                         * RFC 2246 7.4.9. The value handshake_messages includes all
                         * handshake messages starting at client hello up to, but not
                         * including, this finished message. [..] Note: [Also,] Hello Request
                         * messages are omitted from handshake hashes.
                         */
                        switch (type)
                        {
                        case HandshakeType.hello_request:
                        case HandshakeType.finished:
                            break;

                        default:
                            rs.UpdateHandshakeData(beginning, 0, 4);
                            rs.UpdateHandshakeData(buf, 0, len);
                            break;
                        }

                        /*
                         * Now, parse the message.
                         */
                        ProcessHandshakeMessage(type, buf);
                        read = true;
                    }
                }
            }while (read);
        }
        private void ProcessHandshakeMessage(HandshakeType type, byte[] buf)
        {
            MemoryStream inStr = new MemoryStream(buf, false);

            /*
            * Check the type.
            */
            switch (type)
            {
                case HandshakeType.certificate:
                {
                    switch (connection_state)
                    {
                        case CS_SERVER_HELLO_RECEIVED:
                        {
                            // Parse the Certificate message and send to cipher suite

                            Certificate serverCertificate = Certificate.Parse(inStr);

                            AssertEmpty(inStr);

                            this.keyExchange.ProcessServerCertificate(serverCertificate);

                            this.authentication = tlsClient.GetAuthentication();
                            this.authentication.NotifyServerCertificate(serverCertificate);

                            break;
                        }
                        default:
                            this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                            break;
                    }

                    connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
                    break;
                }
                case HandshakeType.finished:
                    switch (connection_state)
                    {
                        case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
                            /*
                             * Read the checksum from the finished message, it has always 12 bytes.
                             */
                            byte[] serverVerifyData = new byte[12];
                            TlsUtilities.ReadFully(serverVerifyData, inStr);

                            AssertEmpty(inStr);

                            /*
                             * Calculate our own checksum.
                             */
                            byte[] expectedServerVerifyData = TlsUtilities.PRF(
                                securityParameters.masterSecret, "server finished",
                                rs.GetCurrentHash(), 12);

                            /*
                             * Compare both checksums.
                             */
                            if (!Arrays.ConstantTimeAreEqual(expectedServerVerifyData, serverVerifyData))
                            {
                                /*
                                 * Wrong checksum in the finished message.
                                 */
                                this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                            }

                            connection_state = CS_DONE;

                            /*
                            * We are now ready to receive application data.
                            */
                            this.appDataReady = true;
                            break;
                        default:
                            this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                            break;
                    }
                    break;
                case HandshakeType.server_hello:
                    switch (connection_state)
                    {
                        case CS_CLIENT_HELLO_SEND:
                            /*
                             * Read the server hello message
                             */
                            TlsUtilities.CheckVersion(inStr);

                            /*
                             * Read the server random
                             */
                            securityParameters.serverRandom = new byte[32];
                            TlsUtilities.ReadFully(securityParameters.serverRandom, inStr);

                            byte[] sessionID = TlsUtilities.ReadOpaque8(inStr);
                            if (sessionID.Length > 32)
                            {
                                this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                            }

                            this.tlsClient.NotifySessionID(sessionID);

                            /*
                             * Find out which CipherSuite the server has chosen and check that
                             * it was one of the offered ones.
                             */
                            CipherSuite selectedCipherSuite = (CipherSuite)TlsUtilities.ReadUint16(inStr);
                            if (!ArrayContains(offeredCipherSuites, selectedCipherSuite)
                                || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
                            {
                                this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                            }

                            this.tlsClient.NotifySelectedCipherSuite(selectedCipherSuite);

                            /*
                             * Find out which CompressionMethod the server has chosen and check that
                             * it was one of the offered ones.
                             */
                            CompressionMethod selectedCompressionMethod = (CompressionMethod)TlsUtilities.ReadUint8(inStr);
                            if (!ArrayContains(offeredCompressionMethods, selectedCompressionMethod))
                            {
                                this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                            }

                            this.tlsClient.NotifySelectedCompressionMethod(selectedCompressionMethod);

                            /*
                             * RFC3546 2.2 The extended server hello message format MAY be
                             * sent in place of the server hello message when the client has
                             * requested extended functionality via the extended client hello
                             * message specified in Section 2.1.
                             * ...
                             * Note that the extended server hello message is only sent in response
                             * to an extended client hello message.  This prevents the possibility
                             * that the extended server hello message could "break" existing TLS 1.0
                             * clients.
                             */

                            /*
                             * 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.
                             */

                            // ExtensionType -> byte[]
                            IDictionary serverExtensions = Platform.CreateHashtable();

                            if (inStr.Position < inStr.Length)
                            {
                                // Process extensions from extended server hello
                                byte[] extBytes = TlsUtilities.ReadOpaque16(inStr);

                                MemoryStream ext = new MemoryStream(extBytes, false);
                                while (ext.Position < ext.Length)
                                {
                                    ExtensionType extType = (ExtensionType)TlsUtilities.ReadUint16(ext);
                                    byte[] extValue = TlsUtilities.ReadOpaque16(ext);

                                    // Note: RFC 5746 makes a special case for EXT_RenegotiationInfo
                                    if (extType != ExtensionType.renegotiation_info
                                        && !clientExtensions.Contains(extType))
                                    {
                                        /*
                                         * RFC 3546 2.3
                                         * Note that for all extension types (including those defined in
                                         * future), the extension type MUST NOT appear in the extended server
                                         * hello unless the same extension type appeared in the corresponding
                                         * client hello.  Thus clients MUST abort the handshake if they receive
                                         * an extension type in the extended server hello that they did not
                                         * request in the associated (extended) client hello.
                                         */
                                        this.FailWithError(AlertLevel.fatal, AlertDescription.unsupported_extension);
                                    }

                                    if (serverExtensions.Contains(extType))
                                    {
                                        /*
                                         * RFC 3546 2.3
                                         * Also note that when multiple extensions of different types are
                                         * present in the extended client hello or the extended server hello,
                                         * the extensions may appear in any order. There MUST NOT be more than
                                         * one extension of the same type.
                                         */
                                        this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                                    }

                                    serverExtensions.Add(extType, extValue);
                                }
                            }

                            AssertEmpty(inStr);

                            /*
                             * RFC 5746 3.4. When a ServerHello is received, the client MUST check if it
                             * includes the "renegotiation_info" extension:
                             */
                            {
                                bool secure_negotiation = serverExtensions.Contains(ExtensionType.renegotiation_info);

                                /*
                                 * If the extension is present, set the secure_renegotiation flag
                                 * to TRUE.  The client MUST then verify that the length of the
                                 * "renegotiated_connection" field is zero, and if it is not, MUST
                                 * abort the handshake (by sending a fatal handshake_failure
                                 * alert).
                                 */
                                if (secure_negotiation)
                                {
                                    byte[] renegExtValue = (byte[])serverExtensions[ExtensionType.renegotiation_info];

                                    if (!Arrays.ConstantTimeAreEqual(renegExtValue,
                                        CreateRenegotiationInfo(emptybuf)))
                                    {
                                        this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                                    }
                                }

                                tlsClient.NotifySecureRenegotiation(secure_negotiation);
                            }

                            if (clientExtensions != null)
                            {
                                tlsClient.ProcessServerExtensions(serverExtensions);
                            }

                            this.keyExchange = tlsClient.GetKeyExchange();

                            connection_state = CS_SERVER_HELLO_RECEIVED;
                            break;
                        default:
                            this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                            break;
                    }
                    break;
                case HandshakeType.server_hello_done:
                    switch (connection_state)
                    {
                        case CS_SERVER_HELLO_RECEIVED:
                        case CS_SERVER_CERTIFICATE_RECEIVED:
                        case CS_SERVER_KEY_EXCHANGE_RECEIVED:
                        case CS_CERTIFICATE_REQUEST_RECEIVED:

                            // NB: Original code used case label fall-through

                            if (connection_state == CS_SERVER_HELLO_RECEIVED)
                            {
                                // There was no server certificate message; check it's OK
                                this.keyExchange.SkipServerCertificate();
                                this.authentication = null;

                                // There was no server key exchange message; check it's OK
                                this.keyExchange.SkipServerKeyExchange();
                            }
                            else if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
                            {
                                // There was no server key exchange message; check it's OK
                                this.keyExchange.SkipServerKeyExchange();
                            }

                            AssertEmpty(inStr);

                            connection_state = CS_SERVER_HELLO_DONE_RECEIVED;

                            TlsCredentials clientCreds = null;
                            if (certificateRequest == null)
                            {
                                this.keyExchange.SkipClientCredentials();
                            }
                            else
                            {
                                clientCreds = this.authentication.GetClientCredentials(certificateRequest);

                                Certificate clientCert;
                                if (clientCreds == null)
                                {
                                    this.keyExchange.SkipClientCredentials();
                                    clientCert = Certificate.EmptyChain;
                                }
                                else
                                {
                                    this.keyExchange.ProcessClientCredentials(clientCreds);
                                    clientCert = clientCreds.Certificate;
                                }

                                SendClientCertificate(clientCert);
                            }

                            /*
                             * Send the client key exchange message, depending on the key
                             * exchange we are using in our CipherSuite.
                             */
                            SendClientKeyExchange();

                            connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;

                            if (clientCreds != null && clientCreds is TlsSignerCredentials)
                            {
                                TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds;
                                byte[] md5andsha1 = rs.GetCurrentHash();
                                byte[] clientCertificateSignature = signerCreds.GenerateCertificateSignature(
                                    md5andsha1);
                                SendCertificateVerify(clientCertificateSignature);

                                connection_state = CS_CERTIFICATE_VERIFY_SEND;
                            }

                            /*
                            * Now, we send change cipher state
                            */
                            byte[] cmessage = new byte[1];
                            cmessage[0] = 1;
                            rs.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length);

                            connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;

                            /*
                             * Calculate the master_secret
                             */
                            byte[] pms = this.keyExchange.GeneratePremasterSecret();

                            securityParameters.masterSecret = TlsUtilities.PRF(pms, "master secret",
                                TlsUtilities.Concat(securityParameters.clientRandom, securityParameters.serverRandom),
                                48);

                            // TODO Is there a way to ensure the data is really overwritten?
                            /*
                             * RFC 2246 8.1. The pre_master_secret should be deleted from
                             * memory once the master_secret has been computed.
                             */
                            Array.Clear(pms, 0, pms.Length);

                            /*
                             * Initialize our cipher suite
                             */
                            rs.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher());

                            /*
                             * Send our finished message.
                             */
                            byte[] clientVerifyData = TlsUtilities.PRF(securityParameters.masterSecret,
                                "client finished", rs.GetCurrentHash(), 12);

                            MemoryStream bos = new MemoryStream();
                            TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos);
                            TlsUtilities.WriteOpaque24(clientVerifyData, bos);
                            byte[] message = bos.ToArray();

                            rs.WriteMessage(ContentType.handshake, message, 0, message.Length);

                            this.connection_state = CS_CLIENT_FINISHED_SEND;
                            break;
                        default:
                            this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                            break;
                    }
                    break;
                case HandshakeType.server_key_exchange:
                {
                    switch (connection_state)
                    {
                        case CS_SERVER_HELLO_RECEIVED:
                        case CS_SERVER_CERTIFICATE_RECEIVED:
                        {
                            // NB: Original code used case label fall-through
                            if (connection_state == CS_SERVER_HELLO_RECEIVED)
                            {
                                // There was no server certificate message; check it's OK
                                this.keyExchange.SkipServerCertificate();
                                this.authentication = null;
                            }

                            this.keyExchange.ProcessServerKeyExchange(inStr);

                            AssertEmpty(inStr);
                            break;
                        }
                        default:
                            this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                            break;
                    }

                    this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
                    break;
                }
                case HandshakeType.certificate_request:
                    switch (connection_state)
                    {
                        case CS_SERVER_CERTIFICATE_RECEIVED:
                        case CS_SERVER_KEY_EXCHANGE_RECEIVED:
                        {
                            // NB: Original code used case label fall-through
                            if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
                            {
                                // There was no server key exchange message; check it's OK
                                this.keyExchange.SkipServerKeyExchange();
                            }

                            if (this.authentication == null)
                            {
                                /*
                                 * RFC 2246 7.4.4. It is a fatal handshake_failure alert
                                 * for an anonymous server to request client identification.
                                 */
                                this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                            }

                            int numTypes = TlsUtilities.ReadUint8(inStr);
                            ClientCertificateType[] certificateTypes = new ClientCertificateType[numTypes];
                            for (int i = 0; i < numTypes; ++i)
                            {
                                certificateTypes[i] = (ClientCertificateType)TlsUtilities.ReadUint8(inStr);
                            }

                            byte[] authorities = TlsUtilities.ReadOpaque16(inStr);

                            AssertEmpty(inStr);

                            IList authorityDNs = Platform.CreateArrayList();

                            MemoryStream bis = new MemoryStream(authorities, false);
                            while (bis.Position < bis.Length)
                            {
                                byte[] dnBytes = TlsUtilities.ReadOpaque16(bis);
                                // TODO Switch to X500Name when available
                                authorityDNs.Add(X509Name.GetInstance(Asn1Object.FromByteArray(dnBytes)));
                            }

                            this.certificateRequest = new CertificateRequest(certificateTypes,
                                authorityDNs);
                            this.keyExchange.ValidateCertificateRequest(this.certificateRequest);

                            break;
                        }
                        default:
                            this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                            break;
                    }

                    this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
                    break;
                case HandshakeType.hello_request:
                    /*
                     * RFC 2246 7.4.1.1 Hello request
                     * This message will be ignored by the client if the client is currently
                     * negotiating a session. This message may be ignored by the client if it
                     * does not wish to renegotiate a session, or the client may, if it wishes,
                     * respond with a no_renegotiation alert.
                     */
                    if (connection_state == CS_DONE)
                    {
                        // Renegotiation not supported yet
                        SendAlert(AlertLevel.warning, AlertDescription.no_renegotiation);
                    }
                    break;
                case HandshakeType.client_key_exchange:
                case HandshakeType.certificate_verify:
                case HandshakeType.client_hello:
                default:
                    // We do not support this!
                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                    break;
            }
        }
예제 #3
0
        private void ProcessHandshakeMessage(HandshakeType type, byte[] buf)
        {
            MemoryStream inStr = new MemoryStream(buf, false);

            /*
             * Check the type.
             */
            switch (type)
            {
            case HandshakeType.certificate:
            {
                switch (connection_state)
                {
                case CS_SERVER_HELLO_RECEIVED:
                {
                    // Parse the Certificate message and send to cipher suite

                    Certificate serverCertificate = Certificate.Parse(inStr);

                    AssertEmpty(inStr);

                    this.keyExchange.ProcessServerCertificate(serverCertificate);

                    this.authentication = tlsClient.GetAuthentication();
                    this.authentication.NotifyServerCertificate(serverCertificate);

                    break;
                }

                default:
                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                    break;
                }

                connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
                break;
            }

            case HandshakeType.finished:
                switch (connection_state)
                {
                case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
                    /*
                     * Read the checksum from the finished message, it has always 12 bytes.
                     */
                    byte[] serverVerifyData = new byte[12];
                    TlsUtilities.ReadFully(serverVerifyData, inStr);

                    AssertEmpty(inStr);

                    /*
                     * Calculate our own checksum.
                     */
                    byte[] expectedServerVerifyData = TlsUtilities.PRF(
                        securityParameters.masterSecret, "server finished",
                        rs.GetCurrentHash(), 12);

                    /*
                     * Compare both checksums.
                     */
                    if (!Arrays.ConstantTimeAreEqual(expectedServerVerifyData, serverVerifyData))
                    {
                        /*
                         * Wrong checksum in the finished message.
                         */
                        this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                    }

                    connection_state = CS_DONE;

                    /*
                     * We are now ready to receive application data.
                     */
                    this.appDataReady = true;
                    break;

                default:
                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                    break;
                }
                break;

            case HandshakeType.server_hello:
                switch (connection_state)
                {
                case CS_CLIENT_HELLO_SEND:
                    /*
                     * Read the server hello message
                     */
                    TlsUtilities.CheckVersion(inStr, this);

                    /*
                     * Read the server random
                     */
                    securityParameters.serverRandom = new byte[32];
                    TlsUtilities.ReadFully(securityParameters.serverRandom, inStr);

                    byte[] sessionID = TlsUtilities.ReadOpaque8(inStr);
                    if (sessionID.Length > 32)
                    {
                        this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                    }

                    this.tlsClient.NotifySessionID(sessionID);

                    /*
                     * Find out which CipherSuite the server has chosen and check that
                     * it was one of the offered ones.
                     */
                    CipherSuite selectedCipherSuite = (CipherSuite)TlsUtilities.ReadUint16(inStr);
                    if (!ArrayContains(offeredCipherSuites, selectedCipherSuite) ||
                        selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
                    {
                        this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                    }

                    this.tlsClient.NotifySelectedCipherSuite(selectedCipherSuite);

                    /*
                     * Find out which CompressionMethod the server has chosen and check that
                     * it was one of the offered ones.
                     */
                    CompressionMethod selectedCompressionMethod = (CompressionMethod)TlsUtilities.ReadUint8(inStr);
                    if (!ArrayContains(offeredCompressionMethods, selectedCompressionMethod))
                    {
                        this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                    }

                    this.tlsClient.NotifySelectedCompressionMethod(selectedCompressionMethod);

                    /*
                     * RFC3546 2.2 The extended server hello message format MAY be
                     * sent in place of the server hello message when the client has
                     * requested extended functionality via the extended client hello
                     * message specified in Section 2.1.
                     * ...
                     * Note that the extended server hello message is only sent in response
                     * to an extended client hello message.  This prevents the possibility
                     * that the extended server hello message could "break" existing TLS 1.0
                     * clients.
                     */

                    /*
                     * 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.
                     */

                    // ExtensionType -> byte[]
                    IDictionary serverExtensions = Platform.CreateHashtable();

                    if (inStr.Position < inStr.Length)
                    {
                        // Process extensions from extended server hello
                        byte[] extBytes = TlsUtilities.ReadOpaque16(inStr);

                        MemoryStream ext = new MemoryStream(extBytes, false);
                        while (ext.Position < ext.Length)
                        {
                            ExtensionType extType  = (ExtensionType)TlsUtilities.ReadUint16(ext);
                            byte[]        extValue = TlsUtilities.ReadOpaque16(ext);

                            // Note: RFC 5746 makes a special case for EXT_RenegotiationInfo
                            if (extType != ExtensionType.renegotiation_info &&
                                !clientExtensions.Contains(extType))
                            {
                                /*
                                 * RFC 3546 2.3
                                 * Note that for all extension types (including those defined in
                                 * future), the extension type MUST NOT appear in the extended server
                                 * hello unless the same extension type appeared in the corresponding
                                 * client hello.  Thus clients MUST abort the handshake if they receive
                                 * an extension type in the extended server hello that they did not
                                 * request in the associated (extended) client hello.
                                 */
                                this.FailWithError(AlertLevel.fatal, AlertDescription.unsupported_extension);
                            }

                            if (serverExtensions.Contains(extType))
                            {
                                /*
                                 * RFC 3546 2.3
                                 * Also note that when multiple extensions of different types are
                                 * present in the extended client hello or the extended server hello,
                                 * the extensions may appear in any order. There MUST NOT be more than
                                 * one extension of the same type.
                                 */
                                this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                            }

                            serverExtensions.Add(extType, extValue);
                        }
                    }

                    AssertEmpty(inStr);

                    /*
                     * RFC 5746 3.4. When a ServerHello is received, the client MUST check if it
                     * includes the "renegotiation_info" extension:
                     */
                    {
                        bool secure_negotiation = serverExtensions.Contains(ExtensionType.renegotiation_info);

                        /*
                         * If the extension is present, set the secure_renegotiation flag
                         * to TRUE.  The client MUST then verify that the length of the
                         * "renegotiated_connection" field is zero, and if it is not, MUST
                         * abort the handshake (by sending a fatal handshake_failure
                         * alert).
                         */
                        if (secure_negotiation)
                        {
                            byte[] renegExtValue = (byte[])serverExtensions[ExtensionType.renegotiation_info];

                            if (!Arrays.ConstantTimeAreEqual(renegExtValue,
                                                             CreateRenegotiationInfo(emptybuf)))
                            {
                                this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                            }
                        }

                        tlsClient.NotifySecureRenegotiation(secure_negotiation);
                    }

                    if (clientExtensions != null)
                    {
                        tlsClient.ProcessServerExtensions(serverExtensions);
                    }

                    this.keyExchange = tlsClient.GetKeyExchange();

                    connection_state = CS_SERVER_HELLO_RECEIVED;
                    break;

                default:
                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                    break;
                }
                break;

            case HandshakeType.server_hello_done:
                switch (connection_state)
                {
                case CS_SERVER_CERTIFICATE_RECEIVED:
                case CS_SERVER_KEY_EXCHANGE_RECEIVED:
                case CS_CERTIFICATE_REQUEST_RECEIVED:

                    // NB: Original code used case label fall-through
                    if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
                    {
                        // There was no server key exchange message; check it's OK
                        this.keyExchange.SkipServerKeyExchange();
                    }

                    AssertEmpty(inStr);

                    connection_state = CS_SERVER_HELLO_DONE_RECEIVED;

                    TlsCredentials clientCreds = null;
                    if (certificateRequest == null)
                    {
                        this.keyExchange.SkipClientCredentials();
                    }
                    else
                    {
                        clientCreds = this.authentication.GetClientCredentials(certificateRequest);

                        Certificate clientCert;
                        if (clientCreds == null)
                        {
                            this.keyExchange.SkipClientCredentials();
                            clientCert = Certificate.EmptyChain;
                        }
                        else
                        {
                            this.keyExchange.ProcessClientCredentials(clientCreds);
                            clientCert = clientCreds.Certificate;
                        }

                        SendClientCertificate(clientCert);
                    }

                    /*
                     * Send the client key exchange message, depending on the key
                     * exchange we are using in our CipherSuite.
                     */
                    SendClientKeyExchange();

                    connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;

                    if (clientCreds != null && clientCreds is TlsSignerCredentials)
                    {
                        TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds;
                        byte[] md5andsha1 = rs.GetCurrentHash();
                        byte[] clientCertificateSignature = signerCreds.GenerateCertificateSignature(
                            md5andsha1);
                        SendCertificateVerify(clientCertificateSignature);

                        connection_state = CS_CERTIFICATE_VERIFY_SEND;
                    }

                    /*
                     * Now, we send change cipher state
                     */
                    byte[] cmessage = new byte[1];
                    cmessage[0] = 1;
                    rs.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length);

                    connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;

                    /*
                     * Calculate the master_secret
                     */
                    byte[] pms = this.keyExchange.GeneratePremasterSecret();

                    securityParameters.masterSecret = TlsUtilities.PRF(pms, "master secret",
                                                                       TlsUtilities.Concat(securityParameters.clientRandom, securityParameters.serverRandom),
                                                                       48);

                    // TODO Is there a way to ensure the data is really overwritten?

                    /*
                     * RFC 2246 8.1. The pre_master_secret should be deleted from
                     * memory once the master_secret has been computed.
                     */
                    Array.Clear(pms, 0, pms.Length);

                    /*
                     * Initialize our cipher suite
                     */
                    rs.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher());

                    /*
                     * Send our finished message.
                     */
                    byte[] clientVerifyData = TlsUtilities.PRF(securityParameters.masterSecret,
                                                               "client finished", rs.GetCurrentHash(), 12);

                    MemoryStream bos = new MemoryStream();
                    TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos);
                    TlsUtilities.WriteOpaque24(clientVerifyData, bos);
                    byte[] message = bos.ToArray();

                    rs.WriteMessage(ContentType.handshake, message, 0, message.Length);

                    this.connection_state = CS_CLIENT_FINISHED_SEND;
                    break;

                default:
                    this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                    break;
                }
                break;

            case HandshakeType.server_key_exchange:
            {
                switch (connection_state)
                {
                case CS_SERVER_HELLO_RECEIVED:
                case CS_SERVER_CERTIFICATE_RECEIVED:
                {
                    // NB: Original code used case label fall-through
                    if (connection_state == CS_SERVER_HELLO_RECEIVED)
                    {
                        // There was no server certificate message; check it's OK
                        this.keyExchange.SkipServerCertificate();
                        this.authentication = null;
                    }

                    this.keyExchange.ProcessServerKeyExchange(inStr);

                    AssertEmpty(inStr);
                    break;
                }

                default:
                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                    break;
                }

                this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
                break;
            }

            case HandshakeType.certificate_request:
                switch (connection_state)
                {
                case CS_SERVER_CERTIFICATE_RECEIVED:
                case CS_SERVER_KEY_EXCHANGE_RECEIVED:
                {
                    // NB: Original code used case label fall-through
                    if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
                    {
                        // There was no server key exchange message; check it's OK
                        this.keyExchange.SkipServerKeyExchange();
                    }

                    if (this.authentication == null)
                    {
                        /*
                         * RFC 2246 7.4.4. It is a fatal handshake_failure alert
                         * for an anonymous server to request client identification.
                         */
                        this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
                    }

                    int numTypes = TlsUtilities.ReadUint8(inStr);
                    ClientCertificateType[] certificateTypes = new ClientCertificateType[numTypes];
                    for (int i = 0; i < numTypes; ++i)
                    {
                        certificateTypes[i] = (ClientCertificateType)TlsUtilities.ReadUint8(inStr);
                    }

                    byte[] authorities = TlsUtilities.ReadOpaque16(inStr);

                    AssertEmpty(inStr);

                    IList authorityDNs = Platform.CreateArrayList();

                    MemoryStream bis = new MemoryStream(authorities, false);
                    while (bis.Position < bis.Length)
                    {
                        byte[] dnBytes = TlsUtilities.ReadOpaque16(bis);
                        // TODO Switch to X500Name when available
                        authorityDNs.Add(X509Name.GetInstance(Asn1Object.FromByteArray(dnBytes)));
                    }

                    this.certificateRequest = new CertificateRequest(certificateTypes,
                                                                     authorityDNs);
                    this.keyExchange.ValidateCertificateRequest(this.certificateRequest);

                    break;
                }

                default:
                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                    break;
                }

                this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
                break;

            case HandshakeType.hello_request:
                /*
                 * RFC 2246 7.4.1.1 Hello request
                 * This message will be ignored by the client if the client is currently
                 * negotiating a session. This message may be ignored by the client if it
                 * does not wish to renegotiate a session, or the client may, if it wishes,
                 * respond with a no_renegotiation alert.
                 */
                if (connection_state == CS_DONE)
                {
                    // Renegotiation not supported yet
                    SendAlert(AlertLevel.warning, AlertDescription.no_renegotiation);
                }
                break;

            case HandshakeType.client_key_exchange:
            case HandshakeType.certificate_verify:
            case HandshakeType.client_hello:
            default:
                // We do not support this!
                this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
                break;
            }
        }