summary refs log tree commit diff
path: root/crypto/test/src
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/test/src')
-rw-r--r--crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs820
-rw-r--r--crypto/test/src/tls/test/ByteQueueInputStreamTest.cs134
-rw-r--r--crypto/test/src/tls/test/CertChainUtilities.cs123
-rw-r--r--crypto/test/src/tls/test/DtlsProtocolTest.cs102
-rw-r--r--crypto/test/src/tls/test/DtlsPskProtocolTest.cs102
-rw-r--r--crypto/test/src/tls/test/DtlsTestCase.cs164
-rw-r--r--crypto/test/src/tls/test/DtlsTestClientProtocol.cs27
-rw-r--r--crypto/test/src/tls/test/DtlsTestServerProtocol.cs16
-rw-r--r--crypto/test/src/tls/test/DtlsTestSuite.cs260
-rw-r--r--crypto/test/src/tls/test/LoggingDatagramTransport.cs87
-rw-r--r--crypto/test/src/tls/test/MockDatagramAssociation.cs107
-rw-r--r--crypto/test/src/tls/test/MockDtlsClient.cs170
-rw-r--r--crypto/test/src/tls/test/MockDtlsServer.cs146
-rw-r--r--crypto/test/src/tls/test/MockPskDtlsClient.cs161
-rw-r--r--crypto/test/src/tls/test/MockPskDtlsServer.cs113
-rw-r--r--crypto/test/src/tls/test/MockPskTlsClient.cs186
-rw-r--r--crypto/test/src/tls/test/MockPskTlsServer.cs122
-rw-r--r--crypto/test/src/tls/test/MockSrpTlsClient.cs182
-rw-r--r--crypto/test/src/tls/test/MockSrpTlsServer.cs143
-rw-r--r--crypto/test/src/tls/test/MockTlsClient.cs190
-rw-r--r--crypto/test/src/tls/test/MockTlsServer.cs149
-rw-r--r--crypto/test/src/tls/test/NetworkStream.cs101
-rw-r--r--crypto/test/src/tls/test/PipedStream.cs134
-rw-r--r--crypto/test/src/tls/test/PrfTest.cs97
-rw-r--r--crypto/test/src/tls/test/PskTlsClientTest.cs113
-rw-r--r--crypto/test/src/tls/test/PskTlsServerTest.cs82
-rw-r--r--crypto/test/src/tls/test/TlsClientTest.cs97
-rw-r--r--crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs126
-rw-r--r--crypto/test/src/tls/test/TlsProtocolTest.cs75
-rw-r--r--crypto/test/src/tls/test/TlsPskProtocolTest.cs75
-rw-r--r--crypto/test/src/tls/test/TlsServerTest.cs82
-rw-r--r--crypto/test/src/tls/test/TlsSrpProtocolTest.cs75
-rw-r--r--crypto/test/src/tls/test/TlsTestCase.cs182
-rw-r--r--crypto/test/src/tls/test/TlsTestClientImpl.cs370
-rw-r--r--crypto/test/src/tls/test/TlsTestClientProtocol.cs32
-rw-r--r--crypto/test/src/tls/test/TlsTestConfig.cs128
-rw-r--r--crypto/test/src/tls/test/TlsTestServerImpl.cs310
-rw-r--r--crypto/test/src/tls/test/TlsTestServerProtocol.cs22
-rw-r--r--crypto/test/src/tls/test/TlsTestSuite.cs300
-rw-r--r--crypto/test/src/tls/test/TlsTestUtilities.cs412
-rw-r--r--crypto/test/src/tls/test/TlsUtilitiesTest.cs66
-rw-r--r--crypto/test/src/tls/test/UnreliableDatagramTransport.cs79
42 files changed, 6462 insertions, 0 deletions
diff --git a/crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs b/crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs
new file mode 100644
index 000000000..a57212c73
--- /dev/null
+++ b/crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs
@@ -0,0 +1,820 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Tls.Tests;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Tests
+{
+    [TestFixture]
+    public class BcTlsCryptoTest
+    {
+        private static readonly byte[] ClientHello = Hex("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63"
+            + "ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83"
+            + "02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b"
+            + "00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00"
+            + "12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23"
+            + "00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2"
+            + "3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a"
+            + "af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03"
+            + "02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06"
+            + "02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01");
+        private static readonly byte[] ServerHello = Hex("02 00 00 56 03 03 a6 af 06 a4 12 18 60"
+            + "dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e"
+            + "d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88"
+            + "76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1"
+            + "dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04");
+        private static readonly byte[] EncryptedExtensions = Hex("08 00 00 24 00 22 00 0a 00 14 00"
+            + "12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c"
+            + "00 02 40 01 00 00 00 00");
+        private static readonly byte[] Certificate = Hex("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82"
+            + "01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48"
+            + "86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03"
+            + "72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17"
+            + "0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06"
+            + "03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7"
+            + "0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f"
+            + "82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26"
+            + "d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c"
+            + "1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52"
+            + "4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74"
+            + "80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93"
+            + "ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03"
+            + "01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06"
+            + "03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01"
+            + "01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a"
+            + "72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea"
+            + "e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01"
+            + "51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be"
+            + "c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b"
+            + "1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8"
+            + "96 12 29 ac 91 87 b4 2b 4d e1 00 00");
+        private static readonly byte[] CertificateVerify = Hex("0f 00 00 84 08 04 00 80 5a 74 7c"
+            + "5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a"
+            + "b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07"
+            + "86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b"
+            + "be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44"
+            + "5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a"
+            + "3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3");
+        private static readonly byte[] ServerFinished = Hex("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb"
+            + "dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07"
+            + "18");
+        private static readonly byte[] ClientFinished = Hex("14 00 00 20 a8 ec 43 6d 67 76 34 ae 52 5a"
+            + "c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce"
+            + "61");
+
+        private readonly TlsCrypto m_crypto = new BcTlsCrypto(new SecureRandom());
+
+        protected TlsCredentialedSigner LoadCredentialedSigner(TlsCryptoParameters cryptoParams, string resource,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            return TlsTestUtilities.LoadSignerCredentials(cryptoParams, m_crypto,
+                new string[]{ "x509-server-" + resource + ".pem" }, "x509-server-key-" + resource + ".pem",
+                signatureAndHashAlgorithm);
+        }
+
+        protected TlsCredentialedSigner LoadCredentialedSignerLegacy(TlsCryptoParameters cryptoParams,
+            short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.dsa:
+                return LoadCredentialedSigner(cryptoParams, "dsa", null);
+            case SignatureAlgorithm.ecdsa:
+                return LoadCredentialedSigner(cryptoParams, "ecdsa", null);
+            case SignatureAlgorithm.rsa:
+                return LoadCredentialedSigner(cryptoParams, "rsa-sign", null);
+            default:
+                return null;
+            }
+        }
+
+        protected TlsCredentialedSigner LoadCredentialedSigner12(TlsCryptoParameters cryptoParams,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            switch (signatureAndHashAlgorithm.Signature)
+            {
+            case SignatureAlgorithm.dsa:
+                return LoadCredentialedSigner(cryptoParams, "dsa", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.ecdsa:
+                return LoadCredentialedSigner(cryptoParams, "ecdsa", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.ed25519:
+                return LoadCredentialedSigner(cryptoParams, "ed25519", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.ed448:
+                return LoadCredentialedSigner(cryptoParams, "ed448", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_256", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_384", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_512", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa-sign", signatureAndHashAlgorithm);
+
+            // TODO[draft-smyshlyaev-tls12-gost-suites-10] Add test resources for these
+            case SignatureAlgorithm.gostr34102012_256:
+            case SignatureAlgorithm.gostr34102012_512:
+
+            default:
+                return null;
+            }
+        }
+
+        protected TlsCredentialedSigner LoadCredentialedSigner13(TlsCryptoParameters cryptoParams, int signatureScheme)
+        {
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = SignatureScheme.GetSignatureAndHashAlgorithm(
+                signatureScheme);
+
+            switch (signatureScheme)
+            {
+            case SignatureScheme.ecdsa_secp256r1_sha256:
+                return LoadCredentialedSigner(cryptoParams, "ecdsa", signatureAndHashAlgorithm);
+            case SignatureScheme.ed25519:
+                return LoadCredentialedSigner(cryptoParams, "ed25519", signatureAndHashAlgorithm);
+            case SignatureScheme.ed448:
+                return LoadCredentialedSigner(cryptoParams, "ed448", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_pss_sha256:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_256", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_pss_sha384:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_384", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_pss_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_512", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_rsae_sha256:
+            case SignatureScheme.rsa_pss_rsae_sha384:
+            case SignatureScheme.rsa_pss_rsae_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa-sign", signatureAndHashAlgorithm);
+
+            // TODO[tls] Add test resources for these
+            case SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512:
+            case SignatureScheme.ecdsa_secp384r1_sha384:
+            case SignatureScheme.ecdsa_secp521r1_sha512:
+            case SignatureScheme.sm2sig_sm3:
+
+            default:
+                return null;
+            }
+        }
+
+        [Test]
+        public void TestDHDomain()
+        {
+            if (!m_crypto.HasDHAgreement())
+            {
+                return;
+            }
+
+            for (int namedGroup = 256; namedGroup < 512; ++namedGroup)
+            {
+                if (!NamedGroup.RefersToASpecificFiniteField(namedGroup) || !m_crypto.HasNamedGroup(namedGroup))
+                {
+                    continue;
+                }
+
+                ImplTestDHDomain(new TlsDHConfig(namedGroup, false));
+                ImplTestDHDomain(new TlsDHConfig(namedGroup, true));
+            }
+
+            IList groups = new TestTlsDHGroupVerifier().Groups;
+            foreach (DHGroup dhGroup in groups)
+            {
+                int namedGroup = TlsDHUtilities.GetNamedGroupForDHParameters(dhGroup.P, dhGroup.G);
+                if (NamedGroup.RefersToASpecificFiniteField(namedGroup))
+                {
+                    // Already tested the named groups
+                    continue;
+                }
+
+                ImplTestDHDomain(new TlsDHConfig(dhGroup));
+            }
+        }
+
+        [Test]
+        public void TestECDomain()
+        {
+            if (!m_crypto.HasECDHAgreement())
+            {
+                return;
+            }
+
+            for (int namedGroup = 0; namedGroup < 256; ++namedGroup)
+            {
+                if (!NamedGroup.RefersToAnECDHCurve(namedGroup) || !m_crypto.HasNamedGroup(namedGroup))
+                {
+                    continue;
+                }
+
+                ImplTestECDomain(new TlsECConfig(namedGroup));
+            }
+        }
+
+        [Test]
+        public void TestHkdf()
+        {
+            /*
+             * Test vectors drawn from the server-side calculations of example handshake trace in RFC 8448, section 3.
+             */
+
+            int hash = CryptoHashAlgorithm.sha256;
+            int hashLen = TlsCryptoUtilities.GetHashOutputSize(hash);
+
+            TlsSecret init = m_crypto.HkdfInit(hash), early, handshake, master, c_hs_t, s_hs_t, c_ap_t, s_ap_t, exp_master, res_master;
+
+            TlsHash prfHash = m_crypto.CreateHash(hash);
+
+            byte[] emptyTranscriptHash = GetCurrentHash(prfHash);
+            Expect(emptyTranscriptHash, "e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55");
+
+            // {server}  extract secret "early":
+            {
+                byte[] ikm = new byte[32];
+                early = init.HkdfExtract(hash, ikm);
+                Expect(early, "33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a");
+            }
+
+            // {server}  derive secret for handshake "tls13 derived":
+            {
+                string label = "derived";
+                handshake = TlsCryptoUtilities.HkdfExpandLabel(early, hash, label, emptyTranscriptHash, hashLen);
+                Expect(handshake, "6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba");
+            }
+
+            // {server}  extract secret "handshake":
+            {
+                byte[] ikm = Hex("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d");
+                handshake = handshake.HkdfExtract(hash, ikm);
+                Expect(handshake, "1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac");
+            }
+
+            prfHash.Update(ClientHello, 0, ClientHello.Length);
+            prfHash.Update(ServerHello, 0, ServerHello.Length);
+
+            byte[] serverHelloTranscriptHash = GetCurrentHash(prfHash);
+            Expect(serverHelloTranscriptHash, "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8");
+
+            // {server}  derive secret "tls13 c hs traffic":
+            {
+                string label = "c hs traffic";
+                c_hs_t = TlsCryptoUtilities.HkdfExpandLabel(handshake, hash, label, serverHelloTranscriptHash, hashLen);
+                Expect(c_hs_t, "b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21");
+            }
+
+            // {server}  derive secret "tls13 s hs traffic":
+            {
+                string label = "s hs traffic";
+                s_hs_t = TlsCryptoUtilities.HkdfExpandLabel(handshake, hash, label, serverHelloTranscriptHash, hashLen);
+                Expect(s_hs_t, "b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38");
+            }
+
+            // {server}  derive secret for master "tls13 derived":
+            {
+                string label = "derived";
+                master = TlsCryptoUtilities.HkdfExpandLabel(handshake, hash, label, emptyTranscriptHash, hashLen);
+                Expect(master, "43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4");
+            }
+
+            // {server}  extract secret "master":
+            {
+                byte[] ikm = new byte[32];
+                master = master.HkdfExtract(hash, ikm);
+                Expect(master, "18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19");
+            }
+
+            // {server}  derive write traffic keys for handshake data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(s_hs_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(s_hs_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "5d 31 3e b2 67 12 76 ee 13 00 0b 30");
+            }
+
+            prfHash.Update(EncryptedExtensions, 0, EncryptedExtensions.Length);
+            prfHash.Update(Certificate, 0, Certificate.Length);
+            prfHash.Update(CertificateVerify, 0, CertificateVerify.Length);
+
+            // {server}  calculate (server) finished "tls13 finished":
+            {
+                TlsSecret expanded = TlsCryptoUtilities.HkdfExpandLabel(s_hs_t, hash, "finished", TlsUtilities.EmptyBytes, hashLen);
+                Expect(expanded, "00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8");
+
+                // TODO Mention this transcript hash in RFC 8448 data?
+                byte[] transcriptHash = GetCurrentHash(prfHash);
+                Expect(transcriptHash, "ed b7 72 5f a7 a3 47 3b 03 1e c8 ef 65 a2 48 54 93 90 01 38 a2 b9 12 91 40 7d 79 51 a0 61 10 ed");
+
+                byte[] finished = CalculateHmac(hash, expanded, transcriptHash);
+                Expect(finished, Hex("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"));
+            }
+
+            prfHash.Update(ServerFinished, 0, ServerFinished.Length);
+
+            byte[] serverFinishedTranscriptHash = GetCurrentHash(prfHash);
+            Expect(serverFinishedTranscriptHash, "96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13");
+
+            // {server}  derive secret "tls13 c ap traffic":
+            {
+                string label = "c ap traffic";
+                c_ap_t = TlsCryptoUtilities.HkdfExpandLabel(master, hash, label, serverFinishedTranscriptHash, hashLen);
+                Expect(c_ap_t, "9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5");
+            }
+
+            // {server}  derive secret "tls13 s ap traffic":
+            {
+                string label = "s ap traffic";
+                s_ap_t = TlsCryptoUtilities.HkdfExpandLabel(master, hash, label, serverFinishedTranscriptHash, hashLen);
+                Expect(s_ap_t, "a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43");
+            }
+
+            // {server}  derive secret "tls13 exp master":
+            {
+                string label = "exp master";
+                exp_master = TlsCryptoUtilities.HkdfExpandLabel(master, hash, label, serverFinishedTranscriptHash, hashLen);
+                Expect(exp_master, "fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50");
+            }
+
+            // {server}  derive write traffic keys for application data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(s_ap_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(s_ap_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "cf 78 2b 88 dd 83 54 9a ad f1 e9 84");
+            }
+
+            // {server}  derive read traffic keys for handshake data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(c_hs_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(c_hs_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f");
+            }
+
+            // {server}  calculate (client) finished "tls13 finished":
+            {
+                TlsSecret expanded = TlsCryptoUtilities.HkdfExpandLabel(c_hs_t, hash, "finished", TlsUtilities.EmptyBytes, hashLen);
+                Expect(expanded, "b8 0a d0 10 15 fb 2f 0b d6 5f f7 d4 da 5d 6b f8 3f 84 82 1d 1f 87 fd c7 d3 c7 5b 5a 7b 42 d9 c4");
+
+                // TODO Mention this transcript hash in RFC 8448 data?
+                byte[] finished = CalculateHmac(hash, expanded, serverFinishedTranscriptHash);
+                Expect(finished, Hex("a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"));
+            }
+
+            prfHash.Update(ClientFinished, 0, ClientFinished.Length);
+
+            byte[] clientFinishedTranscriptHash = GetCurrentHash(prfHash);
+            Expect(clientFinishedTranscriptHash, "20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26 84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d");
+
+            // {server}  derive read traffic keys for application data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(c_ap_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "17 42 2d da 59 6e d5 d9 ac d8 90 e3 c6 3f 50 51");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(c_ap_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "5b 78 92 3d ee 08 57 90 33 e5 23 d9");
+            }
+
+            // {server}  derive secret "tls13 res master":
+            {
+                res_master = TlsCryptoUtilities.HkdfExpandLabel(master, hash, "res master", clientFinishedTranscriptHash, hashLen);
+                Expect(res_master, "7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c");
+            }
+
+            // {server}  generate resumption secret "tls13 resumption":
+            {
+                byte[] context = Hex("00 00");
+                TlsSecret expanded = TlsCryptoUtilities.HkdfExpandLabel(res_master, hash, "resumption", context, hashLen);
+                Expect(expanded, "4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c a4 c5 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3");
+            }
+        }
+
+        [Test]
+        public void TestHkdfExpandLimit()
+        {
+            int[] hashes = new int[] { CryptoHashAlgorithm.md5, CryptoHashAlgorithm.sha1, CryptoHashAlgorithm.sha224,
+                CryptoHashAlgorithm.sha256, CryptoHashAlgorithm.sha384, CryptoHashAlgorithm.sha512,
+                CryptoHashAlgorithm.sm3 };
+
+            for (int i = 0; i < hashes.Length; ++i)
+            {
+                int hash = hashes[i];
+                int hashLen = TlsCryptoUtilities.GetHashOutputSize(hash);
+                byte[] zeroes = new byte[hashLen];
+
+                int limit = 255 * hashLen;
+
+                TlsSecret secret = m_crypto.HkdfInit(hash).HkdfExtract(hash, zeroes);
+
+                try
+                {
+                    secret.HkdfExpand(hash, TlsUtilities.EmptyBytes, limit);
+                }
+                catch (Exception e)
+                {
+                    Assert.Fail("Unexpected exception: " + e.Message);
+                }
+
+                try
+                {
+                    secret.HkdfExpand(hash, TlsUtilities.EmptyBytes, limit + 1);
+                    Assert.Fail("Expected an exception!");
+                }
+                catch (ArgumentException)
+                {
+                    // Expected
+                }
+                catch (Exception e)
+                {
+                    Assert.Fail("Unexpected exception: " + e.Message);
+                }
+            }
+        }
+
+        [Test]
+        public void TestSignaturesLegacy()
+        {
+            short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.dsa, SignatureAlgorithm.ecdsa,
+                SignatureAlgorithm.rsa };
+
+            TlsCryptoParameters cryptoParams = new TestTlsCryptoParameters(ProtocolVersion.TLSv11);
+
+            for (int i = 0; i < signatureAlgorithms.Length; ++i)
+            {
+                short signatureAlgorithm = signatureAlgorithms[i];
+                if (!m_crypto.HasSignatureAlgorithm(signatureAlgorithm))
+                    continue;
+
+                TlsCredentialedSigner credentialedSigner = LoadCredentialedSignerLegacy(cryptoParams,
+                    signatureAlgorithm);
+                if (null != credentialedSigner)
+                {
+                    ImplTestSignatureLegacy(credentialedSigner);
+                }
+            }
+        }
+
+        [Test]
+        public void TestSignatures12()
+        {
+            short[] hashAlgorithms = new short[]{ HashAlgorithm.md5, HashAlgorithm.sha1, HashAlgorithm.sha224,
+                HashAlgorithm.sha256, HashAlgorithm.sha384, HashAlgorithm.sha512 };
+            short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.dsa, SignatureAlgorithm.ecdsa,
+                SignatureAlgorithm.rsa };
+
+            TlsCryptoParameters cryptoParams = new TestTlsCryptoParameters(ProtocolVersion.TLSv12);
+
+            for (int i = 0; i < signatureAlgorithms.Length; ++i)
+            {
+                short signatureAlgorithm = signatureAlgorithms[i];
+
+                for (int j = 0; j < hashAlgorithms.Length; ++j)
+                {
+                    SignatureAndHashAlgorithm signatureAndHashAlgorithm = SignatureAndHashAlgorithm.GetInstance(
+                        hashAlgorithms[j], signatureAlgorithm);
+                    if (!m_crypto.HasSignatureAndHashAlgorithm(signatureAndHashAlgorithm))
+                        continue;
+
+                    TlsCredentialedSigner credentialedSigner = LoadCredentialedSigner12(cryptoParams,
+                        signatureAndHashAlgorithm);
+                    if (null != credentialedSigner)
+                    {
+                        ImplTestSignature12(credentialedSigner, signatureAndHashAlgorithm);
+                    }
+                }
+            }
+
+            // Signature algorithms usable with HashAlgorithm.Intrinsic in TLS 1.2
+            short[] intrinsicSignatureAlgorithms = new short[] { SignatureAlgorithm.ed25519, SignatureAlgorithm.ed448,
+                SignatureAlgorithm.gostr34102012_256, SignatureAlgorithm.gostr34102012_512,
+                SignatureAlgorithm.rsa_pss_pss_sha256, SignatureAlgorithm.rsa_pss_pss_sha384,
+                SignatureAlgorithm.rsa_pss_pss_sha512, SignatureAlgorithm.rsa_pss_rsae_sha256,
+                SignatureAlgorithm.rsa_pss_rsae_sha384, SignatureAlgorithm.rsa_pss_rsae_sha512, };
+
+            for (int i = 0; i < intrinsicSignatureAlgorithms.Length; ++i)
+            {
+                SignatureAndHashAlgorithm signatureAndHashAlgorithm = SignatureAndHashAlgorithm.GetInstance(
+                    HashAlgorithm.Intrinsic, intrinsicSignatureAlgorithms[i]);
+                if (!m_crypto.HasSignatureAndHashAlgorithm(signatureAndHashAlgorithm))
+                    continue;
+
+                TlsCredentialedSigner credentialedSigner = LoadCredentialedSigner12(cryptoParams,
+                    signatureAndHashAlgorithm);
+                if (null != credentialedSigner)
+                {
+                    ImplTestSignature12(credentialedSigner, signatureAndHashAlgorithm);
+                }
+            }
+        }
+
+        [Test]
+        public void TestSignatures13()
+        {
+            int[] signatureSchemes = new int[] { SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
+                SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384, SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
+                SignatureScheme.ecdsa_secp256r1_sha256, SignatureScheme.ecdsa_secp384r1_sha384,
+                SignatureScheme.ecdsa_secp521r1_sha512, SignatureScheme.ed25519, SignatureScheme.ed448,
+                SignatureScheme.rsa_pss_pss_sha256, SignatureScheme.rsa_pss_pss_sha384, SignatureScheme.rsa_pss_pss_sha512,
+                SignatureScheme.rsa_pss_rsae_sha256, SignatureScheme.rsa_pss_rsae_sha384,
+                SignatureScheme.rsa_pss_rsae_sha512, SignatureScheme.sm2sig_sm3,
+                // These are only used for certs in 1.3 (cert verification is not done by TlsCrypto)
+                //SignatureScheme.ecdsa_sha1, SignatureScheme.rsa_pkcs1_sha1, SignatureScheme.rsa_pkcs1_sha256,
+                //SignatureScheme.rsa_pkcs1_sha384, SignatureScheme.rsa_pkcs1_sha512,
+            };
+
+            TlsCryptoParameters cryptoParams = new TestTlsCryptoParameters(ProtocolVersion.TLSv13);
+
+            for (int i = 0; i < signatureSchemes.Length; ++i)
+            {
+                int signatureScheme = signatureSchemes[i];
+                if (!m_crypto.HasSignatureScheme(signatureScheme))
+                    continue;
+
+                TlsCredentialedSigner credentialedSigner = LoadCredentialedSigner13(cryptoParams, signatureScheme);
+                if (null != credentialedSigner)
+                {
+                    ImplTestSignature13(credentialedSigner, signatureScheme);
+                }
+            }
+        }
+
+        private byte[] CalculateHmac(int cryptoHashAlgorithm, TlsSecret hmacKey, byte[] hmacInput)
+        {
+            byte[] keyBytes = Extract(hmacKey);
+
+            TlsHmac hmac = m_crypto.CreateHmacForHash(cryptoHashAlgorithm);
+            hmac.SetKey(keyBytes, 0, keyBytes.Length);
+            hmac.Update(hmacInput, 0, hmacInput.Length);
+            return hmac.CalculateMac();
+        }
+
+        private void Expect(TlsSecret secret, string expectedHex)
+        {
+            Expect(Extract(secret), Hex(expectedHex));
+        }
+
+        private void Expect(byte[] actualOctets, string expectedHex)
+        {
+            Expect(actualOctets, Hex(expectedHex));
+        }
+
+        private void Expect(byte[] actualOctets, byte[] expectedOctets)
+        {
+            AssertArrayEquals(actualOctets, expectedOctets);
+        }
+
+        private byte[] Extract(TlsSecret secret)
+        {
+            return m_crypto.AdoptSecret(secret).Extract();
+        }
+
+        private byte[] GetCurrentHash(TlsHash hash)
+        {
+            return hash.CloneHash().CalculateHash();
+        }
+
+        private static void AssertArrayEquals(byte[] a, byte[] b)
+        {
+            Assert.IsTrue(Arrays.AreEqual(a, b));
+        }
+
+        private static byte[] Hex(string s)
+        {
+            return Utilities.Encoders.Hex.Decode(s.Replace(" ", ""));
+        }
+
+        private void ImplTestAgreement(TlsAgreement aA, TlsAgreement aB)
+        {
+            byte[] pA = aA.GenerateEphemeral();
+            byte[] pB = aB.GenerateEphemeral();
+
+            aA.ReceivePeerValue(pB);
+            aB.ReceivePeerValue(pA);
+
+            TlsSecret sA = aA.CalculateSecret();
+            TlsSecret sB = aB.CalculateSecret();
+
+            AssertArrayEquals(Extract(sA), Extract(sB));
+        }
+
+        private void ImplTestDHDomain(TlsDHConfig dhConfig)
+        {
+            int namedGroup = dhConfig.NamedGroup;
+            int bits = namedGroup >= 0
+                ? NamedGroup.GetFiniteFieldBits(namedGroup)
+                : dhConfig.ExplicitGroup.P.BitLength;
+
+            int rounds = System.Math.Max(2, 11 - (bits >> 10));
+
+            TlsDHDomain d = m_crypto.CreateDHDomain(dhConfig);
+
+            for (int i = 0; i < rounds; ++i)
+            {
+                TlsAgreement aA = d.CreateDH();
+                TlsAgreement aB = d.CreateDH();
+
+                ImplTestAgreement(aA, aB);
+            }
+        }
+
+        private void ImplTestECDomain(TlsECConfig ecConfig)
+        {
+            int bits = NamedGroup.GetCurveBits(ecConfig.NamedGroup);
+            int rounds = System.Math.Max(2, 12 - (bits >> 6));
+
+            TlsECDomain d = m_crypto.CreateECDomain(ecConfig);
+
+            for (int i = 0; i < rounds; ++i)
+            {
+                TlsAgreement aA = d.CreateECDH();
+                TlsAgreement aB = d.CreateECDH();
+
+                ImplTestAgreement(aA, aB);
+            }
+        }
+
+        private void ImplTestSignatureLegacy(TlsCredentialedSigner credentialedSigner)
+        {
+            byte[] message = m_crypto.CreateNonceGenerator(TlsUtilities.EmptyBytes).GenerateNonce(100);
+
+            byte[] signature;
+            TlsStreamSigner tlsStreamSigner = credentialedSigner.GetStreamSigner();
+            if (null != tlsStreamSigner)
+            {
+                Stream output = tlsStreamSigner.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                signature = tlsStreamSigner.GetSignature();
+            }
+            else
+            {
+                TlsHash tlsHash = new CombinedHash(m_crypto);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(null, signature);
+
+            TlsCertificate tlsCertificate = credentialedSigner.Certificate.GetCertificateAt(0);
+            TlsVerifier tlsVerifier = tlsCertificate.CreateVerifier(tlsCertificate.GetLegacySignatureAlgorithm());
+
+            bool verified;
+            TlsStreamVerifier tlsStreamVerifier = tlsVerifier.GetStreamVerifier(digitallySigned);
+            if (null != tlsStreamVerifier)
+            {
+                Stream output = tlsStreamVerifier.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                verified = tlsStreamVerifier.IsVerified();
+            }
+            else
+            {
+                TlsHash tlsHash = new CombinedHash(m_crypto);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                verified = tlsVerifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            Assert.IsTrue(verified);
+        }
+
+        private void ImplTestSignature12(TlsCredentialedSigner credentialedSigner,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            short hashAlgorithm = signatureAndHashAlgorithm.Hash;
+
+            byte[] message = m_crypto.CreateNonceGenerator(TlsUtilities.EmptyBytes).GenerateNonce(100);
+
+            byte[] signature;
+            TlsStreamSigner tlsStreamSigner = credentialedSigner.GetStreamSigner();
+            if (null != tlsStreamSigner)
+            {
+                Stream output = tlsStreamSigner.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                signature = tlsStreamSigner.GetSignature();
+            }
+            else
+            {
+                // Currently 1.2 relies on these being handled by stream signers 
+                Assert.IsTrue(HashAlgorithm.Intrinsic != hashAlgorithm);
+
+                int cryptoHashAlgorithm = TlsCryptoUtilities.GetHash(hashAlgorithm);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(signatureAndHashAlgorithm, signature);
+
+            TlsCertificate tlsCertificate = credentialedSigner.Certificate.GetCertificateAt(0);
+            TlsVerifier tlsVerifier = tlsCertificate.CreateVerifier(signatureAndHashAlgorithm.Signature);
+
+            bool verified;
+            TlsStreamVerifier tlsStreamVerifier = tlsVerifier.GetStreamVerifier(digitallySigned);
+            if (null != tlsStreamVerifier)
+            {
+                Stream output = tlsStreamVerifier.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                verified = tlsStreamVerifier.IsVerified();
+            }
+            else
+            {
+                // Currently 1.2 relies on these being handled by stream verifiers 
+                Assert.IsTrue(HashAlgorithm.Intrinsic != hashAlgorithm);
+
+                int cryptoHashAlgorithm = TlsCryptoUtilities.GetHash(hashAlgorithm);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                verified = tlsVerifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            Assert.IsTrue(verified);
+        }
+
+        private void ImplTestSignature13(TlsCredentialedSigner credentialedSigner, int signatureScheme)
+        {
+            byte[] message = m_crypto.CreateNonceGenerator(TlsUtilities.EmptyBytes).GenerateNonce(100);
+
+            byte[] signature;
+            TlsStreamSigner tlsStreamSigner = credentialedSigner.GetStreamSigner();
+            if (null != tlsStreamSigner)
+            {
+                Stream output = tlsStreamSigner.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                signature = tlsStreamSigner.GetSignature();
+            }
+            else
+            {
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(
+                SignatureScheme.GetSignatureAndHashAlgorithm(signatureScheme), signature);
+
+            TlsCertificate tlsCertificate = credentialedSigner.Certificate.GetCertificateAt(0);
+            TlsVerifier tlsVerifier = tlsCertificate.CreateVerifier(signatureScheme);
+
+            bool verified;
+            TlsStreamVerifier tlsStreamVerifier = tlsVerifier.GetStreamVerifier(digitallySigned);
+            if (null != tlsStreamVerifier)
+            {
+                Stream output = tlsStreamVerifier.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                verified = tlsStreamVerifier.IsVerified();
+            }
+            else
+            {
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                verified = tlsVerifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            Assert.IsTrue(verified);
+        }
+
+        private class TestTlsCryptoParameters
+            : TlsCryptoParameters
+        {
+            private readonly ProtocolVersion m_serverVersion;
+
+            internal TestTlsCryptoParameters(ProtocolVersion serverVersion)
+                : base(null)
+            {
+                this.m_serverVersion = serverVersion;
+            }
+
+            public override ProtocolVersion ServerVersion
+            {
+                get { return m_serverVersion; }
+            }
+        }
+
+        private class TestTlsDHGroupVerifier
+            : DefaultTlsDHGroupVerifier
+        {
+            internal IList Groups
+            {
+                get { return m_groups; }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/ByteQueueInputStreamTest.cs b/crypto/test/src/tls/test/ByteQueueInputStreamTest.cs
new file mode 100644
index 000000000..9edb3109b
--- /dev/null
+++ b/crypto/test/src/tls/test/ByteQueueInputStreamTest.cs
@@ -0,0 +1,134 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class ByteQueueInputStreamTest
+    {
+        [Test]
+        public void TestAvailable()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+
+            // buffer is empty
+            Assert.AreEqual(0, input.Available);
+
+            // after adding once
+            input.AddBytes(new byte[10]);
+            Assert.AreEqual(10, input.Available);
+
+            // after adding more than once
+            input.AddBytes(new byte[5]);
+            Assert.AreEqual(15, input.Available);
+
+            // after reading a single byte
+            input.ReadByte();
+            Assert.AreEqual(14, input.Available);
+
+            // after reading into a byte array
+            input.Read(new byte[4], 0, 4);
+            Assert.AreEqual(10, input.Available);
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestSkip()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+
+            // skip when buffer is empty
+            Assert.AreEqual(0, input.Skip(10));
+
+            // skip equal to available
+            input.AddBytes(new byte[2]);
+            Assert.AreEqual(2, input.Skip(2));
+            Assert.AreEqual(0, input.Available);
+
+            // skip less than available
+            input.AddBytes(new byte[10]);
+            Assert.AreEqual(5, input.Skip(5));
+            Assert.AreEqual(5, input.Available);
+
+            // skip more than available
+            Assert.AreEqual(5, input.Skip(20));
+            Assert.AreEqual(0, input.Available);
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestRead()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+            input.AddBytes(new byte[]{ 0x01, 0x02 });
+            input.AddBytes(new byte[]{ 0x03 });
+
+            Assert.AreEqual(0x01, input.ReadByte());
+            Assert.AreEqual(0x02, input.ReadByte());
+            Assert.AreEqual(0x03, input.ReadByte());
+            Assert.AreEqual(-1, input.ReadByte());
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestReadArray()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+            input.AddBytes(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+            byte[] buffer = new byte[5];
+
+            // read less than available into specified position
+            Assert.AreEqual(1, input.Read(buffer, 2, 1));
+            AssertArrayEquals(new byte[]{ 0x00, 0x00, 0x01, 0x00, 0x00 }, buffer);
+
+            // read equal to available
+            Assert.AreEqual(5, input.Read(buffer, 0, buffer.Length));
+            AssertArrayEquals(new byte[]{ 0x02, 0x03, 0x04, 0x05, 0x06 }, buffer);
+
+            // read more than available
+            input.AddBytes(new byte[]{ 0x01, 0x02, 0x03 });
+            Assert.AreEqual(3, input.Read(buffer, 0, buffer.Length));
+            AssertArrayEquals(new byte[]{ 0x01, 0x02, 0x03, 0x05, 0x06 }, buffer);
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestPeek()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+
+            byte[] buffer = new byte[5];
+
+            // peek more than available
+            Assert.AreEqual(0, input.Peek(buffer));
+            AssertArrayEquals(new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00 }, buffer);
+
+            // peek less than available
+            input.AddBytes(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+            Assert.AreEqual(5, input.Peek(buffer));
+            AssertArrayEquals(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05 }, buffer);
+            Assert.AreEqual(6, input.Available);
+
+            // peek equal to available
+            input.ReadByte();
+            Assert.AreEqual(5, input.Peek(buffer));
+            AssertArrayEquals(new byte[]{ 0x02, 0x03, 0x04, 0x05, 0x06 }, buffer);
+            Assert.AreEqual(5, input.Available);
+
+            input.Close();
+        }
+
+        private static void AssertArrayEquals(byte[] a, byte[] b)
+        {
+            Assert.IsTrue(Arrays.AreEqual(a, b));
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/CertChainUtilities.cs b/crypto/test/src/tls/test/CertChainUtilities.cs
new file mode 100644
index 000000000..8df8e48bc
--- /dev/null
+++ b/crypto/test/src/tls/test/CertChainUtilities.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Threading;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Operators;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Extension;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class CertChainUtilities
+    {
+        private static readonly SecureRandom Random = new SecureRandom();
+
+        private static long serialNumber = 1L;
+
+        /// <summary>We generate the CA's certificate.</summary>
+        public static X509Certificate CreateMasterCert(string rootDN, AsymmetricCipherKeyPair keyPair)
+        {
+            //
+            // create the certificate - version 1
+            //
+            X509V1CertificateGenerator gen = new X509V1CertificateGenerator();
+            gen.SetIssuerDN(new X509Name(rootDN));
+            gen.SetSerialNumber(BigInteger.ValueOf(Interlocked.Increment(ref serialNumber)));
+            gen.SetNotBefore(DateTime.UtcNow.AddDays(-30));
+            gen.SetNotAfter(DateTime.UtcNow.AddDays(30));
+            gen.SetSubjectDN(new X509Name(rootDN));
+            gen.SetPublicKey(keyPair.Public);
+
+            return SignV1(gen, keyPair.Private);
+        }
+
+        /// <summary>We generate an intermediate certificate signed by our CA.</summary>
+        public static X509Certificate CreateIntermediateCert(string interDN, AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter caPrivKey, X509Certificate caCert)
+        {
+            //
+            // create the certificate - version 3
+            //
+            X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
+            gen.SetIssuerDN(caCert.SubjectDN);
+            gen.SetSerialNumber(BigInteger.ValueOf(Interlocked.Increment(ref serialNumber)));
+            gen.SetNotBefore(DateTime.UtcNow.AddDays(-30));
+            gen.SetNotAfter(DateTime.UtcNow.AddDays(30));
+            gen.SetSubjectDN(new X509Name(interDN));
+            gen.SetPublicKey(pubKey);
+
+            //
+            // extensions
+            //
+            gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
+                new SubjectKeyIdentifierStructure(pubKey));
+            gen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false,
+                new AuthorityKeyIdentifierStructure(caCert));
+            gen.AddExtension(X509Extensions.BasicConstraints, true,
+                new BasicConstraints(0));
+
+            return SignV3(gen, caPrivKey);
+        }
+
+        /// <summary>We generate a certificate signed by our CA's intermediate certificate.</summary>
+        public static X509Certificate CreateEndEntityCert(string endEntityDN, AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter caPrivKey, X509Certificate caCert)
+        {
+            X509V3CertificateGenerator gen = CreateBaseEndEntityGenerator(endEntityDN, pubKey, caCert);
+
+            return SignV3(gen, caPrivKey);
+        }
+
+        /// <summary>We generate a certificate signed by our CA's intermediate certificate with ExtendedKeyUsage
+        /// extension.</summary>
+        public static X509Certificate CreateEndEntityCert(string endEntityDN, AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter caPrivKey, X509Certificate caCert, KeyPurposeID keyPurposeID)
+        {
+            X509V3CertificateGenerator gen = CreateBaseEndEntityGenerator(endEntityDN, pubKey, caCert);
+
+            gen.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(keyPurposeID));
+
+            return SignV3(gen, caPrivKey);
+        }
+
+        private static X509V3CertificateGenerator CreateBaseEndEntityGenerator(string endEntityDN,
+            AsymmetricKeyParameter pubKey, X509Certificate caCert)
+        {
+            //
+            // create the certificate - version 3
+            //
+            X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
+            gen.SetIssuerDN(caCert.SubjectDN);
+            gen.SetSerialNumber(BigInteger.ValueOf(Interlocked.Increment(ref serialNumber)));
+            gen.SetNotBefore(DateTime.UtcNow.AddDays(-30));
+            gen.SetNotAfter(DateTime.UtcNow.AddDays(30));
+            gen.SetSubjectDN(new X509Name(endEntityDN));
+            gen.SetPublicKey(pubKey);
+
+            //
+            // add the extensions
+            //
+            gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
+                new SubjectKeyIdentifierStructure(pubKey));
+            gen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false,
+                new AuthorityKeyIdentifierStructure(caCert.GetPublicKey()));
+            gen.AddExtension(X509Extensions.BasicConstraints, true,
+                new BasicConstraints(false));
+
+            return gen;
+        }
+
+        private static X509Certificate SignV1(X509V1CertificateGenerator gen, AsymmetricKeyParameter caPrivKey)
+        {
+            return gen.Generate(new Asn1SignatureFactory("SHA256withRSA", caPrivKey, Random));
+        }
+
+        private static X509Certificate SignV3(X509V3CertificateGenerator gen, AsymmetricKeyParameter caPrivKey)
+        {
+            return gen.Generate(new Asn1SignatureFactory("SHA256withRSA", caPrivKey, Random));
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsProtocolTest.cs b/crypto/test/src/tls/test/DtlsProtocolTest.cs
new file mode 100644
index 000000000..388003666
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsProtocolTest.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class DtlsProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            SecureRandom secureRandom = new SecureRandom();
+
+            DtlsClientProtocol clientProtocol = new DtlsClientProtocol();
+            DtlsServerProtocol serverProtocol = new DtlsServerProtocol();
+
+            MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+            Server server = new Server(serverProtocol, network.Server);
+
+            Thread serverThread = new Thread(new ThreadStart(server.Run));
+            serverThread.Start();
+
+            DatagramTransport clientTransport = network.Client;
+
+            clientTransport = new UnreliableDatagramTransport(clientTransport, secureRandom, 0, 0);
+
+            clientTransport = new LoggingDatagramTransport(clientTransport, Console.Out);
+
+            MockDtlsClient client = new MockDtlsClient(null);
+
+            DtlsTransport dtlsClient = clientProtocol.Connect(client, clientTransport);
+
+            for (int i = 1; i <= 10; ++i)
+            {
+                byte[] data = new byte[i];
+                Arrays.Fill(data, (byte)i);
+                dtlsClient.Send(data, 0, data.Length);
+            }
+
+            byte[] buf = new byte[dtlsClient.GetReceiveLimit()];
+            while (dtlsClient.Receive(buf, 0, buf.Length, 100) >= 0)
+            {
+            }
+
+            dtlsClient.Close();
+
+            server.Shutdown(serverThread);
+        }
+
+        internal class Server
+        {
+            private readonly DtlsServerProtocol m_serverProtocol;
+            private readonly DatagramTransport m_serverTransport;
+            private volatile bool m_isShutdown = false;
+
+            internal Server(DtlsServerProtocol serverProtocol, DatagramTransport serverTransport)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_serverTransport = serverTransport;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockDtlsServer server = new MockDtlsServer();
+                    DtlsTransport dtlsServer = m_serverProtocol.Accept(server, m_serverTransport);
+                    byte[] buf = new byte[dtlsServer.GetReceiveLimit()];
+                    while (!m_isShutdown)
+                    {
+                        int length = dtlsServer.Receive(buf, 0, buf.Length, 1000);
+                        if (length >= 0)
+                        {
+                            dtlsServer.Send(buf, 0, length);
+                        }
+                    }
+                    dtlsServer.Close();
+                }
+                catch (Exception e)
+                {
+                    Console.Error.WriteLine(e);
+                    Console.Error.Flush();
+                }
+            }
+
+            internal void Shutdown(Thread serverThread)
+            {
+                if (!m_isShutdown)
+                {
+                    this.m_isShutdown = true;
+                    serverThread.Join();
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsPskProtocolTest.cs b/crypto/test/src/tls/test/DtlsPskProtocolTest.cs
new file mode 100644
index 000000000..0da6cb661
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsPskProtocolTest.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class DlsPskProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            SecureRandom secureRandom = new SecureRandom();
+
+            DtlsClientProtocol clientProtocol = new DtlsClientProtocol();
+            DtlsServerProtocol serverProtocol = new DtlsServerProtocol();
+
+            MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+            Server server = new Server(serverProtocol, network.Server);
+
+            Thread serverThread = new Thread(new ThreadStart(server.Run));
+            serverThread.Start();
+
+            DatagramTransport clientTransport = network.Client;
+
+            clientTransport = new UnreliableDatagramTransport(clientTransport, secureRandom, 0, 0);
+
+            clientTransport = new LoggingDatagramTransport(clientTransport, Console.Out);
+
+            MockPskDtlsClient client = new MockPskDtlsClient(null);
+
+            DtlsTransport dtlsClient = clientProtocol.Connect(client, clientTransport);
+
+            for (int i = 1; i <= 10; ++i)
+            {
+                byte[] data = new byte[i];
+                Arrays.Fill(data, (byte)i);
+                dtlsClient.Send(data, 0, data.Length);
+            }
+
+            byte[] buf = new byte[dtlsClient.GetReceiveLimit()];
+            while (dtlsClient.Receive(buf, 0, buf.Length, 100) >= 0)
+            {
+            }
+
+            dtlsClient.Close();
+
+            server.Shutdown(serverThread);
+        }
+
+        internal class Server
+        {
+            private readonly DtlsServerProtocol m_serverProtocol;
+            private readonly DatagramTransport m_serverTransport;
+            private volatile bool m_isShutdown = false;
+
+            internal Server(DtlsServerProtocol serverProtocol, DatagramTransport serverTransport)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_serverTransport = serverTransport;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockPskDtlsServer server = new MockPskDtlsServer();
+                    DtlsTransport dtlsServer = m_serverProtocol.Accept(server, m_serverTransport);
+                    byte[] buf = new byte[dtlsServer.GetReceiveLimit()];
+                    while (!m_isShutdown)
+                    {
+                        int length = dtlsServer.Receive(buf, 0, buf.Length, 1000);
+                        if (length >= 0)
+                        {
+                            dtlsServer.Send(buf, 0, length);
+                        }
+                    }
+                    dtlsServer.Close();
+                }
+                catch (Exception e)
+                {
+                    Console.Error.WriteLine(e);
+                    Console.Error.Flush();
+                }
+            }
+
+            internal void Shutdown(Thread serverThread)
+            {
+                if (!m_isShutdown)
+                {
+                    this.m_isShutdown = true;
+                    serverThread.Join();
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestCase.cs b/crypto/test/src/tls/test/DtlsTestCase.cs
new file mode 100644
index 000000000..d93f17c27
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestCase.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class DtlsTestCase
+    {
+        private static void CheckDtlsVersions(ProtocolVersion[] versions)
+        {
+            if (versions != null)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    if (!versions[i].IsDtls)
+                        throw new InvalidOperationException("Non-DTLS version");
+                }
+            }
+        }
+
+        [Test, TestCaseSource(typeof(DtlsTestSuite), "Suite")]
+        public void RunTest(TlsTestConfig config)
+        {
+            CheckDtlsVersions(config.clientSupportedVersions);
+            CheckDtlsVersions(config.serverSupportedVersions);
+
+            DtlsTestClientProtocol clientProtocol = new DtlsTestClientProtocol(config);
+            DtlsTestServerProtocol serverProtocol = new DtlsTestServerProtocol(config);
+
+            MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+            TlsTestClientImpl clientImpl = new TlsTestClientImpl(config);
+            TlsTestServerImpl serverImpl = new TlsTestServerImpl(config);
+
+            Server server = new Server(this, serverProtocol, network.Server, serverImpl);
+
+            Thread serverThread = new Thread(new ThreadStart(server.Run));
+            serverThread.Start();
+
+            Exception caught = null;
+            try
+            {
+                DatagramTransport clientTransport = network.Client;
+
+                if (TlsTestConfig.Debug)
+                {
+                    clientTransport = new LoggingDatagramTransport(clientTransport, Console.Out);
+                }
+
+                DtlsTransport dtlsClient = clientProtocol.Connect(clientImpl, clientTransport);
+
+                for (int i = 1; i <= 10; ++i)
+                {
+                    byte[] data = new byte[i];
+                    Arrays.Fill(data, (byte)i);
+                    dtlsClient.Send(data, 0, data.Length);
+                }
+
+                byte[] buf = new byte[dtlsClient.GetReceiveLimit()];
+                while (dtlsClient.Receive(buf, 0, buf.Length, 100) >= 0)
+                {
+                }
+
+                dtlsClient.Close();
+            }
+            catch (Exception e)
+            {
+                caught = e;
+                LogException(caught);
+            }
+
+            server.Shutdown(serverThread);
+
+            // TODO Add checks that the various streams were closed
+
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, clientImpl.FirstFatalAlertConnectionEnd,
+                "Client fatal alert connection end");
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, serverImpl.FirstFatalAlertConnectionEnd,
+                "Server fatal alert connection end");
+
+            Assert.AreEqual(config.expectFatalAlertDescription, clientImpl.FirstFatalAlertDescription,
+                "Client fatal alert description");
+            Assert.AreEqual(config.expectFatalAlertDescription, serverImpl.FirstFatalAlertDescription,
+                "Server fatal alert description");
+
+            if (config.expectFatalAlertConnectionEnd == -1)
+            {
+                Assert.IsNull(caught, "Unexpected client exception");
+                Assert.IsNull(server.Caught, "Unexpected server exception");
+            }
+        }
+
+        protected void LogException(Exception e)
+        {
+            if (TlsTestConfig.Debug)
+            {
+                Console.Error.WriteLine(e);
+                Console.Error.Flush();
+            }
+        }
+
+        internal class Server
+        {
+            private readonly DtlsTestCase m_outer;
+            private readonly DtlsTestServerProtocol m_serverProtocol;
+            private readonly DatagramTransport m_serverTransport;
+            private readonly TlsTestServerImpl m_serverImpl;
+
+            private volatile bool m_isShutdown = false;
+            private Exception m_caught = null;
+
+            internal Server(DtlsTestCase outer, DtlsTestServerProtocol serverProtocol,
+                DatagramTransport serverTransport, TlsTestServerImpl serverImpl)
+            {
+                this.m_outer = outer;
+                this.m_serverProtocol = serverProtocol;
+                this.m_serverTransport = serverTransport;
+                this.m_serverImpl = serverImpl;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    DtlsTransport dtlsServer = m_serverProtocol.Accept(m_serverImpl, m_serverTransport);
+                    byte[] buf = new byte[dtlsServer.GetReceiveLimit()];
+                    while (!m_isShutdown)
+                    {
+                        int length = dtlsServer.Receive(buf, 0, buf.Length, 100);
+                        if (length >= 0)
+                        {
+                            dtlsServer.Send(buf, 0, length);
+                        }
+                    }
+                    dtlsServer.Close();
+                }
+                catch (Exception e)
+                {
+                    this.m_caught = e;
+                    m_outer.LogException(m_caught);
+                }
+            }
+
+            internal void Shutdown(Thread serverThread)
+            {
+                if (!m_isShutdown)
+                {
+                    this.m_isShutdown = true;
+                    //serverThread.Interrupt();
+                    serverThread.Join();
+                }
+            }
+
+            internal Exception Caught
+            {
+                get { return m_caught; }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestClientProtocol.cs b/crypto/test/src/tls/test/DtlsTestClientProtocol.cs
new file mode 100644
index 000000000..99a7fa07b
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestClientProtocol.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class DtlsTestClientProtocol
+        : DtlsClientProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        public DtlsTestClientProtocol(TlsTestConfig config)
+            : base()
+        {
+            this.m_config = config;
+        }
+
+        protected override byte[] GenerateCertificateVerify(ClientHandshakeState state,
+            DigitallySigned certificateVerify)
+        {
+            if (certificateVerify.Algorithm != null && m_config.clientAuthSigAlgClaimed != null)
+            {
+                certificateVerify = new DigitallySigned(m_config.clientAuthSigAlgClaimed, certificateVerify.Signature);
+            }
+
+            return base.GenerateCertificateVerify(state, certificateVerify);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestServerProtocol.cs b/crypto/test/src/tls/test/DtlsTestServerProtocol.cs
new file mode 100644
index 000000000..08fc2b6a9
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestServerProtocol.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class DtlsTestServerProtocol
+        : DtlsServerProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        public DtlsTestServerProtocol(TlsTestConfig config)
+            : base()
+        {
+            this.m_config = config;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestSuite.cs b/crypto/test/src/tls/test/DtlsTestSuite.cs
new file mode 100644
index 000000000..0af2be32c
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestSuite.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections;
+
+using NUnit.Framework;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class DtlsTestSuite
+    {
+        // Make the access to constants less verbose 
+        internal class C : TlsTestConfig {}
+
+        public DtlsTestSuite()
+        {
+        }
+
+        public static IEnumerable Suite()
+        {
+            IList testSuite = new ArrayList();
+
+            AddFallbackTests(testSuite);
+            AddVersionTests(testSuite, ProtocolVersion.DTLSv10);
+            AddVersionTests(testSuite, ProtocolVersion.DTLSv12);
+
+            return testSuite;
+        }
+
+        private static void AddFallbackTests(IList testSuite)
+        {
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(ProtocolVersion.DTLSv12);
+                c.clientFallback = true;
+
+                AddTestCase(testSuite, c, "FallbackGood");
+            }
+
+            /*
+             * NOTE: Temporarily disabled automatic test runs because of problems getting a clean exit
+             * of the DTLS server after a fatal alert. As of writing, manual runs show the correct
+             * alerts being raised
+             */
+
+#if false
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(ProtocolVersion.DTLSv12);
+                c.clientFallback = true;
+                c.clientSupportedVersions = ProtocolVersion.DTLSv10.Only();
+                c.ExpectServerFatalAlert(AlertDescription.inappropriate_fallback);
+
+                AddTestCase(testSuite, c, "FallbackBad");
+            }
+#endif
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(ProtocolVersion.DTLSv12);
+                c.clientSupportedVersions = ProtocolVersion.DTLSv10.Only();
+
+                AddTestCase(testSuite, c, "FallbackNone");
+            }
+        }
+
+        private static void AddVersionTests(IList testSuite, ProtocolVersion version)
+        {
+            string prefix = version.ToString()
+                .Replace(" ", "")
+                .Replace("\\", "")
+                .Replace(".", "")
+                + "_";
+
+            /*
+             * NOTE: Temporarily disabled automatic test runs because of problems getting a clean exit
+             * of the DTLS server after a fatal alert. As of writing, manual runs show the correct
+             * alerts being raised
+             */
+
+#if false
+            /*
+             * Server only declares support for SHA1/RSA, client selects MD5/RSA. Since the client is
+             * NOT actually tracking MD5 over the handshake, we expect fatal alert from the client.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultRsaSignatureAlgorithms();
+                c.ExpectClientFatalAlert(AlertDescription.internal_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifyHashAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client selects SHA1/RSA. Since the client is
+             * actually tracking SHA1 over the handshake, we expect fatal alert to come from the server
+             * when it verifies the selected algorithm against the CertificateRequest supported
+             * algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.serverCheckSigAlgOfClientCerts = false;
+                c.ExpectServerFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client signs with SHA1/RSA, but sends
+             * SHA1/ECDSA in the CertificateVerify. Since the client is actually tracking SHA1 over the
+             * handshake, and the claimed algorithm is in the CertificateRequest supported algorithms,
+             * we expect fatal alert to come from the server when it finds the claimed algorithm
+             * doesn't match the client certificate.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.clientAuthSigAlgClaimed = new SignatureAndHashAlgorithm(HashAlgorithm.sha1,
+                    SignatureAlgorithm.ecdsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.ExpectServerFatalAlert(AlertDescription.decrypt_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlgMismatch");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_VERIFY;
+                c.ExpectServerFatalAlert(AlertDescription.decrypt_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySignature");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_CERT;
+                c.ExpectServerFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadClientCertificate");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+                c.serverCertReq = C.SERVER_CERT_REQ_MANDATORY;
+                c.ExpectServerFatalAlert(AlertDescription.handshake_failure);
+
+                AddTestCase(testSuite, c, prefix + "BadMandatoryCertReqDeclined");
+            }
+
+            /*
+             * Server sends SHA-256/RSA certificate, which is not the default {sha1,rsa} implied by the
+             * absent signature_algorithms extension. We expect fatal alert from the client when it
+             * verifies the certificate's 'signatureAlgorithm' against the implicit default signature_algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientSendSignatureAlgorithms = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.certificate_unknown);
+
+                AddTestCase(testSuite, c, prefix + "BadServerCertSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not in the default
+             * supported signature algorithms that the client sent. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the supported algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not the default {sha1,rsa}
+             * implied by the absent signature_algorithms extension. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the implicit default.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientCheckSigAlgOfServerCerts = false;
+                c.clientSendSignatureAlgorithms = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg2");
+            }
+#endif
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+
+                AddTestCase(testSuite, c, prefix + "GoodDefault");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.serverCertReq = C.SERVER_CERT_REQ_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodNoCertReq");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodOptionalCertReqDeclined");
+            }
+
+#if false
+            /*
+             * Server generates downgraded (RFC 8446) ServerHello. We expect fatal alert
+             * (illegal_parameter) from the client.
+             */
+            if (!TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.serverNegotiateVersion = version;
+                c.serverSupportedVersions = ProtocolVersion.DTLSv12.DownTo(version);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadDowngrade");
+            }
+#endif
+        }
+
+        private static void AddTestCase(IList testSuite, TlsTestConfig config, string name)
+        {
+            testSuite.Add(new TestCaseData(config).SetName(name));
+        }
+
+        private static TlsTestConfig CreateDtlsTestConfig(ProtocolVersion serverMaxVersion)
+        {
+            TlsTestConfig c = new TlsTestConfig();
+            c.clientSupportedVersions = ProtocolVersion.DTLSv12.DownTo(ProtocolVersion.DTLSv10);
+            c.serverSupportedVersions = serverMaxVersion.DownTo(ProtocolVersion.DTLSv10);
+            return c;
+        }
+
+        public static void RunTests()
+        {
+            foreach (TestCaseData data in Suite())
+            {
+                Console.WriteLine(data.TestName);
+                new DtlsTestCase().RunTest((TlsTestConfig)data.Arguments[0]);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/LoggingDatagramTransport.cs b/crypto/test/src/tls/test/LoggingDatagramTransport.cs
new file mode 100644
index 000000000..f675b72fc
--- /dev/null
+++ b/crypto/test/src/tls/test/LoggingDatagramTransport.cs
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class LoggingDatagramTransport
+        : DatagramTransport
+    {
+        private static readonly string HEX_CHARS = "0123456789ABCDEF";
+
+        private readonly DatagramTransport m_transport;
+        private readonly TextWriter m_output;
+        private readonly long m_launchTimestamp;
+
+        public LoggingDatagramTransport(DatagramTransport transport, TextWriter output)
+        {
+            this.m_transport = transport;
+            this.m_output = output;
+            this.m_launchTimestamp = DateTimeUtilities.CurrentUnixMs();
+        }
+
+        public virtual int GetReceiveLimit()
+        {
+            return m_transport.GetReceiveLimit();
+        }
+
+        public virtual int GetSendLimit()
+        {
+            return m_transport.GetSendLimit();
+        }
+
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            int length = m_transport.Receive(buf, off, len, waitMillis);
+            if (length >= 0)
+            {
+                DumpDatagram("Received", buf, off, length);
+            }
+            return length;
+        }
+
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            DumpDatagram("Sending", buf, off, len);
+            m_transport.Send(buf, off, len);
+        }
+
+        public virtual void Close()
+        {
+            m_transport.Close();
+        }
+
+        private void DumpDatagram(string verb, byte[] buf, int off, int len)
+        {
+            long timestamp = DateTimeUtilities.CurrentUnixMs() - m_launchTimestamp;
+            StringBuilder sb = new StringBuilder("(+" + timestamp + "ms) " + verb + " " + len + " byte datagram:");
+            for (int pos = 0; pos < len; ++pos)
+            {
+                if (pos % 16 == 0)
+                {
+                    sb.Append(Environment.NewLine);
+                    sb.Append("    ");
+                }
+                else if (pos % 16 == 8)
+                {
+                    sb.Append('-');
+                }
+                else
+                {
+                    sb.Append(' ');
+                }
+                int val = buf[off + pos] & 0xFF;
+                sb.Append(HEX_CHARS[val >> 4]);
+                sb.Append(HEX_CHARS[val & 0xF]);
+            }
+            Dump(sb.ToString());
+        }
+
+        private void Dump(string s)
+        {
+            lock (this) m_output.WriteLine(s);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockDatagramAssociation.cs b/crypto/test/src/tls/test/MockDatagramAssociation.cs
new file mode 100644
index 000000000..3e0c0f52b
--- /dev/null
+++ b/crypto/test/src/tls/test/MockDatagramAssociation.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.Threading;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class MockDatagramAssociation
+    {
+        private int m_mtu;
+        private MockDatagramTransport m_client, m_server;
+
+        public MockDatagramAssociation(int mtu)
+        {
+            this.m_mtu = mtu;
+
+            IList clientQueue = new ArrayList();
+            IList serverQueue = new ArrayList();
+
+            this.m_client = new MockDatagramTransport(this, clientQueue, serverQueue);
+            this.m_server = new MockDatagramTransport(this, serverQueue, clientQueue);
+        }
+
+        public virtual DatagramTransport Client
+        {
+            get { return m_client; }
+        }
+
+        public virtual DatagramTransport Server
+        {
+            get { return m_server; }
+        }
+
+        private class MockDatagramTransport
+            : DatagramTransport
+        {
+            private readonly MockDatagramAssociation m_outer;
+            private IList m_receiveQueue, m_sendQueue;
+
+            internal MockDatagramTransport(MockDatagramAssociation outer, IList receiveQueue, IList sendQueue)
+            {
+                this.m_outer = outer;
+                this.m_receiveQueue = receiveQueue;
+                this.m_sendQueue = sendQueue;
+            }
+
+            public virtual int GetReceiveLimit()
+            {
+                return m_outer.m_mtu;
+            }
+
+            public virtual int GetSendLimit()
+            {
+                return m_outer.m_mtu;
+            }
+
+            public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+            {
+                lock (m_receiveQueue)
+                {
+                    if (m_receiveQueue.Count < 1)
+                    {
+                        try
+                        {
+                            Monitor.Wait(m_receiveQueue, waitMillis);
+                        }
+                        catch (ThreadInterruptedException)
+                        {
+                            // TODO Keep waiting until full wait expired?
+                        }
+
+                        if (m_receiveQueue.Count < 1)
+                            return -1;
+                    }
+
+                    byte[] packet = (byte[])m_receiveQueue[0];
+                    m_receiveQueue.RemoveAt(0);
+                    int copyLength = System.Math.Min(len, packet.Length);
+                    Array.Copy(packet, 0, buf, off, copyLength);
+                    return copyLength;
+                }
+            }
+
+            public virtual void Send(byte[] buf, int off, int len)
+            {
+                if (len > m_outer.m_mtu)
+                {
+                    // TODO Simulate rejection?
+                }
+
+                byte[] packet = Arrays.CopyOfRange(buf, off, off + len);
+
+                lock (m_sendQueue)
+                {
+                    m_sendQueue.Add(packet);
+                    Monitor.PulseAll(m_sendQueue);
+                }
+            }
+
+            public virtual void Close()
+            {
+                // TODO?
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockDtlsClient.cs b/crypto/test/src/tls/test/MockDtlsClient.cs
new file mode 100644
index 000000000..5aa1ebbd3
--- /dev/null
+++ b/crypto/test/src/tls/test/MockDtlsClient.cs
@@ -0,0 +1,170 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockDtlsClient
+        : DefaultTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockDtlsClient(TlsSession session)
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+            this.m_session = session;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return this.m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("DTLS client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.DownTo(ProtocolVersion.DTLSv10);
+        }
+
+        internal class MyTlsAuthentication
+            : TlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("DTLS client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new String[]{ "x509-server-dsa.pem", "x509-server-ecdh.pem",
+                    "x509-server-ecdsa.pem", "x509-server-ed25519.pem", "x509-server-ed448.pem",
+                    "x509-server-rsa_pss_256.pem", "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem",
+                    "x509-server-rsa-enc.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+
+            public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                short[] certificateTypes = certificateRequest.CertificateTypes;
+                if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                    return null;
+
+                return TlsTestUtilities.LoadSignerCredentials(m_context,
+                    certificateRequest.SupportedSignatureAlgorithms, SignatureAlgorithm.rsa, "x509-client-rsa.pem",
+                    "x509-client-key-rsa.pem");
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockDtlsServer.cs b/crypto/test/src/tls/test/MockDtlsServer.cs
new file mode 100644
index 000000000..18e53628e
--- /dev/null
+++ b/crypto/test/src/tls/test/MockDtlsServer.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockDtlsServer
+        : DefaultTlsServer
+    {
+        internal MockDtlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("DTLS server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            short[] certificateTypes = new short[]{ ClientCertificateType.rsa_sign,
+                ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign };
+
+            IList serverSigAlgs = null;
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion))
+            {
+                serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-dsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-ecdsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-rsa.pem").Subject);
+
+            // All the CA certificates are currently configured with this subject
+            certificateAuthorities.Add(new X509Name("CN=BouncyCastle TLS Test CA"));
+
+            return new CertificateRequest(certificateTypes, serverSigAlgs, certificateAuthorities);
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            TlsCertificate[] chain = clientCertificate.GetCertificateList();
+
+            Console.WriteLine("DTLS server received client certificate chain of length " + chain.Length);
+            for (int i = 0; i != chain.Length; i++)
+            {
+                X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                // TODO Create fingerprint based on certificate signature algorithm digest
+                Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                    + entry.Subject + ")");
+            }
+
+            bool isEmpty = (clientCertificate == null || clientCertificate.IsEmpty);
+
+            if (isEmpty)
+                return;
+
+            string[] trustedCertResources = new string[]{ "x509-client-dsa.pem", "x509-client-ecdh.pem",
+                "x509-client-ecdsa.pem", "x509-client-ed25519.pem", "x509-client-ed448.pem",
+                "x509-client-rsa_pss_256.pem", "x509-client-rsa_pss_384.pem", "x509-client-rsa_pss_512.pem",
+                "x509-client-rsa.pem" };
+
+            TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                trustedCertResources);
+
+            if (null == certPath)
+                throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+            TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[]{ "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.rsa);
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.DownTo(ProtocolVersion.DTLSv10);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskDtlsClient.cs b/crypto/test/src/tls/test/MockPskDtlsClient.cs
new file mode 100644
index 000000000..c83c9e7fd
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskDtlsClient.cs
@@ -0,0 +1,161 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskDtlsClient
+        : PskTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockPskDtlsClient(TlsSession session)
+            : this(session, new BasicTlsPskIdentity("client", Strings.ToUtf8ByteArray("TLS_TEST_PSK")))
+        {
+        }
+
+        internal MockPskDtlsClient(TlsSession session, TlsPskIdentity pskIdentity)
+            : base(new BcTlsCrypto(new SecureRandom()), pskIdentity)
+        {
+            this.m_session = session;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("DTLS-PSK client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.Only();
+        }
+
+        internal class MyTlsAuthentication
+            : ServerOnlyTlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public override void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("DTLS-PSK client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[] { "x509-server-rsa-enc.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskDtlsServer.cs b/crypto/test/src/tls/test/MockPskDtlsServer.cs
new file mode 100644
index 000000000..bb084535a
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskDtlsServer.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskDtlsServer
+        : PskTlsServer
+    {
+        internal MockPskDtlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()), new MyIdentityManager())
+        {
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("DTLS-PSK server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+
+            byte[] pskIdentity = m_context.SecurityParameters.PskIdentity;
+            if (pskIdentity != null)
+            {
+                string name = Strings.FromUtf8ByteArray(pskIdentity);
+                Console.WriteLine("DTLS-PSK server completed handshake for PSK identity: " + name);
+            }
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[] { "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.Only();
+        }
+
+        internal class MyIdentityManager
+            : TlsPskIdentityManager
+        {
+            public byte[] GetHint()
+            {
+                return Strings.ToUtf8ByteArray("hint");
+            }
+
+            public byte[] GetPsk(byte[] identity)
+            {
+                if (identity != null)
+                {
+                    string name = Strings.FromUtf8ByteArray(identity);
+                    if (name.Equals("client"))
+                    {
+                        return Strings.ToUtf8ByteArray("TLS_TEST_PSK");
+                    }
+                }
+                return null;
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskTlsClient.cs b/crypto/test/src/tls/test/MockPskTlsClient.cs
new file mode 100644
index 000000000..46774266b
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskTlsClient.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskTlsClient
+        : PskTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockPskTlsClient(TlsSession session)
+            : this(session, new BasicTlsPskIdentity("client", Strings.ToUtf8ByteArray("TLS_TEST_PSK")))
+        {
+        }
+
+        internal MockPskTlsClient(TlsSession session, TlsPskIdentity pskIdentity)
+            : base(new BcTlsCrypto(new SecureRandom()), pskIdentity)
+        {
+            this.m_session = session;
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_1_1);
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            return protocolNames;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+
+            {
+                /*
+                 * NOTE: If you are copying test code, do not blindly set these extensions in your own client.
+                 */
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+                TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, m_context.Crypto.SecureRandom.Next(16));
+                TlsExtensionsUtilities.AddTruncatedHmacExtension(clientExtensions);
+            }
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS-PSK client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.Only();
+        }
+
+        internal class MyTlsAuthentication
+            : ServerOnlyTlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public override void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("TLS-PSK client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[] { "x509-server-rsa-enc.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskTlsServer.cs b/crypto/test/src/tls/test/MockPskTlsServer.cs
new file mode 100644
index 000000000..743073b04
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskTlsServer.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskTlsServer
+        : PskTlsServer
+    {
+        internal MockPskTlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()), new MyIdentityManager())
+        {
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            protocolNames.Add(ProtocolName.Http_1_1);
+            return protocolNames;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS-PSK server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+
+            byte[] pskIdentity = m_context.SecurityParameters.PskIdentity;
+            if (pskIdentity != null)
+            {
+                string name = Strings.FromUtf8ByteArray(pskIdentity);
+                Console.WriteLine("TLS-PSK server completed handshake for PSK identity: " + name);
+            }
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[] { "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.Only();
+        }
+
+        internal class MyIdentityManager
+            : TlsPskIdentityManager
+        {
+            public byte[] GetHint()
+            {
+                return Strings.ToUtf8ByteArray("hint");
+            }
+
+            public byte[] GetPsk(byte[] identity)
+            {
+                if (identity != null)
+                {
+                    string name = Strings.FromUtf8ByteArray(identity);
+                    if (name.Equals("client"))
+                    {
+                        return Strings.ToUtf8ByteArray("TLS_TEST_PSK");
+                    }
+                }
+                return null;
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockSrpTlsClient.cs b/crypto/test/src/tls/test/MockSrpTlsClient.cs
new file mode 100644
index 000000000..3d2232893
--- /dev/null
+++ b/crypto/test/src/tls/test/MockSrpTlsClient.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockSrpTlsClient
+        : SrpTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockSrpTlsClient(TlsSession session, TlsSrpIdentity srpIdentity)
+            : base(new BcTlsCrypto(new SecureRandom()), srpIdentity)
+        {
+            this.m_session = session;
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_1_1);
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            return protocolNames;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+
+            {
+                /*
+                 * NOTE: If you are copying test code, do not blindly set these extensions in your own client.
+                 */
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+                TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, m_context.Crypto.SecureRandom.Next(16));
+                TlsExtensionsUtilities.AddTruncatedHmacExtension(clientExtensions);
+            }
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS-SRP client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.Only();
+        }
+
+        internal class MyTlsAuthentication
+            : ServerOnlyTlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public override void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("TLS-SRP client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[] { "x509-server-dsa.pem", "x509-server-rsa_pss_256.pem",
+                    "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/MockSrpTlsServer.cs b/crypto/test/src/tls/test/MockSrpTlsServer.cs
new file mode 100644
index 000000000..725901811
--- /dev/null
+++ b/crypto/test/src/tls/test/MockSrpTlsServer.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Agreement.Srp;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockSrpTlsServer
+        : SrpTlsServer
+    {
+        internal static readonly Srp6Group TEST_GROUP = Tls.Crypto.Srp6StandardGroups.rfc5054_1024;
+        internal static readonly byte[] TEST_IDENTITY = Strings.ToUtf8ByteArray("client");
+        internal static readonly byte[] TEST_PASSWORD = Strings.ToUtf8ByteArray("password");
+        internal static readonly TlsSrpIdentity TEST_SRP_IDENTITY = new BasicTlsSrpIdentity(TEST_IDENTITY,
+            TEST_PASSWORD);
+        internal static readonly byte[] TEST_SALT = Strings.ToUtf8ByteArray("salt");
+        internal static readonly byte[] TEST_SEED_KEY = Strings.ToUtf8ByteArray("seed_key");
+
+        internal MockSrpTlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()), new MyIdentityManager(new BcTlsCrypto(new SecureRandom())))
+        {
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            protocolNames.Add(ProtocolName.Http_1_1);
+            return protocolNames;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS-SRP server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+
+            byte[] srpIdentity = m_context.SecurityParameters.SrpIdentity;
+            if (srpIdentity != null)
+            {
+                string name = Strings.FromUtf8ByteArray(srpIdentity);
+                Console.WriteLine("TLS-SRP server completed handshake for SRP identity: " + name);
+            }
+        }
+
+        protected override TlsCredentialedSigner GetDsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.dsa);
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.rsa);
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        internal class MyIdentityManager
+            : TlsSrpIdentityManager
+        {
+            protected SimulatedTlsSrpIdentityManager m_unknownIdentityManager;
+
+            internal MyIdentityManager(TlsCrypto crypto)
+            {
+                m_unknownIdentityManager = SimulatedTlsSrpIdentityManager.GetRfc5054Default(crypto, TEST_GROUP,
+                    TEST_SEED_KEY);
+            }
+
+            public TlsSrpLoginParameters GetLoginParameters(byte[] identity)
+            {
+                if (Arrays.ConstantTimeAreEqual(TEST_IDENTITY, identity))
+                {
+                    Srp6VerifierGenerator verifierGenerator = new Srp6VerifierGenerator();
+                    verifierGenerator.Init(TEST_GROUP.N, TEST_GROUP.G, new Sha1Digest());
+
+                    BigInteger verifier = verifierGenerator.GenerateVerifier(TEST_SALT, identity, TEST_PASSWORD);
+
+                    TlsSrpConfig srpConfig = new TlsSrpConfig();
+                    srpConfig.SetExplicitNG(new BigInteger[]{ TEST_GROUP.N, TEST_GROUP.G });
+
+                    return new TlsSrpLoginParameters(identity, srpConfig, verifier, TEST_SALT);
+                }
+
+                return m_unknownIdentityManager.GetLoginParameters(identity);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockTlsClient.cs b/crypto/test/src/tls/test/MockTlsClient.cs
new file mode 100644
index 000000000..62b699590
--- /dev/null
+++ b/crypto/test/src/tls/test/MockTlsClient.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockTlsClient
+        : DefaultTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockTlsClient(TlsSession session)
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+            this.m_session = session;
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_1_1);
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            return protocolNames;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+
+            {
+                /*
+                 * NOTE: If you are copying test code, do not blindly set these extensions in your own client.
+                 */
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+                TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, m_context.Crypto.SecureRandom.Next(16));
+                TlsExtensionsUtilities.AddTruncatedHmacExtension(clientExtensions);
+            }
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        internal class MyTlsAuthentication
+            : TlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("TLS client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[]{ "x509-server-dsa.pem", "x509-server-ecdh.pem",
+                    "x509-server-ecdsa.pem", "x509-server-ed25519.pem", "x509-server-ed448.pem",
+                    "x509-server-rsa_pss_256.pem", "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem",
+                    "x509-server-rsa-enc.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+
+            public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                short[] certificateTypes = certificateRequest.CertificateTypes;
+                if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                    return null;
+
+                return TlsTestUtilities.LoadSignerCredentials(m_context,
+                    certificateRequest.SupportedSignatureAlgorithms, SignatureAlgorithm.rsa, "x509-client-rsa.pem",
+                    "x509-client-key-rsa.pem");
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/MockTlsServer.cs b/crypto/test/src/tls/test/MockTlsServer.cs
new file mode 100644
index 000000000..94d4c7dfd
--- /dev/null
+++ b/crypto/test/src/tls/test/MockTlsServer.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockTlsServer
+        : DefaultTlsServer
+    {
+        internal MockTlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            protocolNames.Add(ProtocolName.Http_1_1);
+            return protocolNames;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            short[] certificateTypes = new short[]{ ClientCertificateType.rsa_sign,
+                ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign };
+
+            IList serverSigAlgs = null;
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion))
+            {
+                serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-dsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-ecdsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-rsa.pem").Subject);
+
+            // All the CA certificates are currently configured with this subject
+            certificateAuthorities.Add(new X509Name("CN=BouncyCastle TLS Test CA"));
+
+            return new CertificateRequest(certificateTypes, serverSigAlgs, certificateAuthorities);
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            TlsCertificate[] chain = clientCertificate.GetCertificateList();
+
+            Console.WriteLine("TLS server received client certificate chain of length " + chain.Length);
+            for (int i = 0; i < chain.Length; ++i)
+            {
+                X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                // TODO Create fingerprint based on certificate signature algorithm digest
+                Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                    + entry.Subject + ")");
+            }
+
+            bool isEmpty = (clientCertificate == null || clientCertificate.IsEmpty);
+
+            if (isEmpty)
+                return;
+
+            string[] trustedCertResources = new string[]{ "x509-client-dsa.pem", "x509-client-ecdh.pem",
+                "x509-client-ecdsa.pem", "x509-client-ed25519.pem", "x509-client-ed448.pem",
+                "x509-client-rsa_pss_256.pem", "x509-client-rsa_pss_384.pem", "x509-client-rsa_pss_512.pem",
+                "x509-client-rsa.pem" };
+
+            TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                trustedCertResources);
+
+            if (null == certPath)
+                throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+            TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[]{ "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.rsa);
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/NetworkStream.cs b/crypto/test/src/tls/test/NetworkStream.cs
new file mode 100644
index 000000000..c5f1dfd59
--- /dev/null
+++ b/crypto/test/src/tls/test/NetworkStream.cs
@@ -0,0 +1,101 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class NetworkStream
+        : Stream
+    {
+        private readonly Stream m_inner;
+        private bool m_closed = false;
+
+        internal NetworkStream(Stream inner)
+        {
+            this.m_inner = inner;
+        }
+
+        internal virtual bool IsClosed
+        {
+            get { lock (this) return m_closed; }
+        }
+
+        public override bool CanRead
+        {
+            get { return m_inner.CanRead; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return m_inner.CanSeek; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return m_inner.CanWrite; }
+        }
+
+        public override void Close()
+        {
+            lock (this) m_closed = true;
+        }
+
+        public override void Flush()
+        {
+            m_inner.Flush();
+        }
+
+        public override long Length
+        {
+            get { return m_inner.Length; }
+        }
+
+        public override long Position
+        {
+            get { return m_inner.Position; }
+            set { m_inner.Position = value; }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            return m_inner.Seek(offset, origin);
+        }
+
+        public override void SetLength(long value)
+        {
+            m_inner.SetLength(value);
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            CheckNotClosed();
+            return m_inner.Read(buffer, offset, count);
+        }
+
+        public override int ReadByte()
+        {
+            CheckNotClosed();
+            return m_inner.ReadByte();
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            CheckNotClosed();
+            m_inner.Write(buf, off, len);
+        }
+
+        public override void WriteByte(byte value)
+        {
+            CheckNotClosed();
+            m_inner.WriteByte(value);
+        }
+
+        private void CheckNotClosed()
+        {
+            lock (this)
+            {
+                if (m_closed)
+                    throw new ObjectDisposedException(this.GetType().Name);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PipedStream.cs b/crypto/test/src/tls/test/PipedStream.cs
new file mode 100644
index 000000000..6de5703e1
--- /dev/null
+++ b/crypto/test/src/tls/test/PipedStream.cs
@@ -0,0 +1,134 @@
+using System;
+using System.IO;
+using System.Threading;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class PipedStream
+        : Stream
+    {
+        private readonly MemoryStream m_buf = new MemoryStream();
+        private bool m_closed = false;
+
+        private PipedStream m_other = null;
+        private long m_readPos = 0;
+
+        internal PipedStream()
+        {
+        }
+
+        internal PipedStream(PipedStream other)
+        {
+            lock (other)
+            {
+                this.m_other = other;
+                other.m_other = this;
+            }
+        }
+
+        public override bool CanRead
+        {
+            get { return true; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return false; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return true; }
+        }
+
+        public override void Close()
+        {
+            lock (this)
+            {
+                m_closed = true;
+                Monitor.PulseAll(this);
+            }
+        }
+
+        public override void Flush()
+        {
+        }
+
+        public override long Length
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        public override long Position
+        {
+            get { throw new NotImplementedException(); }
+            set { throw new NotImplementedException(); }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            lock (m_other)
+            {
+                WaitForData();
+                int len = (int)System.Math.Min(count, m_other.m_buf.Position - m_readPos);
+                Array.Copy(m_other.m_buf.GetBuffer(), m_readPos, buffer, offset, len);
+                m_readPos += len;
+                return len;
+            }
+        }
+
+        public override int ReadByte()
+        {
+            lock (m_other)
+            {
+                WaitForData();
+                bool eof = (m_readPos >= m_other.m_buf.Position);
+                return eof ? -1 : m_other.m_buf.GetBuffer()[m_readPos++];
+            }
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            lock (this)
+            {
+                CheckOpen();
+                m_buf.Write(buf, off, len);
+                Monitor.PulseAll(this);
+            }
+        }
+
+        public override void WriteByte(byte value)
+        {
+            lock (this)
+            {
+                CheckOpen();
+                m_buf.WriteByte(value);
+                Monitor.PulseAll(m_buf);
+            }
+        }
+
+        private void CheckOpen()
+        {
+            if (m_closed)
+                throw new ObjectDisposedException(this.GetType().Name);
+        }
+
+        private void WaitForData()
+        {
+            while (m_readPos >= m_other.m_buf.Position && !m_other.m_closed)
+            {
+                Monitor.Wait(m_other);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PrfTest.cs b/crypto/test/src/tls/test/PrfTest.cs
new file mode 100644
index 000000000..de00166ed
--- /dev/null
+++ b/crypto/test/src/tls/test/PrfTest.cs
@@ -0,0 +1,97 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class PrfTest
+    {
+        [Test]
+        public void TestLwTls11()
+        {
+            byte[] pre_master_secret = Hex.Decode("86051948e4d9a0cd273b6cd3a76557fc695e2ad9517cda97081ed009588a20ab48d0b128de8f917da74e711879460b60");
+            byte[] serverHello_random = Hex.Decode("55f1f273d4cdd4abb97f6856ed10f83a799dc42403c3f60c4e504419db4fd727");
+            byte[] clientHello_random = Hex.Decode("0b71e1f7232e675112510cf654a5e6280b3bd8ff078b67ec55276bfaddb92075");
+            byte[] server_random = Hex.Decode("a62615ee7fee41993588b2542735f90910c5a0f9c5dcb64898fdf3e90dc72a5f");
+            byte[] client_random = Hex.Decode("7798a130b732d7789e59a5fc14ad331ae91199f7d122e7fd4a594036b0694873");
+            byte[] master_secret = Hex.Decode("37841ef801f8cbdb49b6a164025de3e0ea8169604ffe80bd98b45cdd34105251cedac7223045ff4c7b67c8a12bf3141c");
+            byte[] key_block = Hex.Decode("c520e2409fa54facd3da01910f50a28f2f50986beb56b0c7b4cee9122e8f7428b7f7b8277bda931c71d35fdc2ea92127a5a143f63fe145275af5bcdab26113deffbb87a67f965b3964ea1ca29df1841c1708e6f42aacd87c12c4471913f61bb994fe3790b735dd11");
+
+            byte[] msSeed = Arrays.Concatenate(clientHello_random, serverHello_random);
+
+            BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom());
+            TlsSecret masterSecret = new BcTlsSecret(crypto, pre_master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_legacy, ExporterLabel.master_secret, msSeed, master_secret.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(master_secret, masterSecret.Extract()), "master secret wrong");
+
+            byte[] keSeed = Arrays.Concatenate(server_random, client_random);
+
+            TlsSecret keyExpansion = new BcTlsSecret(crypto, master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_legacy, ExporterLabel.key_expansion, keSeed, key_block.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(key_block, keyExpansion.Extract()), "key expansion error");
+        }
+
+        [Test]
+        public void TestLwTls12_Sha256Prf()
+        {
+            byte[] pre_master_secret = Hex.Decode("f8938ecc9edebc5030c0c6a441e213cd24e6f770a50dda07876f8d55da062bcadb386b411fd4fe4313a604fce6c17fbc");
+            byte[] serverHello_random = Hex.Decode("f6c9575ed7ddd73e1f7d16eca115415812a43c2b747daaaae043abfb50053fce");
+            byte[] clientHello_random = Hex.Decode("36c129d01a3200894b9179faac589d9835d58775f9b5ea3587cb8fd0364cae8c");
+            byte[] server_random = Hex.Decode("ae6c806f8ad4d80784549dff28a4b58fd837681a51d928c3e30ee5ff14f39868");
+            byte[] client_random = Hex.Decode("62e1fd91f23f558a605f28478c58cf72637b89784d959df7e946d3f07bd1b616");
+            byte[] master_secret = Hex.Decode("202c88c00f84a17a20027079604787461176455539e705be730890602c289a5001e34eeb3a043e5d52a65e66125188bf");
+            byte[] key_block = Hex.Decode("d06139889fffac1e3a71865f504aa5d0d2a2e89506c6f2279b670c3e1b74f531016a2530c51a3a0f7e1d6590d0f0566b2f387f8d11fd4f731cdd572d2eae927f6f2f81410b25e6960be68985add6c38445ad9f8c64bf8068bf9a6679485d966f1ad6f68b43495b10a683755ea2b858d70ccac7ec8b053c6bd41ca299d4e51928");
+
+            byte[] msSeed = Arrays.Concatenate(clientHello_random, serverHello_random);
+
+            BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom());
+            TlsSecret masterSecret = new BcTlsSecret(crypto, pre_master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha256, ExporterLabel.master_secret, msSeed, master_secret.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(master_secret, masterSecret.Extract()), "master secret wrong");
+
+            byte[] keSeed = Arrays.Concatenate(server_random, client_random);
+
+            TlsSecret keyExpansion = new BcTlsSecret(crypto, master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha256, ExporterLabel.key_expansion, keSeed, key_block.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(key_block, keyExpansion.Extract()), "key expansion error");
+        }
+
+        [Test]
+        public void TestLwTls12_Sha384Prf()
+        {
+            byte[] pre_master_secret = Hex.Decode("a5e2642633f5b8c81ad3fe0c2fe3a8e5ef806b06121dd10df4bb0fe857bfdcf522558e05d2682c9a80c741a3aab1716f");
+            byte[] serverHello_random = Hex.Decode("cb6e0b3eb02976b6466dfa9651c2919414f1648fd3a7838d02153e5bd39535b6");
+            byte[] clientHello_random = Hex.Decode("abe4bf5527429ac8eb13574d2709e8012bd1a113c6d3b1d3aa2c3840518778ac");
+            byte[] server_random = Hex.Decode("1b1c8568344a65c30828e7483c0e353e2c68641c9551efae6927d9cd627a107c");
+            byte[] client_random = Hex.Decode("954b5fe1849c2ede177438261f099a2fcd884d001b9fe1de754364b1f6a6dd8e");
+            byte[] master_secret = Hex.Decode("b4d49bfa87747fe815457bc3da15073d6ac73389e703079a3503c09e14bd559a5b3c7c601c7365f6ea8c68d3d9596827");
+            byte[] key_block = Hex.Decode("10fd89ef689c7ef033387b8a8f3e5e8e7c11f680f6bdd71fbac3246a73e98d45d03185dde686e6b2369e4503e9dc5a6d2cee3e2bf2fa3f41d3de57dff3e197c8a9d5f74cc2d277119d894f8584b07a0a5822f0bd68b3433ec6adaf5c9406c5f3ddbb71bbe17ce98f3d4d5893d3179ef369f57aad908e2bf710639100c3ce7e0c");
+
+            byte[] msSeed = Arrays.Concatenate(clientHello_random, serverHello_random);
+
+            BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom());
+            TlsSecret masterSecret = new BcTlsSecret(crypto, pre_master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha384, ExporterLabel.master_secret, msSeed, master_secret.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(master_secret, masterSecret.Extract()), "master secret wrong");
+
+            byte[] keSeed = Arrays.Concatenate(server_random, client_random);
+
+            TlsSecret keyExpansion = new BcTlsSecret(crypto, master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha384, ExporterLabel.key_expansion, keSeed, key_block.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(key_block, keyExpansion.Extract()), "key expansion error");
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PskTlsClientTest.cs b/crypto/test/src/tls/test/PskTlsClientTest.cs
new file mode 100644
index 000000000..62cfc0dc2
--- /dev/null
+++ b/crypto/test/src/tls/test/PskTlsClientTest.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS server.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS server.<br/<br/
+    /// In both cases, extra options are required to enable PSK ciphersuites and configure identities/keys.
+    /// </remarks>
+    [TestFixture]
+    public class PskTlsClientTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            string host = "localhost";
+            int port = 5556;
+
+            long time1 = DateTimeUtilities.CurrentUnixMs();
+
+            /*
+             * Note: This is the default PSK identity for 'openssl s_server' testing, the server must be
+             * started with "-psk 6161616161" to make the keys match, and possibly the "-psk_hint"
+             * option should be present.
+             */
+            //string psk_identity = "Client_identity";
+            //byte[] psk = new byte[] { 0x61, 0x61, 0x61, 0x61, 0x61 };
+
+            // These correspond to the configuration of MockPskTlsServer
+            string psk_identity = "client";
+            byte[] psk = Strings.ToUtf8ByteArray("TLS_TEST_PSK");
+
+            BasicTlsPskIdentity pskIdentity = new BasicTlsPskIdentity(psk_identity, psk);
+
+            MockPskTlsClient client = new MockPskTlsClient(null, pskIdentity);
+            TlsClientProtocol protocol = OpenTlsClientConnection(host, port, client);
+            protocol.Close();
+
+            long time2 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 1: " + (time2 - time1) + "ms");
+
+            client = new MockPskTlsClient(client.GetSessionToResume(), pskIdentity);
+            protocol = OpenTlsClientConnection(host, port, client);
+
+            long time3 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 2: " + (time3 - time2) + "ms");
+
+            Http11Get(host, port, protocol.Stream);
+
+            protocol.Close();
+        }
+
+        private static void Http11Get(string host, int port, Stream s)
+        {
+            WriteUtf8Line(s, "GET / HTTP/1.1");
+            //WriteUtf8Line(s, "Host: " + host + ":" + port);
+            WriteUtf8Line(s, "");
+            s.Flush();
+
+            Console.WriteLine("---");
+
+            string[] ends = new string[] { "</HTML>", "HTTP/1.1 3", "HTTP/1.1 4" };
+
+            StreamReader reader = new StreamReader(s);
+
+            bool finished = false;
+            string line;
+            while (!finished && (line = reader.ReadLine()) != null)
+            {
+                Console.WriteLine("<<< " + line);
+
+                string upperLine = TlsTestUtilities.ToUpperInvariant(line);
+
+                // TEST CODE ONLY. This is not a robust way of parsing the result!
+                foreach (string end in ends)
+                {
+                    if (upperLine.Contains(end))
+                    {
+                        finished = true;
+                        break;
+                    }
+                }
+            }
+
+            Console.Out.Flush();
+        }
+
+        private static TlsClientProtocol OpenTlsClientConnection(string hostname, int port, TlsClient client)
+        {
+            TcpClient tcp = new TcpClient(hostname, port);
+
+            TlsClientProtocol protocol = new TlsClientProtocol(tcp.GetStream());
+            protocol.Connect(client);
+            return protocol;
+        }
+
+        private static void WriteUtf8Line(Stream output, string line)
+        {
+            byte[] buf = Encoding.UTF8.GetBytes(line + "\r\n");
+            output.Write(buf, 0, buf.Length);
+            Console.WriteLine(">>> " + line);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PskTlsServerTest.cs b/crypto/test/src/tls/test/PskTlsServerTest.cs
new file mode 100644
index 000000000..9d87a8d35
--- /dev/null
+++ b/crypto/test/src/tls/test/PskTlsServerTest.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS client.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS client.
+    /// </remarks>
+    [TestFixture]
+    public class PskTlsServerTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            int port = 5556;
+
+            TcpListener ss = new TcpListener(IPAddress.Any, port);
+            ss.Start();
+            Stream stdout = Console.OpenStandardOutput();
+            try
+            {
+                while (true)
+                {
+                    TcpClient s = ss.AcceptTcpClient();
+                    Console.WriteLine("--------------------------------------------------------------------------------");
+                    Console.WriteLine("Accepted " + s);
+                    Server serverRun = new Server(s, stdout);
+                    Thread t = new Thread(new ThreadStart(serverRun.Run));
+                    t.Start();
+                }
+            }
+            finally
+            {
+                ss.Stop();
+            }
+        }
+
+        internal class Server
+        {
+            private readonly TcpClient s;
+            private readonly Stream stdout;
+
+            internal Server(TcpClient s, Stream stdout)
+            {
+                this.s = s;
+                this.stdout = stdout;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockPskTlsServer server = new MockPskTlsServer();
+                    TlsServerProtocol serverProtocol = new TlsServerProtocol(s.GetStream());
+                    serverProtocol.Accept(server);
+                    Stream log = new TeeOutputStream(serverProtocol.Stream, stdout);
+                    Streams.PipeAll(serverProtocol.Stream, log);
+                    serverProtocol.Close();
+                }
+                finally
+                {
+                    try
+                    {
+                        s.Close();
+                    }
+                    catch (IOException)
+                    {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsClientTest.cs b/crypto/test/src/tls/test/TlsClientTest.cs
new file mode 100644
index 000000000..27e5f342b
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsClientTest.cs
@@ -0,0 +1,97 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS server.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS server.
+    /// </remarks>
+    [TestFixture]
+    public class TlsClientTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            string host = "localhost";
+            int port = 5556;
+
+            long time1 = DateTimeUtilities.CurrentUnixMs();
+
+            MockTlsClient client = new MockTlsClient(null);
+            TlsClientProtocol protocol = OpenTlsClientConnection(host, port, client);
+            protocol.Close();
+
+            long time2 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 1: " + (time2 - time1) + "ms");
+
+            client = new MockTlsClient(client.GetSessionToResume());
+            protocol = OpenTlsClientConnection(host, port, client);
+
+            long time3 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 2: " + (time3 - time2) + "ms");
+
+            Http11Get(host, port, protocol.Stream);
+
+            protocol.Close();
+        }
+
+        private static void Http11Get(string host, int port, Stream s)
+        {
+            WriteUtf8Line(s, "GET / HTTP/1.1");
+            //WriteUtf8Line(s, "Host: " + host + ":" + port);
+            WriteUtf8Line(s, "");
+            s.Flush();
+
+            Console.WriteLine("---");
+
+            string[] ends = new string[] { "</HTML>", "HTTP/1.1 3", "HTTP/1.1 4" };
+
+            StreamReader reader = new StreamReader(s);
+
+            bool finished = false;
+            string line;
+            while (!finished && (line = reader.ReadLine()) != null)
+            {
+                Console.WriteLine("<<< " + line);
+
+                string upperLine = TlsTestUtilities.ToUpperInvariant(line);
+
+                // TEST CODE ONLY. This is not a robust way of parsing the result!
+                foreach (string end in ends)
+                {
+                    if (upperLine.Contains(end))
+                    {
+                        finished = true;
+                        break;
+                    }
+                }
+            }
+
+            Console.Out.Flush();
+        }
+
+        private static TlsClientProtocol OpenTlsClientConnection(string hostname, int port, TlsClient client)
+        {
+            TcpClient tcp = new TcpClient(hostname, port);
+
+            TlsClientProtocol protocol = new TlsClientProtocol(tcp.GetStream());
+            protocol.Connect(client);
+            return protocol;
+        }
+
+        private static void WriteUtf8Line(Stream output, string line)
+        {
+            byte[] buf = Encoding.UTF8.GetBytes(line + "\r\n");
+            output.Write(buf, 0, buf.Length);
+            Console.WriteLine(">>> " + line);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs b/crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs
new file mode 100644
index 000000000..56cd39e87
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs
@@ -0,0 +1,126 @@
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsProtocolNonBlockingTest
+    {
+        [Test]
+        public void TestClientServerFragmented()
+        {
+            // tests if it's really non-blocking when partial records arrive
+            ImplTestClientServer(true);
+        }
+
+        [Test]
+        public void TestClientServerNonFragmented()
+        {
+            ImplTestClientServer(false);
+        }
+
+        private static void ImplTestClientServer(bool fragment)
+        {
+            TlsClientProtocol clientProtocol = new TlsClientProtocol();
+            TlsServerProtocol serverProtocol = new TlsServerProtocol();
+
+            MockTlsClient client = new MockTlsClient(null);
+            MockTlsServer server = new MockTlsServer();
+
+            clientProtocol.Connect(client);
+            serverProtocol.Accept(server);
+
+            // pump handshake
+            bool hadDataFromServer = true;
+            bool hadDataFromClient = true;
+            while (hadDataFromServer || hadDataFromClient)
+            {
+                hadDataFromServer = PumpData(serverProtocol, clientProtocol, fragment);
+                hadDataFromClient = PumpData(clientProtocol, serverProtocol, fragment);
+            }
+
+            // send data in both directions
+            byte[] data = new byte[1024];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            WriteAndRead(clientProtocol, serverProtocol, data, fragment);
+            WriteAndRead(serverProtocol, clientProtocol, data, fragment);
+
+            // close the connection
+            clientProtocol.Close();
+            PumpData(clientProtocol, serverProtocol, fragment);
+            serverProtocol.CloseInput();
+            CheckClosed(serverProtocol);
+            CheckClosed(clientProtocol);
+        }
+
+        private static void WriteAndRead(TlsProtocol writer, TlsProtocol reader, byte[] data, bool fragment)
+        {
+            int dataSize = data.Length;
+            writer.WriteApplicationData(data, 0, dataSize);
+            PumpData(writer, reader, fragment);
+
+            Assert.AreEqual(dataSize, reader.GetAvailableInputBytes());
+            byte[] readData = new byte[dataSize];
+            reader.ReadInput(readData, 0, dataSize);
+            AssertArrayEquals(data, readData);
+        }
+
+        private static bool PumpData(TlsProtocol from, TlsProtocol to, bool fragment)
+        {
+            int byteCount = from.GetAvailableOutputBytes();
+            if (byteCount == 0)
+                return false;
+
+            if (fragment)
+            {
+                byte[] buffer = new byte[1];
+                while (from.GetAvailableOutputBytes() > 0)
+                {
+                    from.ReadOutput(buffer, 0, 1);
+                    to.OfferInput(buffer);
+                }
+            }
+            else
+            {
+                byte[] buffer = new byte[byteCount];
+                from.ReadOutput(buffer, 0, buffer.Length);
+                to.OfferInput(buffer);
+            }
+
+            return true;
+        }
+
+        private static void CheckClosed(TlsProtocol protocol)
+        {
+            Assert.IsTrue(protocol.IsClosed);
+
+            try
+            {
+                protocol.OfferInput(new byte[10]);
+                Assert.Fail("Input was accepted after close");
+            }
+            catch (IOException e)
+            {
+            }
+
+            try
+            {
+                protocol.WriteApplicationData(new byte[10], 0, 10);
+                Assert.Fail("Output was accepted after close");
+            }
+            catch (IOException e)
+            {
+            }
+        }
+
+        private static void AssertArrayEquals(byte[] a, byte[] b)
+        {
+            Assert.IsTrue(Arrays.AreEqual(a, b));
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsProtocolTest.cs b/crypto/test/src/tls/test/TlsProtocolTest.cs
new file mode 100644
index 000000000..b4f79f9ba
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsProtocolTest.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            TlsClientProtocol clientProtocol = new TlsClientProtocol(clientPipe);
+            TlsServerProtocol serverProtocol = new TlsServerProtocol(serverPipe);
+
+            MockTlsClient client = new MockTlsClient(null);
+            MockTlsServer server = new MockTlsServer();
+
+            Server serverRun = new Server(serverProtocol, server);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            clientProtocol.Connect(client);
+
+            byte[] data = new byte[1000];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            Stream output = clientProtocol.Stream;
+            output.Write(data, 0, data.Length);
+
+            byte[] echo = new byte[data.Length];
+            int count = Streams.ReadFully(clientProtocol.Stream, echo);
+
+            Assert.AreEqual(count, data.Length);
+            Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+            output.Close();
+
+            serverThread.Join();
+        }
+
+        internal class Server
+        {
+            private readonly TlsServerProtocol m_serverProtocol;
+            private readonly TlsServer m_server;
+
+            internal Server(TlsServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsPskProtocolTest.cs b/crypto/test/src/tls/test/TlsPskProtocolTest.cs
new file mode 100644
index 000000000..97e6f133b
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsPskProtocolTest.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsPskProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            TlsClientProtocol clientProtocol = new TlsClientProtocol(clientPipe);
+            TlsServerProtocol serverProtocol = new TlsServerProtocol(serverPipe);
+
+            MockPskTlsClient client = new MockPskTlsClient(null);
+            MockPskTlsServer server = new MockPskTlsServer();
+
+            Server serverRun = new Server(serverProtocol, server);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            clientProtocol.Connect(client);
+
+            byte[] data = new byte[1000];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            Stream output = clientProtocol.Stream;
+            output.Write(data, 0, data.Length);
+
+            byte[] echo = new byte[data.Length];
+            int count = Streams.ReadFully(clientProtocol.Stream, echo);
+
+            Assert.AreEqual(count, data.Length);
+            Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+            output.Close();
+
+            serverThread.Join();
+        }
+
+        internal class Server
+        {
+            private readonly TlsServerProtocol m_serverProtocol;
+            private readonly TlsServer m_server;
+
+            internal Server(TlsServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsServerTest.cs b/crypto/test/src/tls/test/TlsServerTest.cs
new file mode 100644
index 000000000..333ff5664
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsServerTest.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS client.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS client.
+    /// </remarks>
+    [TestFixture]
+    public class TlsServerTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            int port = 5556;
+
+            TcpListener ss = new TcpListener(IPAddress.Any, port);
+            ss.Start();
+            Stream stdout = Console.OpenStandardOutput();
+            try
+            {
+                while (true)
+                {
+                    TcpClient s = ss.AcceptTcpClient();
+                    Console.WriteLine("--------------------------------------------------------------------------------");
+                    Console.WriteLine("Accepted " + s);
+                    Server serverRun = new Server(s, stdout);
+                    Thread t = new Thread(new ThreadStart(serverRun.Run));
+                    t.Start();
+                }
+            }
+            finally
+            {
+                ss.Stop();
+            }
+        }
+
+        internal class Server
+        {
+            private readonly TcpClient s;
+            private readonly Stream stdout;
+
+            internal Server(TcpClient s, Stream stdout)
+            {
+                this.s = s;
+                this.stdout = stdout;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockTlsServer server = new MockTlsServer();
+                    TlsServerProtocol serverProtocol = new TlsServerProtocol(s.GetStream());
+                    serverProtocol.Accept(server);
+                    Stream log = new TeeOutputStream(serverProtocol.Stream, stdout);
+                    Streams.PipeAll(serverProtocol.Stream, log);
+                    serverProtocol.Close();
+                }
+                finally
+                {
+                    try
+                    {
+                        s.Close();
+                    }
+                    catch (IOException)
+                    {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsSrpProtocolTest.cs b/crypto/test/src/tls/test/TlsSrpProtocolTest.cs
new file mode 100644
index 000000000..555b1f1f7
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsSrpProtocolTest.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsSrpProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            TlsClientProtocol clientProtocol = new TlsClientProtocol(clientPipe);
+            TlsServerProtocol serverProtocol = new TlsServerProtocol(serverPipe);
+
+            MockSrpTlsClient client = new MockSrpTlsClient(null, MockSrpTlsServer.TEST_SRP_IDENTITY);
+            MockSrpTlsServer server = new MockSrpTlsServer();
+
+            Server serverRun = new Server(serverProtocol, server);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            clientProtocol.Connect(client);
+
+            byte[] data = new byte[1000];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            Stream output = clientProtocol.Stream;
+            output.Write(data, 0, data.Length);
+
+            byte[] echo = new byte[data.Length];
+            int count = Streams.ReadFully(clientProtocol.Stream, echo);
+
+            Assert.AreEqual(count, data.Length);
+            Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+            output.Close();
+
+            serverThread.Join();
+        }
+
+        internal class Server
+        {
+            private readonly TlsServerProtocol m_serverProtocol;
+            private readonly TlsServer m_server;
+
+            internal Server(TlsServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestCase.cs b/crypto/test/src/tls/test/TlsTestCase.cs
new file mode 100644
index 000000000..0489d22c1
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestCase.cs
@@ -0,0 +1,182 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsTestCase
+    {
+        private static void CheckTlsVersions(ProtocolVersion[] versions)
+        {
+            if (versions != null)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    if (!versions[i].IsTls)
+                        throw new InvalidOperationException("Non-TLS version");
+                }
+            }
+        }
+
+        [Test, TestCaseSource(typeof(TlsTestSuite), "Suite")]
+        public void RunTest(TlsTestConfig config)
+        {
+            // Disable the test if it is not being run via TlsTestSuite
+            if (config == null)
+                return;
+
+            CheckTlsVersions(config.clientSupportedVersions);
+            CheckTlsVersions(config.serverSupportedVersions);
+
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            NetworkStream clientNet = new NetworkStream(clientPipe);
+            NetworkStream serverNet = new NetworkStream(serverPipe);
+
+            TlsTestClientProtocol clientProtocol = new TlsTestClientProtocol(clientNet, config);
+            TlsTestServerProtocol serverProtocol = new TlsTestServerProtocol(serverNet, config);
+
+            clientProtocol.IsResumableHandshake = true;
+            serverProtocol.IsResumableHandshake = true;
+
+            TlsTestClientImpl clientImpl = new TlsTestClientImpl(config);
+            TlsTestServerImpl serverImpl = new TlsTestServerImpl(config);
+
+            Server serverRun = new Server(this, serverProtocol, serverImpl);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            Exception caught = null;
+            try
+            {
+                clientProtocol.Connect(clientImpl);
+
+                byte[] data = new byte[1000];
+                clientImpl.Crypto.SecureRandom.NextBytes(data);
+
+                Stream stream = clientProtocol.Stream;
+                stream.Write(data, 0, data.Length);
+
+                byte[] echo = new byte[data.Length];
+                int count = Streams.ReadFully(stream, echo, 0, echo.Length);
+
+                Assert.AreEqual(count, data.Length);
+                Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+                Assert.IsTrue(Arrays.AreEqual(clientImpl.m_tlsServerEndPoint, serverImpl.m_tlsServerEndPoint));
+
+                if (!TlsUtilities.IsTlsV13(clientImpl.m_negotiatedVersion))
+                {
+                    Assert.NotNull(clientImpl.m_tlsUnique);
+                    Assert.NotNull(serverImpl.m_tlsUnique);
+                }
+                Assert.IsTrue(Arrays.AreEqual(clientImpl.m_tlsUnique, serverImpl.m_tlsUnique));
+
+                stream.Close();
+            }
+            catch (Exception e)
+            {
+                caught = e;
+                LogException(caught);
+            }
+
+            serverRun.AllowExit();
+            serverThread.Join();
+
+            Assert.IsTrue(clientNet.IsClosed, "Client Stream not closed");
+            Assert.IsTrue(serverNet.IsClosed, "Server Stream not closed");
+
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, clientImpl.FirstFatalAlertConnectionEnd,
+                "Client fatal alert connection end");
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, serverImpl.FirstFatalAlertConnectionEnd,
+                "Server fatal alert connection end");
+
+            Assert.AreEqual(config.expectFatalAlertDescription, clientImpl.FirstFatalAlertDescription,
+                "Client fatal alert description");
+            Assert.AreEqual(config.expectFatalAlertDescription, serverImpl.FirstFatalAlertDescription,
+                "Server fatal alert description");
+
+            if (config.expectFatalAlertConnectionEnd == -1)
+            {
+                Assert.IsNull(caught, "Unexpected client exception");
+                Assert.IsNull(serverRun.m_caught, "Unexpected server exception");
+            }
+        }
+
+        protected virtual void LogException(Exception e)
+        {
+            if (TlsTestConfig.Debug)
+            {
+                Console.Error.WriteLine(e);
+                Console.Error.Flush();
+            }
+        }
+
+        internal class Server
+        {
+            protected readonly TlsTestCase m_outer;
+            protected readonly TlsServerProtocol m_serverProtocol;
+            protected readonly TlsServer m_server;
+
+            internal bool m_canExit = false;
+            internal Exception m_caught = null;
+
+            internal Server(TlsTestCase outer, TlsTestServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_outer = outer;
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            internal void AllowExit()
+            {
+                lock (this)
+                {
+                    m_canExit = true;
+                    Monitor.PulseAll(this);
+                }
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                    m_caught = e;
+                    m_outer.LogException(m_caught);
+                }
+
+                WaitExit();
+            }
+
+            protected void WaitExit()
+            {
+                lock (this)
+                {
+                    while (!m_canExit)
+                    {
+                        try
+                        {
+                            Monitor.Wait(this);
+                        }
+                        catch (ThreadInterruptedException)
+                        {
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestClientImpl.cs b/crypto/test/src/tls/test/TlsTestClientImpl.cs
new file mode 100644
index 000000000..d436df3f7
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestClientImpl.cs
@@ -0,0 +1,370 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestClientImpl
+        : DefaultTlsClient
+    {
+        private static readonly int[] TestCipherSuites = new int[]
+        {
+            /*
+             * TLS 1.3
+             */
+            CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_AES_128_GCM_SHA256,
+
+            /*
+             * pre-TLS 1.3
+             */
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+        };
+
+        protected readonly TlsTestConfig m_config;
+
+        protected int m_firstFatalAlertConnectionEnd = -1;
+        protected short m_firstFatalAlertDescription = -1;
+
+        internal ProtocolVersion m_negotiatedVersion = null;
+        internal byte[] m_tlsServerEndPoint = null;
+        internal byte[] m_tlsUnique = null;
+
+        internal TlsTestClientImpl(TlsTestConfig config)
+            : base(TlsTestSuite.GetCrypto(config))
+        {
+            this.m_config = config;
+        }
+
+        internal int FirstFatalAlertConnectionEnd
+        {
+            get { return m_firstFatalAlertConnectionEnd; }
+        }
+
+        internal short FirstFatalAlertDescription
+        {
+            get { return m_firstFatalAlertDescription; }
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = base.GetClientExtensions();
+            if (clientExtensions != null)
+            {
+                if (!m_config.clientSendSignatureAlgorithms)
+                {
+                    clientExtensions.Remove(ExtensionType.signature_algorithms);
+                    this.m_supportedSignatureAlgorithms = null;
+                }
+                if (!m_config.clientSendSignatureAlgorithmsCert)
+                {
+                    clientExtensions.Remove(ExtensionType.signature_algorithms_cert);
+                    this.m_supportedSignatureAlgorithmsCert = null;
+                }
+            }
+            return clientExtensions;
+        }
+
+        public override IList GetEarlyKeyShareGroups()
+        {
+            if (m_config.clientEmptyKeyShare)
+                return null;
+
+            return base.GetEarlyKeyShareGroups();
+        }
+
+        public override bool IsFallback()
+        {
+            return m_config.clientFallback;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.client;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+                if (message != null)
+                {
+                    output.WriteLine("> " + message);
+                }
+                if (cause != null)
+                {
+                    output.WriteLine(cause);
+                }
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.server;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS client received alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+            }
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            m_tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            m_tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS client reports 'tls-server-end-point' = " + ToHexString(m_tlsServerEndPoint));
+                Console.WriteLine("TLS client reports 'tls-unique' = " + ToHexString(m_tlsUnique));
+            }
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            this.m_negotiatedVersion = serverVersion;
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS client negotiated " + serverVersion);
+            }
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(this, m_context);
+        }
+
+        protected virtual Certificate CorruptCertificate(Certificate cert)
+        {
+            CertificateEntry[] certEntryList = cert.GetCertificateEntryList();
+            CertificateEntry ee = certEntryList[0];
+            TlsCertificate corruptCert = CorruptCertificateSignature(ee.Certificate);
+            certEntryList[0] = new CertificateEntry(corruptCert, ee.Extensions);
+            return new Certificate(cert.GetCertificateRequestContext(), certEntryList);
+        }
+
+        protected virtual TlsCertificate CorruptCertificateSignature(TlsCertificate tlsCertificate)
+        {
+            X509CertificateStructure cert = X509CertificateStructure.GetInstance(tlsCertificate.GetEncoded());
+
+            Asn1EncodableVector v = new Asn1EncodableVector();
+            v.Add(cert.TbsCertificate);
+            v.Add(cert.SignatureAlgorithm);
+            v.Add(CorruptSignature(cert.Signature));
+
+            cert = X509CertificateStructure.GetInstance(new DerSequence(v));
+
+            return Crypto.CreateCertificate(cert.GetEncoded(Asn1Encodable.Der));
+        }
+
+        protected virtual DerBitString CorruptSignature(DerBitString bs)
+        {
+            return new DerBitString(CorruptBit(bs.GetOctets()));
+        }
+
+        protected virtual byte[] CorruptBit(byte[] bs)
+        {
+            bs = Arrays.Clone(bs);
+
+            // Flip a random bit
+            int bit = m_context.Crypto.SecureRandom.Next(bs.Length << 3);
+            bs[bit >> 3] ^= (byte)(1 << (bit & 7));
+
+            return bs;
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, TestCipherSuites);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            if (null != m_config.clientSupportedVersions)
+            {
+                return m_config.clientSupportedVersions;
+            }
+
+            return base.GetSupportedVersions();
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        internal class MyTlsAuthentication
+            : TlsAuthentication
+        {
+            private readonly TlsTestClientImpl m_outer;
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsTestClientImpl outer, TlsContext context)
+            {
+                this.m_outer = outer;
+                this.m_context = context;
+            }
+
+            public virtual void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                if (TlsTestConfig.Debug)
+                {
+                    Console.WriteLine("TLS client received server certificate chain of length " + chain.Length);
+                    for (int i = 0; i < chain.Length; ++i)
+                    {
+                        X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                        // TODO Create fingerprint based on certificate signature algorithm digest
+                        Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                            + entry.Subject + ")");
+                    }
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[]{ "x509-server-dsa.pem", "x509-server-ecdh.pem",
+                    "x509-server-ecdsa.pem", "x509-server-ed25519.pem", "x509-server-ed448.pem",
+                    "x509-server-rsa_pss_256.pem", "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem",
+                    "x509-server-rsa-enc.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                if (m_outer.m_config.clientCheckSigAlgOfServerCerts)
+                {
+                    TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+                }
+            }
+
+            public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                TlsTestConfig config = m_outer.m_config;
+
+                if (config.serverCertReq == TlsTestConfig.SERVER_CERT_REQ_NONE)
+                    throw new InvalidOperationException();
+                if (config.clientAuth == TlsTestConfig.CLIENT_AUTH_NONE)
+                    return null;
+
+                bool isTlsV13 = TlsUtilities.IsTlsV13(m_context);
+
+                if (!isTlsV13)
+                {
+                    short[] certificateTypes = certificateRequest.CertificateTypes;
+                    if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                        return null;
+                }
+
+                IList supportedSigAlgs = certificateRequest.SupportedSignatureAlgorithms;
+                if (supportedSigAlgs != null && config.clientAuthSigAlg != null)
+                {
+                    supportedSigAlgs = new ArrayList(1);
+                    supportedSigAlgs.Add(config.clientAuthSigAlg);
+                }
+
+                // TODO[tls13] Check also supportedSigAlgsCert against the chain signature(s)
+
+                TlsCredentialedSigner signerCredentials = TlsTestUtilities.LoadSignerCredentials(m_context,
+                    supportedSigAlgs, SignatureAlgorithm.rsa, "x509-client-rsa.pem", "x509-client-key-rsa.pem");
+
+                if (config.clientAuth == TlsTestConfig.CLIENT_AUTH_VALID)
+                    return signerCredentials;
+
+                return new MyTlsCredentialedSigner(m_outer, signerCredentials);
+            }
+        }
+
+        internal class MyTlsCredentialedSigner
+            : TlsCredentialedSigner
+        {
+            private readonly TlsTestClientImpl m_outer;
+            private readonly TlsCredentialedSigner m_inner;
+
+            internal MyTlsCredentialedSigner(TlsTestClientImpl outer, TlsCredentialedSigner inner)
+            {
+                this.m_outer = outer;
+                this.m_inner = inner;
+            }
+
+            public virtual byte[] GenerateRawSignature(byte[] hash)
+            {
+                byte[] sig = m_inner.GenerateRawSignature(hash);
+
+                if (m_outer.m_config.clientAuth == TlsTestConfig.CLIENT_AUTH_INVALID_VERIFY)
+                {
+                    sig = m_outer.CorruptBit(sig);
+                }
+
+                return sig;
+            }
+
+            public virtual Certificate Certificate
+            {
+                get
+                {
+                    Certificate cert = m_inner.Certificate;
+
+                    if (m_outer.m_config.clientAuth == TlsTestConfig.CLIENT_AUTH_INVALID_CERT)
+                    {
+                        cert = m_outer.CorruptCertificate(cert);
+                    }
+
+                    return cert;
+                }
+            }
+
+            public virtual SignatureAndHashAlgorithm SignatureAndHashAlgorithm
+            {
+                get { return m_inner.SignatureAndHashAlgorithm; }
+            }
+
+            public virtual TlsStreamSigner GetStreamSigner()
+            {
+                return null;
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestClientProtocol.cs b/crypto/test/src/tls/test/TlsTestClientProtocol.cs
new file mode 100644
index 000000000..f7e94680a
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestClientProtocol.cs
@@ -0,0 +1,32 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestClientProtocol
+        : TlsClientProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        internal TlsTestClientProtocol(Stream stream, TlsTestConfig config)
+            : this(stream, stream, config)
+        {
+        }
+
+        internal TlsTestClientProtocol(Stream input, Stream output, TlsTestConfig config)
+            : base(input, output)
+        {
+            this.m_config = config;
+        }
+
+        protected override void SendCertificateVerifyMessage(DigitallySigned certificateVerify)
+        {
+            if (certificateVerify.Algorithm != null && m_config.clientAuthSigAlgClaimed != null)
+            {
+                certificateVerify = new DigitallySigned(m_config.clientAuthSigAlgClaimed, certificateVerify.Signature);
+            }
+
+            base.SendCertificateVerifyMessage(certificateVerify);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestConfig.cs b/crypto/test/src/tls/test/TlsTestConfig.cs
new file mode 100644
index 000000000..a15d4e535
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestConfig.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class TlsTestConfig
+    {
+        // TODO[tls-port]
+        public static readonly bool Debug = true;
+
+        /// <summary>Client does not authenticate, ignores any certificate request.</summary>
+        public const int CLIENT_AUTH_NONE = 0;
+
+        /// <summary>Client will authenticate if it receives a certificate request.</summary>
+        public const int CLIENT_AUTH_VALID = 1;
+
+        /// <summary>Client will authenticate if it receives a certificate request, with an invalid certificate.
+        /// </summary>
+        public const int CLIENT_AUTH_INVALID_CERT = 2;
+
+        /// <summary>Client will authenticate if it receives a certificate request, with an invalid CertificateVerify
+        /// signature.</summary>
+        public const int CLIENT_AUTH_INVALID_VERIFY = 3;
+
+        public const int CRYPTO_BC = 0;
+
+        /// <summary>Server will not request a client certificate.</summary>
+        public const int SERVER_CERT_REQ_NONE = 0;
+
+        /// <summary>Server will request a client certificate but receiving one is optional.</summary>
+        public const int SERVER_CERT_REQ_OPTIONAL = 1;
+
+        /// <summary>Server will request a client certificate and receiving one is mandatory.</summary>
+        public const int SERVER_CERT_REQ_MANDATORY = 2;
+
+        /// <summary>Configures the client authentication behaviour of the test client. Use CLIENT_AUTH_* constants.
+        /// </summary>
+        public int clientAuth = CLIENT_AUTH_VALID;
+
+        /// <summary>If not null, and TLS 1.2 or higher is negotiated, selects a fixed signature/ hash algorithm to be
+        /// used for the CertificateVerify signature(if one is sent).</summary>
+        public SignatureAndHashAlgorithm clientAuthSigAlg = null;
+
+        /// <summary>If not null, and TLS 1.2 or higher is negotiated, selects a fixed signature/ hash algorithm to be
+        /// _claimed_ in the CertificateVerify (if one is sent), independently of what was actually used.</summary>
+        public SignatureAndHashAlgorithm clientAuthSigAlgClaimed = null;
+
+        /// <summary>Control whether the client will call
+        /// <see cref="TlsUtilities.CheckPeerSigAlgs(TlsContext, Crypto.TlsCertificate[])"/> to check the server
+        /// certificate chain.</summary>
+        public bool clientCheckSigAlgOfServerCerts = true;
+
+        public int clientCrypto = CRYPTO_BC;
+
+        /// <summary>Configures whether the client will send an empty key_share extension in initial ClientHello.
+        /// </summary>
+        public bool clientEmptyKeyShare = false;
+
+        /// <summary>Configures whether the client will indicate version fallback via TLS_FALLBACK_SCSV.</summary>
+        public bool clientFallback = false;
+
+        /// <summary>Configures whether a (TLS 1.2+) client may send the signature_algorithms extension in ClientHello.
+        /// </summary>
+        public bool clientSendSignatureAlgorithms = true;
+
+        /// <summary>Configures whether a (TLS 1.2+) client may send the signature_algorithms_cert extension in
+        /// ClientHello.</summary>
+        public bool clientSendSignatureAlgorithmsCert = true;
+
+        /// <summary>Configures the supported protocol versions for the client. If null, uses the library's default.
+        /// </summary>
+        public ProtocolVersion[] clientSupportedVersions = null;
+
+        /// <summary>If not null, and TLS 1.2 or higher is negotiated, selects a fixed signature/ hash algorithm to be
+        /// used for the ServerKeyExchange signature(if one is sent).</summary>
+        public SignatureAndHashAlgorithm serverAuthSigAlg = null;
+
+        /// <summary>Configures whether the test server will send a certificate request.</summary>
+        public int serverCertReq = SERVER_CERT_REQ_OPTIONAL;
+
+        /// <summary>If TLS 1.2 or higher is negotiated, configures the set of supported signature algorithms in the
+        /// CertificateRequest (if one is sent). If null, uses a default set.</summary>
+        public IList serverCertReqSigAlgs = null;
+
+        /// <summary>Control whether the server will call
+        /// <see cref="TlsUtilities.CheckPeerSigAlgs(TlsContext, Crypto.TlsCertificate[])"/> to check the client
+        /// certificate chain.</summary>
+        public bool serverCheckSigAlgOfClientCerts = true;
+
+        public int serverCrypto = CRYPTO_BC;
+
+        /// <summary>Configures a protocol version the server will unconditionally negotiate.</summary>
+        /// <remarks>
+        /// Ignored if null.
+        /// </remarks>
+        public ProtocolVersion serverNegotiateVersion = null;
+
+        /// <summary>Configures the supported protocol versions for the server.</summary>
+        /// <remarks>
+        /// If null, uses the library's default.
+        /// </remarks>
+        public ProtocolVersion[] serverSupportedVersions = null;
+
+        /// <summary>Configures the connection end at which a fatal alert is expected to be raised.</summary>
+        /// <remarks>
+        /// Use <see cref="ConnectionEnd"/> constants.
+        /// </remarks>
+        public int expectFatalAlertConnectionEnd = -1;
+
+        /// <summary>Configures the type of fatal alert expected to be raised.</summary>
+        /// <remarks>
+        /// Use <see cref="AlertDescription"/> constants.
+        /// </remarks>
+        public short expectFatalAlertDescription = -1;
+
+        public virtual void ExpectClientFatalAlert(short alertDescription)
+        {
+            this.expectFatalAlertConnectionEnd = ConnectionEnd.client;
+            this.expectFatalAlertDescription = alertDescription;
+        }
+
+        public virtual void ExpectServerFatalAlert(short alertDescription)
+        {
+            this.expectFatalAlertConnectionEnd = ConnectionEnd.server;
+            this.expectFatalAlertDescription = alertDescription;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestServerImpl.cs b/crypto/test/src/tls/test/TlsTestServerImpl.cs
new file mode 100644
index 000000000..6bc4d315d
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestServerImpl.cs
@@ -0,0 +1,310 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestServerImpl
+        : DefaultTlsServer
+    {
+        private static readonly int[] TestCipherSuites = new int[]
+        {
+            /*
+             * TLS 1.3
+             */
+            CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_AES_256_GCM_SHA384,
+            CipherSuite.TLS_AES_128_GCM_SHA256,
+
+            /*
+             * pre-TLS 1.3
+             */
+            CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            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_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_DHE_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,
+        };
+
+        protected readonly TlsTestConfig m_config;
+
+        protected int m_firstFatalAlertConnectionEnd = -1;
+        protected short m_firstFatalAlertDescription = -1;
+
+        internal byte[] m_tlsServerEndPoint = null;
+        internal byte[] m_tlsUnique = null;
+
+        internal TlsTestServerImpl(TlsTestConfig config)
+            : base(TlsTestSuite.GetCrypto(config))
+        {
+            this.m_config = config;
+        }
+
+        internal int FirstFatalAlertConnectionEnd
+        {
+            get { return m_firstFatalAlertConnectionEnd; }
+        }
+
+        internal short FirstFatalAlertDescription
+        {
+            get { return m_firstFatalAlertDescription; }
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            /*
+             * TODO[tls13] Should really be finding the first client-supported signature scheme that the
+             * server also supports and has credentials for.
+             */
+            if (TlsUtilities.IsTlsV13(m_context))
+            {
+                return GetRsaSignerCredentials();
+            }
+
+            return base.GetCredentials();
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.server;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+                if (message != null)
+                {
+                    output.WriteLine("> " + message);
+                }
+                if (cause != null)
+                {
+                    output.WriteLine(cause);
+                }
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.client;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS server received alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+            }
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            m_tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            m_tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS server reports 'tls-server-end-point' = " + ToHexString(m_tlsServerEndPoint));
+                Console.WriteLine("TLS server reports 'tls-unique' = " + ToHexString(m_tlsUnique));
+            }
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = (null != m_config.serverNegotiateVersion)
+                ?   m_config.serverNegotiateVersion
+                :   base.GetServerVersion();
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS server negotiated " + serverVersion);
+            }
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            if (m_config.serverCertReq == TlsTestConfig.SERVER_CERT_REQ_NONE)
+                return null;
+
+            IList serverSigAlgs = null;
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion))
+            {
+                serverSigAlgs = m_config.serverCertReqSigAlgs;
+                if (serverSigAlgs == null)
+                {
+                    serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+                }
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-dsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-ecdsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-rsa.pem").Subject);
+
+            // All the CA certificates are currently configured with this subject
+            certificateAuthorities.Add(new X509Name("CN=BouncyCastle TLS Test CA"));
+
+            if (TlsUtilities.IsTlsV13(m_context))
+            {
+                // TODO[tls13] Support for non-empty request context
+                byte[] certificateRequestContext = TlsUtilities.EmptyBytes;
+
+                // TODO[tls13] Add TlsTestConfig.serverCertReqSigAlgsCert
+                IList serverSigAlgsCert = null;
+
+                return new CertificateRequest(certificateRequestContext, serverSigAlgs, serverSigAlgsCert,
+                    certificateAuthorities);
+            }
+            else
+            {
+                short[] certificateTypes = new short[]{ ClientCertificateType.rsa_sign,
+                    ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign };
+
+                return new CertificateRequest(certificateTypes, serverSigAlgs, certificateAuthorities);
+            }
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            bool isEmpty = (clientCertificate == null || clientCertificate.IsEmpty);
+
+            if (isEmpty != (m_config.clientAuth == TlsTestConfig.CLIENT_AUTH_NONE))
+                throw new InvalidOperationException();
+
+            if (isEmpty && (m_config.serverCertReq == TlsTestConfig.SERVER_CERT_REQ_MANDATORY))
+            {
+                short alertDescription = TlsUtilities.IsTlsV13(m_context)
+                    ?   AlertDescription.certificate_required
+                    :   AlertDescription.handshake_failure;
+
+                throw new TlsFatalAlert(alertDescription);
+            }
+
+            TlsCertificate[] chain = clientCertificate.GetCertificateList();
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS server received client certificate chain of length " + chain.Length);
+                for (int i = 0; i < chain.Length; ++i)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[0].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+            }
+
+            if (isEmpty)
+                return;
+
+            string[] trustedCertResources = new string[]{ "x509-client-dsa.pem", "x509-client-ecdh.pem",
+                "x509-client-ecdsa.pem", "x509-client-ed25519.pem", "x509-client-ed448.pem",
+                "x509-client-rsa_pss_256.pem", "x509-client-rsa_pss_384.pem", "x509-client-rsa_pss_512.pem",
+                "x509-client-rsa.pem" };
+
+            TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                trustedCertResources);
+
+            if (null == certPath)
+                throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+            if (m_config.serverCheckSigAlgOfClientCerts)
+            {
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        }
+
+        protected virtual IList GetSupportedSignatureAlgorithms()
+        {
+            if (TlsUtilities.IsTlsV12(m_context) && m_config.serverAuthSigAlg != null)
+            {
+                IList signatureAlgorithms = new ArrayList(1);
+                signatureAlgorithms.Add(m_config.serverAuthSigAlg);
+                return signatureAlgorithms;
+            }
+
+            return m_context.SecurityParameters.ClientSigAlgs;
+        }
+
+        protected override TlsCredentialedSigner GetDsaSignerCredentials()
+        {
+            return LoadSignerCredentials(SignatureAlgorithm.dsa);
+        }
+
+        protected override TlsCredentialedSigner GetECDsaSignerCredentials()
+        {
+            // TODO[RFC 8422] Code should choose based on client's supported sig algs?
+            return LoadSignerCredentials(SignatureAlgorithm.ecdsa);
+            //return LoadSignerCredentials(SignatureAlgorithm.ed25519);
+            //return LoadSignerCredentials(SignatureAlgorithm.ed448);
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[]{ "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            return LoadSignerCredentials(SignatureAlgorithm.rsa);
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, TestCipherSuites);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            if (m_config.serverSupportedVersions != null)
+            {
+                return m_config.serverSupportedVersions;
+            }
+
+            return base.GetSupportedVersions();
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        private TlsCredentialedSigner LoadSignerCredentials(short signatureAlgorithm)
+        {
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, GetSupportedSignatureAlgorithms(),
+                signatureAlgorithm);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestServerProtocol.cs b/crypto/test/src/tls/test/TlsTestServerProtocol.cs
new file mode 100644
index 000000000..632f6b8bf
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestServerProtocol.cs
@@ -0,0 +1,22 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestServerProtocol
+        : TlsServerProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        internal TlsTestServerProtocol(Stream stream, TlsTestConfig config)
+            : this(stream, stream, config)
+        {
+        }
+
+        internal TlsTestServerProtocol(Stream input, Stream output, TlsTestConfig config)
+            : base(input, output)
+        {
+            this.m_config = config;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestSuite.cs b/crypto/test/src/tls/test/TlsTestSuite.cs
new file mode 100644
index 000000000..adedd8249
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestSuite.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Collections;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class TlsTestSuite
+    {
+        internal static TlsCrypto BC_CRYPTO = new BcTlsCrypto(new SecureRandom());
+
+        internal static TlsCrypto GetCrypto(TlsTestConfig config)
+        {
+            switch (config.clientCrypto)
+            {
+                case TlsTestConfig.CRYPTO_BC:
+                default:
+                    return BC_CRYPTO;
+            }
+        }
+
+        // Make the access to constants less verbose 
+        internal abstract class C : TlsTestConfig {}
+
+        public TlsTestSuite()
+        {
+        }
+
+        public static IEnumerable Suite()
+        {
+            IList testSuite = new ArrayList();
+            AddAllTests(testSuite, TlsTestConfig.CRYPTO_BC, TlsTestConfig.CRYPTO_BC);
+            return testSuite;
+        }
+
+        private static void AddAllTests(IList testSuite, int clientCrypto, int serverCrypto)
+        {
+            AddFallbackTests(testSuite, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.SSLv3, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv10, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv11, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv13, clientCrypto, serverCrypto);
+        }
+
+        private static void AddFallbackTests(IList testSuite, int clientCrypto, int serverCrypto)
+        {
+            string prefix = GetCryptoName(clientCrypto) + "_" + GetCryptoName(serverCrypto) + "_";
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+                c.clientFallback = true;
+
+                AddTestCase(testSuite, c, prefix + "FallbackGood");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+                c.clientFallback = true;
+                c.clientSupportedVersions = ProtocolVersion.TLSv11.DownTo(ProtocolVersion.TLSv10);
+                c.ExpectServerFatalAlert(AlertDescription.inappropriate_fallback);
+
+                AddTestCase(testSuite, c, prefix + "FallbackBad");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+                c.clientSupportedVersions = ProtocolVersion.TLSv11.DownTo(ProtocolVersion.TLSv10);
+
+                AddTestCase(testSuite, c, prefix + "FallbackNone");
+            }
+        }
+
+        private static void AddVersionTests(IList testSuite, ProtocolVersion version, int clientCrypto,
+            int serverCrypto)
+        {
+            string prefix = GetCryptoName(clientCrypto) + "_" + GetCryptoName(serverCrypto) + "_"
+                + version.ToString().Replace(" ", "").Replace(".", "") + "_";
+
+            bool isTlsV12 = TlsUtilities.IsTlsV12(version);
+            bool isTlsV13 = TlsUtilities.IsTlsV13(version);
+            bool isTlsV12Exactly = isTlsV12 && !isTlsV13;
+
+            short certReqDeclinedAlert = TlsUtilities.IsTlsV13(version)
+                ?   AlertDescription.certificate_required
+                :   AlertDescription.handshake_failure;
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+
+                AddTestCase(testSuite, c, prefix + "GoodDefault");
+            }
+
+            if (isTlsV13)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientEmptyKeyShare = true;
+
+                AddTestCase(testSuite, c, prefix + "GoodEmptyKeyShare");
+            }
+
+            /*
+             * Server only declares support for SHA1/RSA, client selects MD5/RSA. Since the client is
+             * NOT actually tracking MD5 over the handshake, we expect fatal alert from the client.
+             */
+            if (isTlsV12Exactly)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultRsaSignatureAlgorithms();
+                c.serverCheckSigAlgOfClientCerts = false;
+                c.ExpectClientFatalAlert(AlertDescription.internal_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifyHashAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client selects SHA1/RSA. Since the client is
+             * actually tracking SHA1 over the handshake, we expect fatal alert to come from the server
+             * when it verifies the selected algorithm against the CertificateRequest supported
+             * algorithms.
+             */
+            if (isTlsV12)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.serverCheckSigAlgOfClientCerts = false;
+                c.ExpectServerFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client signs with SHA1/RSA, but sends
+             * SHA1/ECDSA in the CertificateVerify. Since the client is actually tracking SHA1 over the
+             * handshake, and the claimed algorithm is in the CertificateRequest supported algorithms,
+             * we expect fatal alert to come from the server when it finds the claimed algorithm
+             * doesn't match the client certificate.
+             */
+            if (isTlsV12)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.clientAuthSigAlgClaimed = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.ExpectServerFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlgMismatch");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_VERIFY;
+                c.ExpectServerFatalAlert(AlertDescription.decrypt_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySignature");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_CERT;
+                c.ExpectServerFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadClientCertificate");
+            }
+
+            if (isTlsV13)
+            {
+                /*
+                 * For TLS 1.3 the supported_algorithms extension is required in ClientHello when the
+                 * server authenticates via a certificate.
+                 */
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientSendSignatureAlgorithms = false;
+                c.clientSendSignatureAlgorithmsCert = false;
+                c.ExpectServerFatalAlert(AlertDescription.missing_extension);
+
+                AddTestCase(testSuite, c, prefix + "BadClientSigAlgs");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+                c.serverCertReq = C.SERVER_CERT_REQ_MANDATORY;
+                c.ExpectServerFatalAlert(certReqDeclinedAlert);
+
+                AddTestCase(testSuite, c, prefix + "BadMandatoryCertReqDeclined");
+            }
+
+            /*
+             * Server sends SHA-256/RSA certificate, which is not the default {sha1,rsa} implied by the
+             * absent signature_algorithms extension. We expect fatal alert from the client when it
+             * verifies the certificate's 'signatureAlgorithm' against the implicit default signature_algorithms.
+             */
+            if (isTlsV12Exactly)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientSendSignatureAlgorithms = false;
+                c.clientSendSignatureAlgorithmsCert = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadServerCertSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not in the default
+             * supported signature algorithms that the client sent. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the supported algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not the default {sha1,rsa}
+             * implied by the absent signature_algorithms extension. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the implicit default.
+             */
+            if (isTlsV12Exactly)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientCheckSigAlgOfServerCerts = false;
+                c.clientSendSignatureAlgorithms = false;
+                c.clientSendSignatureAlgorithmsCert = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg2");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.serverCertReq = C.SERVER_CERT_REQ_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodNoCertReq");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodOptionalCertReqDeclined");
+            }
+
+            /*
+             * Server generates downgraded (RFC 8446) 1.1 ServerHello. We expect fatal alert
+             * (illegal_parameter) from the client.
+             */
+            if (!isTlsV13)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.serverNegotiateVersion = version;
+                c.serverSupportedVersions = ProtocolVersion.TLSv13.DownTo(version);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadDowngrade");
+            }
+        }
+
+        private static void AddTestCase(IList testSuite, TlsTestConfig config, string name)
+        {
+            testSuite.Add(new TestCaseData(config).SetName(name));
+        }
+
+        private static TlsTestConfig CreateTlsTestConfig(ProtocolVersion serverMaxVersion, int clientCrypto,
+            int serverCrypto)
+        {
+            TlsTestConfig c = new TlsTestConfig();
+            c.clientCrypto = clientCrypto;
+            c.clientSupportedVersions = ProtocolVersion.TLSv13.DownTo(ProtocolVersion.SSLv3);
+            c.serverCrypto = serverCrypto;
+            c.serverSupportedVersions = serverMaxVersion.DownTo(ProtocolVersion.SSLv3);
+            return c;
+        }
+
+        private static string GetCryptoName(int crypto)
+        {
+            switch (crypto)
+            {
+            case TlsTestConfig.CRYPTO_BC:
+            default:
+                return "BC";
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestUtilities.cs b/crypto/test/src/tls/test/TlsTestUtilities.cs
new file mode 100644
index 000000000..3ecacb0f0
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestUtilities.cs
@@ -0,0 +1,412 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Sec;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO.Pem;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class TlsTestUtilities
+    {
+        internal static readonly byte[] RsaCertData = Base64.Decode(
+            "MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2" +
+            "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq" +
+            "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA2MDIwNVoXDTEzMDIyNT" +
+            "A2MDM0NVowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw" +
+            "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG" +
+            "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy" +
+            "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCBSAwEgYDVR" +
+            "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAHU55Ncz" +
+            "eglREcTg54YLUlGWu2WOYWhit/iM1eeq8Kivro7q98eW52jTuMI3CI5ulqd0hYzshQKQaZ5GDzErMyM=");
+
+        internal static readonly byte[] DudRsaCertData = Base64.Decode(
+            "MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2" +
+            "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq" +
+            "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA1NDcyOFoXDTEzMDIyNT" +
+            "A1NDkwOFowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw" +
+            "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG" +
+            "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy" +
+            "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCAAEwEgYDVR" +
+            "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAJg55PBS" +
+            "weg6obRUKF4FF6fCrWFi6oCYSQ99LWcAeupc5BofW5MstFMhCOaEucuGVqunwT5G7/DweazzCIrSzB0=");
+
+        internal static bool EqualsIgnoreCase(string a, string b)
+        {
+            return ToUpperInvariant(a) == ToUpperInvariant(b);
+        }
+
+        internal static string ToUpperInvariant(string s)
+        {
+            return s.ToUpper(CultureInfo.InvariantCulture);
+        }
+
+        internal static string Fingerprint(X509CertificateStructure c)
+        {
+            byte[] der = c.GetEncoded();
+            byte[] hash = Sha256DigestOf(der);
+            byte[] hexBytes = Hex.Encode(hash);
+            string hex = ToUpperInvariant(Encoding.ASCII.GetString(hexBytes));
+
+            StringBuilder fp = new StringBuilder();
+            int i = 0;
+            fp.Append(hex.Substring(i, 2));
+            while ((i += 2) < hex.Length)
+            {
+                fp.Append(':');
+                fp.Append(hex.Substring(i, 2));
+            }
+            return fp.ToString();
+        }
+
+        internal static byte[] Sha256DigestOf(byte[] input)
+        {
+            return DigestUtilities.CalculateDigest("SHA256", input);
+        }
+
+        internal static string GetCACertResource(short signatureAlgorithm)
+        {
+            return "x509-ca-" + GetResourceName(signatureAlgorithm) + ".pem";
+        }
+
+        internal static string GetCACertResource(string eeCertResource)
+        {
+            if (eeCertResource.StartsWith("x509-client-"))
+            {
+                eeCertResource = eeCertResource.Substring("x509-client-".Length);
+            }
+            if (eeCertResource.StartsWith("x509-server-"))
+            {
+                eeCertResource = eeCertResource.Substring("x509-server-".Length);
+            }
+            if (eeCertResource.EndsWith(".pem"))
+            {
+                eeCertResource = eeCertResource.Substring(0, eeCertResource.Length - ".pem".Length);
+            }
+
+            if (EqualsIgnoreCase("dsa", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.dsa);
+            }
+
+            if (EqualsIgnoreCase("ecdh", eeCertResource)
+                || EqualsIgnoreCase("ecdsa", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.ecdsa);
+            }
+
+            if (EqualsIgnoreCase("ed25519", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.ed25519);
+            }
+
+            if (EqualsIgnoreCase("ed448", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.ed448);
+            }
+
+            if (EqualsIgnoreCase("rsa", eeCertResource)
+                || EqualsIgnoreCase("rsa-enc", eeCertResource)
+                || EqualsIgnoreCase("rsa-sign", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa);
+            }
+
+            if (EqualsIgnoreCase("rsa_pss_256", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa_pss_pss_sha256);
+            }
+            if (EqualsIgnoreCase("rsa_pss_384", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa_pss_pss_sha384);
+            }
+            if (EqualsIgnoreCase("rsa_pss_512", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa_pss_pss_sha512);
+            }
+
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        internal static string GetResourceName(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return "rsa";
+            case SignatureAlgorithm.dsa:
+                return "dsa";
+            case SignatureAlgorithm.ecdsa:
+                return "ecdsa";
+            case SignatureAlgorithm.ed25519:
+                return "ed25519";
+            case SignatureAlgorithm.ed448:
+                return "ed448";
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                return "rsa_pss_256";
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                return "rsa_pss_384";
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return "rsa_pss_512";
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        internal static TlsCredentialedAgreement LoadAgreementCredentials(TlsContext context, string[] certResources,
+            string keyResource)
+        {
+            TlsCrypto crypto = context.Crypto;
+            Certificate certificate = LoadCertificateChain(context, certResources);
+
+            // TODO[tls-ops] Need to have TlsCrypto construct the credentials from the certs/key (as raw data)
+            if (crypto is BcTlsCrypto)
+            {
+                AsymmetricKeyParameter privateKey = LoadBcPrivateKeyResource(keyResource);
+
+                return new BcDefaultTlsCredentialedAgreement((BcTlsCrypto)crypto, certificate, privateKey);
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        internal static TlsCredentialedDecryptor LoadEncryptionCredentials(TlsContext context, string[] certResources,
+            string keyResource)
+        {
+            TlsCrypto crypto = context.Crypto;
+            Certificate certificate = LoadCertificateChain(context, certResources);
+
+            // TODO[tls-ops] Need to have TlsCrypto construct the credentials from the certs/key (as raw data)
+            if (crypto is BcTlsCrypto)
+            {
+                AsymmetricKeyParameter privateKey = LoadBcPrivateKeyResource(keyResource);
+
+                return new BcDefaultTlsCredentialedDecryptor((BcTlsCrypto)crypto, certificate, privateKey);
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        public static TlsCredentialedSigner LoadSignerCredentials(TlsCryptoParameters cryptoParams, TlsCrypto crypto,
+            string[] certResources, string keyResource, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            Certificate certificate = LoadCertificateChain(cryptoParams.ServerVersion, crypto, certResources);
+
+            // TODO[tls-ops] Need to have TlsCrypto construct the credentials from the certs/key (as raw data)
+            if (crypto is BcTlsCrypto)
+            {
+                AsymmetricKeyParameter privateKey = LoadBcPrivateKeyResource(keyResource);
+
+                return new BcDefaultTlsCredentialedSigner(cryptoParams, (BcTlsCrypto)crypto, privateKey, certificate, signatureAndHashAlgorithm);
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        internal static TlsCredentialedSigner LoadSignerCredentials(TlsContext context, string[] certResources,
+            string keyResource, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            TlsCrypto crypto = context.Crypto;
+            TlsCryptoParameters cryptoParams = new TlsCryptoParameters(context);
+
+            return LoadSignerCredentials(cryptoParams, crypto, certResources, keyResource, signatureAndHashAlgorithm);
+        }
+
+        internal static TlsCredentialedSigner LoadSignerCredentials(TlsContext context,
+            IList supportedSignatureAlgorithms, short signatureAlgorithm, string certResource, string keyResource)
+        {
+            if (supportedSignatureAlgorithms == null)
+            {
+                supportedSignatureAlgorithms = TlsUtilities.GetDefaultSignatureAlgorithms(signatureAlgorithm);
+            }
+
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = null;
+
+            foreach (SignatureAndHashAlgorithm alg in supportedSignatureAlgorithms)
+            {
+                if (alg.Signature == signatureAlgorithm)
+                {
+                    // Just grab the first one we find
+                    signatureAndHashAlgorithm = alg;
+                    break;
+                }
+            }
+
+            if (signatureAndHashAlgorithm == null)
+                return null;
+
+            return LoadSignerCredentials(context, new string[]{ certResource }, keyResource,
+                signatureAndHashAlgorithm);
+        }
+
+        internal static TlsCredentialedSigner LoadSignerCredentialsServer(TlsContext context,
+            IList supportedSignatureAlgorithms, short signatureAlgorithm)
+        {
+            string sigName = GetResourceName(signatureAlgorithm);
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                sigName += "-sign";
+                break;
+            }
+
+            string certResource = "x509-server-" + sigName + ".pem";
+            string keyResource = "x509-server-key-" + sigName + ".pem";
+
+            return LoadSignerCredentials(context, supportedSignatureAlgorithms, signatureAlgorithm, certResource,
+                keyResource);
+        }
+
+        internal static Certificate LoadCertificateChain(ProtocolVersion protocolVersion, TlsCrypto crypto,
+            string[] resources)
+        {
+            if (TlsUtilities.IsTlsV13(protocolVersion))
+            {
+                CertificateEntry[] certificateEntryList = new CertificateEntry[resources.Length];
+                for (int i = 0; i < resources.Length; ++i)
+                {
+                    TlsCertificate certificate = LoadCertificateResource(crypto, resources[i]);
+
+                    // TODO[tls13] Add possibility of specifying e.g. CertificateStatus 
+                    IDictionary extensions = null;
+
+                    certificateEntryList[i] = new CertificateEntry(certificate, extensions);
+                }
+
+                // TODO[tls13] Support for non-empty request context
+                byte[] certificateRequestContext = TlsUtilities.EmptyBytes;
+
+                return new Certificate(certificateRequestContext, certificateEntryList);
+            }
+            else
+            {
+                TlsCertificate[] chain = new TlsCertificate[resources.Length];
+                for (int i = 0; i < resources.Length; ++i)
+                {
+                    chain[i] = LoadCertificateResource(crypto, resources[i]);
+                }
+                return new Certificate(chain);
+            }
+        }
+
+        internal static Certificate LoadCertificateChain(TlsContext context, string[] resources)
+        {
+            return LoadCertificateChain(context.ServerVersion, context.Crypto, resources);
+        }
+
+        internal static X509CertificateStructure LoadBcCertificateResource(string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.EndsWith("CERTIFICATE"))
+            {
+                return X509CertificateStructure.GetInstance(pem.Content);
+            }
+            throw new ArgumentException("doesn't specify a valid certificate", "resource");
+        }
+
+        internal static TlsCertificate LoadCertificateResource(TlsCrypto crypto, string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.EndsWith("CERTIFICATE"))
+            {
+                return crypto.CreateCertificate(pem.Content);
+            }
+            throw new ArgumentException("doesn't specify a valid certificate", "resource");
+        }
+
+        internal static AsymmetricKeyParameter LoadBcPrivateKeyResource(string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.Equals("PRIVATE KEY"))
+            {
+                return PrivateKeyFactory.CreateKey(pem.Content);
+            }
+            if (pem.Type.Equals("ENCRYPTED PRIVATE KEY"))
+            {
+                throw new NotSupportedException("Encrypted PKCS#8 keys not supported");
+            }
+            if (pem.Type.Equals("RSA PRIVATE KEY"))
+            {
+                RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(pem.Content);
+                return new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent,
+                    rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1,
+                    rsa.Exponent2, rsa.Coefficient);
+            }
+            if (pem.Type.Equals("EC PRIVATE KEY"))
+            {
+                ECPrivateKeyStructure pKey = ECPrivateKeyStructure.GetInstance(pem.Content);
+                AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.IdECPublicKey,
+                    pKey.GetParameters());
+                PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey);
+                return PrivateKeyFactory.CreateKey(privInfo);
+            }
+            throw new ArgumentException("doesn't specify a valid private key", "resource");
+        }
+
+        internal static PemObject LoadPemResource(string resource)
+           
+        {
+            Stream s = SimpleTest.GetTestDataAsStream("tls." + resource);
+            PemReader p = new PemReader(new StreamReader(s));
+            PemObject o = p.ReadPemObject();
+            p.Reader.Close();
+            return o;
+        }
+
+        internal static bool AreSameCertificate(TlsCrypto crypto, TlsCertificate cert, string resource)
+        {
+            // TODO Cache test resources?
+            return AreSameCertificate(cert, LoadCertificateResource(crypto, resource));
+        }
+
+        internal static bool AreSameCertificate(TlsCertificate a, TlsCertificate b)
+        {
+            // TODO[tls-ops] Support equals on TlsCertificate?
+            return Arrays.AreEqual(a.GetEncoded(), b.GetEncoded());
+        }
+
+        internal static TlsCertificate[] GetTrustedCertPath(TlsCrypto crypto, TlsCertificate cert, string[] resources)
+        {
+            foreach (string eeCertResource in resources)
+            {
+                TlsCertificate eeCert = LoadCertificateResource(crypto, eeCertResource);
+                if (AreSameCertificate(cert, eeCert))
+                {
+                    string caCertResource = GetCACertResource(eeCertResource);
+                    TlsCertificate caCert = LoadCertificateResource(crypto, caCertResource);
+                    if (null != caCert)
+                    {
+                        return new TlsCertificate[]{ eeCert, caCert };
+                    }
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsUtilitiesTest.cs b/crypto/test/src/tls/test/TlsUtilitiesTest.cs
new file mode 100644
index 000000000..702c40082
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsUtilitiesTest.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+
+using NUnit.Framework;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsUtilitiesTest
+    {
+        [Test]
+        public void TestChooseSignatureAndHash()
+        {
+            int keyExchangeAlgorithm = KeyExchangeAlgorithm.ECDHE_RSA;
+            short signatureAlgorithm = TlsUtilities.GetLegacySignatureAlgorithmServer(keyExchangeAlgorithm);
+
+            IList supportedSignatureAlgorithms = GetSignatureAlgorithms(false);
+            SignatureAndHashAlgorithm sigAlg = TlsUtilities.ChooseSignatureAndHashAlgorithm(ProtocolVersion.TLSv12,
+                supportedSignatureAlgorithms, signatureAlgorithm);
+            Assert.AreEqual(HashAlgorithm.sha256, sigAlg.Hash);
+
+            for (int count = 0; count < 10; ++count)
+            {
+                supportedSignatureAlgorithms = GetSignatureAlgorithms(true);
+                sigAlg = TlsUtilities.ChooseSignatureAndHashAlgorithm(ProtocolVersion.TLSv12,
+                    supportedSignatureAlgorithms, signatureAlgorithm);
+                Assert.AreEqual(HashAlgorithm.sha256, sigAlg.Hash);
+            }
+        }
+
+        private static IList GetSignatureAlgorithms(bool randomise)
+        {
+            short[] hashAlgorithms = new short[]{ HashAlgorithm.sha1, HashAlgorithm.sha224, HashAlgorithm.sha256,
+                HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.md5 };
+            short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.rsa, SignatureAlgorithm.dsa,
+                SignatureAlgorithm.ecdsa };
+
+            IList result = new ArrayList();
+            for (int i = 0; i < signatureAlgorithms.Length; ++i)
+            {
+                for (int j = 0; j < hashAlgorithms.Length; ++j)
+                {
+                    result.Add(SignatureAndHashAlgorithm.GetInstance(hashAlgorithms[j], signatureAlgorithms[i]));
+                }
+            }
+
+            if (randomise)
+            {
+                Random r = new Random();
+                int count = result.Count;
+                for (int src = 0; src < count; ++src)
+                {
+                    int dst = r.Next(count);
+                    if (src != dst)
+                    {
+                        object a = result[src], b = result[dst];
+                        result[dst] = a;
+                        result[src] = b;
+                    }
+                }
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/UnreliableDatagramTransport.cs b/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
new file mode 100644
index 000000000..bdbfd6e67
--- /dev/null
+++ b/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
@@ -0,0 +1,79 @@
+using System;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class UnreliableDatagramTransport
+        : DatagramTransport
+    {
+        private readonly DatagramTransport m_transport;
+        private readonly Random m_random;
+        private readonly int m_percentPacketLossReceiving, m_percentPacketLossSending;
+
+        public UnreliableDatagramTransport(DatagramTransport transport, Random random,
+            int percentPacketLossReceiving, int percentPacketLossSending)
+        {
+            if (percentPacketLossReceiving < 0 || percentPacketLossReceiving > 100)
+                throw new ArgumentException("out of range", "percentPacketLossReceiving");
+            if (percentPacketLossSending < 0 || percentPacketLossSending > 100)
+                throw new ArgumentException("out of range", "percentPacketLossSending");
+
+            this.m_transport = transport;
+            this.m_random = random;
+            this.m_percentPacketLossReceiving = percentPacketLossReceiving;
+            this.m_percentPacketLossSending = percentPacketLossSending;
+        }
+
+        public virtual int GetReceiveLimit()
+        {
+            return m_transport.GetReceiveLimit();
+        }
+
+        public virtual int GetSendLimit()
+        {
+            return m_transport.GetSendLimit();
+        }
+
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            long endMillis = DateTimeUtilities.CurrentUnixMs() + waitMillis;
+            for (;;)
+            {
+                int length = m_transport.Receive(buf, off, len, waitMillis);
+                if (length < 0 || !LostPacket(m_percentPacketLossReceiving))
+                    return length;
+
+                Console.WriteLine("PACKET LOSS (" + length + " byte packet not received)");
+
+                long now = DateTimeUtilities.CurrentUnixMs();
+                if (now >= endMillis)
+                    return -1;
+
+                waitMillis = (int)(endMillis - now);
+            }
+        }
+
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            if (LostPacket(m_percentPacketLossSending))
+            {
+                Console.WriteLine("PACKET LOSS (" + len + " byte packet not sent)");
+            }
+            else
+            {
+                m_transport.Send(buf, off, len);
+            }
+        }
+
+        public virtual void Close()
+        {
+            m_transport.Close();
+        }
+
+        private bool LostPacket(int percentPacketLoss)
+        {
+            return percentPacketLoss > 0 && m_random.Next(100) < percentPacketLoss;
+        }
+    }
+}