From da656890c709963fb112d4813a2542302994bb35 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sun, 24 Aug 2014 16:47:32 +0700 Subject: Finish initial porting of TLS 1.2 client from Java API --- crypto/src/asn1/pkcs/RSAPrivateKeyStructure.cs | 145 +-- crypto/src/crypto/agreement/DHStandardGroups.cs | 206 ++++ crypto/src/crypto/tls/AbstractTlsContext.cs | 18 +- crypto/src/crypto/tls/CertificateRequest.cs | 14 +- crypto/src/crypto/tls/DefaultTlsServer.cs | 548 +++++++++++ crypto/src/crypto/tls/RecordStream.cs | 356 +++++-- crypto/src/crypto/tls/TlsClientProtocol.cs | 861 +++++++++++++++++ crypto/src/crypto/tls/TlsEccUtilities.cs | 4 +- crypto/src/crypto/tls/TlsProtocol.cs | 1040 +++++++++++++++++++- crypto/src/crypto/tls/TlsProtocolHandler.cs | 1158 +---------------------- crypto/src/crypto/tls/TlsStream.cs | 5 +- crypto/src/crypto/tls/TlsUtilities.cs | 34 - crypto/src/util/Arrays.cs | 13 + 13 files changed, 3032 insertions(+), 1370 deletions(-) create mode 100644 crypto/src/crypto/agreement/DHStandardGroups.cs create mode 100644 crypto/src/crypto/tls/DefaultTlsServer.cs create mode 100644 crypto/src/crypto/tls/TlsClientProtocol.cs (limited to 'crypto/src') 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. *
          *      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
+{
+    /// Standard Diffie-Hellman groups from various IETF specifications.
+    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
 {
-    /// An implementation of the TLS 1.0 record layer.
+    /// An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3.
     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.
+         * 

+ * 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; + } + } + + ///

The secure bidirectional stream for this connection + 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 { - /// An implementation of all high level protocols in TLS 1.0. + [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) { } /// Both streams can be the same object - public TlsProtocolHandler( - Stream inStr, - Stream outStr) - : this(inStr, outStr, CreateSecureRandom()) - { - } - - /// Both streams can be the same object - 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. - *

- * 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; - } - } - } - - ///

The secure bidirectional stream for this connection - public virtual Stream Stream - { - get { return this.tlsStream; } - } - - /** - * Terminate this connection with an alert. - *

- * 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); - } - - ///

Closes this connection - /// If something goes wrong during closing. - 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) -- cgit 1.5.1