summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2014-08-24 16:47:32 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2014-08-24 16:47:32 +0700
commitda656890c709963fb112d4813a2542302994bb35 (patch)
tree9cf32b3ce55555eea21fc95fe8f19844f3134550
parentAdd TLS test data from Java API (diff)
downloadBouncyCastle.NET-ed25519-da656890c709963fb112d4813a2542302994bb35.tar.xz
Finish initial porting of TLS 1.2 client from Java API
-rw-r--r--crypto/crypto.csproj30
-rw-r--r--crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs145
-rw-r--r--crypto/src/crypto/agreement/DHStandardGroups.cs206
-rw-r--r--crypto/src/crypto/tls/AbstractTlsContext.cs18
-rw-r--r--crypto/src/crypto/tls/CertificateRequest.cs14
-rw-r--r--crypto/src/crypto/tls/DefaultTlsServer.cs548
-rw-r--r--crypto/src/crypto/tls/RecordStream.cs356
-rw-r--r--crypto/src/crypto/tls/TlsClientProtocol.cs861
-rw-r--r--crypto/src/crypto/tls/TlsEccUtilities.cs4
-rw-r--r--crypto/src/crypto/tls/TlsProtocol.cs1040
-rw-r--r--crypto/src/crypto/tls/TlsProtocolHandler.cs1158
-rw-r--r--crypto/src/crypto/tls/TlsStream.cs5
-rw-r--r--crypto/src/crypto/tls/TlsUtilities.cs34
-rw-r--r--crypto/src/util/Arrays.cs13
-rw-r--r--crypto/test/src/crypto/tls/test/MockTlsClient.cs158
-rw-r--r--crypto/test/src/crypto/tls/test/TlsClientTest.cs66
-rw-r--r--crypto/test/src/crypto/tls/test/TlsTestUtilities.cs137
17 files changed, 3423 insertions, 1370 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 41980b256..cb4710669 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -3089,6 +3089,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\agreement\DHStandardGroups.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\agreement\ECDHBasicAgreement.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4449,6 +4454,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\DefaultTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\DefaultTlsSignerCredentials.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4699,6 +4709,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsClientProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsCompression.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -10896,6 +10911,21 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\crypto\tls\test\MockTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\crypto\tls\test\TlsClientTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\crypto\tls\test\TlsTestUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\math\ec\test\AllTests.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs b/crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs
index dbb07c744..721299105 100644
--- a/crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs
+++ b/crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs
@@ -18,7 +18,21 @@ namespace Org.BouncyCastle.Asn1.Pkcs
         private readonly BigInteger	exponent2;
         private readonly BigInteger	coefficient;
 
-		public RsaPrivateKeyStructure(
+        public static RsaPrivateKeyStructure GetInstance(Asn1TaggedObject obj, bool isExplicit)
+        {
+            return GetInstance(Asn1Sequence.GetInstance(obj, isExplicit));
+        }
+
+        public static RsaPrivateKeyStructure GetInstance(object obj)
+        {
+            if (obj == null)
+                return null;
+            if (obj is RsaPrivateKeyStructure)
+                return (RsaPrivateKeyStructure)obj;
+            return new RsaPrivateKeyStructure(Asn1Sequence.GetInstance(obj));
+        }
+
+        public RsaPrivateKeyStructure(
             BigInteger	modulus,
             BigInteger	publicExponent,
             BigInteger	privateExponent,
@@ -38,64 +52,65 @@ namespace Org.BouncyCastle.Asn1.Pkcs
             this.coefficient = coefficient;
         }
 
-		public RsaPrivateKeyStructure(
+        [Obsolete("Use 'GetInstance' method(s) instead")]
+        public RsaPrivateKeyStructure(
             Asn1Sequence seq)
         {
-			BigInteger version = ((DerInteger) seq[0]).Value;
-			if (version.IntValue != 0)
+            BigInteger version = ((DerInteger) seq[0]).Value;
+            if (version.IntValue != 0)
                 throw new ArgumentException("wrong version for RSA private key");
 
-			modulus = ((DerInteger) seq[1]).Value;
-			publicExponent = ((DerInteger) seq[2]).Value;
-			privateExponent = ((DerInteger) seq[3]).Value;
-			prime1 = ((DerInteger) seq[4]).Value;
-			prime2 = ((DerInteger) seq[5]).Value;
-			exponent1 = ((DerInteger) seq[6]).Value;
-			exponent2 = ((DerInteger) seq[7]).Value;
-			coefficient = ((DerInteger) seq[8]).Value;
-		}
-
-		public BigInteger Modulus
-		{
-			get { return modulus; }
-		}
-
-		public BigInteger PublicExponent
-		{
-			get { return publicExponent; }
-		}
-
-		public BigInteger PrivateExponent
-		{
-			get { return privateExponent; }
-		}
-
-		public BigInteger Prime1
-		{
-			get { return prime1; }
-		}
-
-		public BigInteger Prime2
-		{
-			get { return prime2; }
-		}
-
-		public BigInteger Exponent1
-		{
-			get { return exponent1; }
-		}
-
-		public BigInteger Exponent2
-		{
-			get { return exponent2; }
-		}
-
-		public BigInteger Coefficient
-		{
-			get { return coefficient; }
-		}
-
-		/**
+            modulus = ((DerInteger) seq[1]).Value;
+            publicExponent = ((DerInteger) seq[2]).Value;
+            privateExponent = ((DerInteger) seq[3]).Value;
+            prime1 = ((DerInteger) seq[4]).Value;
+            prime2 = ((DerInteger) seq[5]).Value;
+            exponent1 = ((DerInteger) seq[6]).Value;
+            exponent2 = ((DerInteger) seq[7]).Value;
+            coefficient = ((DerInteger) seq[8]).Value;
+        }
+
+        public BigInteger Modulus
+        {
+            get { return modulus; }
+        }
+
+        public BigInteger PublicExponent
+        {
+            get { return publicExponent; }
+        }
+
+        public BigInteger PrivateExponent
+        {
+            get { return privateExponent; }
+        }
+
+        public BigInteger Prime1
+        {
+            get { return prime1; }
+        }
+
+        public BigInteger Prime2
+        {
+            get { return prime2; }
+        }
+
+        public BigInteger Exponent1
+        {
+            get { return exponent1; }
+        }
+
+        public BigInteger Exponent2
+        {
+            get { return exponent2; }
+        }
+
+        public BigInteger Coefficient
+        {
+            get { return coefficient; }
+        }
+
+        /**
          * This outputs the key in Pkcs1v2 format.
          * <pre>
          *      RsaPrivateKey ::= Sequence {
@@ -116,16 +131,16 @@ namespace Org.BouncyCastle.Asn1.Pkcs
          */
         public override Asn1Object ToAsn1Object()
         {
-			return new DerSequence(
-				new DerInteger(0), // version
-				new DerInteger(Modulus),
-				new DerInteger(PublicExponent),
-				new DerInteger(PrivateExponent),
-				new DerInteger(Prime1),
-				new DerInteger(Prime2),
-				new DerInteger(Exponent1),
-				new DerInteger(Exponent2),
-				new DerInteger(Coefficient));
+            return new DerSequence(
+                new DerInteger(0), // version
+                new DerInteger(Modulus),
+                new DerInteger(PublicExponent),
+                new DerInteger(PrivateExponent),
+                new DerInteger(Prime1),
+                new DerInteger(Prime2),
+                new DerInteger(Exponent1),
+                new DerInteger(Exponent2),
+                new DerInteger(Coefficient));
         }
     }
 }
diff --git a/crypto/src/crypto/agreement/DHStandardGroups.cs b/crypto/src/crypto/agreement/DHStandardGroups.cs
new file mode 100644
index 000000000..6c46b60de
--- /dev/null
+++ b/crypto/src/crypto/agreement/DHStandardGroups.cs
@@ -0,0 +1,206 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Crypto.Agreement
+{
+    /// <summary>Standard Diffie-Hellman groups from various IETF specifications.</summary>
+    public class DHStandardGroups
+    {
+        private static DHParameters FromPG(string hexP, string hexG)
+        {
+            BigInteger p = new BigInteger(1, Hex.Decode(hexP));
+            BigInteger g = new BigInteger(1, Hex.Decode(hexG));
+            return new DHParameters(p, g);
+        }
+
+        private static DHParameters FromPGQ(string hexP, string hexG, string hexQ)
+        {
+            BigInteger p = new BigInteger(1, Hex.Decode(hexP));
+            BigInteger g = new BigInteger(1, Hex.Decode(hexG));
+            BigInteger q = new BigInteger(1, Hex.Decode(hexQ));
+            return new DHParameters(p, g, q);
+        }
+
+        /*
+         * RFC 2409
+         */
+        private static readonly string rfc2409_768_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF";
+        private static readonly string rfc2409_768_g = "02";
+        public static readonly DHParameters rfc2409_768 = FromPG(rfc2409_768_p, rfc2409_768_g);
+
+        private static readonly string rfc2409_1024_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"
+            + "FFFFFFFFFFFFFFFF";
+        private static readonly string rfc2409_1024_g = "02";
+        public static readonly DHParameters rfc2409_1024 = FromPG(rfc2409_1024_p, rfc2409_1024_g);
+
+        /*
+         * RFC 3526
+         */
+        private static readonly string rfc3526_1536_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
+        private static readonly string rfc3526_1536_g = "02";
+        public static readonly DHParameters rfc3526_1536 = FromPG(rfc3526_1536_p, rfc3526_1536_g);
+
+        private static readonly string rfc3526_2048_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF";
+        private static readonly string rfc3526_2048_g = "02";
+        public static readonly DHParameters rfc3526_2048 = FromPG(rfc3526_2048_p, rfc3526_2048_g);
+
+        private static readonly string rfc3526_3072_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+            + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+            + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+            + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
+        private static readonly string rfc3526_3072_g = "02";
+        public static readonly DHParameters rfc3526_3072 = FromPG(rfc3526_3072_p, rfc3526_3072_g);
+
+        private static readonly string rfc3526_4096_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+            + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+            + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+            + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+            + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+            + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+            + "FFFFFFFFFFFFFFFF";
+        private static readonly string rfc3526_4096_g = "02";
+        public static readonly DHParameters rfc3526_4096 = FromPG(rfc3526_4096_p, rfc3526_4096_g);
+
+        private static readonly string rfc3526_6144_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+            + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+            + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+            + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+            + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+            + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+            + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+            + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+            + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+            + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+            + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+            + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+            + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+            + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+            + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+            + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+            + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+            + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+            + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+            + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+            + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+            + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+            + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+            + "6DCC4024FFFFFFFFFFFFFFFF";
+        private static readonly string rfc3526_6144_g = "02";
+        public static readonly DHParameters rfc3526_6144 = FromPG(rfc3526_6144_p, rfc3526_6144_g);
+
+        private static readonly string rfc3526_8192_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+            + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+            + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+            + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+            + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+            + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+            + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831"
+            + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF"
+            + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3"
+            + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328"
+            + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE"
+            + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300"
+            + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+            + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A"
+            + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
+            + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
+            + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF";
+        private static readonly string rfc3526_8192_g = "02";
+        public static readonly DHParameters rfc3526_8192 = FromPG(rfc3526_8192_p, rfc3526_8192_g);
+
+        /*
+         * RFC 4306
+         */
+        public static readonly DHParameters rfc4306_768 = rfc2409_768;
+        public static readonly DHParameters rfc4306_1024 = rfc2409_1024;
+
+        /*
+         * RFC 5114
+         */
+        private static readonly string rfc5114_1024_160_p = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6"
+            + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0" + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70"
+            + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0" + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708"
+            + "DF1FB2BC2E4A4371";
+        private static readonly string rfc5114_1024_160_g = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F"
+            + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213" + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1"
+            + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A" + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24"
+            + "855E6EEB22B3B2E5";
+        private static readonly string rfc5114_1024_160_q = "F518AA8781A8DF278ABA4E7D64B7CB9D49462353";
+        public static readonly DHParameters rfc5114_1024_160 = FromPGQ(rfc5114_1024_160_p, rfc5114_1024_160_g,
+            rfc5114_1024_160_q);
+
+        private static readonly string rfc5114_2048_224_p = "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1"
+            + "B54B1597B61D0A75E6FA141DF95A56DBAF9A3C407BA1DF15" + "EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC212"
+            + "9037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207" + "C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708"
+            + "B3BF8A317091883681286130BC8985DB1602E714415D9330" + "278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486D"
+            + "CDF93ACC44328387315D75E198C641A480CD86A1B9E587E8" + "BE60E69CC928B2B9C52172E413042E9B23F10B0E16E79763"
+            + "C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71" + "CF9DE5384E71B81C0AC4DFFE0C10E64F";
+        private static readonly string rfc5114_2048_224_g = "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF"
+            + "74866A08CFE4FFE3A6824A4E10B9A6F0DD921F01A70C4AFA" + "AB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7"
+            + "C17669101999024AF4D027275AC1348BB8A762D0521BC98A" + "E247150422EA1ED409939D54DA7460CDB5F6C6B250717CBE"
+            + "F180EB34118E98D119529A45D6F834566E3025E316A330EF" + "BB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB"
+            + "10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381" + "B539CCE3409D13CD566AFBB48D6C019181E1BCFE94B30269"
+            + "EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC0179" + "81BC087F2A7065B384B890D3191F2BFA";
+        private static readonly string rfc5114_2048_224_q = "801C0D34C58D93FE997177101F80535A4738CEBCBF389A99B36371EB";
+        public static readonly DHParameters rfc5114_2048_224 = FromPGQ(rfc5114_2048_224_p, rfc5114_2048_224_g,
+            rfc5114_2048_224_q);
+
+        private static readonly string rfc5114_2048_256_p = "87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F2"
+            + "5D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA30" + "16C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD"
+            + "5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B" + "6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C"
+            + "4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0E" + "F13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D9"
+            + "67E144E5140564251CCACB83E6B486F6B3CA3F7971506026" + "C0B857F689962856DED4010ABD0BE621C3A3960A54E710C3"
+            + "75F26375D7014103A4B54330C198AF126116D2276E11715F" + "693877FAD7EF09CADB094AE91E1A1597";
+        private static readonly string rfc5114_2048_256_g = "3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF2054"
+            + "07F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555" + "BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18"
+            + "A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B" + "777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC83"
+            + "1D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55" + "A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14"
+            + "C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915" + "B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6"
+            + "184B523D1DB246C32F63078490F00EF8D647D148D4795451" + "5E2327CFEF98C582664B4C0F6CC41659";
+        private static readonly string rfc5114_2048_256_q = "8CF83642A709A097B447997640129DA299B1A47D1EB3750B"
+            + "A308B0FE64F5FBD3";
+        public static readonly DHParameters rfc5114_2048_256 = FromPGQ(rfc5114_2048_256_p, rfc5114_2048_256_g,
+            rfc5114_2048_256_q);
+
+        /*
+         * RFC 5996
+         */
+        public static readonly DHParameters rfc5996_768 = rfc4306_768;
+        public static readonly DHParameters rfc5996_1024 = rfc4306_1024;
+    }
+}
diff --git a/crypto/src/crypto/tls/AbstractTlsContext.cs b/crypto/src/crypto/tls/AbstractTlsContext.cs
index 7a7e636d2..6c663f54d 100644
--- a/crypto/src/crypto/tls/AbstractTlsContext.cs
+++ b/crypto/src/crypto/tls/AbstractTlsContext.cs
@@ -58,19 +58,31 @@ namespace Org.BouncyCastle.Crypto.Tls
         public virtual ProtocolVersion ClientVersion
         {
             get { return mClientVersion; }
-            set { this.mClientVersion = value; }
+        }
+
+        internal virtual void SetClientVersion(ProtocolVersion clientVersion)
+        {
+            this.mClientVersion = clientVersion;
         }
 
         public virtual ProtocolVersion ServerVersion
         {
             get { return mServerVersion; }
-            set { this.mServerVersion = value; }
+        }
+
+        internal virtual void SetServerVersion(ProtocolVersion serverVersion)
+        {
+            this.mServerVersion = serverVersion;
         }
 
         public virtual TlsSession ResumableSession
         {
             get { return mSession; }
-            set { this.mSession = value; }
+        }
+
+        internal virtual void SetResumableSession(TlsSession session)
+        {
+            this.mSession = session;
         }
 
         public virtual object UserObject
diff --git a/crypto/src/crypto/tls/CertificateRequest.cs b/crypto/src/crypto/tls/CertificateRequest.cs
index 100398ffb..f3dcb3bbd 100644
--- a/crypto/src/crypto/tls/CertificateRequest.cs
+++ b/crypto/src/crypto/tls/CertificateRequest.cs
@@ -123,8 +123,7 @@ namespace Org.BouncyCastle.Crypto.Tls
          * @return a {@link CertificateRequest} object.
          * @throws IOException
          */
-        public static CertificateRequest Parse(//TlsContext context,
-            Stream input)
+        public static CertificateRequest Parse(TlsContext context, Stream input)
         {
             int numTypes = TlsUtilities.ReadUint8(input);
             byte[] certificateTypes = new byte[numTypes];
@@ -133,13 +132,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 certificateTypes[i] = TlsUtilities.ReadUint8(input);
             }
 
-            // TODO Add TLS 1.2 support here
             IList supportedSignatureAlgorithms = null;
-            //if (TlsUtilities.IsTLSv12(context))
-            //{
-            //    // TODO Check whether SignatureAlgorithm.anonymous is allowed here
-            //    supportedSignatureAlgorithms = TlsUtilities.ParseSupportedSignatureAlgorithms(false, input);
-            //}
+            if (TlsUtilities.IsTlsV12(context))
+            {
+                // TODO Check whether SignatureAlgorithm.anonymous is allowed here
+                supportedSignatureAlgorithms = TlsUtilities.ParseSupportedSignatureAlgorithms(false, input);
+            }
 
             IList certificateAuthorities = Platform.CreateArrayList();
             byte[] certAuthData = TlsUtilities.ReadOpaque16(input);
diff --git a/crypto/src/crypto/tls/DefaultTlsServer.cs b/crypto/src/crypto/tls/DefaultTlsServer.cs
new file mode 100644
index 000000000..017ed0d85
--- /dev/null
+++ b/crypto/src/crypto/tls/DefaultTlsServer.cs
@@ -0,0 +1,548 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public abstract class DefaultTlsServer
+        :   AbstractTlsServer
+    {
+        public DefaultTlsServer()
+            :   base()
+        {
+        }
+
+        public DefaultTlsServer(TlsCipherFactory cipherFactory)
+            :   base(cipherFactory)
+        {
+        }
+
+        protected virtual TlsEncryptionCredentials GetRsaEncryptionCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected virtual TlsSignerCredentials GetRsaSignerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected virtual DHParameters GetDHParameters()
+        {
+            return DHStandardGroups.rfc5114_1024_160;
+        }
+
+        protected override int[] GetCipherSuites()
+        {
+            return new int[]
+            {
+                CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+                CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+                CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+                CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+                CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
+                CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+                CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
+                CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+                CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+                CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+            };
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            switch (mSelectedCipherSuite)
+            {
+            case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+            case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+                return GetRsaEncryptionCredentials();
+
+            case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+                return GetRsaSignerCredentials();
+
+            default:
+                /*
+                 * Note: internal error here; selected a key exchange we don't implement!
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsKeyExchange GetKeyExchange()
+        {
+            switch (mSelectedCipherSuite)
+            {
+            case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+                return CreateDHKeyExchange(KeyExchangeAlgorithm.DH_DSS);
+
+            case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+                return CreateDHKeyExchange(KeyExchangeAlgorithm.DH_RSA);
+
+            case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+                return CreateDheKeyExchange(KeyExchangeAlgorithm.DHE_DSS);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+                return CreateDheKeyExchange(KeyExchangeAlgorithm.DHE_RSA);
+
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+                return CreateECDHKeyExchange(KeyExchangeAlgorithm.ECDH_ECDSA);
+
+            case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+                return CreateECDHKeyExchange(KeyExchangeAlgorithm.ECDH_RSA);
+
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+                return CreateECDheKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA);
+
+            case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+                return CreateECDheKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA);
+
+            case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+            case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+                return createRSAKeyExchange();
+
+            default:
+                /*
+                 * Note: internal error here; selected a key exchange we don't implement!
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsCipher GetCipher()
+        {
+            switch (mSelectedCipherSuite)
+            {
+            case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.cls_3DES_EDE_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AEAD_CHACHA20_POLY1305, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_CBC, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_CBC, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_CBC, MacAlgorithm.hmac_sha384);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.ESTREAM_SALSA20, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_md5);
+
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.RC4_128, MacAlgorithm.hmac_md5);
+
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.RC4_128, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.SALSA20, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.SEED_CBC, MacAlgorithm.hmac_sha1);
+
+            default:
+                /*
+                 * Note: internal error here; selected a cipher suite we don't implement!
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        protected virtual TlsKeyExchange CreateDHKeyExchange(int keyExchange)
+        {
+            return new TlsDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, GetDHParameters());
+        }
+
+        protected virtual TlsKeyExchange CreateDheKeyExchange(int keyExchange)
+        {
+            return new TlsDheKeyExchange(keyExchange, mSupportedSignatureAlgorithms, GetDHParameters());
+        }
+
+        protected virtual TlsKeyExchange CreateECDHKeyExchange(int keyExchange)
+        {
+            return new TlsECDHKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats,
+                mServerECPointFormats);
+        }
+
+        protected virtual TlsKeyExchange CreateECDheKeyExchange(int keyExchange)
+        {
+            return new TlsECDheKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mNamedCurves, mClientECPointFormats,
+                mServerECPointFormats);
+        }
+
+        protected virtual TlsKeyExchange createRSAKeyExchange()
+        {
+            return new TlsRsaKeyExchange(mSupportedSignatureAlgorithms);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/RecordStream.cs b/crypto/src/crypto/tls/RecordStream.cs
index b71416c10..db5b158bc 100644
--- a/crypto/src/crypto/tls/RecordStream.cs
+++ b/crypto/src/crypto/tls/RecordStream.cs
@@ -3,171 +3,331 @@ using System.IO;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
-    /// <summary>An implementation of the TLS 1.0 record layer.</summary>
+    /// <summary>An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3.</summary>
     internal class RecordStream
     {
-        private TlsProtocolHandler handler;
-        private Stream inStr;
-        private Stream outStr;
-        private CombinedHash hash;
-        private TlsCompression readCompression = null;
-        private TlsCompression writeCompression = null;
-        private TlsCipher readCipher = null;
-        private TlsCipher writeCipher = null;
-        private long readSeqNo = 0, writeSeqNo = 0;
-        private MemoryStream buffer = new MemoryStream();
+        private const int DEFAULT_PLAINTEXT_LIMIT = (1 << 14);
 
-        internal RecordStream(
-            TlsProtocolHandler	handler,
-            Stream				inStr,
-            Stream				outStr)
+        private TlsProtocol mHandler;
+        private Stream mInput;
+        private Stream mOutput;
+        private TlsCompression mPendingCompression = null, mReadCompression = null, mWriteCompression = null;
+        private TlsCipher mPendingCipher = null, mReadCipher = null, mWriteCipher = null;
+        private long mReadSeqNo = 0, mWriteSeqNo = 0;
+        private MemoryStream mBuffer = new MemoryStream();
+
+        private TlsHandshakeHash mHandshakeHash = null;
+
+        private ProtocolVersion mReadVersion = null, mWriteVersion = null;
+        private bool mRestrictReadVersion = true;
+
+        private int mPlaintextLimit, mCompressedLimit, mCiphertextLimit;
+
+        internal RecordStream(TlsProtocol handler, Stream input, Stream output)
+        {
+            this.mHandler = handler;
+            this.mInput = input;
+            this.mOutput = output;
+            this.mReadCompression = new TlsNullCompression();
+            this.mWriteCompression = this.mReadCompression;
+        }
+
+        internal virtual void Init(TlsContext context)
+        {
+            this.mReadCipher = new TlsNullCipher(context);
+            this.mWriteCipher = this.mReadCipher;
+            this.mHandshakeHash = new DeferredHash();
+            this.mHandshakeHash.Init(context);
+
+            SetPlaintextLimit(DEFAULT_PLAINTEXT_LIMIT);
+        }
+
+        internal virtual int GetPlaintextLimit()
+        {
+            return mPlaintextLimit;
+        }
+
+        internal virtual void SetPlaintextLimit(int plaintextLimit)
+        {
+            this.mPlaintextLimit = plaintextLimit;
+            this.mCompressedLimit = this.mPlaintextLimit + 1024;
+            this.mCiphertextLimit = this.mCompressedLimit + 1024;
+        }
+
+        internal virtual ProtocolVersion ReadVersion
         {
-            this.handler = handler;
-            this.inStr = inStr;
-            this.outStr = outStr;
-            this.readCompression = new TlsNullCompression();
-            this.writeCompression = this.readCompression;
+            get { return mReadVersion; }
+            set { this.mReadVersion = value; }
         }
 
-        internal void Init(TlsContext context)
+        internal virtual void SetWriteVersion(ProtocolVersion writeVersion)
         {
-            this.readCipher = new TlsNullCipher(context);
-            this.writeCipher = this.readCipher;
-            this.hash = new CombinedHash();
+            this.mWriteVersion = writeVersion;
         }
 
-        internal void ClientCipherSpecDecided(TlsCompression tlsCompression, TlsCipher tlsCipher)
+        /**
+         * RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the
+         * record layer version number (TLSPlaintext.version) should contain when sending ClientHello
+         * (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers
+         * compliant with this specification MUST accept any value {03,XX} as the record layer version
+         * number for ClientHello."
+         */
+        internal virtual void SetRestrictReadVersion(bool enabled)
         {
-            this.writeCompression = tlsCompression;
-            this.writeCipher = tlsCipher;
-            this.writeSeqNo = 0;
+            this.mRestrictReadVersion = enabled;
         }
 
-        internal void ServerClientSpecReceived()
+        internal virtual void SetPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher)
         {
-            this.readCompression = this.writeCompression;
-            this.readCipher = this.writeCipher;
-            this.readSeqNo = 0;
+            this.mPendingCompression = tlsCompression;
+            this.mPendingCipher = tlsCipher;
         }
 
-        public void ReadData()
+        internal virtual void SentWriteCipherSpec()
         {
-            byte contentType = TlsUtilities.ReadUint8(inStr);
-            TlsUtilities.CheckVersion(inStr);
-            int size = TlsUtilities.ReadUint16(inStr);
-            byte[] buf = DecodeAndVerify(contentType, inStr, size);
-            handler.ProcessData(contentType, buf, 0, buf.Length);
+            if (mPendingCompression == null || mPendingCipher == null)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            this.mWriteCompression = this.mPendingCompression;
+            this.mWriteCipher = this.mPendingCipher;
+            this.mWriteSeqNo = 0;
         }
 
-        internal byte[] DecodeAndVerify(
-            byte        contentType,
-            Stream		inStr,
-            int			len)
+        internal virtual void ReceivedReadCipherSpec()
         {
-            byte[] buf = new byte[len];
-            TlsUtilities.ReadFully(buf, inStr);
-            byte[] decoded = readCipher.DecodeCiphertext(readSeqNo++, contentType, buf, 0, buf.Length);
+            if (mPendingCompression == null || mPendingCipher == null)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
 
-            Stream cOut = readCompression.Decompress(buffer);
+            this.mReadCompression = this.mPendingCompression;
+            this.mReadCipher = this.mPendingCipher;
+            this.mReadSeqNo = 0;
+        }
 
-            if (cOut == buffer)
+        internal virtual void FinaliseHandshake()
+        {
+            if (mReadCompression != mPendingCompression || mWriteCompression != mPendingCompression
+                || mReadCipher != mPendingCipher || mWriteCipher != mPendingCipher)
             {
-                return decoded;
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
             }
+            this.mPendingCompression = null;
+            this.mPendingCipher = null;
+        }
 
-            cOut.Write(decoded, 0, decoded.Length);
-            cOut.Flush();
-            byte[] contents = buffer.ToArray();
-            buffer.SetLength(0);
-            return contents;
+        internal virtual bool ReadRecord()
+        {
+            byte[] recordHeader = TlsUtilities.ReadAllOrNothing(5, mInput);
+            if (recordHeader == null)
+                return false;
+
+            byte type = TlsUtilities.ReadUint8(recordHeader, 0);
+
+            /*
+             * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an
+             * unexpected_message alert.
+             */
+            CheckType(type, AlertDescription.unexpected_message);
+
+            if (!mRestrictReadVersion)
+            {
+                int version = TlsUtilities.ReadVersionRaw(recordHeader, 1);
+                if ((version & 0xffffff00) != 0x0300)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+            else
+            {
+                ProtocolVersion version = TlsUtilities.ReadVersion(recordHeader, 1);
+                if (mReadVersion == null)
+                {
+                    mReadVersion = version;
+                }
+                else if (!version.Equals(mReadVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+            }
+
+            int length = TlsUtilities.ReadUint16(recordHeader, 3);
+            byte[] plaintext = DecodeAndVerify(type, mInput, length);
+            mHandler.ProcessRecord(type, plaintext, 0, plaintext.Length);
+            return true;
         }
 
-        internal void WriteMessage(
-            byte    type,
-            byte[]  message,
-            int		offset,
-            int		len)
+        internal virtual byte[] DecodeAndVerify(byte type, Stream input, int len)
         {
+            CheckLength(len, mCiphertextLimit, AlertDescription.record_overflow);
+
+            byte[] buf = TlsUtilities.ReadFully(len, input);
+            byte[] decoded = mReadCipher.DecodeCiphertext(mReadSeqNo++, type, buf, 0, buf.Length);
+
+            CheckLength(decoded.Length, mCompressedLimit, AlertDescription.record_overflow);
+
+            /*
+             * TODO RFC5264 6.2.2. Implementation note: Decompression functions are responsible for
+             * ensuring that messages cannot cause internal buffer overflows.
+             */
+            Stream cOut = mReadCompression.Decompress(mBuffer);
+            if (cOut != mBuffer)
+            {
+                cOut.Write(decoded, 0, decoded.Length);
+                cOut.Flush();
+                decoded = GetBufferContents();
+            }
+
+            /*
+             * RFC 5264 6.2.2. If the decompression function encounters a TLSCompressed.fragment that
+             * would decompress to a length in excess of 2^14 bytes, it should report a fatal
+             * decompression failure error.
+             */
+            CheckLength(decoded.Length, mPlaintextLimit, AlertDescription.decompression_failure);
+
+            /*
+             * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (decoded.Length < 1 && type != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return decoded;
+        }
+
+        internal virtual void WriteRecord(byte type, byte[] plaintext, int plaintextOffset, int plaintextLength)
+        {
+            // Never send anything until a valid ClientHello has been received
+            if (mWriteVersion == null)
+                return;
+
+            /*
+             * RFC 5264 6. Implementations MUST NOT send record types not defined in this document
+             * unless negotiated by some extension.
+             */
+            CheckType(type, AlertDescription.internal_error);
+
+            /*
+             * RFC 5264 6.2.1 The length should not exceed 2^14.
+             */
+            CheckLength(plaintextLength, mPlaintextLimit, AlertDescription.internal_error);
+
+            /*
+             * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (plaintextLength < 1 && type != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
             if (type == ContentType.handshake)
             {
-                UpdateHandshakeData(message, offset, len);
+                UpdateHandshakeData(plaintext, plaintextOffset, plaintextLength);
             }
 
-            Stream cOut = writeCompression.Compress(buffer);
+            Stream cOut = mWriteCompression.Compress(mBuffer);
 
             byte[] ciphertext;
-            if (cOut == buffer)
+            if (cOut == mBuffer)
             {
-                ciphertext = writeCipher.EncodePlaintext(writeSeqNo++, type, message, offset, len);
+                ciphertext = mWriteCipher.EncodePlaintext(mWriteSeqNo++, type, plaintext, plaintextOffset, plaintextLength);
             }
             else
             {
-                cOut.Write(message, offset, len);
+                cOut.Write(plaintext, plaintextOffset, plaintextLength);
                 cOut.Flush();
-                ciphertext = writeCipher.EncodePlaintext(writeSeqNo++, type, buffer.GetBuffer(), 0, (int)buffer.Position);
-                buffer.SetLength(0);
+                byte[] compressed = GetBufferContents();
+
+                /*
+                 * RFC5264 6.2.2. Compression must be lossless and may not increase the content length
+                 * by more than 1024 bytes.
+                 */
+                CheckLength(compressed.Length, plaintextLength + 1024, AlertDescription.internal_error);
+
+                ciphertext = mWriteCipher.EncodePlaintext(mWriteSeqNo++, type, compressed, 0, compressed.Length);
             }
 
-            byte[] writeMessage = new byte[ciphertext.Length + 5];
-            TlsUtilities.WriteUint8((byte)type, writeMessage, 0);
-            TlsUtilities.WriteVersion(writeMessage, 1);
-            TlsUtilities.WriteUint16(ciphertext.Length, writeMessage, 3);
-            Array.Copy(ciphertext, 0, writeMessage, 5, ciphertext.Length);
-            outStr.Write(writeMessage, 0, writeMessage.Length);
-            outStr.Flush();
+            /*
+             * RFC 5264 6.2.3. The length may not exceed 2^14 + 2048.
+             */
+            CheckLength(ciphertext.Length, mCiphertextLimit, AlertDescription.internal_error);
+
+            byte[] record = new byte[ciphertext.Length + 5];
+            TlsUtilities.WriteUint8(type, record, 0);
+            TlsUtilities.WriteVersion(mWriteVersion, record, 1);
+            TlsUtilities.WriteUint16(ciphertext.Length, record, 3);
+            Array.Copy(ciphertext, 0, record, 5, ciphertext.Length);
+            mOutput.Write(record, 0, record.Length);
+            mOutput.Flush();
         }
 
-        internal void UpdateHandshakeData(
-            byte[]	message,
-            int		offset,
-            int		len)
+        internal virtual void NotifyHelloComplete()
         {
-            hash.BlockUpdate(message, offset, len);
+            this.mHandshakeHash = mHandshakeHash.NotifyPrfDetermined();
         }
 
-        internal byte[] GetCurrentHash()
+        internal virtual TlsHandshakeHash HandshakeHash
         {
-            return DoFinal(new CombinedHash(hash));
+            get { return mHandshakeHash; }
         }
 
-        internal void Close()
+        internal virtual TlsHandshakeHash PrepareToFinish()
+        {
+            TlsHandshakeHash result = mHandshakeHash;
+            this.mHandshakeHash = mHandshakeHash.StopTracking();
+            return result;
+        }
+
+        internal virtual void UpdateHandshakeData(byte[] message, int offset, int len)
+        {
+            mHandshakeHash.BlockUpdate(message, offset, len);
+        }
+
+        internal virtual void SafeClose()
         {
-            IOException e = null;
             try
             {
-                inStr.Close();
+                mInput.Close();
             }
-            catch (IOException ex)
+            catch (IOException)
             {
-                e = ex;
             }
 
             try
             {
-                // NB: This is harmless if outStr == inStr
-                outStr.Close();
+                mOutput.Close();
             }
-            catch (IOException ex)
+            catch (IOException)
             {
-                e = ex;
             }
+        }
 
-            if (e != null)
-            {
-                throw e;
-            }
+        internal virtual void Flush()
+        {
+            mOutput.Flush();
+        }
+
+        private byte[] GetBufferContents()
+        {
+            byte[] contents = mBuffer.ToArray();
+            mBuffer.SetLength(0);
+            return contents;
         }
 
-        internal void Flush()
+        private static void CheckType(byte type, byte alertDescription)
         {
-            outStr.Flush();
+            switch (type)
+            {
+            case ContentType.application_data:
+            case ContentType.alert:
+            case ContentType.change_cipher_spec:
+            case ContentType.handshake:
+            case ContentType.heartbeat:
+                break;
+            default:
+                throw new TlsFatalAlert(alertDescription);
+            }
         }
 
-        private static byte[] DoFinal(CombinedHash ch)
+        private static void CheckLength(int length, int limit, byte alertDescription)
         {
-            byte[] bs = new byte[ch.GetDigestSize()];
-            ch.DoFinal(bs, 0);
-            return bs;
+            if (length > limit)
+                throw new TlsFatalAlert(alertDescription);
         }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsClientProtocol.cs b/crypto/src/crypto/tls/TlsClientProtocol.cs
new file mode 100644
index 000000000..e48c92d30
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsClientProtocol.cs
@@ -0,0 +1,861 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class TlsClientProtocol
+        :   TlsProtocol
+    {
+        protected TlsClient mTlsClient = null;
+        internal TlsClientContextImpl mTlsClientContext = null;
+
+        protected byte[] mSelectedSessionID = null;
+
+        protected TlsKeyExchange mKeyExchange = null;
+        protected TlsAuthentication mAuthentication = null;
+
+        protected CertificateStatus mCertificateStatus = null;
+        protected CertificateRequest mCertificateRequest = null;
+
+        public TlsClientProtocol(Stream stream, SecureRandom secureRandom)
+            :   base(stream, secureRandom)
+        {
+        }
+
+        public TlsClientProtocol(Stream input, Stream output, SecureRandom secureRandom)
+            :   base(input, output, secureRandom)
+        {
+        }
+
+        /**
+         * Initiates a TLS handshake in the role of client
+         *
+         * @param tlsClient The {@link TlsClient} to use for the handshake.
+         * @throws IOException If handshake was not successful.
+         */
+        public virtual void Connect(TlsClient tlsClient)
+        {
+            if (tlsClient == null)
+                throw new ArgumentNullException("tlsClient");
+            if (this.mTlsClient != null)
+                throw new InvalidOperationException("'Connect' can only be called once");
+
+            this.mTlsClient = tlsClient;
+
+            this.mSecurityParameters = new SecurityParameters();
+            this.mSecurityParameters.entity = ConnectionEnd.client;
+
+            this.mTlsClientContext = new TlsClientContextImpl(mSecureRandom, mSecurityParameters);
+
+            this.mSecurityParameters.clientRandom = CreateRandomBlock(tlsClient.ShouldUseGmtUnixTime(),
+                mTlsClientContext.NonceRandomGenerator);
+
+            this.mTlsClient.Init(mTlsClientContext);
+            this.mRecordStream.Init(mTlsClientContext);
+
+            TlsSession sessionToResume = tlsClient.GetSessionToResume();
+            if (sessionToResume != null)
+            {
+                SessionParameters sessionParameters = sessionToResume.ExportSessionParameters();
+                if (sessionParameters != null)
+                {
+                    this.mTlsSession = sessionToResume;
+                    this.mSessionParameters = sessionParameters;
+                }
+            }
+
+            SendClientHelloMessage();
+            this.mConnectionState = CS_CLIENT_HELLO;
+
+            CompleteHandshake();
+        }
+
+        protected override void CleanupHandshake()
+        {
+            base.CleanupHandshake();
+
+            this.mSelectedSessionID = null;
+            this.mKeyExchange = null;
+            this.mAuthentication = null;
+            this.mCertificateStatus = null;
+            this.mCertificateRequest = null;
+        }
+
+        protected override TlsContext Context
+        {
+            get { return mTlsClientContext; }
+        }
+
+        internal override AbstractTlsContext ContextAdmin
+        {
+            get { return mTlsClientContext; }
+        }
+
+        protected override TlsPeer Peer
+        {
+            get { return mTlsClient; }
+        }
+
+        protected override void HandleHandshakeMessage(byte type, byte[] data)
+        {
+            MemoryStream buf = new MemoryStream(data, false);
+
+            if (this.mResumedSession)
+            {
+                if (type != HandshakeType.finished || this.mConnectionState != CS_SERVER_HELLO)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                ProcessFinishedMessage(buf);
+                this.mConnectionState = CS_SERVER_FINISHED;
+
+                SendFinishedMessage();
+                this.mConnectionState = CS_CLIENT_FINISHED;
+                this.mConnectionState = CS_END;
+
+                return;
+            }
+
+            switch (type)
+            {
+            case HandshakeType.certificate:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO:
+                case CS_SERVER_SUPPLEMENTAL_DATA:
+                {
+                    if (this.mConnectionState == CS_SERVER_HELLO)
+                    {
+                        HandleSupplementalData(null);
+                    }
+
+                    // Parse the Certificate message and Send to cipher suite
+
+                    this.mPeerCertificate = Certificate.Parse(buf);
+
+                    AssertEmpty(buf);
+
+                    // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+                    if (this.mPeerCertificate == null || this.mPeerCertificate.IsEmpty)
+                    {
+                        this.mAllowCertificateStatus = false;
+                    }
+
+                    this.mKeyExchange.ProcessServerCertificate(this.mPeerCertificate);
+
+                    this.mAuthentication = mTlsClient.GetAuthentication();
+                    this.mAuthentication.NotifyServerCertificate(this.mPeerCertificate);
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.mConnectionState = CS_SERVER_CERTIFICATE;
+                break;
+            }
+            case HandshakeType.certificate_status:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_CERTIFICATE:
+                {
+                    if (!this.mAllowCertificateStatus)
+                    {
+                        /*
+                         * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the
+                         * server MUST have included an extension of type "status_request" with empty
+                         * "extension_data" in the extended server hello..
+                         */
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    this.mCertificateStatus = CertificateStatus.Parse(buf);
+
+                    AssertEmpty(buf);
+
+                    // TODO[RFC 3546] Figure out how to provide this to the client/authentication.
+
+                    this.mConnectionState = CS_CERTIFICATE_STATUS;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_CLIENT_FINISHED:
+                case CS_SERVER_SESSION_TICKET:
+                {
+                    if (this.mConnectionState == CS_CLIENT_FINISHED && this.mExpectSessionTicket)
+                    {
+                        /*
+                         * RFC 5077 3.3. This message MUST be sent if the server included a
+                         * SessionTicket extension in the ServerHello.
+                         */
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    ProcessFinishedMessage(buf);
+                    this.mConnectionState = CS_SERVER_FINISHED;
+                    this.mConnectionState = CS_END;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.server_hello:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_CLIENT_HELLO:
+                {
+                    ReceiveServerHelloMessage(buf);
+                    this.mConnectionState = CS_SERVER_HELLO;
+
+                    if (this.mSecurityParameters.maxFragmentLength >= 0)
+                    {
+                        int plainTextLimit = 1 << (8 + this.mSecurityParameters.maxFragmentLength);
+                        mRecordStream.SetPlaintextLimit(plainTextLimit);
+                    }
+
+                    this.mSecurityParameters.prfAlgorithm = GetPrfAlgorithm(Context,
+                        this.mSecurityParameters.CipherSuite);
+
+                    /*
+                     * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify
+                     * verify_data_length has a verify_data_length equal to 12. This includes all
+                     * existing cipher suites.
+                     */
+                    this.mSecurityParameters.verifyDataLength = 12;
+
+                    this.mRecordStream.NotifyHelloComplete();
+
+                    if (this.mResumedSession)
+                    {
+                        this.mSecurityParameters.masterSecret = Arrays.Clone(this.mSessionParameters.MasterSecret);
+                        this.mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher());
+
+                        SendChangeCipherSpecMessage();
+                    }
+                    else
+                    {
+                        InvalidateSession();
+
+                        if (this.mSelectedSessionID.Length > 0)
+                        {
+                            this.mTlsSession = new TlsSessionImpl(this.mSelectedSessionID, null);
+                        }
+                    }
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.supplemental_data:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO:
+                {
+                    HandleSupplementalData(ReadSupplementalDataMessage(buf));
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.server_hello_done:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO:
+                case CS_SERVER_SUPPLEMENTAL_DATA:
+                case CS_SERVER_CERTIFICATE:
+                case CS_CERTIFICATE_STATUS:
+                case CS_SERVER_KEY_EXCHANGE:
+                case CS_CERTIFICATE_REQUEST:
+                {
+                    if (mConnectionState < CS_SERVER_SUPPLEMENTAL_DATA)
+                    {
+                        HandleSupplementalData(null);
+                    }
+
+                    if (mConnectionState < CS_SERVER_CERTIFICATE)
+                    {
+                        // There was no server certificate message; check it's OK
+                        this.mKeyExchange.SkipServerCredentials();
+                        this.mAuthentication = null;
+                    }
+
+                    if (mConnectionState < CS_SERVER_KEY_EXCHANGE)
+                    {
+                        // There was no server key exchange message; check it's OK
+                        this.mKeyExchange.SkipServerKeyExchange();
+                    }
+
+                    AssertEmpty(buf);
+
+                    this.mConnectionState = CS_SERVER_HELLO_DONE;
+
+                    this.mRecordStream.HandshakeHash.SealHashAlgorithms();
+
+                    IList clientSupplementalData = mTlsClient.GetClientSupplementalData();
+                    if (clientSupplementalData != null)
+                    {
+                        SendSupplementalDataMessage(clientSupplementalData);
+                    }
+                    this.mConnectionState = CS_CLIENT_SUPPLEMENTAL_DATA;
+
+                    TlsCredentials clientCreds = null;
+                    if (mCertificateRequest == null)
+                    {
+                        this.mKeyExchange.SkipClientCredentials();
+                    }
+                    else
+                    {
+                        clientCreds = this.mAuthentication.GetClientCredentials(mCertificateRequest);
+
+                        if (clientCreds == null)
+                        {
+                            this.mKeyExchange.SkipClientCredentials();
+
+                            /*
+                             * 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.
+                             */
+                            SendCertificateMessage(Certificate.EmptyChain);
+                        }
+                        else
+                        {
+                            this.mKeyExchange.ProcessClientCredentials(clientCreds);
+
+                            SendCertificateMessage(clientCreds.Certificate);
+                        }
+                    }
+
+                    this.mConnectionState = CS_CLIENT_CERTIFICATE;
+
+                    /*
+                     * Send the client key exchange message, depending on the key exchange we are using
+                     * in our CipherSuite.
+                     */
+                    SendClientKeyExchangeMessage();
+                    this.mConnectionState = CS_CLIENT_KEY_EXCHANGE;
+
+                    EstablishMasterSecret(Context, mKeyExchange);
+                    mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher());
+
+                    TlsHandshakeHash prepareFinishHash = mRecordStream.PrepareToFinish();
+
+                    if (clientCreds != null && clientCreds is TlsSignerCredentials)
+                    {
+                        TlsSignerCredentials signerCredentials = (TlsSignerCredentials)clientCreds;
+
+                        /*
+                         * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+                         */
+                        SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+                        byte[] hash;
+
+                        if (TlsUtilities.IsTlsV12(Context))
+                        {
+                            signatureAndHashAlgorithm = signerCredentials.SignatureAndHashAlgorithm;
+                            if (signatureAndHashAlgorithm == null)
+                                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                            hash = prepareFinishHash.GetFinalHash(signatureAndHashAlgorithm.Hash);
+                        }
+                        else
+                        {
+                            signatureAndHashAlgorithm = null;
+                            hash = GetCurrentPrfHash(Context, prepareFinishHash, null);
+                        }
+
+                        byte[] signature = signerCredentials.GenerateCertificateSignature(hash);
+                        DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature);
+                        SendCertificateVerifyMessage(certificateVerify);
+
+                        this.mConnectionState = CS_CERTIFICATE_VERIFY;
+                    }
+
+                    SendChangeCipherSpecMessage();
+                    SendFinishedMessage();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+
+                this.mConnectionState = CS_CLIENT_FINISHED;
+                break;
+            }
+            case HandshakeType.server_key_exchange:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_HELLO:
+                case CS_SERVER_SUPPLEMENTAL_DATA:
+                case CS_SERVER_CERTIFICATE:
+                case CS_CERTIFICATE_STATUS:
+                {
+                    if (mConnectionState < CS_SERVER_SUPPLEMENTAL_DATA)
+                    {
+                        HandleSupplementalData(null);
+                    }
+
+                    if (mConnectionState < CS_SERVER_CERTIFICATE)
+                    {
+                        // There was no server certificate message; check it's OK
+                        this.mKeyExchange.SkipServerCredentials();
+                        this.mAuthentication = null;
+                    }
+
+                    this.mKeyExchange.ProcessServerKeyExchange(buf);
+
+                    AssertEmpty(buf);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.mConnectionState = CS_SERVER_KEY_EXCHANGE;
+                break;
+            }
+            case HandshakeType.certificate_request:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_SERVER_CERTIFICATE:
+                case CS_CERTIFICATE_STATUS:
+                case CS_SERVER_KEY_EXCHANGE:
+                {
+                    if (this.mConnectionState != CS_SERVER_KEY_EXCHANGE)
+                    {
+                        // There was no server key exchange message; check it's OK
+                        this.mKeyExchange.SkipServerKeyExchange();
+                    }
+
+                    if (this.mAuthentication == null)
+                    {
+                        /*
+                         * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server
+                         * to request client identification.
+                         */
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+
+                    this.mCertificateRequest = CertificateRequest.Parse(Context, buf);
+
+                    AssertEmpty(buf);
+
+                    this.mKeyExchange.ValidateCertificateRequest(this.mCertificateRequest);
+
+                    /*
+                     * TODO Give the client a chance to immediately select the CertificateVerify hash
+                     * algorithm here to avoid tracking the other hash algorithms unnecessarily?
+                     */
+                    TlsUtilities.TrackHashAlgorithms(this.mRecordStream.HandshakeHash,
+                        this.mCertificateRequest.SupportedSignatureAlgorithms);
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.mConnectionState = CS_CERTIFICATE_REQUEST;
+                break;
+            }
+            case HandshakeType.session_ticket:
+            {
+                switch (this.mConnectionState)
+                {
+                case CS_CLIENT_FINISHED:
+                {
+                    if (!this.mExpectSessionTicket)
+                    {
+                        /*
+                         * RFC 5077 3.3. This message MUST NOT be sent if the server did not include a
+                         * SessionTicket extension in the ServerHello.
+                         */
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    /*
+                     * RFC 5077 3.4. If the client receives a session ticket from the server, then it
+                     * discards any Session ID that was sent in the ServerHello.
+                     */
+                    InvalidateSession();
+
+                    ReceiveNewSessionTicketMessage(buf);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.mConnectionState = CS_SERVER_SESSION_TICKET;
+                break;
+            }
+            case HandshakeType.hello_request:
+            {
+                AssertEmpty(buf);
+
+                /*
+                 * 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 (this.mConnectionState == CS_END)
+                {
+                    /*
+                     * RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal
+                     * handshake_failure alert.
+                     */
+                    if (TlsUtilities.IsSsl(Context))
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                    string message = "Renegotiation not supported";
+                    RaiseWarning(AlertDescription.no_renegotiation, message);
+                }
+                break;
+            }
+            case HandshakeType.client_hello:
+            case HandshakeType.client_key_exchange:
+            case HandshakeType.certificate_verify:
+            case HandshakeType.hello_verify_request:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected virtual void HandleSupplementalData(IList serverSupplementalData)
+        {
+            this.mTlsClient.ProcessServerSupplementalData(serverSupplementalData);
+            this.mConnectionState = CS_SERVER_SUPPLEMENTAL_DATA;
+
+            this.mKeyExchange = mTlsClient.GetKeyExchange();
+            this.mKeyExchange.Init(Context);
+        }
+
+        protected virtual void ReceiveNewSessionTicketMessage(MemoryStream buf)
+        {
+            NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            mTlsClient.NotifyNewSessionTicket(newSessionTicket);
+        }
+
+        protected virtual void ReceiveServerHelloMessage(MemoryStream buf)
+        {
+            ProtocolVersion server_version = TlsUtilities.ReadVersion(buf);
+            if (server_version.IsDtls)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            // Check that this matches what the server is Sending in the record layer
+            if (!server_version.Equals(this.mRecordStream.ReadVersion))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            ProtocolVersion client_version = Context.ClientVersion;
+            if (!server_version.IsEqualOrEarlierVersionOf(client_version))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.mRecordStream.SetWriteVersion(server_version);
+            ContextAdmin.SetServerVersion(server_version);
+            this.mTlsClient.NotifyServerVersion(server_version);
+
+            /*
+             * Read the server random
+             */
+            this.mSecurityParameters.serverRandom = TlsUtilities.ReadFully(32, buf);
+
+            this.mSelectedSessionID = TlsUtilities.ReadOpaque8(buf);
+            if (this.mSelectedSessionID.Length > 32)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.mTlsClient.NotifySessionID(this.mSelectedSessionID);
+
+            this.mResumedSession = this.mSelectedSessionID.Length > 0 && this.mTlsSession != null
+                && Arrays.AreEqual(this.mSelectedSessionID, this.mTlsSession.SessionID);
+
+            /*
+             * Find out which CipherSuite the server has chosen and check that it was one of the offered
+             * ones, and is a valid selection for the negotiated version.
+             */
+            int selectedCipherSuite = TlsUtilities.ReadUint16(buf);
+            if (!Arrays.Contains(this.mOfferedCipherSuites, selectedCipherSuite)
+                || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+                || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+                || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            this.mTlsClient.NotifySelectedCipherSuite(selectedCipherSuite);
+
+            /*
+             * Find out which CompressionMethod the server has chosen and check that it was one of the
+             * offered ones.
+             */
+            byte selectedCompressionMethod = TlsUtilities.ReadUint8(buf);
+            if (!Arrays.Contains(this.mOfferedCompressionMethods, selectedCompressionMethod))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.mTlsClient.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.
+             */
+            this.mServerExtensions = ReadExtensions(buf);
+
+            /*
+             * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
+             * extended client hello message.
+             * 
+             * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server
+             * Hello is always allowed.
+             */
+            if (this.mServerExtensions != null)
+            {
+                foreach (int extType in this.mServerExtensions.Keys)
+                {
+                    /*
+                     * RFC 5746 3.6. 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 (extType == ExtensionType.renegotiation_info)
+                        continue;
+
+                    /*
+                     * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore
+                     * extensions appearing in the client hello, and Send a server hello containing no
+                     * extensions[.]
+                     */
+                    if (this.mResumedSession)
+                    {
+                        // TODO[compat-gnutls] GnuTLS test server Sends server extensions e.g. ec_point_formats
+                        // TODO[compat-openssl] OpenSSL test server Sends server extensions e.g. ec_point_formats
+                        // TODO[compat-polarssl] PolarSSL test server Sends server extensions e.g. ec_point_formats
+    //                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                    }
+
+                    /*
+                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
+                     * same extension type appeared in the corresponding ClientHello. If a client
+                     * receives an extension type in ServerHello that it did not request in the
+                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
+                     * fatal alert.
+                     */
+                    if (null == TlsUtilities.GetExtensionData(this.mClientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+                }
+            }
+
+            /*
+             * RFC 5746 3.4. Client Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * When a ServerHello is received, the client MUST check if it includes the
+                 * "renegotiation_info" extension:
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(this.mServerExtensions, ExtensionType.renegotiation_info);
+                if (renegExtData != null)
+                {
+                    /*
+                     * 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).
+                     */
+                    this.mSecureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(renegExtData, CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+            }
+
+            // TODO[compat-gnutls] GnuTLS test server fails to Send renegotiation_info extension when resuming
+            this.mTlsClient.NotifySecureRenegotiation(this.mSecureRenegotiation);
+
+            IDictionary sessionClientExtensions = mClientExtensions, sessionServerExtensions = mServerExtensions;
+            if (this.mResumedSession)
+            {
+                if (selectedCipherSuite != this.mSessionParameters.CipherSuite
+                    || selectedCompressionMethod != this.mSessionParameters.CompressionAlgorithm)
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                sessionClientExtensions = null;
+                sessionServerExtensions = this.mSessionParameters.ReadServerExtensions();
+            }
+
+            this.mSecurityParameters.cipherSuite = selectedCipherSuite;
+            this.mSecurityParameters.compressionAlgorithm = selectedCompressionMethod;
+
+            if (sessionServerExtensions != null)
+            {
+                /*
+                 * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an encrypt-then-MAC
+                 * request extension from a client and then selects a stream or AEAD cipher suite, it
+                 * MUST NOT Send an encrypt-then-MAC response extension back to the client.
+                 */
+                bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(sessionServerExtensions);
+                if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(selectedCipherSuite))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                this.mSecurityParameters.encryptThenMac = serverSentEncryptThenMAC;
+
+                this.mSecurityParameters.maxFragmentLength = ProcessMaxFragmentLengthExtension(sessionClientExtensions,
+                    sessionServerExtensions, AlertDescription.illegal_parameter);
+
+                this.mSecurityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(sessionServerExtensions);
+
+                /*
+                 * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in
+                 * a session resumption handshake.
+                 */
+                this.mAllowCertificateStatus = !this.mResumedSession
+                    && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions, ExtensionType.status_request,
+                        AlertDescription.illegal_parameter);
+
+                this.mExpectSessionTicket = !this.mResumedSession
+                    && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions, ExtensionType.session_ticket,
+                        AlertDescription.illegal_parameter);
+            }
+
+            if (sessionClientExtensions != null)
+            {
+                this.mTlsClient.ProcessServerExtensions(sessionServerExtensions);
+            }
+        }
+
+        protected virtual void SendCertificateVerifyMessage(DigitallySigned certificateVerify)
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_verify);
+
+            certificateVerify.Encode(message);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendClientHelloMessage()
+        {
+            this.mRecordStream.SetWriteVersion(this.mTlsClient.ClientHelloRecordLayerVersion);
+
+            ProtocolVersion client_version = this.mTlsClient.ClientVersion;
+            if (client_version.IsDtls)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            ContextAdmin.SetClientVersion(client_version);
+
+            /*
+             * TODO RFC 5077 3.4. When presenting a ticket, the client MAY generate and include a
+             * Session ID in the TLS ClientHello.
+             */
+            byte[] session_id = TlsUtilities.EmptyBytes;
+            if (this.mTlsSession != null)
+            {
+                session_id = this.mTlsSession.SessionID;
+                if (session_id == null || session_id.Length > 32)
+                {
+                    session_id = TlsUtilities.EmptyBytes;
+                }
+            }
+
+            this.mOfferedCipherSuites = this.mTlsClient.GetCipherSuites();
+
+            this.mOfferedCompressionMethods = this.mTlsClient.GetCompressionMethods();
+
+            if (session_id.Length > 0 && this.mSessionParameters != null)
+            {
+                if (!Arrays.Contains(this.mOfferedCipherSuites, mSessionParameters.CipherSuite)
+                    || !Arrays.Contains(this.mOfferedCompressionMethods, mSessionParameters.CompressionAlgorithm))
+                {
+                    session_id = TlsUtilities.EmptyBytes;
+                }
+            }
+
+            this.mClientExtensions = this.mTlsClient.GetClientExtensions();
+
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.client_hello);
+
+            TlsUtilities.WriteVersion(client_version, message);
+
+            message.Write(this.mSecurityParameters.ClientRandom);
+
+            TlsUtilities.WriteOpaque8(session_id, message);
+
+            // Cipher Suites (and SCSV)
+            {
+                /*
+                 * 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.
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, ExtensionType.renegotiation_info);
+                bool noRenegExt = (null == renegExtData);
+
+                bool noSCSV = !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+
+                if (noRenegExt && noSCSV)
+                {
+                    // TODO Consider whether to default to a client extension instead
+    //                this.mClientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(this.mClientExtensions);
+    //                this.mClientExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(TlsUtilities.EmptyBytes);
+                    this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                }
+
+                TlsUtilities.WriteUint16ArrayWithUint16Length(mOfferedCipherSuites, message);
+            }
+
+            TlsUtilities.WriteUint8ArrayWithUint8Length(mOfferedCompressionMethods, message);
+
+            if (mClientExtensions != null)
+            {
+                WriteExtensions(message, mClientExtensions);
+            }
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendClientKeyExchangeMessage()
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.client_key_exchange);
+
+            this.mKeyExchange.GenerateClientKeyExchange(message);
+
+            message.WriteToRecordStream(this);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsEccUtilities.cs b/crypto/src/crypto/tls/TlsEccUtilities.cs
index 889c6932b..64c3c1593 100644
--- a/crypto/src/crypto/tls/TlsEccUtilities.cs
+++ b/crypto/src/crypto/tls/TlsEccUtilities.cs
@@ -83,7 +83,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             int[] namedCurves = TlsUtilities.ReadUint16Array(length / 2, buf);
 
-            TlsProtocolHandler.AssertEmpty(buf);
+            TlsProtocol.AssertEmpty(buf);
 
             return namedCurves;
         }
@@ -101,7 +101,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             byte[] ecPointFormats = TlsUtilities.ReadUint8Array(length, buf);
 
-            TlsProtocolHandler.AssertEmpty(buf);
+            TlsProtocol.AssertEmpty(buf);
 
             if (!Arrays.Contains(ecPointFormats, ECPointFormat.uncompressed))
             {
diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs
index 764892d0b..2e2ef9214 100644
--- a/crypto/src/crypto/tls/TlsProtocol.cs
+++ b/crypto/src/crypto/tls/TlsProtocol.cs
@@ -2,24 +2,822 @@
 using System.Collections;
 using System.IO;
 
+using Org.BouncyCastle.Crypto.Prng;
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
     public abstract class TlsProtocol
     {
+        private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
+
+        /*
+         * Our Connection states
+         */
+        protected const short CS_START = 0;
+        protected const short CS_CLIENT_HELLO = 1;
+        protected const short CS_SERVER_HELLO = 2;
+        protected const short CS_SERVER_SUPPLEMENTAL_DATA = 3;
+        protected const short CS_SERVER_CERTIFICATE = 4;
+        protected const short CS_CERTIFICATE_STATUS = 5;
+        protected const short CS_SERVER_KEY_EXCHANGE = 6;
+        protected const short CS_CERTIFICATE_REQUEST = 7;
+        protected const short CS_SERVER_HELLO_DONE = 8;
+        protected const short CS_CLIENT_SUPPLEMENTAL_DATA = 9;
+        protected const short CS_CLIENT_CERTIFICATE = 10;
+        protected const short CS_CLIENT_KEY_EXCHANGE = 11;
+        protected const short CS_CERTIFICATE_VERIFY = 12;
+        protected const short CS_CLIENT_FINISHED = 13;
+        protected const short CS_SERVER_SESSION_TICKET = 14;
+        protected const short CS_SERVER_FINISHED = 15;
+        protected const short CS_END = 16;
+
+        /*
+         * Queues for data from some protocols.
+         */
+        private ByteQueue mApplicationDataQueue = new ByteQueue();
+        private ByteQueue mAlertQueue = new ByteQueue(2);
+        private ByteQueue mHandshakeQueue = new ByteQueue();
+    //    private ByteQueue mHeartbeatQueue = new ByteQueue();
+
+        /*
+         * The Record Stream we use
+         */
+        internal RecordStream mRecordStream;
+        protected SecureRandom mSecureRandom;
+
+        private TlsStream mTlsStream = null;
+
+        private volatile bool mClosed = false;
+        private volatile bool mFailedWithError = false;
+        private volatile bool mAppDataReady = false;
+        private volatile bool mSplitApplicationDataRecords = true;
+        private byte[] mExpectedVerifyData = null;
+
+        protected TlsSession mTlsSession = null;
+        protected SessionParameters mSessionParameters = null;
+        protected SecurityParameters mSecurityParameters = null;
+        protected Certificate mPeerCertificate = null;
+
+        protected int[] mOfferedCipherSuites = null;
+        protected byte[] mOfferedCompressionMethods = null;
+        protected IDictionary mClientExtensions = null;
+        protected IDictionary mServerExtensions = null;
+
+        protected short mConnectionState = CS_START;
+        protected bool mResumedSession = false;
+        protected bool mReceivedChangeCipherSpec = false;
+        protected bool mSecureRenegotiation = false;
+        protected bool mAllowCertificateStatus = false;
+        protected bool mExpectSessionTicket = false;
+
+        public TlsProtocol(Stream stream, SecureRandom secureRandom)
+            :   this(stream, stream, secureRandom)
+        {
+        }
+
+        public TlsProtocol(Stream input, Stream output, SecureRandom secureRandom)
+        {
+            this.mRecordStream = new RecordStream(this, input, output);
+            this.mSecureRandom = secureRandom;
+        }
+
+        protected abstract TlsContext Context { get; }
+
+        internal abstract AbstractTlsContext ContextAdmin { get; }
+
+        protected abstract TlsPeer Peer { get; }
+
+        protected virtual void HandleChangeCipherSpecMessage()
+        {
+        }
+
+        protected abstract void HandleHandshakeMessage(byte type, byte[] buf);
+
+        protected virtual void HandleWarningMessage(byte description)
+        {
+        }
+
+        protected virtual void CleanupHandshake()
+        {
+            if (this.mExpectedVerifyData != null)
+            {
+                Arrays.Fill(this.mExpectedVerifyData, (byte)0);
+                this.mExpectedVerifyData = null;
+            }
+
+            this.mSecurityParameters.Clear();
+            this.mPeerCertificate = null;
+
+            this.mOfferedCipherSuites = null;
+            this.mOfferedCompressionMethods = null;
+            this.mClientExtensions = null;
+            this.mServerExtensions = null;
+
+            this.mResumedSession = false;
+            this.mReceivedChangeCipherSpec = false;
+            this.mSecureRenegotiation = false;
+            this.mAllowCertificateStatus = false;
+            this.mExpectSessionTicket = false;
+        }
+
+        protected virtual void CompleteHandshake()
+        {
+            try
+            {
+                /*
+                 * We will now read data, until we have completed the handshake.
+                 */
+                while (this.mConnectionState != CS_END)
+                {
+                    if (this.mClosed)
+                    {
+                        // TODO What kind of exception/alert?
+                    }
+
+                    SafeReadRecord();
+                }
+
+                this.mRecordStream.FinaliseHandshake();
+
+                this.mSplitApplicationDataRecords = !TlsUtilities.IsTlsV11(Context);
+
+                /*
+                 * If this was an initial handshake, we are now ready to send and receive application data.
+                 */
+                if (!mAppDataReady)
+                {
+                    this.mAppDataReady = true;
+
+                    this.mTlsStream = new TlsStream(this);
+                }
+
+                if (this.mTlsSession != null)
+                {
+                    if (this.mSessionParameters == null)
+                    {
+                        this.mSessionParameters = new SessionParameters.Builder()
+                            .SetCipherSuite(this.mSecurityParameters.cipherSuite)
+                            .SetCompressionAlgorithm(this.mSecurityParameters.compressionAlgorithm)
+                            .SetMasterSecret(this.mSecurityParameters.masterSecret)
+                            .SetPeerCertificate(this.mPeerCertificate)
+                            // TODO Consider filtering extensions that aren't relevant to resumed sessions
+                            .SetServerExtensions(this.mServerExtensions)
+                            .Build();
+
+                        this.mTlsSession = new TlsSessionImpl(this.mTlsSession.SessionID, this.mSessionParameters);
+                    }
+
+                    ContextAdmin.SetResumableSession(this.mTlsSession);
+                }
+
+                Peer.NotifyHandshakeComplete();
+            }
+            finally
+            {
+                CleanupHandshake();
+            }
+        }
+
+        protected internal void ProcessRecord(byte protocol, byte[] buf, int offset, int len)
+        {
+            /*
+             * Have a look at the protocol type, and add it to the correct queue.
+             */
+            switch (protocol)
+            {
+            case ContentType.alert:
+            {
+                mAlertQueue.AddData(buf, offset, len);
+                ProcessAlert();
+                break;
+            }
+            case ContentType.application_data:
+            {
+                if (!mAppDataReady)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                mApplicationDataQueue.AddData(buf, offset, len);
+                ProcessApplicationData();
+                break;
+            }
+            case ContentType.change_cipher_spec:
+            {
+                ProcessChangeCipherSpec(buf, offset, len);
+                break;
+            }
+            case ContentType.handshake:
+            {
+                mHandshakeQueue.AddData(buf, offset, len);
+                ProcessHandshake();
+                break;
+            }
+            case ContentType.heartbeat:
+            {
+                if (!mAppDataReady)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                // TODO[RFC 6520]
+    //            mHeartbeatQueue.AddData(buf, offset, len);
+    //            ProcessHeartbeat();
+                break;
+            }
+            default:
+                /*
+                 * Uh, we don't know this protocol.
+                 * 
+                 * RFC2246 defines on page 13, that we should ignore this.
+                 */
+                break;
+            }
+        }
+
+        private void ProcessHandshake()
+        {
+            bool read;
+            do
+            {
+                read = false;
+                /*
+                 * We need the first 4 bytes, they contain type and length of the message.
+                 */
+                if (mHandshakeQueue.Available >= 4)
+                {
+                    byte[] beginning = new byte[4];
+                    mHandshakeQueue.Read(beginning, 0, 4, 0);
+                    byte type = TlsUtilities.ReadUint8(beginning, 0);
+                    int len = TlsUtilities.ReadUint24(beginning, 1);
+
+                    /*
+                     * Check if we have enough bytes in the buffer to read the full message.
+                     */
+                    if (mHandshakeQueue.Available >= (len + 4))
+                    {
+                        /*
+                         * Read the message.
+                         */
+                        byte[] buf = mHandshakeQueue.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:
+                            break;
+                        case HandshakeType.finished:
+                        default:
+                            if (type == HandshakeType.finished && this.mExpectedVerifyData == null)
+                            {
+                                this.mExpectedVerifyData = CreateVerifyData(!Context.IsServer);
+                            }
+
+                            mRecordStream.UpdateHandshakeData(beginning, 0, 4);
+                            mRecordStream.UpdateHandshakeData(buf, 0, len);
+                            break;
+                        }
+
+                        /*
+                         * Now, parse the message.
+                         */
+                        HandleHandshakeMessage(type, buf);
+                        read = true;
+                    }
+                }
+            }
+            while (read);
+        }
+
+        private void ProcessApplicationData()
+        {
+            /*
+             * There is nothing we need to do here.
+             * 
+             * This function could be used for callbacks when application data arrives in the future.
+             */
+        }
+
+        private void ProcessAlert()
+        {
+            while (mAlertQueue.Available >= 2)
+            {
+                /*
+                 * An alert is always 2 bytes. Read the alert.
+                 */
+                byte[] tmp = mAlertQueue.RemoveData(2, 0);
+                byte level = tmp[0];
+                byte description = tmp[1];
+
+                Peer.NotifyAlertReceived(level, description);
+
+                if (level == AlertLevel.fatal)
+                {
+                    /*
+                     * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
+                     * without proper close_notify messages with level equal to warning.
+                     */
+                    InvalidateSession();
+
+                    this.mFailedWithError = true;
+                    this.mClosed = true;
+
+                    mRecordStream.SafeClose();
+
+                    throw new IOException(TLS_ERROR_MESSAGE);
+                }
+                else
+                {
+
+                    /*
+                     * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own
+                     * and close down the connection immediately, discarding any pending writes.
+                     */
+                    // TODO Can close_notify be a fatal alert?
+                    if (description == AlertDescription.close_notify)
+                    {
+                        HandleClose(false);
+                    }
+
+                    /*
+                     * If it is just a warning, we continue.
+                     */
+                    HandleWarningMessage(description);
+                }
+            }
+        }
+
+        /**
+         * This method is called, when a change cipher spec message is received.
+         *
+         * @throws IOException If the message has an invalid content or the handshake is not in the correct
+         * state.
+         */
+        private void ProcessChangeCipherSpec(byte[] buf, int off, int len)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                byte message = TlsUtilities.ReadUint8(buf, off + i);
+
+                if (message != ChangeCipherSpec.change_cipher_spec)
+                    throw new TlsFatalAlert(AlertDescription.decode_error);
+
+                if (this.mReceivedChangeCipherSpec
+                    || mAlertQueue.Available > 0
+                    || mHandshakeQueue.Available > 0)
+                {
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                mRecordStream.ReceivedReadCipherSpec();
+
+                this.mReceivedChangeCipherSpec = true;
+
+                HandleChangeCipherSpecMessage();
+            }
+        }
+
+        protected internal virtual int ApplicationDataAvailable()
+        {
+            return mApplicationDataQueue.Available;
+        }
+
+        /**
+         * Read data from the network. The method will return immediately, if there is still some data
+         * left in the buffer, or block until some application data has been read from the network.
+         *
+         * @param buf    The buffer where the data will be copied to.
+         * @param offset The position where the data will be placed in the buffer.
+         * @param len    The maximum number of bytes to read.
+         * @return The number of bytes read.
+         * @throws IOException If something goes wrong during reading data.
+         */
+        protected internal virtual int ReadApplicationData(byte[] buf, int offset, int len)
+        {
+            if (len < 1)
+                return 0;
+
+            while (mApplicationDataQueue.Available == 0)
+            {
+                /*
+                 * We need to read some data.
+                 */
+                if (this.mClosed)
+                {
+                    if (this.mFailedWithError)
+                    {
+                        /*
+                         * Something went terribly wrong, we should throw an IOException
+                         */
+                        throw new IOException(TLS_ERROR_MESSAGE);
+                    }
+
+                    /*
+                     * Connection has been closed, there is no more data to read.
+                     */
+                    return 0;
+                }
+
+                SafeReadRecord();
+            }
+
+            len = System.Math.Min(len, mApplicationDataQueue.Available);
+            mApplicationDataQueue.RemoveData(buf, offset, len, 0);
+            return len;
+        }
+
+        protected virtual void SafeReadRecord()
+        {
+            try
+            {
+                if (!mRecordStream.ReadRecord())
+                {
+                    // TODO It would be nicer to allow graceful connection close if between records
+    //                this.FailWithError(AlertLevel.warning, AlertDescription.close_notify);
+                    throw new EndOfStreamException();
+                }
+            }
+            catch (TlsFatalAlert e)
+            {
+                if (!mClosed)
+                {
+                    this.FailWithError(AlertLevel.fatal, e.AlertDescription, "Failed to read record", e);
+                }
+                throw e;
+            }
+            catch (Exception e)
+            {
+                if (!mClosed)
+                {
+                    this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e);
+                }
+                throw e;
+            }
+        }
+
+        protected virtual void SafeWriteRecord(byte type, byte[] buf, int offset, int len)
+        {
+            try
+            {
+                mRecordStream.WriteRecord(type, buf, offset, len);
+            }
+            catch (TlsFatalAlert e)
+            {
+                if (!mClosed)
+                {
+                    this.FailWithError(AlertLevel.fatal, e.AlertDescription, "Failed to write record", e);
+                }
+                throw e;
+            }
+            catch (Exception e)
+            {
+                if (!mClosed)
+                {
+                    this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e);
+                }
+                throw e;
+            }
+        }
+
+        /**
+         * Send some application data to the remote system.
+         * <p/>
+         * The method will handle fragmentation internally.
+         *
+         * @param buf    The buffer with the data.
+         * @param offset The position in the buffer where the data is placed.
+         * @param len    The length of the data.
+         * @throws IOException If something goes wrong during sending.
+         */
+        protected internal virtual void WriteData(byte[] buf, int offset, int len)
+        {
+            if (this.mClosed)
+            {
+                if (this.mFailedWithError)
+                    throw new IOException(TLS_ERROR_MESSAGE);
+
+                throw new IOException("Sorry, connection has been closed, you cannot write more data");
+            }
+
+            while (len > 0)
+            {
+                /*
+                 * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
+                 * potentially useful as a traffic analysis countermeasure.
+                 * 
+                 * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
+                 */
+
+                if (this.mSplitApplicationDataRecords)
+                {
+                    /*
+                     * Protect against known IV attack!
+                     * 
+                     * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+                     */
+                    SafeWriteRecord(ContentType.application_data, buf, offset, 1);
+                    ++offset;
+                    --len;
+                }
+
+                if (len > 0)
+                {
+                    // Fragment data according to the current fragment limit.
+                    int toWrite = System.Math.Min(len, mRecordStream.GetPlaintextLimit());
+                    SafeWriteRecord(ContentType.application_data, buf, offset, toWrite);
+                    offset += toWrite;
+                    len -= toWrite;
+                }
+            }
+        }
+
+        protected void WriteHandshakeMessage(byte[] buf, int off, int len)
+        {
+            while (len > 0)
+            {
+                // Fragment data according to the current fragment limit.
+                int toWrite = System.Math.Min(len, mRecordStream.GetPlaintextLimit());
+                SafeWriteRecord(ContentType.handshake, buf, off, toWrite);
+                off += toWrite;
+                len -= toWrite;
+            }
+        }
+
+        /// <summary>The secure bidirectional stream for this connection</summary>
+        public virtual Stream Stream
+        {
+            get { return this.mTlsStream; }
+        }
+
         /**
-        * Make sure the Stream is now empty. Fail otherwise.
-        *
-        * @param is The Stream to check.
-        * @throws IOException If is is not empty.
-        */
+         * Terminate this connection with an alert. Can be used for normal closure too.
+         * 
+         * @param alertLevel
+         *            See {@link AlertLevel} for values.
+         * @param alertDescription
+         *            See {@link AlertDescription} for values.
+         * @throws IOException
+         *             If alert was fatal.
+         */
+        protected virtual void FailWithError(byte alertLevel, byte alertDescription, string message, Exception cause)
+        {
+            /*
+             * Check if the connection is still open.
+             */
+            if (!mClosed)
+            {
+                /*
+                 * Prepare the message
+                 */
+                this.mClosed = true;
+
+                if (alertLevel == AlertLevel.fatal)
+                {
+                    /*
+                     * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
+                     * without proper close_notify messages with level equal to warning.
+                     */
+                    // TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete.
+                    InvalidateSession();
+
+                    this.mFailedWithError = true;
+                }
+                RaiseAlert(alertLevel, alertDescription, message, cause);
+                mRecordStream.SafeClose();
+                if (alertLevel != AlertLevel.fatal)
+                {
+                    return;
+                }
+            }
+
+            throw new IOException(TLS_ERROR_MESSAGE);
+        }
+
+        protected virtual void InvalidateSession()
+        {
+            if (this.mSessionParameters != null)
+            {
+                this.mSessionParameters.Clear();
+                this.mSessionParameters = null;
+            }
+
+            if (this.mTlsSession != null)
+            {
+                this.mTlsSession.Invalidate();
+                this.mTlsSession = null;
+            }
+        }
+
+        protected virtual void ProcessFinishedMessage(MemoryStream buf)
+        {
+            byte[] verify_data = TlsUtilities.ReadFully(mExpectedVerifyData.Length, buf);
+
+            AssertEmpty(buf);
+
+            /*
+             * Compare both checksums.
+             */
+            if (!Arrays.ConstantTimeAreEqual(mExpectedVerifyData, verify_data))
+            {
+                /*
+                 * Wrong checksum in the finished message.
+                 */
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+        }
+
+        protected virtual void RaiseAlert(byte alertLevel, byte alertDescription, string message, Exception cause)
+        {
+            Peer.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+            byte[] error = new byte[]{ alertLevel, alertDescription };
+
+            SafeWriteRecord(ContentType.alert, error, 0, 2);
+        }
+
+        protected virtual void RaiseWarning(byte alertDescription, string message)
+        {
+            RaiseAlert(AlertLevel.warning, alertDescription, message, null);
+        }
+
+        protected virtual void SendCertificateMessage(Certificate certificate)
+        {
+            if (certificate == null)
+            {
+                certificate = Certificate.EmptyChain;
+            }
+
+            if (certificate.IsEmpty)
+            {
+                TlsContext context = Context;
+                if (!context.IsServer)
+                {
+                    ProtocolVersion serverVersion = Context.ServerVersion;
+                    if (serverVersion.IsSsl)
+                    {
+                        string errorMessage = serverVersion.ToString() + " client didn't provide credentials";
+                        RaiseWarning(AlertDescription.no_certificate, errorMessage);
+                        return;
+                    }
+                }
+            }
+
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate);
+
+            certificate.Encode(message);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendChangeCipherSpecMessage()
+        {
+            byte[] message = new byte[]{ 1 };
+            SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length);
+            mRecordStream.SentWriteCipherSpec();
+        }
+
+        protected virtual void SendFinishedMessage()
+        {
+            byte[] verify_data = CreateVerifyData(Context.IsServer);
+
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.Length);
+
+            message.Write(verify_data, 0, verify_data.Length);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected virtual void SendSupplementalDataMessage(IList supplementalData)
+        {
+            HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data);
+
+            WriteSupplementalData(message, supplementalData);
+
+            message.WriteToRecordStream(this);
+        }
+
+        protected byte[] CreateVerifyData(bool isServer)
+        {
+            TlsContext context = Context;
+            string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished;
+            byte[] sslSender = isServer ? TlsUtilities.SSL_SERVER : TlsUtilities.SSL_CLIENT;
+            byte[] hash = GetCurrentPrfHash(context, mRecordStream.HandshakeHash, sslSender);
+            return TlsUtilities.CalculateVerifyData(context, asciiLabel, hash);
+        }
+
+        /**
+         * Closes this connection.
+         *
+         * @throws IOException If something goes wrong during closing.
+         */
+        public virtual void Close()
+        {
+            HandleClose(true);
+        }
+
+        protected virtual void HandleClose(bool user_canceled)
+        {
+            if (!mClosed)
+            {
+                if (user_canceled && !mAppDataReady)
+                {
+                    RaiseWarning(AlertDescription.user_canceled, "User canceled handshake");
+                }
+                this.FailWithError(AlertLevel.warning, AlertDescription.close_notify, "Connection closed", null);
+            }
+        }
+
+        protected internal virtual void Flush()
+        {
+            mRecordStream.Flush();
+        }
+
+        protected internal virtual bool IsClosed
+        {
+            get { return mClosed; }
+        }
+
+        protected virtual short ProcessMaxFragmentLengthExtension(IDictionary clientExtensions, IDictionary serverExtensions,
+            byte alertDescription)
+        {
+            short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
+            if (maxFragmentLength >= 0 && !this.mResumedSession)
+            {
+                if (maxFragmentLength != TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions))
+                    throw new TlsFatalAlert(alertDescription);
+            }
+            return maxFragmentLength;
+        }
+
+        /**
+         * Make sure the InputStream 'buf' now empty. Fail otherwise.
+         *
+         * @param buf The InputStream to check.
+         * @throws IOException If 'buf' is not empty.
+         */
         protected internal static void AssertEmpty(MemoryStream buf)
         {
             if (buf.Position < buf.Length)
                 throw new TlsFatalAlert(AlertDescription.decode_error);
         }
 
+        protected internal static byte[] CreateRandomBlock(bool useGmtUnixTime, IRandomGenerator randomGenerator)
+        {
+            byte[] result = new byte[32];
+            randomGenerator.NextBytes(result);
+
+            if (useGmtUnixTime)
+            {
+                TlsUtilities.WriteGmtUnixTime(result, 0);
+            }
+
+            return result;
+        }
+
+        protected internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
+        {
+            return TlsUtilities.EncodeOpaque8(renegotiated_connection);
+        }
+
+        protected internal static void EstablishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
+        {
+            byte[] pre_master_secret = keyExchange.GeneratePremasterSecret();
+
+            try
+            {
+                context.SecurityParameters.masterSecret = TlsUtilities.CalculateMasterSecret(context, pre_master_secret);
+            }
+            finally
+            {
+                // 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.
+                 */
+                if (pre_master_secret != null)
+                {
+                    Arrays.Fill(pre_master_secret, (byte)0);
+                }
+            }
+        }
+
+        /**
+         * 'sender' only relevant to SSLv3
+         */
+        protected internal static byte[] GetCurrentPrfHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender)
+        {
+            IDigest d = handshakeHash.ForkPrfHash();
+
+            if (sslSender != null && TlsUtilities.IsSsl(context))
+            {
+                d.BlockUpdate(sslSender, 0, sslSender.Length);
+            }
+
+            return DigestUtilities.DoFinal(d);
+        }
+
         protected internal static IDictionary ReadExtensions(MemoryStream input)
         {
             if (input.Position >= input.Length)
@@ -51,6 +849,27 @@ namespace Org.BouncyCastle.Crypto.Tls
             return extensions;
         }
 
+        protected internal static IList ReadSupplementalDataMessage(MemoryStream input)
+        {
+            byte[] supp_data = TlsUtilities.ReadOpaque24(input);
+
+            AssertEmpty(input);
+
+            MemoryStream buf = new MemoryStream(supp_data, false);
+
+            IList supplementalData = Platform.CreateArrayList();
+
+            while (buf.Position < buf.Length)
+            {
+                int supp_data_type = TlsUtilities.ReadUint16(buf);
+                byte[] data = TlsUtilities.ReadOpaque16(buf);
+
+                supplementalData.Add(new SupplementalDataEntry(supp_data_type, data));
+            }
+
+            return supplementalData;
+        }
+
         protected internal static void WriteExtensions(Stream output, IDictionary extensions)
         {
             MemoryStream buf = new MemoryStream();
@@ -68,5 +887,216 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             TlsUtilities.WriteOpaque16(extBytes, output);
         }
+
+        protected internal static void WriteSupplementalData(Stream output, IList supplementalData)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            foreach (SupplementalDataEntry entry in supplementalData)
+            {
+                int supp_data_type = entry.DataType;
+                TlsUtilities.CheckUint16(supp_data_type);
+                TlsUtilities.WriteUint16(supp_data_type, buf);
+                TlsUtilities.WriteOpaque16(entry.Data, buf);
+            }
+
+            byte[] supp_data = buf.ToArray();
+
+            TlsUtilities.WriteOpaque24(supp_data, output);
+        }
+
+        protected internal static int GetPrfAlgorithm(TlsContext context, int ciphersuite)
+        {
+            bool isTLSv12 = TlsUtilities.IsTlsV12(context);
+
+            switch (ciphersuite)
+            {
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            {
+                if (isTLSv12)
+                {
+                    return PrfAlgorithm.tls_prf_sha256;
+                }
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            {
+                if (isTLSv12)
+                {
+                    return PrfAlgorithm.tls_prf_sha384;
+                }
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+            {
+                if (isTLSv12)
+                {
+                    return PrfAlgorithm.tls_prf_sha384;
+                }
+                return PrfAlgorithm.tls_prf_legacy;
+            }
+
+            default:
+            {
+                if (isTLSv12)
+                {
+                    return PrfAlgorithm.tls_prf_sha256;
+                }
+                return PrfAlgorithm.tls_prf_legacy;
+            }
+            }
+        }
+
+        internal class HandshakeMessage
+            :   MemoryStream
+        {
+            internal HandshakeMessage(byte handshakeType)
+                :   this(handshakeType, 60)
+            {
+            }
+
+            internal HandshakeMessage(byte handshakeType, int length)
+                :   base(length + 4)
+            {
+                TlsUtilities.WriteUint8(handshakeType, this);
+                // Reserve space for length
+                TlsUtilities.WriteUint24(0, this);
+            }
+
+            internal void Write(byte[] data)
+            {
+                Write(data, 0, data.Length);
+            }
+
+            internal void WriteToRecordStream(TlsProtocol protocol)
+            {
+                // Patch actual length back in
+                long length = Length - 4;
+                TlsUtilities.CheckUint24(length);
+                this.Position = 1;
+                TlsUtilities.WriteUint24((int)length, this);
+                protocol.WriteHandshakeMessage(GetBuffer(), 0, (int)Length);
+                this.Close();
+            }
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsProtocolHandler.cs b/crypto/src/crypto/tls/TlsProtocolHandler.cs
index 620c73587..6f223467f 100644
--- a/crypto/src/crypto/tls/TlsProtocolHandler.cs
+++ b/crypto/src/crypto/tls/TlsProtocolHandler.cs
@@ -21,1165 +21,19 @@ using Org.BouncyCastle.Utilities.Date;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
-    /// <remarks>An implementation of all high level protocols in TLS 1.0.</remarks>
+    [Obsolete("Use 'TlsClientProtocol' instead")]
     public class TlsProtocolHandler
-        : TlsProtocol
+        :   TlsClientProtocol
     {
-        /*
-        * Our Connection states
-        */
-        private const short CS_CLIENT_HELLO_SEND = 1;
-        private const short CS_SERVER_HELLO_RECEIVED = 2;
-        private const short CS_SERVER_CERTIFICATE_RECEIVED = 3;
-        private const short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4;
-        private const short CS_CERTIFICATE_REQUEST_RECEIVED = 5;
-        private const short CS_SERVER_HELLO_DONE_RECEIVED = 6;
-        private const short CS_CLIENT_KEY_EXCHANGE_SEND = 7;
-        private const short CS_CERTIFICATE_VERIFY_SEND = 8;
-        private const short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 9;
-        private const short CS_CLIENT_FINISHED_SEND = 10;
-        private const short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 11;
-        private const short CS_DONE = 12;
-
-        private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
-
-        /*
-        * Queues for data from some protocols.
-        */
-
-        private ByteQueue applicationDataQueue = new ByteQueue();
-        private ByteQueue alertQueue = new ByteQueue(2);
-        private ByteQueue handshakeQueue = new ByteQueue();
-
-        /*
-        * The Record Stream we use
-        */
-        private RecordStream recordStream;
-        private SecureRandom random;
-
-        private TlsStream tlsStream = null;
-
-        private bool closed = false;
-        private bool failedWithError = false;
-        private bool appDataReady = false;
-        private IDictionary clientExtensions;
-
-        private SecurityParameters securityParameters = null;
-
-        private TlsClientContextImpl tlsClientContext = null;
-        private TlsClient tlsClient = null;
-        private int[] offeredCipherSuites = null;
-        private byte[] offeredCompressionMethods = null;
-        private TlsKeyExchange keyExchange = null;
-        private TlsAuthentication authentication = null;
-        private CertificateRequest certificateRequest = null;
-
-        private short connection_state = 0;
-
-        private static SecureRandom CreateSecureRandom()
-        {
-            /*
-             * We use our threaded seed generator to generate a good random seed. If the user
-             * has a better random seed, he should use the constructor with a SecureRandom.
-             *
-             * Hopefully, 20 bytes in fast mode are good enough.
-             */
-            byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true);
-
-            return new SecureRandom(seed);
-        }
-
-        public TlsProtocolHandler(
-            Stream s)
-            : this(s, s)
-        {
-        }
-
-        public TlsProtocolHandler(
-            Stream			s,
-            SecureRandom	sr)
-            : this(s, s, sr)
+        public TlsProtocolHandler(Stream stream, SecureRandom secureRandom)
+            :   base(stream, stream, secureRandom)
         {
         }
 
         /// <remarks>Both streams can be the same object</remarks>
-        public TlsProtocolHandler(
-            Stream	inStr,
-            Stream	outStr)
-            : this(inStr, outStr, CreateSecureRandom())
-        {
-        }
-
-        /// <remarks>Both streams can be the same object</remarks>
-        public TlsProtocolHandler(
-            Stream			inStr,
-            Stream			outStr,
-            SecureRandom	sr)
-        {
-            this.recordStream = new RecordStream(this, inStr, outStr);
-            this.random = sr;
-        }
-
-        internal void ProcessData(
-            byte    contentType,
-            byte[]	buf,
-            int		offset,
-            int		len)
-        {
-            /*
-            * Have a look at the protocol type, and add it to the correct queue.
-            */
-            switch (contentType)
-            {
-                case ContentType.change_cipher_spec:
-                    ProcessChangeCipherSpec(buf, offset, len);
-                    break;
-                case ContentType.alert:
-                    alertQueue.AddData(buf, offset, len);
-                    ProcessAlert();
-                    break;
-                case ContentType.handshake:
-                    handshakeQueue.AddData(buf, offset, len);
-                    ProcessHandshake();
-                    break;
-                case ContentType.application_data:
-                    if (!appDataReady)
-                    {
-                        this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
-                    }
-                    applicationDataQueue.AddData(buf, offset, len);
-                    ProcessApplicationData();
-                    break;
-                default:
-                    /*
-                    * Uh, we don't know this protocol.
-                    *
-                    * RFC2246 defines on page 13, that we should ignore this.
-                    */
-                    break;
-            }
-        }
-
-        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);
-                    byte 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 = 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 (handshakeType)
-                        {
-                            case HandshakeType.hello_request:
-                            case HandshakeType.finished:
-                                break;
-                            default:
-                                recordStream.UpdateHandshakeData(beginning, 0, 4);
-                                recordStream.UpdateHandshakeData(buf, 0, len);
-                                break;
-                        }
-
-                        /*
-                        * Now, parse the message.
-                        */
-                        ProcessHandshakeMessage(handshakeType, buf);
-                        read = true;
-                    }
-                }
-            }
-            while (read);
-        }
-
-        private void ProcessHandshakeMessage(byte handshakeType, byte[] buf)
-        {
-            MemoryStream inStr = new MemoryStream(buf, false);
-
-            /*
-            * Check the type.
-            */
-            switch (handshakeType)
-            {
-                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(tlsClientContext,
-                                securityParameters.masterSecret, ExporterLabel.server_finished, recordStream.GetCurrentHash(), 12);
-
-                            /*
-                             * Compare both checksums.
-                             */
-                            if (!Arrays.ConstantTimeAreEqual(expectedServerVerifyData, serverVerifyData))
-                            {
-                                /*
-                                 * Wrong checksum in the finished message.
-                                 */
-                                this.FailWithError(AlertLevel.fatal, AlertDescription.decrypt_error);
-                            }
-
-                            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.
-                             */
-                            int selectedCipherSuite = TlsUtilities.ReadUint16(inStr);
-                            if (!Arrays.Contains(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.
-                             */
-                            byte selectedCompressionMethod = TlsUtilities.ReadUint8(inStr);
-                            if (!Arrays.Contains(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.
-                             */
-
-                            // Int32 -> byte[]
-                            IDictionary serverExtensions = ReadExtensions(inStr);
-
-                            /*
-                             * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
-                             * extended client hello message.
-                             * 
-                             * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server
-                             * Hello is always allowed.
-                             */
-                            if (serverExtensions != null)
-                            {
-                                foreach (int extType in serverExtensions.Keys)
-                                {
-                                    /*
-                                     * RFC 5746 3.6. 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 (ExtensionType.renegotiation_info == extType)
-                                        continue;
-
-                                    // TODO Add session resumption support
-                                    //*
-                                    // * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore
-                                    // * extensions appearing in the client hello, and send a server hello containing no
-                                    // * extensions[.]
-                                    // */
-                                    //if (this.resumedSession)
-                                    //{
-                                    //    // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats
-                                    //    // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats
-                                    //    // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats
-                                    //    //                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
-                                    //}
-
-                                    /*
-                                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
-                                     * same extension type appeared in the corresponding ClientHello. If a client
-                                     * receives an extension type in ServerHello that it did not request in the
-                                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
-                                     * fatal alert.
-                                     */
-                                    if (null == TlsUtilities.GetExtensionData(clientExtensions, extType))
-                                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
-                                }
-                            }
-                            else
-                            {
-                                // TODO Don't need this eventually...
-                                serverExtensions = Platform.CreateHashtable();
-                            }
-
-                            /*
-                             * 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(TlsUtilities.EmptyBytes)))
-                                    {
-                                        this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure);
-                                    }
-                                }
-
-                                tlsClient.NotifySecureRenegotiation(secure_negotiation);
-                            }
-
-                            this.securityParameters.cipherSuite = selectedCipherSuite;
-                            this.securityParameters.compressionAlgorithm = selectedCompressionMethod;
-
-                            if (clientExtensions != null)
-                            {
-                                tlsClient.ProcessServerExtensions(serverExtensions);
-                            }
-
-                            this.keyExchange = tlsClient.GetKeyExchange();
-
-                            connection_state = CS_SERVER_HELLO_RECEIVED;
-
-                            // TODO Just a place-holder until other TLS 1.2 changes arrive
-                            this.securityParameters.prfAlgorithm = PrfAlgorithm.tls_prf_legacy;
-
-                            /*
-                             * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify
-                             * verify_data_length has a verify_data_length equal to 12. This includes all
-                             * existing cipher suites.
-                             */
-                            this.securityParameters.verifyDataLength = 12;
-
-                            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.SkipServerCredentials();
-                                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 = recordStream.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;
-                            recordStream.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(tlsClientContext, pms, ExporterLabel.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
-                             */
-                            recordStream.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher());
-
-                            /*
-                             * Send our finished message.
-                             */
-                            byte[] clientVerifyData = TlsUtilities.PRF(tlsClientContext, securityParameters.masterSecret,
-                                ExporterLabel.client_finished, recordStream.GetCurrentHash(), 12);
-
-                            MemoryStream bos = new MemoryStream();
-                            TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos);
-                            TlsUtilities.WriteOpaque24(clientVerifyData, bos);
-                            byte[] message = bos.ToArray();
-
-                            recordStream.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.SkipServerCredentials();
-                                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);
-                            }
-
-                            this.certificateRequest = CertificateRequest.Parse(//getContext(),
-                                inStr);
-
-                            AssertEmpty(inStr);
-
-                            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;
-            }
-        }
-
-        private void ProcessApplicationData()
-        {
-            /*
-            * There is nothing we need to do here.
-            *
-            * This function could be used for callbacks when application
-            * data arrives in the future.
-            */
-        }
-
-        private void ProcessAlert()
-        {
-            while (alertQueue.Available >= 2)
-            {
-                /*
-                * An alert is always 2 bytes. Read the alert.
-                */
-                byte[] tmp = alertQueue.RemoveData(2, 0);
-                byte level = tmp[0];
-                byte description = tmp[1];
-                if (level == (byte)AlertLevel.fatal)
-                {
-                    this.failedWithError = true;
-                    this.closed = true;
-                    /*
-                    * Now try to Close the stream, ignore errors.
-                    */
-                    try
-                    {
-                        recordStream.Close();
-                    }
-                    catch (Exception)
-                    {
-                    }
-                    throw new IOException(TLS_ERROR_MESSAGE);
-                }
-                else
-                {
-                    if (description == (byte)AlertDescription.close_notify)
-                    {
-                        HandleClose(false);
-                    }
-
-                    /*
-                    * If it is just a warning, we continue.
-                    */
-                }
-            }
-        }
-
-        /**
-        * This method is called, when a change cipher spec message is received.
-        *
-        * @throws IOException If the message has an invalid content or the
-        *                     handshake is not in the correct state.
-        */
-        private void ProcessChangeCipherSpec(byte[] buf, int off, int len)
-        {
-            for (int i = 0; i < len; ++i)
-            {
-                if (buf[off + i] != 1)
-                {
-                    this.FailWithError(AlertLevel.fatal, AlertDescription.decode_error);
-                }
-
-                /*
-                 * Check if we are in the correct connection state.
-                 */
-                if (this.connection_state != CS_CLIENT_FINISHED_SEND)
-                {
-                    this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message);
-                }
-
-                recordStream.ServerClientSpecReceived();
-
-                this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED;
-            }
-        }
-
-        private void SendClientCertificate(Certificate clientCert)
-        {
-            MemoryStream bos = new MemoryStream();
-            TlsUtilities.WriteUint8((byte)HandshakeType.certificate, bos);
-
-            // Reserve space for length
-            TlsUtilities.WriteUint24(0, bos);
-
-            clientCert.Encode(bos);
-            byte[] message = bos.ToArray();
-
-            // Patch actual length back in
-            TlsUtilities.WriteUint24(message.Length - 4, message, 1);
-
-            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
-        }
-
-        private void SendClientKeyExchange()
-        {
-            MemoryStream bos = new MemoryStream();
-            TlsUtilities.WriteUint8((byte)HandshakeType.client_key_exchange, bos);
-
-            // Reserve space for length
-            TlsUtilities.WriteUint24(0, bos);
-
-            this.keyExchange.GenerateClientKeyExchange(bos);
-            byte[] message = bos.ToArray();
-
-            // Patch actual length back in
-            TlsUtilities.WriteUint24(message.Length - 4, message, 1);
-
-            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
-        }
-
-        private void SendCertificateVerify(byte[] data)
-        {
-            /*
-             * Send signature of handshake messages so far to prove we are the owner of
-             * the cert See RFC 2246 sections 4.7, 7.4.3 and 7.4.8
-             */
-            MemoryStream bos = new MemoryStream();
-            TlsUtilities.WriteUint8((byte)HandshakeType.certificate_verify, bos);
-            TlsUtilities.WriteUint24(data.Length + 2, bos);
-            TlsUtilities.WriteOpaque16(data, bos);
-            byte[] message = bos.ToArray();
-
-            recordStream.WriteMessage(ContentType.handshake, message, 0, message.Length);
-        }
-
-        public virtual void Connect(TlsClient tlsClient)
-        {
-            if (tlsClient == null)
-                throw new ArgumentNullException("tlsClient");
-            if (this.tlsClient != null)
-                throw new InvalidOperationException("Connect can only be called once");
-
-            this.tlsClient = tlsClient;
-
-            this.securityParameters = new SecurityParameters();
-            this.securityParameters.entity = ConnectionEnd.client;
-
-            this.tlsClientContext = new TlsClientContextImpl(random, securityParameters);
-
-            this.securityParameters.clientRandom = CreateRandomBlock(tlsClient.ShouldUseGmtUnixTime(),
-                tlsClientContext.NonceRandomGenerator);
-
-            this.tlsClient.Init(tlsClientContext);
-            this.recordStream.Init(tlsClientContext);
-
-            /*
-             * Send Client hello
-             *
-             * First, send the client_random data.
-             */
-            MemoryStream outStr = new MemoryStream();
-            TlsUtilities.WriteVersion(outStr);
-            outStr.Write(securityParameters.clientRandom, 0, 32);
-
-            /*
-            * Length of Session id
-            */
-            TlsUtilities.WriteUint8(0, outStr);
-
-            this.offeredCipherSuites = this.tlsClient.GetCipherSuites();
-
-            // Int32 -> byte[]
-            this.clientExtensions = this.tlsClient.GetClientExtensions();
-
-            // Cipher Suites (and SCSV)
-            {
-                /*
-                 * 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.
-                 */
-                bool noRenegExt = clientExtensions == null
-                    || !clientExtensions.Contains(ExtensionType.renegotiation_info);
-
-                int count = offeredCipherSuites.Length;
-                if (noRenegExt)
-                {
-                    // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV
-                    ++count;
-                }
-
-                TlsUtilities.WriteUint16(2 * count, outStr);
-
-                for (int i = 0; i < offeredCipherSuites.Length; ++i)
-                {
-                    TlsUtilities.WriteUint16((int)offeredCipherSuites[i], outStr);
-                }
-
-                if (noRenegExt)
-                {
-                    TlsUtilities.WriteUint16((int)CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, outStr);
-                }
-            }
-
-            /*
-             * Compression methods, just the null method.
-             */
-            this.offeredCompressionMethods = tlsClient.GetCompressionMethods();
-
-            {
-                TlsUtilities.WriteUint8((byte)offeredCompressionMethods.Length, outStr);
-                for (int i = 0; i < offeredCompressionMethods.Length; ++i)
-                {
-                    TlsUtilities.WriteUint8(offeredCompressionMethods[i], outStr);
-                }
-            }
-
-            if (clientExtensions != null)
-            {
-                WriteExtensions(outStr, clientExtensions);
-            }
-
-            MemoryStream bos = new MemoryStream();
-            TlsUtilities.WriteUint8((byte)HandshakeType.client_hello, bos);
-            TlsUtilities.WriteUint24((int)outStr.Length, bos);
-            byte[] outBytes = outStr.ToArray();
-            bos.Write(outBytes, 0, outBytes.Length);
-            byte[] message = bos.ToArray();
-            SafeWriteMessage(ContentType.handshake, message, 0, message.Length);
-            connection_state = CS_CLIENT_HELLO_SEND;
-
-            /*
-            * We will now read data, until we have completed the handshake.
-            */
-            while (connection_state != CS_DONE)
-            {
-                SafeReadData();
-            }
-
-            this.tlsStream = new TlsStream(this);
-        }
-
-        /**
-        * Read data from the network. The method will return immediately, if there is
-        * still some data left in the buffer, or block until some application
-        * data has been read from the network.
-        *
-        * @param buf    The buffer where the data will be copied to.
-        * @param offset The position where the data will be placed in the buffer.
-        * @param len    The maximum number of bytes to read.
-        * @return The number of bytes read.
-        * @throws IOException If something goes wrong during reading data.
-        */
-        internal int ReadApplicationData(byte[] buf, int offset, int len)
-        {
-            while (applicationDataQueue.Available == 0)
-            {
-                if (this.closed)
-                {
-                    /*
-                    * We need to read some data.
-                    */
-                    if (this.failedWithError)
-                    {
-                        /*
-                        * Something went terribly wrong, we should throw an IOException
-                        */
-                        throw new IOException(TLS_ERROR_MESSAGE);
-                    }
-
-                    /*
-                    * Connection has been closed, there is no more data to read.
-                    */
-                    return 0;
-                }
-
-                SafeReadData();
-            }
-            len = System.Math.Min(len, applicationDataQueue.Available);
-            applicationDataQueue.RemoveData(buf, offset, len, 0);
-            return len;
-        }
-
-        private void SafeReadData()
-        {
-            try
-            {
-                recordStream.ReadData();
-            }
-            catch (TlsFatalAlert e)
-            {
-                if (!this.closed)
-                {
-                    this.FailWithError(AlertLevel.fatal, e.AlertDescription);
-                }
-                throw e;
-            }
-            catch (IOException e)
-            {
-                if (!this.closed)
-                {
-                    this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error);
-                }
-                throw e;
-            }
-            catch (Exception e)
-            {
-                if (!this.closed)
-                {
-                    this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error);
-                }
-                throw e;
-            }
-        }
-
-        private void SafeWriteMessage(byte type, byte[] buf, int offset, int len)
-        {
-            try
-            {
-                recordStream.WriteMessage(type, buf, offset, len);
-            }
-            catch (TlsFatalAlert e)
-            {
-                if (!this.closed)
-                {
-                    this.FailWithError(AlertLevel.fatal, e.AlertDescription);
-                }
-                throw e;
-            }
-            catch (IOException e)
-            {
-                if (!closed)
-                {
-                    this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error);
-                }
-                throw e;
-            }
-            catch (Exception e)
-            {
-                if (!closed)
-                {
-                    this.FailWithError(AlertLevel.fatal, AlertDescription.internal_error);
-                }
-                throw e;
-            }
-        }
-
-        /**
-        * Send some application data to the remote system.
-        * <p/>
-        * The method will handle fragmentation internally.
-        *
-        * @param buf    The buffer with the data.
-        * @param offset The position in the buffer where the data is placed.
-        * @param len    The length of the data.
-        * @throws IOException If something goes wrong during sending.
-        */
-        internal void WriteData(byte[] buf, int offset, int len)
-        {
-            if (this.closed)
-            {
-                if (this.failedWithError)
-                    throw new IOException(TLS_ERROR_MESSAGE);
-
-                throw new IOException("Sorry, connection has been closed, you cannot write more data");
-            }
-
-            while (len > 0)
-            {
-                /*
-                 * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
-                 * potentially useful as a traffic analysis countermeasure.
-                 * 
-                 * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
-                 */
-
-                //if (this.splitApplicationDataRecords)
-                {
-                    /*
-                     * Protect against known IV attack!
-                     * 
-                     * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
-                     */
-                    SafeWriteMessage(ContentType.application_data, buf, offset, 1);
-                    ++offset;
-                    --len;
-                }
-
-                if (len > 0)
-                {
-                    // Fragment data according to the current fragment limit.
-                    //int toWrite = System.Math.Min(len, recordStream.GetPlaintextLimit());
-                    int toWrite = System.Math.Min(len, 1 << 14);
-                    SafeWriteMessage(ContentType.application_data, buf, offset, toWrite);
-                    offset += toWrite;
-                    len -= toWrite;
-                }
-            }
-        }
-
-        /// <summary>The secure bidirectional stream for this connection</summary>
-        public virtual Stream Stream
-        {
-            get { return this.tlsStream; }
-        }
-
-        /**
-        * Terminate this connection with an alert.
-        * <p/>
-        * Can be used for normal closure too.
-        *
-        * @param alertLevel       The level of the alert, an be AlertLevel.fatal or AL_warning.
-        * @param alertDescription The exact alert message.
-        * @throws IOException If alert was fatal.
-        */
-        private void FailWithError(byte alertLevel, byte alertDescription)
-        {
-            /*
-            * Check if the connection is still open.
-            */
-            if (!closed)
-            {
-                /*
-                * Prepare the message
-                */
-                this.closed = true;
-
-                if (alertLevel == AlertLevel.fatal)
-                {
-                    /*
-                    * This is a fatal message.
-                    */
-                    this.failedWithError = true;
-                }
-                SendAlert(alertLevel, alertDescription);
-                recordStream.Close();
-                if (alertLevel == AlertLevel.fatal)
-                {
-                    throw new IOException(TLS_ERROR_MESSAGE);
-                }
-            }
-            else
-            {
-                throw new IOException(TLS_ERROR_MESSAGE);
-            }
-        }
-
-        internal void SendAlert(byte alertLevel, byte alertDescription)
-        {
-            byte[] error = new byte[] { alertLevel, alertDescription };
-
-            recordStream.WriteMessage(ContentType.alert, error, 0, 2);
-        }
-
-        /// <summary>Closes this connection</summary>
-        /// <exception cref="IOException">If something goes wrong during closing.</exception>
-        public virtual void Close()
-        {
-            HandleClose(true);
-        }
-
-        protected virtual void HandleClose(bool user_canceled)
-        {
-            if (!closed)
-            {
-                if (user_canceled && !appDataReady)
-                {
-                    SendAlert(AlertLevel.warning, AlertDescription.user_canceled);
-                }
-                this.FailWithError(AlertLevel.warning, AlertDescription.close_notify);
-            }
-        }
-
-        protected static byte[] CreateRandomBlock(bool useGMTUnixTime, IRandomGenerator randomGenerator)
-        {
-            byte[] result = new byte[32];
-            randomGenerator.NextBytes(result);
-
-            if (useGMTUnixTime)
-            {
-                TlsUtilities.WriteGmtUnixTime(result, 0);
-            }
-
-            return result;
-        }
-
-        internal void Flush()
-        {
-            recordStream.Flush();
-        }
-
-        internal bool IsClosed
-        {
-            get { return closed; }
-        }
-
-        private static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
+        public TlsProtocolHandler(Stream input, Stream output, SecureRandom	secureRandom)
+            :   base(input, output, secureRandom)
         {
-            MemoryStream buf = new MemoryStream();
-            TlsUtilities.WriteOpaque8(renegotiated_connection, buf);
-            return buf.ToArray();
         }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsStream.cs b/crypto/src/crypto/tls/TlsStream.cs
index 84b901d6e..7ff7184e3 100644
--- a/crypto/src/crypto/tls/TlsStream.cs
+++ b/crypto/src/crypto/tls/TlsStream.cs
@@ -6,10 +6,9 @@ namespace Org.BouncyCastle.Crypto.Tls
     internal class TlsStream
         : Stream
     {
-        private readonly TlsProtocolHandler handler;
+        private readonly TlsProtocol handler;
 
-        internal TlsStream(
-            TlsProtocolHandler handler)
+        internal TlsStream(TlsProtocol handler)
         {
             this.handler = handler;
         }
diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs
index f530b01a6..f1ea0996d 100644
--- a/crypto/src/crypto/tls/TlsUtilities.cs
+++ b/crypto/src/crypto/tls/TlsUtilities.cs
@@ -492,26 +492,6 @@ namespace Org.BouncyCastle.Crypto.Tls
             return uints;
         }
 
-        [Obsolete]
-        public static void CheckVersion(byte[] ReadVersion)
-        {
-            if ((ReadVersion[0] != 3) || (ReadVersion[1] != 1))
-            {
-                throw new TlsFatalAlert(AlertDescription.protocol_version);
-            }
-        }
-
-        [Obsolete]
-        public static void CheckVersion(Stream input)
-        {
-            int i1 = input.ReadByte();
-            int i2 = input.ReadByte();
-            if ((i1 != 3) || (i2 != 1))
-            {
-                throw new TlsFatalAlert(AlertDescription.protocol_version);
-            }
-        }
-
         public static ProtocolVersion ReadVersion(byte[] buf, int offset)
         {
             return ProtocolVersion.Get(buf[offset], buf[offset + 1]);
@@ -577,20 +557,6 @@ namespace Org.BouncyCastle.Crypto.Tls
             buf[offset + 3] = (byte)t;
         }
 
-        [Obsolete]
-        public static void WriteVersion(Stream output)
-        {
-            output.WriteByte(3);
-            output.WriteByte(1);
-        }
-
-        [Obsolete]
-        public static void WriteVersion(byte[] buf, int offset)
-        {
-            buf[offset] = 3;
-            buf[offset + 1] = 1;
-        }
-
         public static void WriteVersion(ProtocolVersion version, Stream output)
         {
             output.WriteByte((byte)version.MajorVersion);
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 87bc1ba65..27fd18d6d 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -544,6 +544,19 @@ namespace Org.BouncyCastle.Utilities
             return rv;
         }
 
+        public static int[] Concatenate(int[] a, int[] b)
+        {
+            if (a == null)
+                return Clone(b);
+            if (b == null)
+                return Clone(a);
+
+            int[] rv = new int[a.Length + b.Length];
+            Array.Copy(a, 0, rv, 0, a.Length);
+            Array.Copy(b, 0, rv, a.Length, b.Length);
+            return rv;
+        }
+
         public static byte[] Prepend(byte[] a, byte b)
         {
             if (a == null)
diff --git a/crypto/test/src/crypto/tls/test/MockTlsClient.cs b/crypto/test/src/crypto/tls/test/MockTlsClient.cs
new file mode 100644
index 000000000..c22e98367
--- /dev/null
+++ b/crypto/test/src/crypto/tls/test/MockTlsClient.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Crypto.Tls.Test
+{
+    internal class MockTlsClient
+        :   DefaultTlsClient
+    {
+        internal TlsSession mSession;
+
+        internal MockTlsClient(TlsSession session)
+        {
+            this.mSession = session;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return this.mSession;
+        }
+
+        public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription
+                + ")");
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause.StackTrace);
+            }
+        }
+
+        public override void NotifyAlertReceived(byte alertLevel, byte alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription."
+                + alertDescription + ")");
+        }
+
+        public override int[] GetCipherSuites()
+        {
+            return Arrays.Concatenate(base.GetCipherSuites(),
+                new int[]
+                {
+                    CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+                    CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1,
+                    CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1,
+                    CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1,
+                    CipherSuite.TLS_RSA_WITH_SALSA20_SHA1,
+                });
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(base.GetClientExtensions());
+            TlsExtensionsUtilities.AddEncryptThenMacExtension(clientExtensions);
+            TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+            TlsExtensionsUtilities.AddTruncatedHMacExtension(clientExtensions);
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(mContext);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            TlsSession newSession = mContext.ResumableSession;
+            if (newSession != null)
+            {
+                byte[] newSessionID = newSession.SessionID;
+                string hex = Hex.ToHexString(newSessionID);
+
+                if (this.mSession != null && Arrays.AreEqual(this.mSession.SessionID, newSessionID))
+                {
+                    Console.WriteLine("Resumed session: " + hex);
+                }
+                else
+                {
+                    Console.WriteLine("Established session: " + hex);
+                }
+
+                this.mSession = newSession;
+            }
+        }
+
+        internal class MyTlsAuthentication
+            :   TlsAuthentication
+        {
+            private readonly TlsContext mContext;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.mContext = context;
+            }
+
+            public virtual void NotifyServerCertificate(Certificate serverCertificate)
+            {
+                X509CertificateStructure[] chain = serverCertificate.GetCertificateList();
+                Console.WriteLine("TLS client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = chain[i];
+                    // TODO Create Fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    Fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+            }
+
+            public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                byte[] certificateTypes = certificateRequest.CertificateTypes;
+                if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                    return null;
+
+                SignatureAndHashAlgorithm signatureAndHashAlgorithm = null;
+                IList sigAlgs = certificateRequest.SupportedSignatureAlgorithms;
+                if (sigAlgs != null)
+                {
+                    foreach (SignatureAndHashAlgorithm sigAlg in sigAlgs)
+                    {
+                        if (sigAlg.Signature == SignatureAlgorithm.rsa)
+                        {
+                            signatureAndHashAlgorithm = sigAlg;
+                            break;
+                        }
+                    }
+
+                    if (signatureAndHashAlgorithm == null)
+                    {
+                        return null;
+                    }
+                }
+
+                return TlsTestUtilities.LoadSignerCredentials(mContext, new string[] { "x509-client.pem", "x509-ca.pem" },
+                    "x509-client-key.pem", signatureAndHashAlgorithm);
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/crypto/tls/test/TlsClientTest.cs b/crypto/test/src/crypto/tls/test/TlsClientTest.cs
new file mode 100644
index 000000000..e68f1934c
--- /dev/null
+++ b/crypto/test/src/crypto/tls/test/TlsClientTest.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Crypto.Tls.Test
+{
+    /**
+     * A simple test designed to conduct a TLS handshake with an external TLS server.
+     * <p/>
+     * Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in
+     * this package (under 'src/test/resources') for help configuring an external TLS server.
+     */
+    public class TlsClientTest
+    {
+        private static readonly SecureRandom secureRandom = new SecureRandom();
+
+        public static void Main(string[] args)
+        {
+            string hostname = "localhost";
+            int port = 5556;
+
+            long time1 = DateTime.UtcNow.Ticks;
+
+            MockTlsClient client = new MockTlsClient(null);
+            TlsClientProtocol protocol = OpenTlsConnection(hostname, port, client);
+            protocol.Close();
+
+            long time2 = DateTime.UtcNow.Ticks;
+            Console.WriteLine("Elapsed 1: " + (time2 - time1)/TimeSpan.TicksPerMillisecond + "ms");
+
+            client = new MockTlsClient(client.GetSessionToResume());
+            protocol = OpenTlsConnection(hostname, port, client);
+
+            long time3 = DateTime.UtcNow.Ticks;
+            Console.WriteLine("Elapsed 2: " + (time3 - time2)/TimeSpan.TicksPerMillisecond + "ms");
+
+            byte[] req = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\n\r\n");
+
+            Stream tlsStream = protocol.Stream;
+            tlsStream.Write(req, 0, req.Length);
+            tlsStream.Flush();
+
+            StreamReader reader = new StreamReader(tlsStream);
+
+            String line;
+            while ((line = reader.ReadLine()) != null)
+            {
+                Console.WriteLine(">>> " + line);
+            }
+
+            protocol.Close();
+        }
+
+        internal static TlsClientProtocol OpenTlsConnection(string hostname, int port, TlsClient client)
+        {
+            TcpClient tcp = new TcpClient(hostname, port);
+
+            TlsClientProtocol protocol = new TlsClientProtocol(tcp.GetStream(), secureRandom);
+            protocol.Connect(client);
+            return protocol;
+        }
+    }
+}
diff --git a/crypto/test/src/crypto/tls/test/TlsTestUtilities.cs b/crypto/test/src/crypto/tls/test/TlsTestUtilities.cs
new file mode 100644
index 000000000..85a47d5e9
--- /dev/null
+++ b/crypto/test/src/crypto/tls/test/TlsTestUtilities.cs
@@ -0,0 +1,137 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO.Pem;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tls.Test
+{
+    public abstract class TlsTestUtilities
+    {
+        internal static readonly byte[] RsaCertData = Base64
+            .Decode("MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2"
+                + "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq"
+                + "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA2MDIwNVoXDTEzMDIyNT"
+                + "A2MDM0NVowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw"
+                + "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG"
+                + "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy"
+                + "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCBSAwEgYDVR"
+                + "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAHU55Ncz"
+                + "eglREcTg54YLUlGWu2WOYWhit/iM1eeq8Kivro7q98eW52jTuMI3CI5ulqd0hYzshQKQaZ5GDzErMyM=");
+
+        internal static readonly byte[] DudRsaCertData = Base64
+            .Decode("MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2"
+                + "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq"
+                + "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA1NDcyOFoXDTEzMDIyNT"
+                + "A1NDkwOFowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw"
+                + "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG"
+                + "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy"
+                + "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCAAEwEgYDVR"
+                + "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAJg55PBS"
+                + "weg6obRUKF4FF6fCrWFi6oCYSQ99LWcAeupc5BofW5MstFMhCOaEucuGVqunwT5G7/DweazzCIrSzB0=");
+
+        internal static string Fingerprint(X509CertificateStructure c)
+        {
+            byte[] der = c.GetEncoded();
+            byte[] sha1 = Sha256DigestOf(der);
+            byte[] hexBytes = Hex.Encode(sha1);
+            string hex = Platform.ToUpperInvariant(Encoding.ASCII.GetString(hexBytes));
+
+            StringBuilder fp = new StringBuilder();
+            int i = 0;
+            fp.Append(hex.Substring(i, 2));
+            while ((i += 2) < hex.Length)
+            {
+                fp.Append(':');
+                fp.Append(hex.Substring(i, 2));
+            }
+            return fp.ToString();
+        }
+
+        internal static byte[] Sha256DigestOf(byte[] input)
+        {
+            return DigestUtilities.CalculateDigest("SHA256", input);
+        }
+
+        internal static TlsAgreementCredentials LoadAgreementCredentials(TlsContext context,
+            string[] certResources, string keyResource)
+        {
+            Certificate certificate = LoadCertificateChain(certResources);
+            AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource);
+
+            return new DefaultTlsAgreementCredentials(certificate, privateKey);
+        }
+
+        internal static TlsEncryptionCredentials LoadEncryptionCredentials(TlsContext context,
+            string[] certResources, string keyResource)
+        {
+            Certificate certificate = LoadCertificateChain(certResources);
+            AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource);
+
+            return new DefaultTlsEncryptionCredentials(context, certificate, privateKey);
+        }
+
+        internal static TlsSignerCredentials LoadSignerCredentials(TlsContext context, string[] certResources,
+            string keyResource, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            Certificate certificate = LoadCertificateChain(certResources);
+            AsymmetricKeyParameter privateKey = LoadPrivateKeyResource(keyResource);
+
+            return new DefaultTlsSignerCredentials(context, certificate, privateKey, signatureAndHashAlgorithm);
+        }
+
+        internal static Certificate LoadCertificateChain(string[] resources)
+        {
+            X509CertificateStructure[] chain = new X509CertificateStructure[resources.Length];
+            for (int i = 0; i < resources.Length; ++i)
+            {
+                chain[i] = LoadCertificateResource(resources[i]);
+            }
+            return new Certificate(chain);
+        }
+
+        internal static X509CertificateStructure LoadCertificateResource(string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.EndsWith("CERTIFICATE"))
+            {
+                return X509CertificateStructure.GetInstance(pem.Content);
+            }
+            throw new ArgumentException("doesn't specify a valid certificate", "resource");
+        }
+
+        internal static AsymmetricKeyParameter LoadPrivateKeyResource(string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.EndsWith("RSA PRIVATE KEY"))
+            {
+                RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(pem.Content);
+                return new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent,
+                    rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1,
+                    rsa.Exponent2, rsa.Coefficient);
+            }
+            if (pem.Type.EndsWith("PRIVATE KEY"))
+            {
+                return PrivateKeyFactory.CreateKey(pem.Content);
+            }
+            throw new ArgumentException("doesn't specify a valid private key", "resource");
+        }
+
+        internal static PemObject LoadPemResource(string resource)
+        {
+            Stream s = SimpleTest.GetTestDataAsStream("tls." + resource);
+            PemReader p = new PemReader(new StreamReader(s));
+            PemObject o = p.ReadPemObject();
+            p.Reader.Close();
+            return o;
+        }
+    }
+}