summary refs log tree commit diff
path: root/crypto
diff options
context:
space:
mode:
authorOren Novotny <oren@novotny.org>2015-02-19 08:00:24 -0500
committerOren Novotny <oren@novotny.org>2015-02-19 08:00:24 -0500
commit50eacbf828f74c5a58a24e441061d7256cc17662 (patch)
treea80b95f47273e9a2fdccd741290afeec8200dba7 /crypto
parentMerge branch 'master' into master-vs12 (diff)
parentPort of WNaf precomp optimization from Java (diff)
downloadBouncyCastle.NET-ed25519-50eacbf828f74c5a58a24e441061d7256cc17662.tar.xz
Merge remote-tracking branch 'upstream/master' into master-vs12
Diffstat (limited to 'crypto')
-rw-r--r--crypto/License.html2
-rw-r--r--crypto/crypto.csproj70
-rw-r--r--crypto/src/AssemblyInfo.cs2
-rw-r--r--crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs27
-rw-r--r--crypto/src/asn1/misc/MiscObjectIdentifiers.cs61
-rw-r--r--crypto/src/bcpg/PublicKeyAlgorithmTags.cs27
-rw-r--r--crypto/src/cms/SignerInformationStore.cs53
-rw-r--r--crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs58
-rw-r--r--crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs28
-rw-r--r--crypto/src/crypto/modes/gcm/GcmUtilities.cs292
-rw-r--r--crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs31
-rw-r--r--crypto/src/crypto/paddings/Pkcs7Padding.cs21
-rw-r--r--crypto/src/crypto/tls/AbstractTlsClient.cs54
-rw-r--r--crypto/src/crypto/tls/AbstractTlsServer.cs12
-rw-r--r--crypto/src/crypto/tls/AlertDescription.cs13
-rw-r--r--crypto/src/crypto/tls/BasicTlsPskIdentity.cs43
-rw-r--r--crypto/src/crypto/tls/CipherSuite.cs25
-rw-r--r--crypto/src/crypto/tls/DatagramTransport.cs23
-rw-r--r--crypto/src/crypto/tls/DefaultTlsClient.cs8
-rw-r--r--crypto/src/crypto/tls/DefaultTlsServer.cs8
-rw-r--r--crypto/src/crypto/tls/DtlsClientProtocol.cs844
-rw-r--r--crypto/src/crypto/tls/DtlsEpoch.cs51
-rw-r--r--crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs11
-rw-r--r--crypto/src/crypto/tls/DtlsProtocol.cs72
-rw-r--r--crypto/src/crypto/tls/DtlsReassembler.cs125
-rw-r--r--crypto/src/crypto/tls/DtlsRecordLayer.cs507
-rw-r--r--crypto/src/crypto/tls/DtlsReliableHandshake.cs443
-rw-r--r--crypto/src/crypto/tls/DtlsReplayWindow.cs85
-rw-r--r--crypto/src/crypto/tls/DtlsServerProtocol.cs642
-rw-r--r--crypto/src/crypto/tls/DtlsTransport.cs77
-rw-r--r--crypto/src/crypto/tls/PskTlsClient.cs17
-rw-r--r--crypto/src/crypto/tls/PskTlsServer.cs347
-rw-r--r--crypto/src/crypto/tls/SecurityParameters.cs6
-rw-r--r--crypto/src/crypto/tls/ServerDHParams.cs3
-rw-r--r--crypto/src/crypto/tls/SessionParameters.cs20
-rw-r--r--crypto/src/crypto/tls/TlsBlockCipher.cs26
-rw-r--r--crypto/src/crypto/tls/TlsClient.cs2
-rw-r--r--crypto/src/crypto/tls/TlsClientProtocol.cs19
-rw-r--r--crypto/src/crypto/tls/TlsECDheKeyExchange.cs67
-rw-r--r--crypto/src/crypto/tls/TlsEccUtilities.cs71
-rw-r--r--crypto/src/crypto/tls/TlsProtocol.cs4
-rw-r--r--crypto/src/crypto/tls/TlsPskIdentityManager.cs11
-rw-r--r--crypto/src/crypto/tls/TlsPskKeyExchange.cs97
-rw-r--r--crypto/src/crypto/tls/TlsServer.cs3
-rw-r--r--crypto/src/crypto/tls/TlsServerProtocol.cs5
-rw-r--r--crypto/src/crypto/tls/TlsUtilities.cs8
-rw-r--r--crypto/src/math/ec/ECAlgorithms.cs14
-rw-r--r--crypto/src/math/ec/ECCurve.cs61
-rw-r--r--crypto/src/math/ec/multiplier/WNafUtilities.cs101
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs853
-rw-r--r--crypto/src/security/SignerUtilities.cs4
-rw-r--r--crypto/src/util/Arrays.cs5
52 files changed, 4616 insertions, 843 deletions
diff --git a/crypto/License.html b/crypto/License.html
index 1c5c7b0ec..cd92d1b0e 100644
--- a/crypto/License.html
+++ b/crypto/License.html
@@ -9,7 +9,7 @@
 <h2>The Bouncy Castle Cryptographic C#&reg; API</h2>
 <h3>License:</h3>
 The Bouncy Castle License<br>
-Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc.
+Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc.
 (http://www.bouncycastle.org)<br>
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"), to deal in the
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 81f74e656..fdd5c152b 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -4344,6 +4344,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\BasicTlsPskIdentity.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\ByteQueue.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4434,6 +4439,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\DatagramTransport.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\DefaultTlsAgreementCredentials.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4479,6 +4489,56 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\DtlsClientProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsEpoch.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsHandshakeRetransmit.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsReassembler.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsRecordLayer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsReliableHandshake.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsReplayWindow.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsServerProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\DtlsTransport.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\ECBasisType.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4594,6 +4654,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\PskTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\RecordStream.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4849,6 +4914,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsPskIdentityManager.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsRsaKeyExchange.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/AssemblyInfo.cs b/crypto/src/AssemblyInfo.cs
index 7dd625878..4a813bc5a 100644
--- a/crypto/src/AssemblyInfo.cs
+++ b/crypto/src/AssemblyInfo.cs
@@ -14,7 +14,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("The Legion of the Bouncy Castle Inc.")]
 [assembly: AssemblyProduct("Bouncy Castle for .NET")]
-[assembly: AssemblyCopyright("Copyright (C) 2000-2014")]
+[assembly: AssemblyCopyright("Copyright (C) 2000-2015")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 
diff --git a/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs b/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs
index b8aba7ee9..bc48c3fa2 100644
--- a/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs
+++ b/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs
@@ -2,17 +2,18 @@ using System;
 
 namespace Org.BouncyCastle.Asn1.Microsoft
 {
-	public abstract class MicrosoftObjectIdentifiers
-	{
-		//
-		// Microsoft
-		//       iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) Microsoft(311)
-		//
-		public static readonly DerObjectIdentifier Microsoft               = new DerObjectIdentifier("1.3.6.1.4.1.311");
-		public static readonly DerObjectIdentifier MicrosoftCertTemplateV1 = new DerObjectIdentifier(Microsoft + ".20.2");
-		public static readonly DerObjectIdentifier MicrosoftCAVersion      = new DerObjectIdentifier(Microsoft + ".21.1");
-		public static readonly DerObjectIdentifier MicrosoftPrevCACertHash = new DerObjectIdentifier(Microsoft + ".21.2");
-		public static readonly DerObjectIdentifier MicrosoftCertTemplateV2 = new DerObjectIdentifier(Microsoft + ".21.7");
-		public static readonly DerObjectIdentifier MicrosoftAppPolicies    = new DerObjectIdentifier(Microsoft + ".21.10");
-	}
+    public abstract class MicrosoftObjectIdentifiers
+    {
+        //
+        // Microsoft
+        //       iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) Microsoft(311)
+        //
+        public static readonly DerObjectIdentifier Microsoft               = new DerObjectIdentifier("1.3.6.1.4.1.311");
+        public static readonly DerObjectIdentifier MicrosoftCertTemplateV1 = Microsoft.Branch("20.2");
+        public static readonly DerObjectIdentifier MicrosoftCAVersion      = Microsoft.Branch("21.1");
+        public static readonly DerObjectIdentifier MicrosoftPrevCACertHash = Microsoft.Branch("21.2");
+        public static readonly DerObjectIdentifier MicrosoftCrlNextPublish = Microsoft.Branch("21.4");
+        public static readonly DerObjectIdentifier MicrosoftCertTemplateV2 = Microsoft.Branch("21.7");
+        public static readonly DerObjectIdentifier MicrosoftAppPolicies    = Microsoft.Branch("21.10");
+    }
 }
diff --git a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
index 01004d889..45adce4f7 100644
--- a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
+++ b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs
@@ -1,5 +1,3 @@
-using Org.BouncyCastle.Asn1;
-
 namespace Org.BouncyCastle.Asn1.Misc
 {
     public abstract class MiscObjectIdentifiers
@@ -9,40 +7,47 @@ namespace Org.BouncyCastle.Asn1.Misc
         //       iso/itu(2) joint-assign(16) us(840) uscompany(1) Netscape(113730) cert-extensions(1) }
         //
         public static readonly DerObjectIdentifier Netscape                = new DerObjectIdentifier("2.16.840.1.113730.1");
-        public static readonly DerObjectIdentifier NetscapeCertType        = new DerObjectIdentifier(Netscape + ".1");
-        public static readonly DerObjectIdentifier NetscapeBaseUrl         = new DerObjectIdentifier(Netscape + ".2");
-        public static readonly DerObjectIdentifier NetscapeRevocationUrl   = new DerObjectIdentifier(Netscape + ".3");
-        public static readonly DerObjectIdentifier NetscapeCARevocationUrl = new DerObjectIdentifier(Netscape + ".4");
-        public static readonly DerObjectIdentifier NetscapeRenewalUrl      = new DerObjectIdentifier(Netscape + ".7");
-        public static readonly DerObjectIdentifier NetscapeCAPolicyUrl     = new DerObjectIdentifier(Netscape + ".8");
-        public static readonly DerObjectIdentifier NetscapeSslServerName   = new DerObjectIdentifier(Netscape + ".12");
-        public static readonly DerObjectIdentifier NetscapeCertComment     = new DerObjectIdentifier(Netscape + ".13");
+        public static readonly DerObjectIdentifier NetscapeCertType        = Netscape.Branch("1");
+        public static readonly DerObjectIdentifier NetscapeBaseUrl         = Netscape.Branch("2");
+        public static readonly DerObjectIdentifier NetscapeRevocationUrl   = Netscape.Branch("3");
+        public static readonly DerObjectIdentifier NetscapeCARevocationUrl = Netscape.Branch("4");
+        public static readonly DerObjectIdentifier NetscapeRenewalUrl      = Netscape.Branch("7");
+        public static readonly DerObjectIdentifier NetscapeCAPolicyUrl     = Netscape.Branch("8");
+        public static readonly DerObjectIdentifier NetscapeSslServerName   = Netscape.Branch("12");
+        public static readonly DerObjectIdentifier NetscapeCertComment     = Netscape.Branch("13");
+
         //
         // Verisign
         //       iso/itu(2) joint-assign(16) us(840) uscompany(1) verisign(113733) cert-extensions(1) }
         //
-        internal const string Verisign = "2.16.840.1.113733.1";
+        public static readonly DerObjectIdentifier Verisign = new DerObjectIdentifier("2.16.840.1.113733.1");
 
-		//
+        //
         // CZAG - country, zip, age, and gender
         //
-        public static readonly DerObjectIdentifier VerisignCzagExtension = new DerObjectIdentifier(Verisign + ".6.3");
+        public static readonly DerObjectIdentifier VerisignCzagExtension          = Verisign.Branch("6.3");
 
-		// D&B D-U-N-S number
-		public static readonly DerObjectIdentifier VerisignDnbDunsNumber = new DerObjectIdentifier(Verisign + ".6.15");
+        public static readonly DerObjectIdentifier VerisignPrivate_6_9            = Verisign.Branch("6.9");
+        public static readonly DerObjectIdentifier VerisignOnSiteJurisdictionHash = Verisign.Branch("6.11");
+        public static readonly DerObjectIdentifier VerisignBitString_6_13         = Verisign.Branch("6.13");
 
-		//
-		// Novell
-		//       iso/itu(2) country(16) us(840) organization(1) novell(113719)
-		//
-		public static readonly string				Novell					= "2.16.840.1.113719";
-		public static readonly DerObjectIdentifier	NovellSecurityAttribs	= new DerObjectIdentifier(Novell + ".1.9.4.1");
+        // D&B D-U-N-S number
+        public static readonly DerObjectIdentifier VerisignDnbDunsNumber          = Verisign.Branch("6.15");
 
-		//
-		// Entrust
-		//       iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7)
-		//
-		public static readonly string				Entrust					= "1.2.840.113533.7";
-		public static readonly DerObjectIdentifier	EntrustVersionExtension = new DerObjectIdentifier(Entrust + ".65.0");
-	}
+        public static readonly DerObjectIdentifier VerisignIssStrongCrypto        = Verisign.Branch("8.1");
+
+        //
+        // Novell
+        //       iso/itu(2) country(16) us(840) organization(1) novell(113719)
+        //
+        public static readonly string				Novell					= "2.16.840.1.113719";
+        public static readonly DerObjectIdentifier	NovellSecurityAttribs	= new DerObjectIdentifier(Novell + ".1.9.4.1");
+
+        //
+        // Entrust
+        //       iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7)
+        //
+        public static readonly string				Entrust					= "1.2.840.113533.7";
+        public static readonly DerObjectIdentifier	EntrustVersionExtension = new DerObjectIdentifier(Entrust + ".65.0");
+    }
 }
diff --git a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
index 85ae548eb..4a6704c14 100644
--- a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
+++ b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs
@@ -1,6 +1,6 @@
 namespace Org.BouncyCastle.Bcpg
 {
-	/// <remarks>Public Key Algorithm tag numbers.</remarks>
+    /// <remarks>Public Key Algorithm tag numbers.</remarks>
     public enum PublicKeyAlgorithmTag
     {
         RsaGeneral = 1,			// RSA (Encrypt or Sign)
@@ -9,20 +9,21 @@ namespace Org.BouncyCastle.Bcpg
         ElGamalEncrypt = 16,	// Elgamal (Encrypt-Only), see [ELGAMAL]
         Dsa = 17,				// DSA (Digital Signature Standard)
         EC = 18,				// Reserved for Elliptic Curve
+        ECDH = 18,              // Reserved for Elliptic Curve (actual algorithm name)
         ECDsa = 19,				// Reserved for ECDSA
         ElGamalGeneral = 20,	// Elgamal (Encrypt or Sign)
         DiffieHellman = 21,		// Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME)
 
-		Experimental_1 = 100,
-		Experimental_2 = 101,
-		Experimental_3 = 102,
-		Experimental_4 = 103,
-		Experimental_5 = 104,
-		Experimental_6 = 105,
-		Experimental_7 = 106,
-		Experimental_8 = 107,
-		Experimental_9 = 108,
-		Experimental_10 = 109,
-		Experimental_11 = 110,
-	}
+        Experimental_1 = 100,
+        Experimental_2 = 101,
+        Experimental_3 = 102,
+        Experimental_4 = 103,
+        Experimental_5 = 104,
+        Experimental_6 = 105,
+        Experimental_7 = 106,
+        Experimental_8 = 107,
+        Experimental_9 = 108,
+        Experimental_10 = 109,
+        Experimental_11 = 110,
+    }
 }
diff --git a/crypto/src/cms/SignerInformationStore.cs b/crypto/src/cms/SignerInformationStore.cs
index bd613843d..27940865d 100644
--- a/crypto/src/cms/SignerInformationStore.cs
+++ b/crypto/src/cms/SignerInformationStore.cs
@@ -8,10 +8,31 @@ namespace Org.BouncyCastle.Cms
 {
     public class SignerInformationStore
     {
-		private readonly IList all; //ArrayList[SignerInformation]
-		private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[SignerID, ArrayList[SignerInformation]]
+        private readonly IList all; //ArrayList[SignerInformation]
+        private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[SignerID, ArrayList[SignerInformation]]
 
-		public SignerInformationStore(
+        /**
+         * Create a store containing a single SignerInformation object.
+         *
+         * @param signerInfo the signer information to contain.
+         */
+        public SignerInformationStore(
+            SignerInformation signerInfo)
+        {
+            this.all = Platform.CreateArrayList(1);
+            this.all.Add(signerInfo);
+
+            SignerID sid = signerInfo.SignerID;
+
+            table[sid] = all;
+        }
+
+        /**
+         * Create a store containing a collection of SignerInformation objects.
+         *
+         * @param signerInfos a collection signer information objects to contain.
+         */
+        public SignerInformationStore(
             ICollection signerInfos)
         {
             foreach (SignerInformation signer in signerInfos)
@@ -19,12 +40,12 @@ namespace Org.BouncyCastle.Cms
                 SignerID sid = signer.SignerID;
                 IList list = (IList)table[sid];
 
-				if (list == null)
-				{
-					table[sid] = list = Platform.CreateArrayList(1);
-				}
+                if (list == null)
+                {
+                    table[sid] = list = Platform.CreateArrayList(1);
+                }
 
-				list.Add(signer);
+                list.Add(signer);
             }
 
             this.all = Platform.CreateArrayList(signerInfos);
@@ -40,24 +61,24 @@ namespace Org.BouncyCastle.Cms
         public SignerInformation GetFirstSigner(
             SignerID selector)
         {
-			IList list = (IList) table[selector];
+            IList list = (IList) table[selector];
 
-			return list == null ? null : (SignerInformation) list[0];
+            return list == null ? null : (SignerInformation) list[0];
         }
 
-		/// <summary>The number of signers in the collection.</summary>
-		public int Count
+        /// <summary>The number of signers in the collection.</summary>
+        public int Count
         {
-			get { return all.Count; }
+            get { return all.Count; }
         }
 
-		/// <returns>An ICollection of all signers in the collection</returns>
+        /// <returns>An ICollection of all signers in the collection</returns>
         public ICollection GetSigners()
         {
             return Platform.CreateArrayList(all);
         }
 
-		/**
+        /**
         * Return possible empty collection with signers matching the passed in SignerID
         *
         * @param selector a signer id to select against.
@@ -66,7 +87,7 @@ namespace Org.BouncyCastle.Cms
         public ICollection GetSigners(
             SignerID selector)
         {
-			IList list = (IList) table[selector];
+            IList list = (IList) table[selector];
 
             return list == null ? Platform.CreateArrayList() : Platform.CreateArrayList(list);
         }
diff --git a/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs b/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs
index 98049e1db..5660a1f84 100644
--- a/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs
+++ b/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs
@@ -4,37 +4,37 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Modes.Gcm
 {
-	public class BasicGcmExponentiator
-		: IGcmExponentiator
-	{
-		private byte[] x;
+    public class BasicGcmExponentiator
+        : IGcmExponentiator
+    {
+        private uint[] x;
 
-		public void Init(byte[] x)
-		{
-			this.x = Arrays.Clone(x);
-		}
+        public void Init(byte[] x)
+        {
+            this.x = GcmUtilities.AsUints(x);
+        }
 
-		public void ExponentiateX(long pow, byte[] output)
-		{
-			// Initial value is little-endian 1
-			byte[] y = GcmUtilities.OneAsBytes();
+        public void ExponentiateX(long pow, byte[] output)
+        {
+            // Initial value is little-endian 1
+            uint[] y = GcmUtilities.OneAsUints();
 
-			if (pow > 0)
-			{
-				byte[] powX = Arrays.Clone(x);
-				do
-				{
-					if ((pow & 1L) != 0)
-					{
-						GcmUtilities.Multiply(y, powX);
-					}
-					GcmUtilities.Multiply(powX, powX);
-					pow >>= 1;
-				}
-				while (pow > 0);
-			}
+            if (pow > 0)
+            {
+                uint[] powX = Arrays.Clone(x);
+                do
+                {
+                    if ((pow & 1L) != 0)
+                    {
+                        GcmUtilities.Multiply(y, powX);
+                    }
+                    GcmUtilities.Multiply(powX, powX);
+                    pow >>= 1;
+                }
+                while (pow > 0);
+            }
 
-			Array.Copy(y, 0, output, 0, 16);
-		}
-	}
+            GcmUtilities.AsBytes(y, output);
+        }
+    }
 }
diff --git a/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs b/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs
index 85e3ac9b1..eb89383fb 100644
--- a/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs
+++ b/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs
@@ -1,22 +1,22 @@
 using System;
 
-using Org.BouncyCastle.Utilities;
-
 namespace Org.BouncyCastle.Crypto.Modes.Gcm
 {
-	public class BasicGcmMultiplier
-		: IGcmMultiplier
-	{
-		private byte[] H;
+    public class BasicGcmMultiplier
+        : IGcmMultiplier
+    {
+        private uint[] H;
 
-		public void Init(byte[] H)
-		{
-            this.H = Arrays.Clone(H);
-		}
+        public void Init(byte[] H)
+        {
+            this.H = GcmUtilities.AsUints(H);
+        }
 
         public void MultiplyH(byte[] x)
-		{
-			GcmUtilities.Multiply(x, H);
-		}
-	}
+        {
+            uint[] t = GcmUtilities.AsUints(x);
+            GcmUtilities.Multiply(t, H);
+            GcmUtilities.AsBytes(t, x);
+        }
+    }
 }
diff --git a/crypto/src/crypto/modes/gcm/GcmUtilities.cs b/crypto/src/crypto/modes/gcm/GcmUtilities.cs
index 71e63c8fd..0f241035f 100644
--- a/crypto/src/crypto/modes/gcm/GcmUtilities.cs
+++ b/crypto/src/crypto/modes/gcm/GcmUtilities.cs
@@ -7,6 +7,31 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
 {
     internal abstract class GcmUtilities
     {
+        private const uint E1 = 0xe1000000;
+        private const ulong E1L = (ulong)E1 << 32;
+
+        private static uint[] GenerateLookup()
+        {
+            uint[] lookup = new uint[256];
+
+            for (int c = 0; c < 256; ++c)
+            {
+                uint v = 0;
+                for (int i = 7; i >= 0; --i)
+                {
+                    if ((c & (1 << i)) != 0)
+                    {
+                        v ^= (E1 >> (7 - i));
+                    }
+                }
+                lookup[c] = v;
+            }
+
+            return lookup;
+        }
+
+        private static readonly uint[] LOOKUP = GenerateLookup();
+
         internal static byte[] OneAsBytes()
         {
             byte[] tmp = new byte[16];
@@ -21,6 +46,16 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
             return tmp;
         }
 
+        internal static byte[] AsBytes(uint[] x)
+        {
+            return Pack.UInt32_To_BE(x);
+        }
+
+        internal static void AsBytes(uint[] x, byte[] z)
+        {
+            Pack.UInt32_To_BE(x, z, 0);
+        }
+
         internal static uint[] AsUints(byte[] bs)
         {
             uint[] output = new uint[4];
@@ -33,56 +68,55 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
             Pack.BE_To_UInt32(bs, 0, output);
         }
 
-        internal static void Multiply(byte[] block, byte[] val)
+        internal static void Multiply(byte[] x, byte[] y)
         {
-            byte[] tmp = Arrays.Clone(block);
-            byte[] c = new byte[16];
+            uint[] t1 = GcmUtilities.AsUints(x);
+            uint[] t2 = GcmUtilities.AsUints(y);
+            GcmUtilities.Multiply(t1, t2);
+            GcmUtilities.AsBytes(t1, x);
+        }
 
-            for (int i = 0; i < 16; ++i)
+        internal static void Multiply(uint[] x, uint[] y)
+        {
+            uint r00 = x[0], r01 = x[1], r02 = x[2], r03 = x[3];
+            uint r10 = 0, r11 = 0, r12 = 0, r13 = 0;
+        
+            for (int i = 0; i < 4; ++i)
             {
-                byte bits = val[i];
-                for (int j = 7; j >= 0; --j)
+                int bits = (int)y[i];
+                for (int j = 0; j < 32; ++j)
                 {
-                    if ((bits & (1 << j)) != 0)
-                    {
-                        Xor(c, tmp);
-                    }
-
-                    bool lsb = (tmp[15] & 1) != 0;
-                    ShiftRight(tmp);
-                    if (lsb)
-                    {
-                        // R = new byte[]{ 0xe1, ... };
-                        //GCMUtilities.Xor(tmp, R);
-                        tmp[0] ^= (byte)0xe1;
-                    }
+                    uint m1 = (uint)(bits >> 31); bits <<= 1;
+                    r10 ^= (r00 & m1);
+                    r11 ^= (r01 & m1);
+                    r12 ^= (r02 & m1);
+                    r13 ^= (r03 & m1);
+
+                    uint m2 = (uint)((int)(r03 << 31) >> 8);
+                    r03 = (r03 >> 1) | (r02 << 63);
+                    r02 = (r02 >> 1) | (r01 << 63);
+                    r01 = (r01 >> 1) | (r00 << 63);
+                    r00 = (r00 >> 1) ^ (m2 & E1);
                 }
             }
 
-            Array.Copy(c, 0, block, 0, 16);
+            x[0] = r10;
+            x[1] = r11;
+            x[2] = r12;
+            x[3] = r13;
         }
 
         // P is the value with only bit i=1 set
         internal static void MultiplyP(uint[] x)
         {
-            bool lsb = (x[3] & 1) != 0;
-            ShiftRight(x);
-            if (lsb)
-            {
-                // R = new uint[]{ 0xe1000000, 0, 0, 0 };
-                //Xor(v, R);
-                x[0] ^= 0xe1000000;
-            }
+            uint m = (uint)((int)ShiftRight(x) >> 8);
+            x[0] ^= (m & E1);
         }
 
-        internal static void MultiplyP(uint[] x, uint[] output)
+        internal static void MultiplyP(uint[] x, uint[] z)
         {
-            bool lsb = (x[3] & 1) != 0;
-            ShiftRight(x, output);
-            if (lsb)
-            {
-                output[0] ^= 0xe1000000;
-            }
+            uint m = (uint)((int)ShiftRight(x, z) >> 8);
+            z[0] ^= (m & E1);
         }
 
         internal static void MultiplyP8(uint[] x)
@@ -92,146 +126,128 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
 //				MultiplyP(x);
 //			}
 
-            uint lsw = x[3];
-            ShiftRightN(x, 8);
-            for (int i = 7; i >= 0; --i)
-            {
-                if ((lsw & (1 << i)) != 0)
-                {
-                    x[0] ^= (0xe1000000 >> (7 - i));
-                }
-            }
-        }
-
-        internal static void MultiplyP8(uint[] x, uint[] output)
-        {
-            uint lsw = x[3];
-            ShiftRightN(x, 8, output);
-            for (int i = 7; i >= 0; --i)
-            {
-                if ((lsw & (1 << i)) != 0)
-                {
-                    output[0] ^= (0xe1000000 >> (7 - i));
-                }
-            }
+            uint c = ShiftRightN(x, 8);
+            x[0] ^= LOOKUP[c >> 24];
         }
 
-        internal static void ShiftRight(byte[] block)
+        internal static void MultiplyP8(uint[] x, uint[] y)
         {
-            int i = 0;
-            byte bit = 0;
-            for (; ; )
-            {
-                byte b = block[i];
-                block[i] = (byte)((b >> 1) | bit);
-                if (++i == 16) break;
-                bit = (byte)(b << 7);
-            }
+            uint c = ShiftRightN(x, 8, y);
+            y[0] ^= LOOKUP[c >> 24];
         }
 
-        static void ShiftRight(byte[] block, byte[] output)
+        internal static uint ShiftRight(uint[] x)
         {
-            int i = 0;
-            byte bit = 0;
-            for (;;)
-            {
-                byte b = block[i];
-                output[i] = (byte)((b >> 1) | bit);
-                if (++i == 16) break;
-                bit = (byte)(b << 7);
-            }
+            uint b = x[0];
+            x[0] = b >> 1;
+            uint c = b << 31;
+            b = x[1];
+            x[1] = (b >> 1) | c;
+            c = b << 31;
+            b = x[2];
+            x[2] = (b >> 1) | c;
+            c = b << 31;
+            b = x[3];
+            x[3] = (b >> 1) | c;
+            return b << 31;
         }
 
-        internal static void ShiftRight(uint[] block)
+        internal static uint ShiftRight(uint[] x, uint[] z)
         {
-            int i = 0;
-            uint bit = 0;
-            for (; ; )
-            {
-                uint b = block[i];
-                block[i] = (b >> 1) | bit;
-                if (++i == 4) break;
-                bit = b << 31;
-            }
+            uint b = x[0];
+            z[0] = b >> 1;
+            uint c = b << 31;
+            b = x[1];
+            z[1] = (b >> 1) | c;
+            c = b << 31;
+            b = x[2];
+            z[2] = (b >> 1) | c;
+            c = b << 31;
+            b = x[3];
+            z[3] = (b >> 1) | c;
+            return b << 31;
         }
 
-        internal static void ShiftRight(uint[] block, uint[] output)
+        internal static uint ShiftRightN(uint[] x, int n)
         {
-            int i = 0;
-            uint bit = 0;
-            for (; ; )
-            {
-                uint b = block[i];
-                output[i] = (b >> 1) | bit;
-                if (++i == 4) break;
-                bit = b << 31;
-            }
+            uint b = x[0]; int nInv = 32 - n;
+            x[0] = b >> n;
+            uint c = b << nInv;
+            b = x[1];
+            x[1] = (b >> n) | c;
+            c = b << nInv;
+            b = x[2];
+            x[2] = (b >> n) | c;
+            c = b << nInv;
+            b = x[3];
+            x[3] = (b >> n) | c;
+            return b << nInv;
         }
 
-        internal static void ShiftRightN(uint[] block, int n)
+        internal static uint ShiftRightN(uint[] x, int n, uint[] z)
         {
-            int i = 0;
-            uint bit = 0;
-            for (; ; )
-            {
-                uint b = block[i];
-                block[i] = (b >> n) | bit;
-                if (++i == 4) break;
-                bit = b << (32 - n);
-            }
+            uint b = x[0]; int nInv = 32 - n;
+            z[0] = b >> n;
+            uint c = b << nInv;
+            b = x[1];
+            z[1] = (b >> n) | c;
+            c = b << nInv;
+            b = x[2];
+            z[2] = (b >> n) | c;
+            c = b << nInv;
+            b = x[3];
+            z[3] = (b >> n) | c;
+            return b << nInv;
         }
 
-        internal static void ShiftRightN(uint[] block, int n, uint[] output)
+        internal static void Xor(byte[] x, byte[] y)
         {
             int i = 0;
-            uint bit = 0;
-            for (; ; )
-            {
-                uint b = block[i];
-                output[i] = (b >> n) | bit;
-                if (++i == 4) break;
-                bit = b << (32 - n);
-            }
-        }
-
-        internal static void Xor(byte[] block, byte[] val)
-        {
-            for (int i = 15; i >= 0; --i)
+            do
             {
-                block[i] ^= val[i];
+                x[i] ^= y[i]; ++i;
+                x[i] ^= y[i]; ++i;
+                x[i] ^= y[i]; ++i;
+                x[i] ^= y[i]; ++i;
             }
+            while (i < 16);
         }
 
-        internal static void Xor(byte[] block, byte[] val, int off, int len)
+        internal static void Xor(byte[] x, byte[] y, int yOff, int yLen)
         {
-            while (--len >= 0)
+            while (--yLen >= 0)
             {
-                block[len] ^= val[off + len];
+                x[yLen] ^= y[yOff + yLen];
             }
         }
 
-        internal static void Xor(byte[] block, byte[] val, byte[] output)
+        internal static void Xor(byte[] x, byte[] y, byte[] z)
         {
-            for (int i = 15; i >= 0; --i)
+            int i = 0;
+            do
             {
-                output[i] = (byte)(block[i] ^ val[i]);
+                z[i] = (byte)(x[i] ^ y[i]); ++i;
+                z[i] = (byte)(x[i] ^ y[i]); ++i;
+                z[i] = (byte)(x[i] ^ y[i]); ++i;
+                z[i] = (byte)(x[i] ^ y[i]); ++i;
             }
+            while (i < 16);
         }
 
-        internal static void Xor(uint[] block, uint[] val)
+        internal static void Xor(uint[] x, uint[] y)
         {
-            for (int i = 3; i >= 0; --i)
-            {
-                block[i] ^= val[i];
-            }
+            x[0] ^= y[0];
+            x[1] ^= y[1];
+            x[2] ^= y[2];
+            x[3] ^= y[3];
         }
 
-        internal static void Xor(uint[] block, uint[] val, uint[] output)
+        internal static void Xor(uint[] x, uint[] y, uint[] z)
         {
-            for (int i = 3; i >= 0; --i)
-            {
-                output[i] = block[i] ^ val[i];
-            }
+            z[0] = x[0] ^ y[0];
+            z[1] = x[1] ^ y[1];
+            z[2] = x[2] ^ y[2];
+            z[3] = x[3] ^ y[3];
         }
     }
 }
diff --git a/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs b/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs
index 44933bba7..e649d6770 100644
--- a/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs
+++ b/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs
@@ -5,48 +5,47 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Modes.Gcm
 {
-	public class Tables1kGcmExponentiator
-		: IGcmExponentiator
-	{
+    public class Tables1kGcmExponentiator
+        : IGcmExponentiator
+    {
         // A lookup table of the power-of-two powers of 'x'
         // - lookupPowX2[i] = x^(2^i)
         private IList lookupPowX2;
 
         public void Init(byte[] x)
-		{
-            if (lookupPowX2 != null && Arrays.AreEqual(x, (byte[])lookupPowX2[0]))
-            {
+        {
+            uint[] y = GcmUtilities.AsUints(x);
+            if (lookupPowX2 != null && Arrays.AreEqual(y, (uint[])lookupPowX2[0]))
                 return;
-            }
 
             lookupPowX2 = Platform.CreateArrayList(8);
-            lookupPowX2.Add(Arrays.Clone(x));
-		}
+            lookupPowX2.Add(y);
+        }
 
-		public void ExponentiateX(long pow, byte[] output)
-		{
-			byte[] y = GcmUtilities.OneAsBytes();
+        public void ExponentiateX(long pow, byte[] output)
+        {
+            uint[] y = GcmUtilities.OneAsUints();
             int bit = 0;
             while (pow > 0)
             {
                 if ((pow & 1L) != 0)
                 {
                     EnsureAvailable(bit);
-                    GcmUtilities.Multiply(y, (byte[])lookupPowX2[bit]);
+                    GcmUtilities.Multiply(y, (uint[])lookupPowX2[bit]);
                 }
                 ++bit;
                 pow >>= 1;
             }
 
-			Array.Copy(y, 0, output, 0, 16);
-		}
+            GcmUtilities.AsBytes(y, output);
+        }
 
         private void EnsureAvailable(int bit)
         {
             int count = lookupPowX2.Count;
             if (count <= bit)
             {
-                byte[] tmp = (byte[])lookupPowX2[count - 1];
+                uint[] tmp = (uint[])lookupPowX2[count - 1];
                 do
                 {
                     tmp = Arrays.Clone(tmp);
diff --git a/crypto/src/crypto/paddings/Pkcs7Padding.cs b/crypto/src/crypto/paddings/Pkcs7Padding.cs
index f3166fd96..11585647a 100644
--- a/crypto/src/crypto/paddings/Pkcs7Padding.cs
+++ b/crypto/src/crypto/paddings/Pkcs7Padding.cs
@@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
     * A padder that adds Pkcs7/Pkcs5 padding to a block.
     */
     public class Pkcs7Padding
-		: IBlockCipherPadding
+        : IBlockCipherPadding
     {
         /**
         * Initialise the padder.
@@ -17,7 +17,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
         * @param random - a SecureRandom if available.
         */
         public void Init(
-			SecureRandom random)
+            SecureRandom random)
         {
             // nothing to do.
         }
@@ -32,7 +32,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
             get { return "PKCS7"; }
         }
 
-		/**
+        /**
         * add the pad bytes to the passed in block, returning the
         * number of bytes added.
         */
@@ -55,21 +55,18 @@ namespace Org.BouncyCastle.Crypto.Paddings
         * return the number of pad bytes present in the block.
         */
         public int PadCount(
-			byte[] input)
+            byte[] input)
         {
-            int count = (int) input[input.Length - 1];
+            byte countAsByte = input[input.Length - 1];
+            int count = countAsByte;
 
-			if (count < 1 || count > input.Length)
-            {
+            if (count < 1 || count > input.Length)
                 throw new InvalidCipherTextException("pad block corrupted");
-            }
 
-			for (int i = 1; i <= count; i++)
+            for (int i = 2; i <= count; i++)
             {
-                if (input[input.Length - i] != count)
-                {
+                if (input[input.Length - i] != countAsByte)
                     throw new InvalidCipherTextException("pad block corrupted");
-                }
             }
 
             return count;
diff --git a/crypto/src/crypto/tls/AbstractTlsClient.cs b/crypto/src/crypto/tls/AbstractTlsClient.cs
index 9484afa7d..771bc004b 100644
--- a/crypto/src/crypto/tls/AbstractTlsClient.cs
+++ b/crypto/src/crypto/tls/AbstractTlsClient.cs
@@ -30,6 +30,32 @@ namespace Org.BouncyCastle.Crypto.Tls
             this.mCipherFactory = cipherFactory;
         }
 
+        protected virtual bool AllowUnexpectedServerExtension(int extensionType, byte[] extensionData)
+        {
+            switch (extensionType)
+            {
+            case ExtensionType.elliptic_curves:
+                /*
+                 * Exception added based on field reports that some servers do send this, although the
+                 * Supported Elliptic Curves Extension is clearly intended to be client-only. If
+                 * present, we still require that it is a valid EllipticCurveList.
+                 */
+                TlsEccUtilities.ReadSupportedEllipticCurvesExtension(extensionData);
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        protected virtual void CheckForUnexpectedServerExtension(IDictionary serverExtensions, int extensionType)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(serverExtensions, extensionType);
+            if (extensionData != null && !AllowUnexpectedServerExtension(extensionType, extensionData))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
         public virtual void Init(TlsClientContext context)
         {
             this.mContext = context;
@@ -67,6 +93,16 @@ namespace Org.BouncyCastle.Crypto.Tls
             get { return ProtocolVersion.TLSv12; }
         }
 
+        public virtual bool IsFallback
+        {
+            /*
+             * draft-ietf-tls-downgrade-scsv-00 4. [..] is meant for use by clients that repeat a
+             * connection attempt with a downgraded protocol in order to avoid interoperability problems
+             * with legacy servers.
+             */
+            get { return false; }
+        }
+
         public virtual IDictionary GetClientExtensions()
         {
             IDictionary clientExtensions = null;
@@ -177,16 +213,18 @@ namespace Org.BouncyCastle.Crypto.Tls
                 /*
                  * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension.
                  */
-                if (serverExtensions.Contains(ExtensionType.signature_algorithms))
-                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.signature_algorithms);
 
-                int[] namedCurves = TlsEccUtilities.GetSupportedEllipticCurvesExtension(serverExtensions);
-                if (namedCurves != null)
-                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.elliptic_curves);
 
-                this.mServerECPointFormats = TlsEccUtilities.GetSupportedPointFormatsExtension(serverExtensions);
-                if (this.mServerECPointFormats != null && !TlsEccUtilities.IsEccCipherSuite(this.mSelectedCipherSuite))
-                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                if (TlsEccUtilities.IsEccCipherSuite(this.mSelectedCipherSuite))
+                {
+                    this.mServerECPointFormats = TlsEccUtilities.GetSupportedPointFormatsExtension(serverExtensions);
+                }
+                else
+                {
+                    CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.ec_point_formats);
+                }
             }
         }
 
diff --git a/crypto/src/crypto/tls/AbstractTlsServer.cs b/crypto/src/crypto/tls/AbstractTlsServer.cs
index c2c6fd57c..b0a5f0d52 100644
--- a/crypto/src/crypto/tls/AbstractTlsServer.cs
+++ b/crypto/src/crypto/tls/AbstractTlsServer.cs
@@ -110,6 +110,18 @@ namespace Org.BouncyCastle.Crypto.Tls
             this.mClientVersion = clientVersion;
         }
 
+        public virtual void NotifyFallback(bool isFallback)
+        {
+            /*
+             * draft-ietf-tls-downgrade-scsv-00 3. If TLS_FALLBACK_SCSV appears in
+             * ClientHello.cipher_suites and the highest protocol version supported by the server is
+             * higher than the version indicated in ClientHello.client_version, the server MUST respond
+             * with an inappropriate_fallback alert.
+             */
+            if (isFallback && MaximumVersion.IsLaterVersionOf(mClientVersion))
+                throw new TlsFatalAlert(AlertDescription.inappropriate_fallback);
+        }
+
         public virtual void NotifyOfferedCipherSuites(int[] offeredCipherSuites)
         {
             this.mOfferedCipherSuites = offeredCipherSuites;
diff --git a/crypto/src/crypto/tls/AlertDescription.cs b/crypto/src/crypto/tls/AlertDescription.cs
index 5b6e88bf7..49de60cea 100644
--- a/crypto/src/crypto/tls/AlertDescription.cs
+++ b/crypto/src/crypto/tls/AlertDescription.cs
@@ -214,6 +214,17 @@ namespace Org.BouncyCastle.Crypto.Tls
          */
         public const byte unknown_psk_identity = 115;
 
+        /*
+         * draft-ietf-tls-downgrade-scsv-00
+         */
+
+        /**
+         * If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest protocol version
+         * supported by the server is higher than the version indicated in ClientHello.client_version,
+         * the server MUST respond with an inappropriate_fallback alert.
+         */
+        public const byte inappropriate_fallback = 86;
+
         public static string GetName(byte alertDescription)
         {
             switch (alertDescription)
@@ -278,6 +289,8 @@ namespace Org.BouncyCastle.Crypto.Tls
                 return "bad_certificate_hash_value";
             case unknown_psk_identity:
                 return "unknown_psk_identity";
+            case inappropriate_fallback:
+                return "inappropriate_fallback";
             default:
                 return "UNKNOWN";
             }
diff --git a/crypto/src/crypto/tls/BasicTlsPskIdentity.cs b/crypto/src/crypto/tls/BasicTlsPskIdentity.cs
new file mode 100644
index 000000000..db5954422
--- /dev/null
+++ b/crypto/src/crypto/tls/BasicTlsPskIdentity.cs
@@ -0,0 +1,43 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class BasicTlsPskIdentity
+        : TlsPskIdentity
+    {
+        protected byte[] mIdentity;
+        protected byte[] mPsk;
+
+        public BasicTlsPskIdentity(byte[] identity, byte[] psk)
+        {
+            this.mIdentity = Arrays.Clone(identity);
+            this.mPsk = Arrays.Clone(psk);
+        }
+
+        public BasicTlsPskIdentity(string identity, byte[] psk)
+        {
+            this.mIdentity = Strings.ToUtf8ByteArray(identity);
+            this.mPsk = Arrays.Clone(psk);
+        }
+
+        public virtual void SkipIdentityHint()
+        {
+        }
+
+        public virtual void NotifyIdentityHint(byte[] psk_identity_hint)
+        {
+        }
+
+        public virtual byte[] GetPskIdentity()
+        {
+            return mIdentity;
+        }
+
+        public virtual byte[] GetPsk()
+        {
+            return mPsk;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/CipherSuite.cs b/crypto/src/crypto/tls/CipherSuite.cs
index f034ab802..5ec36aee8 100644
--- a/crypto/src/crypto/tls/CipherSuite.cs
+++ b/crypto/src/crypto/tls/CipherSuite.cs
@@ -323,6 +323,14 @@ namespace Org.BouncyCastle.Crypto.Tls
         public const int TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB;
 
         /*
+         * RFC 7251
+         */
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xC0AC;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xC0AD;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xC0AE;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xC0AF;
+
+        /*
          * draft-agl-tls-chacha20poly1305-04
          */
         public const int TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC13;
@@ -348,5 +356,22 @@ namespace Org.BouncyCastle.Crypto.Tls
         public const int TLS_DHE_PSK_WITH_SALSA20_SHA1 = 0xE41D;
         public const int TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xE41E;
         public const int TLS_DHE_RSA_WITH_SALSA20_SHA1 = 0xE41F;
+
+        /*
+         * draft-ietf-tls-downgrade-scsv-00
+         */
+        public const int TLS_FALLBACK_SCSV = 0x5600;
+
+        public static bool IsScsv(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            case TLS_EMPTY_RENEGOTIATION_INFO_SCSV:
+            case TLS_FALLBACK_SCSV:
+                return true;
+            default:
+                return false;
+            }
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/DatagramTransport.cs b/crypto/src/crypto/tls/DatagramTransport.cs
new file mode 100644
index 000000000..524a8b181
--- /dev/null
+++ b/crypto/src/crypto/tls/DatagramTransport.cs
@@ -0,0 +1,23 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public interface DatagramTransport
+    {
+        /// <exception cref="IOException"/>
+        int GetReceiveLimit();
+
+        /// <exception cref="IOException"/>
+        int GetSendLimit();
+
+        /// <exception cref="IOException"/>
+        int Receive(byte[] buf, int off, int len, int waitMillis);
+
+        /// <exception cref="IOException"/>
+        void Send(byte[] buf, int off, int len);
+
+        /// <exception cref="IOException"/>
+        void Close();
+    }
+}
diff --git a/crypto/src/crypto/tls/DefaultTlsClient.cs b/crypto/src/crypto/tls/DefaultTlsClient.cs
index a2a04a33c..ec98413b7 100644
--- a/crypto/src/crypto/tls/DefaultTlsClient.cs
+++ b/crypto/src/crypto/tls/DefaultTlsClient.cs
@@ -145,9 +145,13 @@ namespace Org.BouncyCastle.Crypto.Tls
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
@@ -258,10 +262,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
             case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null);
 
@@ -301,10 +307,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
             case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null);
 
diff --git a/crypto/src/crypto/tls/DefaultTlsServer.cs b/crypto/src/crypto/tls/DefaultTlsServer.cs
index 017ed0d85..75f6d8d88 100644
--- a/crypto/src/crypto/tls/DefaultTlsServer.cs
+++ b/crypto/src/crypto/tls/DefaultTlsServer.cs
@@ -236,9 +236,13 @@ namespace Org.BouncyCastle.Crypto.Tls
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
@@ -347,10 +351,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
             case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null);
 
@@ -390,10 +396,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
             case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null);
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
                 return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null);
 
diff --git a/crypto/src/crypto/tls/DtlsClientProtocol.cs b/crypto/src/crypto/tls/DtlsClientProtocol.cs
new file mode 100644
index 000000000..2aa4df692
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsClientProtocol.cs
@@ -0,0 +1,844 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class DtlsClientProtocol
+        :   DtlsProtocol
+    {
+        public DtlsClientProtocol(SecureRandom secureRandom)
+            :   base(secureRandom)
+        {
+        }
+
+        public virtual DtlsTransport Connect(TlsClient client, DatagramTransport transport)
+        {
+            if (client == null)
+                throw new ArgumentNullException("client");
+            if (transport == null)
+                throw new ArgumentNullException("transport");
+
+            SecurityParameters securityParameters = new SecurityParameters();
+            securityParameters.entity = ConnectionEnd.client;
+
+            ClientHandshakeState state = new ClientHandshakeState();
+            state.client = client;
+            state.clientContext = new TlsClientContextImpl(mSecureRandom, securityParameters);
+
+            securityParameters.clientRandom = TlsProtocol.CreateRandomBlock(client.ShouldUseGmtUnixTime(),
+                state.clientContext.NonceRandomGenerator);
+
+            client.Init(state.clientContext);
+
+            DtlsRecordLayer recordLayer = new DtlsRecordLayer(transport, state.clientContext, client, ContentType.handshake);
+
+            TlsSession sessionToResume = state.client.GetSessionToResume();
+            if (sessionToResume != null)
+            {
+                SessionParameters sessionParameters = sessionToResume.ExportSessionParameters();
+                if (sessionParameters != null)
+                {
+                    state.tlsSession = sessionToResume;
+                    state.sessionParameters = sessionParameters;
+                }
+            }
+
+            try
+            {
+                return ClientHandshake(state, recordLayer);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                recordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (IOException e)
+            {
+                recordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        internal virtual DtlsTransport ClientHandshake(ClientHandshakeState state, DtlsRecordLayer recordLayer)
+        {
+            SecurityParameters securityParameters = state.clientContext.SecurityParameters;
+            DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.clientContext, recordLayer);
+
+            byte[] clientHelloBody = GenerateClientHello(state, state.client);
+            handshake.SendMessage(HandshakeType.client_hello, clientHelloBody);
+
+            DtlsReliableHandshake.Message serverMessage = handshake.ReceiveMessage();
+
+            while (serverMessage.Type == HandshakeType.hello_verify_request)
+            {
+                ProtocolVersion recordLayerVersion = recordLayer.ResetDiscoveredPeerVersion();
+                ProtocolVersion client_version = state.clientContext.ClientVersion;
+
+                /*
+                 * RFC 6347 4.2.1 DTLS 1.2 server implementations SHOULD use DTLS version 1.0 regardless of
+                 * the version of TLS that is expected to be negotiated. DTLS 1.2 and 1.0 clients MUST use
+                 * the version solely to indicate packet formatting (which is the same in both DTLS 1.2 and
+                 * 1.0) and not as part of version negotiation.
+                 */
+                if (!recordLayerVersion.IsEqualOrEarlierVersionOf(client_version))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                byte[] cookie = ProcessHelloVerifyRequest(state, serverMessage.Body);
+                byte[] patched = PatchClientHelloWithCookie(clientHelloBody, cookie);
+
+                handshake.ResetHandshakeMessagesDigest();
+                handshake.SendMessage(HandshakeType.client_hello, patched);
+
+                serverMessage = handshake.ReceiveMessage();
+            }
+
+            if (serverMessage.Type == HandshakeType.server_hello)
+            {
+                ReportServerVersion(state, recordLayer.DiscoveredPeerVersion);
+
+                ProcessServerHello(state, serverMessage.Body);
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            if (state.maxFragmentLength >= 0)
+            {
+                int plainTextLimit = 1 << (8 + state.maxFragmentLength);
+                recordLayer.SetPlaintextLimit(plainTextLimit);
+            }
+
+            securityParameters.cipherSuite = state.selectedCipherSuite;
+            securityParameters.compressionAlgorithm = (byte)state.selectedCompressionMethod;
+            securityParameters.prfAlgorithm = TlsProtocol.GetPrfAlgorithm(state.clientContext, state.selectedCipherSuite);
+
+            /*
+             * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has
+             * a verify_data_length equal to 12. This includes all existing cipher suites.
+             */
+            securityParameters.verifyDataLength = 12;
+
+            handshake.NotifyHelloComplete();
+
+            bool resumedSession = state.selectedSessionID.Length > 0 && state.tlsSession != null
+                && Arrays.AreEqual(state.selectedSessionID, state.tlsSession.SessionID);
+
+            if (resumedSession)
+            {
+                if (securityParameters.CipherSuite != state.sessionParameters.CipherSuite
+                    || securityParameters.CompressionAlgorithm != state.sessionParameters.CompressionAlgorithm)
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                IDictionary sessionServerExtensions = state.sessionParameters.ReadServerExtensions();
+
+                securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(sessionServerExtensions);
+
+                securityParameters.masterSecret = Arrays.Clone(state.sessionParameters.MasterSecret);
+                recordLayer.InitPendingEpoch(state.client.GetCipher());
+
+                // NOTE: Calculated exclusive of the actual Finished message from the server
+                byte[] resExpectedServerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.server_finished,
+                    TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null));
+                ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), resExpectedServerVerifyData);
+
+                // NOTE: Calculated exclusive of the Finished message itself
+                byte[] resClientVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.client_finished,
+                    TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null));
+                handshake.SendMessage(HandshakeType.finished, resClientVerifyData);
+
+                handshake.Finish();
+
+                state.clientContext.SetResumableSession(state.tlsSession);
+
+                state.client.NotifyHandshakeComplete();
+
+                return new DtlsTransport(recordLayer);
+            }
+
+            InvalidateSession(state);
+
+            if (state.selectedSessionID.Length > 0)
+            {
+                state.tlsSession = new TlsSessionImpl(state.selectedSessionID, null);
+            }
+
+            serverMessage = handshake.ReceiveMessage();
+
+            if (serverMessage.Type == HandshakeType.supplemental_data)
+            {
+                ProcessServerSupplementalData(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                state.client.ProcessServerSupplementalData(null);
+            }
+
+            state.keyExchange = state.client.GetKeyExchange();
+            state.keyExchange.Init(state.clientContext);
+
+            Certificate serverCertificate = null;
+
+            if (serverMessage.Type == HandshakeType.certificate)
+            {
+                serverCertificate = ProcessServerCertificate(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, Certificate is optional
+                state.keyExchange.SkipServerCredentials();
+            }
+
+            // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+            if (serverCertificate == null || serverCertificate.IsEmpty)
+            {
+                state.allowCertificateStatus = false;
+            }
+
+            if (serverMessage.Type == HandshakeType.certificate_status)
+            {
+                ProcessCertificateStatus(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, CertificateStatus is optional
+            }
+
+            if (serverMessage.Type == HandshakeType.server_key_exchange)
+            {
+                ProcessServerKeyExchange(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, ServerKeyExchange is optional
+                state.keyExchange.SkipServerKeyExchange();
+            }
+
+            if (serverMessage.Type == HandshakeType.certificate_request)
+            {
+                ProcessCertificateRequest(state, serverMessage.Body);
+
+                /*
+                 * TODO Give the client a chance to immediately select the CertificateVerify hash
+                 * algorithm here to avoid tracking the other hash algorithms unnecessarily?
+                 */
+                TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash,
+                    state.certificateRequest.SupportedSignatureAlgorithms);
+
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, CertificateRequest is optional
+            }
+
+            if (serverMessage.Type == HandshakeType.server_hello_done)
+            {
+                if (serverMessage.Body.Length != 0)
+                {
+                    throw new TlsFatalAlert(AlertDescription.decode_error);
+                }
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            handshake.HandshakeHash.SealHashAlgorithms();
+
+            IList clientSupplementalData = state.client.GetClientSupplementalData();
+            if (clientSupplementalData != null)
+            {
+                byte[] supplementalDataBody = GenerateSupplementalData(clientSupplementalData);
+                handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+            }
+
+            if (state.certificateRequest != null)
+            {
+                state.clientCredentials = state.authentication.GetClientCredentials(state.certificateRequest);
+
+                /*
+                 * RFC 5246 If no suitable certificate is available, the client MUST send a certificate
+                 * message containing no certificates.
+                 * 
+                 * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                 */
+                Certificate clientCertificate = null;
+                if (state.clientCredentials != null)
+                {
+                    clientCertificate = state.clientCredentials.Certificate;
+                }
+                if (clientCertificate == null)
+                {
+                    clientCertificate = Certificate.EmptyChain;
+                }
+
+                byte[] certificateBody = GenerateCertificate(clientCertificate);
+                handshake.SendMessage(HandshakeType.certificate, certificateBody);
+            }
+
+            if (state.clientCredentials != null)
+            {
+                state.keyExchange.ProcessClientCredentials(state.clientCredentials);
+            }
+            else
+            {
+                state.keyExchange.SkipClientCredentials();
+            }
+
+            byte[] clientKeyExchangeBody = GenerateClientKeyExchange(state);
+            handshake.SendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody);
+
+            TlsHandshakeHash prepareFinishHash = handshake.PrepareToFinish();
+            securityParameters.sessionHash = TlsProtocol.GetCurrentPrfHash(state.clientContext, prepareFinishHash, null);
+
+            TlsProtocol.EstablishMasterSecret(state.clientContext, state.keyExchange);
+            recordLayer.InitPendingEpoch(state.client.GetCipher());
+
+            if (state.clientCredentials != null && state.clientCredentials is TlsSignerCredentials)
+            {
+                TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials;
+
+                /*
+                 * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+                 */
+                SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+                byte[] hash;
+
+                if (TlsUtilities.IsTlsV12(state.clientContext))
+                {
+                    signatureAndHashAlgorithm = signerCredentials.SignatureAndHashAlgorithm;
+                    if (signatureAndHashAlgorithm == null)
+                    {
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                    }
+
+                    hash = prepareFinishHash.GetFinalHash(signatureAndHashAlgorithm.Hash);
+                }
+                else
+                {
+                    signatureAndHashAlgorithm = null;
+                    hash = securityParameters.SessionHash;
+                }
+
+                byte[] signature = signerCredentials.GenerateCertificateSignature(hash);
+                DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature);
+                byte[] certificateVerifyBody = GenerateCertificateVerify(state, certificateVerify);
+                handshake.SendMessage(HandshakeType.certificate_verify, certificateVerifyBody);
+            }
+
+            // NOTE: Calculated exclusive of the Finished message itself
+            byte[] clientVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.client_finished,
+                TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null));
+            handshake.SendMessage(HandshakeType.finished, clientVerifyData);
+
+            if (state.expectSessionTicket)
+            {
+                serverMessage = handshake.ReceiveMessage();
+                if (serverMessage.Type == HandshakeType.session_ticket)
+                {
+                    ProcessNewSessionTicket(state, serverMessage.Body);
+                }
+                else
+                {
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+            }
+
+            // NOTE: Calculated exclusive of the actual Finished message from the server
+            byte[] expectedServerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.server_finished,
+                TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null));
+            ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), expectedServerVerifyData);
+
+            handshake.Finish();
+
+            if (state.tlsSession != null)
+            {
+                state.sessionParameters = new SessionParameters.Builder()
+                    .SetCipherSuite(securityParameters.cipherSuite)
+                    .SetCompressionAlgorithm(securityParameters.compressionAlgorithm)
+                    .SetMasterSecret(securityParameters.masterSecret)
+                    .SetPeerCertificate(serverCertificate)
+                    .SetPskIdentity(securityParameters.pskIdentity)
+                    .Build();
+
+                state.tlsSession = TlsUtilities.ImportSession(state.tlsSession.SessionID, state.sessionParameters);
+
+                state.clientContext.SetResumableSession(state.tlsSession);
+            }
+
+            state.client.NotifyHandshakeComplete();
+
+            return new DtlsTransport(recordLayer);
+        }
+
+        protected virtual byte[] GenerateCertificateVerify(ClientHandshakeState state, DigitallySigned certificateVerify)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificateVerify.Encode(buf);
+            return buf.ToArray();
+        }
+
+        protected virtual byte[] GenerateClientHello(ClientHandshakeState state, TlsClient client)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            ProtocolVersion client_version = client.ClientVersion;
+            if (!client_version.IsDtls)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsClientContextImpl context = state.clientContext;
+
+            context.SetClientVersion(client_version);
+            TlsUtilities.WriteVersion(client_version, buf);
+
+            SecurityParameters securityParameters = context.SecurityParameters;
+            buf.Write(securityParameters.ClientRandom, 0, securityParameters.ClientRandom.Length);
+
+            // Session ID
+            byte[] session_id = TlsUtilities.EmptyBytes;
+            if (state.tlsSession != null)
+            {
+                session_id = state.tlsSession.SessionID;
+                if (session_id == null || session_id.Length > 32)
+                {
+                    session_id = TlsUtilities.EmptyBytes;
+                }
+            }
+            TlsUtilities.WriteOpaque8(session_id, buf);
+
+            // Cookie
+            TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, buf);
+
+            bool fallback = client.IsFallback;
+
+            /*
+             * Cipher suites
+             */
+            state.offeredCipherSuites = client.GetCipherSuites();
+
+            // Integer -> byte[]
+            state.clientExtensions = client.GetClientExtensions();
+
+            securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(state.clientExtensions);
+
+            // Cipher Suites (and SCSV)
+            {
+                /*
+                 * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+                 * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+                 * ClientHello. Including both is NOT RECOMMENDED.
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions, ExtensionType.renegotiation_info);
+                bool noRenegExt = (null == renegExtData);
+
+                bool noRenegSCSV = !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+
+                if (noRenegExt && noRenegSCSV)
+                {
+                    // TODO Consider whether to default to a client extension instead
+                    state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                }
+
+                /*
+                 * draft-ietf-tls-downgrade-scsv-00 4. If a client sends a ClientHello.client_version
+                 * containing a lower value than the latest (highest-valued) version supported by the
+                 * client, it SHOULD include the TLS_FALLBACK_SCSV cipher suite value in
+                 * ClientHello.cipher_suites.
+                 */
+                if (fallback && !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV))
+                {
+                    state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV);
+                }
+
+                TlsUtilities.WriteUint16ArrayWithUint16Length(state.offeredCipherSuites, buf);
+            }
+
+            // TODO Add support for compression
+            // Compression methods
+            // state.offeredCompressionMethods = client.getCompressionMethods();
+            state.offeredCompressionMethods = new byte[]{ CompressionMethod.cls_null };
+
+            TlsUtilities.WriteUint8ArrayWithUint8Length(state.offeredCompressionMethods, buf);
+
+            // Extensions
+            if (state.clientExtensions != null)
+            {
+                TlsProtocol.WriteExtensions(buf, state.clientExtensions);
+            }
+
+            return buf.ToArray();
+        }
+
+        protected virtual byte[] GenerateClientKeyExchange(ClientHandshakeState state)
+        {
+            MemoryStream buf = new MemoryStream();
+            state.keyExchange.GenerateClientKeyExchange(buf);
+            return buf.ToArray();
+        }
+
+        protected virtual void InvalidateSession(ClientHandshakeState state)
+        {
+            if (state.sessionParameters != null)
+            {
+                state.sessionParameters.Clear();
+                state.sessionParameters = null;
+            }
+
+            if (state.tlsSession != null)
+            {
+                state.tlsSession.Invalidate();
+                state.tlsSession = null;
+            }
+        }
+
+        protected virtual void ProcessCertificateRequest(ClientHandshakeState state, byte[] body)
+        {
+            if (state.authentication == null)
+            {
+                /*
+                 * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to
+                 * request client identification.
+                 */
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+            }
+
+            MemoryStream buf = new MemoryStream(body, false);
+
+            state.certificateRequest = CertificateRequest.Parse(state.clientContext, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            state.keyExchange.ValidateCertificateRequest(state.certificateRequest);
+        }
+
+        protected virtual void ProcessCertificateStatus(ClientHandshakeState state, byte[] body)
+        {
+            if (!state.allowCertificateStatus)
+            {
+                /*
+                 * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the
+                 * server MUST have included an extension of type "status_request" with empty
+                 * "extension_data" in the extended server hello..
+                 */
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            MemoryStream buf = new MemoryStream(body, false);
+
+            state.certificateStatus = CertificateStatus.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            // TODO[RFC 3546] Figure out how to provide this to the client/authentication.
+        }
+
+        protected virtual byte[] ProcessHelloVerifyRequest(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            ProtocolVersion server_version = TlsUtilities.ReadVersion(buf);
+            byte[] cookie = TlsUtilities.ReadOpaque8(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            // TODO Seems this behaviour is not yet in line with OpenSSL for DTLS 1.2
+    //        reportServerVersion(state, server_version);
+            if (!server_version.IsEqualOrEarlierVersionOf(state.clientContext.ClientVersion))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            /*
+             * RFC 6347 This specification increases the cookie size limit to 255 bytes for greater
+             * future flexibility. The limit remains 32 for previous versions of DTLS.
+             */
+            if (!ProtocolVersion.DTLSv12.IsEqualOrEarlierVersionOf(server_version) && cookie.Length > 32)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return cookie;
+        }
+
+        protected virtual void ProcessNewSessionTicket(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            state.client.NotifyNewSessionTicket(newSessionTicket);
+        }
+
+        protected virtual Certificate ProcessServerCertificate(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            Certificate serverCertificate = Certificate.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            state.keyExchange.ProcessServerCertificate(serverCertificate);
+            state.authentication = state.client.GetAuthentication();
+            state.authentication.NotifyServerCertificate(serverCertificate);
+
+            return serverCertificate;
+        }
+
+        protected virtual void ProcessServerHello(ClientHandshakeState state, byte[] body)
+        {
+            SecurityParameters securityParameters = state.clientContext.SecurityParameters;
+
+            MemoryStream buf = new MemoryStream(body, false);
+
+            ProtocolVersion server_version = TlsUtilities.ReadVersion(buf);
+            ReportServerVersion(state, server_version);
+
+            securityParameters.serverRandom = TlsUtilities.ReadFully(32, buf);
+
+            state.selectedSessionID = TlsUtilities.ReadOpaque8(buf);
+            if (state.selectedSessionID.Length > 32)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            state.client.NotifySessionID(state.selectedSessionID);
+
+            state.selectedCipherSuite = TlsUtilities.ReadUint16(buf);
+            if (!Arrays.Contains(state.offeredCipherSuites, state.selectedCipherSuite)
+                || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+                || CipherSuite.IsScsv(state.selectedCipherSuite)
+                || !TlsUtilities.IsValidCipherSuiteForVersion(state.selectedCipherSuite, server_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            ValidateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.illegal_parameter);
+
+            state.client.NotifySelectedCipherSuite(state.selectedCipherSuite);
+
+            state.selectedCompressionMethod = TlsUtilities.ReadUint8(buf);
+            if (!Arrays.Contains(state.offeredCompressionMethods, (byte)state.selectedCompressionMethod))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            state.client.NotifySelectedCompressionMethod((byte)state.selectedCompressionMethod);
+
+            /*
+             * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server
+             * hello message when the client has requested extended functionality via the extended
+             * client hello message specified in Section 2.1. ... Note that the extended server hello
+             * message is only sent in response to an extended client hello message. This prevents the
+             * possibility that the extended server hello message could "break" existing TLS 1.0
+             * clients.
+             */
+
+            /*
+             * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+             * extensions appearing in the client hello, and send a server hello containing no
+             * extensions.
+             */
+
+            // Integer -> byte[]
+            IDictionary serverExtensions = TlsProtocol.ReadExtensions(buf);
+
+            /*
+             * draft-ietf-tls-session-hash-01 5.2. If a server receives the "extended_master_secret"
+             * extension, it MUST include the "extended_master_secret" extension in its ServerHello
+             * message.
+             */
+            bool serverSentExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(serverExtensions);
+            if (serverSentExtendedMasterSecret != securityParameters.extendedMasterSecret)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            /*
+             * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
+             * extended client hello message. However, see RFC 5746 exception below. We always include
+             * the SCSV, so an Extended Server Hello is always allowed.
+             */
+            if (serverExtensions != null)
+            {
+                foreach (int extType in serverExtensions.Keys)
+                {
+                    /*
+                     * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a
+                     * ClientHello containing only the SCSV is an explicit exception to the prohibition
+                     * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is
+                     * only allowed because the client is signaling its willingness to receive the
+                     * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                     */
+                    if (extType == ExtensionType.renegotiation_info)
+                        continue;
+
+                    /*
+                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
+                     * same extension type appeared in the corresponding ClientHello. If a client
+                     * receives an extension type in ServerHello that it did not request in the
+                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
+                     * fatal alert.
+                     */
+                    if (null == TlsUtilities.GetExtensionData(state.clientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+
+                    /*
+                     * draft-ietf-tls-session-hash-01 5.2. Implementation note: if the server decides to
+                     * proceed with resumption, the extension does not have any effect. Requiring the
+                     * extension to be included anyway makes the extension negotiation logic easier,
+                     * because it does not depend on whether resumption is accepted or not.
+                     */
+                    if (extType == ExtensionType.extended_master_secret)
+                        continue;
+
+                    /*
+                     * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore
+                     * extensions appearing in the client hello, and send a server hello containing no
+                     * extensions[.]
+                     */
+                    // TODO[sessions]
+    //                if (this.mResumedSession)
+    //                {
+    //                    // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats
+    //                    // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats
+    //                    // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats
+    ////                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+    //                }
+                }
+
+                /*
+                 * RFC 5746 3.4. Client Behavior: Initial Handshake
+                 */
+                {
+                    /*
+                     * When a ServerHello is received, the client MUST check if it includes the
+                     * "renegotiation_info" extension:
+                     */
+                    byte[] renegExtData = (byte[])serverExtensions[ExtensionType.renegotiation_info];
+                    if (renegExtData != null)
+                    {
+                        /*
+                         * If the extension is present, set the secure_renegotiation flag to TRUE. The
+                         * client MUST then verify that the length of the "renegotiated_connection"
+                         * field is zero, and if it is not, MUST abort the handshake (by sending a fatal
+                         * handshake_failure alert).
+                         */
+                        state.secure_renegotiation = true;
+
+                        if (!Arrays.ConstantTimeAreEqual(renegExtData, TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                            throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+
+                /*
+                 * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
+                 * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
+                 * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
+                 * client.
+                 */
+                bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(serverExtensions);
+                if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(state.selectedCipherSuite))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                securityParameters.encryptThenMac = serverSentEncryptThenMAC;
+
+                state.maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.clientExtensions, serverExtensions,
+                    AlertDescription.illegal_parameter);
+
+                securityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(serverExtensions);
+
+                state.allowCertificateStatus = TlsUtilities.HasExpectedEmptyExtensionData(serverExtensions,
+                    ExtensionType.status_request, AlertDescription.illegal_parameter);
+
+                state.expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(serverExtensions,
+                    ExtensionType.session_ticket, AlertDescription.illegal_parameter);
+            }
+
+            state.client.NotifySecureRenegotiation(state.secure_renegotiation);
+
+            if (state.clientExtensions != null)
+            {
+                state.client.ProcessServerExtensions(serverExtensions);
+            }
+        }
+
+        protected virtual void ProcessServerKeyExchange(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            state.keyExchange.ProcessServerKeyExchange(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+        }
+
+        protected virtual void ProcessServerSupplementalData(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            IList serverSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf);
+            state.client.ProcessServerSupplementalData(serverSupplementalData);
+        }
+
+        protected virtual void ReportServerVersion(ClientHandshakeState state, ProtocolVersion server_version)
+        {
+            TlsClientContextImpl clientContext = state.clientContext;
+            ProtocolVersion currentServerVersion = clientContext.ServerVersion;
+            if (null == currentServerVersion)
+            {
+                clientContext.SetServerVersion(server_version);
+                state.client.NotifyServerVersion(server_version);
+            }
+            else if (!currentServerVersion.Equals(server_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
+        protected static byte[] PatchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie)
+        {
+            int sessionIDPos = 34;
+            int sessionIDLength = TlsUtilities.ReadUint8(clientHelloBody, sessionIDPos);
+
+            int cookieLengthPos = sessionIDPos + 1 + sessionIDLength;
+            int cookiePos = cookieLengthPos + 1;
+
+            byte[] patched = new byte[clientHelloBody.Length + cookie.Length];
+            Array.Copy(clientHelloBody, 0, patched, 0, cookieLengthPos);
+            TlsUtilities.CheckUint8(cookie.Length);
+            TlsUtilities.WriteUint8((byte)cookie.Length, patched, cookieLengthPos);
+            Array.Copy(cookie, 0, patched, cookiePos, cookie.Length);
+            Array.Copy(clientHelloBody, cookiePos, patched, cookiePos + cookie.Length, clientHelloBody.Length - cookiePos);
+
+            return patched;
+        }
+
+        protected internal class ClientHandshakeState
+        {
+            internal TlsClient client = null;
+            internal TlsClientContextImpl clientContext = null;
+            internal TlsSession tlsSession = null;
+            internal SessionParameters sessionParameters = null;
+            internal SessionParameters.Builder sessionParametersBuilder = null;
+            internal int[] offeredCipherSuites = null;
+            internal byte[] offeredCompressionMethods = null;
+            internal IDictionary clientExtensions = null;
+            internal byte[] selectedSessionID = null;
+            internal int selectedCipherSuite = -1;
+            internal short selectedCompressionMethod = -1;
+            internal bool secure_renegotiation = false;
+            internal short maxFragmentLength = -1;
+            internal bool allowCertificateStatus = false;
+            internal bool expectSessionTicket = false;
+            internal TlsKeyExchange keyExchange = null;
+            internal TlsAuthentication authentication = null;
+            internal CertificateStatus certificateStatus = null;
+            internal CertificateRequest certificateRequest = null;
+            internal TlsCredentials clientCredentials = null;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsEpoch.cs b/crypto/src/crypto/tls/DtlsEpoch.cs
new file mode 100644
index 000000000..91fffa5e1
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsEpoch.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    internal class DtlsEpoch
+    {
+        private readonly DtlsReplayWindow mReplayWindow = new DtlsReplayWindow();
+
+        private readonly int mEpoch;
+        private readonly TlsCipher mCipher;
+
+        private long mSequenceNumber = 0;
+
+        internal DtlsEpoch(int epoch, TlsCipher cipher)
+        {
+            if (epoch < 0)
+                throw new ArgumentException("must be >= 0", "epoch");
+            if (cipher == null)
+                throw new ArgumentNullException("cipher");
+
+            this.mEpoch = epoch;
+            this.mCipher = cipher;
+        }
+
+        internal long AllocateSequenceNumber()
+        {
+            // TODO Check for overflow
+            return mSequenceNumber++;
+        }
+
+        internal TlsCipher Cipher
+        {
+            get { return mCipher; }
+        }
+
+        internal int Epoch
+        {
+            get { return mEpoch; }
+        }
+
+        internal DtlsReplayWindow ReplayWindow
+        {
+            get { return mReplayWindow; }
+        }
+
+        internal long SequenceNumber
+        {
+            get { return mSequenceNumber; }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs b/crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs
new file mode 100644
index 000000000..8bfae78b1
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs
@@ -0,0 +1,11 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    interface DtlsHandshakeRetransmit
+    {
+        /// <exception cref="IOException"/>
+        void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len);
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsProtocol.cs b/crypto/src/crypto/tls/DtlsProtocol.cs
new file mode 100644
index 000000000..6d62c5a90
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsProtocol.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public abstract class DtlsProtocol
+    {
+        protected readonly SecureRandom mSecureRandom;
+
+        protected DtlsProtocol(SecureRandom secureRandom)
+        {
+            if (secureRandom == null)
+                throw new ArgumentNullException("secureRandom");
+
+            this.mSecureRandom = secureRandom;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessFinished(byte[] body, byte[] expected_verify_data)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            byte[] verify_data = TlsUtilities.ReadFully(expected_verify_data.Length, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data))
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+
+        /// <exception cref="IOException"/>
+        protected static short EvaluateMaxFragmentLengthExtension(IDictionary clientExtensions, IDictionary serverExtensions,
+            byte alertDescription)
+        {
+            short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
+            if (maxFragmentLength >= 0 && maxFragmentLength != TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions))
+                throw new TlsFatalAlert(alertDescription);
+            return maxFragmentLength;
+        }
+
+        /// <exception cref="IOException"/>
+        protected static byte[] GenerateCertificate(Certificate certificate)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificate.Encode(buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        protected static byte[] GenerateSupplementalData(IList supplementalData)
+        {
+            MemoryStream buf = new MemoryStream();
+            TlsProtocol.WriteSupplementalData(buf, supplementalData);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        protected static void ValidateSelectedCipherSuite(int selectedCipherSuite, byte alertDescription)
+        {
+            switch (TlsUtilities.GetEncryptionAlgorithm(selectedCipherSuite))
+            {
+            case EncryptionAlgorithm.RC4_40:
+            case EncryptionAlgorithm.RC4_128:
+                throw new TlsFatalAlert(alertDescription);
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsReassembler.cs b/crypto/src/crypto/tls/DtlsReassembler.cs
new file mode 100644
index 000000000..11fe609cf
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsReassembler.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    class DtlsReassembler
+    {
+        private readonly byte mMsgType;
+        private readonly byte[] mBody;
+
+        private readonly IList mMissing = Platform.CreateArrayList();
+
+        internal DtlsReassembler(byte msg_type, int length)
+        {
+            this.mMsgType = msg_type;
+            this.mBody = new byte[length];
+            this.mMissing.Add(new Range(0, length));
+        }
+
+        internal byte MsgType
+        {
+            get { return mMsgType; }
+        }
+
+        internal byte[] GetBodyIfComplete()
+        {
+            return mMissing.Count == 0 ? mBody : null;
+        }
+
+        internal void ContributeFragment(byte msg_type, int length, byte[] buf, int off, int fragment_offset,
+            int fragment_length)
+        {
+            int fragment_end = fragment_offset + fragment_length;
+
+            if (this.mMsgType != msg_type || this.mBody.Length != length || fragment_end > length)
+            {
+                return;
+            }
+
+            if (fragment_length == 0)
+            {
+                // NOTE: Empty messages still require an empty fragment to complete it
+                if (fragment_offset == 0 && mMissing.Count > 0)
+                {
+                    Range firstRange = (Range)mMissing[0];
+                    if (firstRange.End == 0)
+                    {
+                        mMissing.RemoveAt(0);
+                    }
+                }
+                return;
+            }
+
+            for (int i = 0; i < mMissing.Count; ++i)
+            {
+                Range range = (Range)mMissing[i];
+                if (range.Start >= fragment_end)
+                {
+                    break;
+                }
+                if (range.End > fragment_offset)
+                {
+
+                    int copyStart = System.Math.Max(range.Start, fragment_offset);
+                    int copyEnd = System.Math.Min(range.End, fragment_end);
+                    int copyLength = copyEnd - copyStart;
+
+                    Array.Copy(buf, off + copyStart - fragment_offset, mBody, copyStart,
+                        copyLength);
+
+                    if (copyStart == range.Start)
+                    {
+                        if (copyEnd == range.End)
+                        {
+                            mMissing.RemoveAt(i--);
+                        }
+                        else
+                        {
+                            range.Start = copyEnd;
+                        }
+                    }
+                    else
+                    {
+                        if (copyEnd != range.End)
+                        {
+                            mMissing.Insert(++i, new Range(copyEnd, range.End));
+                        }
+                        range.End = copyStart;
+                    }
+                }
+            }
+        }
+
+        internal void Reset()
+        {
+            this.mMissing.Clear();
+            this.mMissing.Add(new Range(0, mBody.Length));
+        }
+
+        private class Range
+        {
+            private int mStart, mEnd;
+
+            internal Range(int start, int end)
+            {
+                this.mStart = start;
+                this.mEnd = end;
+            }
+
+            public int Start
+            {
+                get { return mStart; }
+                set { this.mStart = value; }
+            }
+
+            public int End
+            {
+                get { return mEnd; }
+                set { this.mEnd = value; }
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsRecordLayer.cs b/crypto/src/crypto/tls/DtlsRecordLayer.cs
new file mode 100644
index 000000000..70befd9e4
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsRecordLayer.cs
@@ -0,0 +1,507 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    internal class DtlsRecordLayer
+        :   DatagramTransport
+    {
+        private const int RECORD_HEADER_LENGTH = 13;
+        private const int MAX_FRAGMENT_LENGTH = 1 << 14;
+        private const long TCP_MSL = 1000L * 60 * 2;
+        private const long RETRANSMIT_TIMEOUT = TCP_MSL * 2;
+
+        private readonly DatagramTransport mTransport;
+        private readonly TlsContext mContext;
+        private readonly TlsPeer mPeer;
+
+        private readonly ByteQueue mRecordQueue = new ByteQueue();
+
+        private volatile bool mClosed = false;
+        private volatile bool mFailed = false;
+        private volatile ProtocolVersion mDiscoveredPeerVersion = null;
+        private volatile bool mInHandshake;
+        private volatile int mPlaintextLimit;
+        private DtlsEpoch mCurrentEpoch, mPendingEpoch;
+        private DtlsEpoch mReadEpoch, mWriteEpoch;
+
+        private DtlsHandshakeRetransmit mRetransmit = null;
+        private DtlsEpoch mRetransmitEpoch = null;
+        private long mRetransmitExpiry = 0;
+
+        internal DtlsRecordLayer(DatagramTransport transport, TlsContext context, TlsPeer peer, byte contentType)
+        {
+            this.mTransport = transport;
+            this.mContext = context;
+            this.mPeer = peer;
+
+            this.mInHandshake = true;
+
+            this.mCurrentEpoch = new DtlsEpoch(0, new TlsNullCipher(context));
+            this.mPendingEpoch = null;
+            this.mReadEpoch = mCurrentEpoch;
+            this.mWriteEpoch = mCurrentEpoch;
+
+            SetPlaintextLimit(MAX_FRAGMENT_LENGTH);
+        }
+
+        internal virtual void SetPlaintextLimit(int plaintextLimit)
+        {
+            this.mPlaintextLimit = plaintextLimit;
+        }
+
+        internal virtual ProtocolVersion DiscoveredPeerVersion
+        {
+            get { return mDiscoveredPeerVersion; }
+        }
+
+        internal virtual ProtocolVersion ResetDiscoveredPeerVersion()
+        {
+            ProtocolVersion result = mDiscoveredPeerVersion;
+            mDiscoveredPeerVersion = null;
+            return result;
+        }
+
+        internal virtual void InitPendingEpoch(TlsCipher pendingCipher)
+        {
+            if (mPendingEpoch != null)
+                throw new InvalidOperationException();
+
+            /*
+             * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations
+             * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment
+             * lifetime."
+             */
+
+            // TODO Check for overflow
+            this.mPendingEpoch = new DtlsEpoch(mWriteEpoch.Epoch + 1, pendingCipher);
+        }
+
+        internal virtual void HandshakeSuccessful(DtlsHandshakeRetransmit retransmit)
+        {
+            if (mReadEpoch == mCurrentEpoch || mWriteEpoch == mCurrentEpoch)
+            {
+                // TODO
+                throw new InvalidOperationException();
+            }
+
+            if (retransmit != null)
+            {
+                this.mRetransmit = retransmit;
+                this.mRetransmitEpoch = mCurrentEpoch;
+                this.mRetransmitExpiry = DateTimeUtilities.CurrentUnixMs() + RETRANSMIT_TIMEOUT;
+            }
+
+            this.mInHandshake = false;
+            this.mCurrentEpoch = mPendingEpoch;
+            this.mPendingEpoch = null;
+        }
+
+        internal virtual void ResetWriteEpoch()
+        {
+            if (mRetransmitEpoch != null)
+            {
+                this.mWriteEpoch = mRetransmitEpoch;
+            }
+            else
+            {
+                this.mWriteEpoch = mCurrentEpoch;
+            }
+        }
+
+        public virtual int GetReceiveLimit()
+        {
+            return System.Math.Min(this.mPlaintextLimit,
+                mReadEpoch.Cipher.GetPlaintextLimit(mTransport.GetReceiveLimit() - RECORD_HEADER_LENGTH));
+        }
+
+        public virtual int GetSendLimit()
+        {
+            return System.Math.Min(this.mPlaintextLimit,
+                mWriteEpoch.Cipher.GetPlaintextLimit(mTransport.GetSendLimit() - RECORD_HEADER_LENGTH));
+        }
+
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            byte[] record = null;
+
+            for (;;)
+            {
+                int receiveLimit = System.Math.Min(len, GetReceiveLimit()) + RECORD_HEADER_LENGTH;
+                if (record == null || record.Length < receiveLimit)
+                {
+                    record = new byte[receiveLimit];
+                }
+
+                try
+                {
+                    if (mRetransmit != null && DateTimeUtilities.CurrentUnixMs() > mRetransmitExpiry)
+                    {
+                        mRetransmit = null;
+                        mRetransmitEpoch = null;
+                    }
+
+                    int received = ReceiveRecord(record, 0, receiveLimit, waitMillis);
+                    if (received < 0)
+                    {
+                        return received;
+                    }
+                    if (received < RECORD_HEADER_LENGTH)
+                    {
+                        continue;
+                    }
+                    int length = TlsUtilities.ReadUint16(record, 11);
+                    if (received != (length + RECORD_HEADER_LENGTH))
+                    {
+                        continue;
+                    }
+
+                    byte type = TlsUtilities.ReadUint8(record, 0);
+
+                    // TODO Support user-specified custom protocols?
+                    switch (type)
+                    {
+                    case ContentType.alert:
+                    case ContentType.application_data:
+                    case ContentType.change_cipher_spec:
+                    case ContentType.handshake:
+                    case ContentType.heartbeat:
+                        break;
+                    default:
+                        // TODO Exception?
+                        continue;
+                    }
+
+                    int epoch = TlsUtilities.ReadUint16(record, 3);
+
+                    DtlsEpoch recordEpoch = null;
+                    if (epoch == mReadEpoch.Epoch)
+                    {
+                        recordEpoch = mReadEpoch;
+                    }
+                    else if (type == ContentType.handshake && mRetransmitEpoch != null
+                        && epoch == mRetransmitEpoch.Epoch)
+                    {
+                        recordEpoch = mRetransmitEpoch;
+                    }
+
+                    if (recordEpoch == null)
+                    {
+                        continue;
+                    }
+
+                    long seq = TlsUtilities.ReadUint48(record, 5);
+                    if (recordEpoch.ReplayWindow.ShouldDiscard(seq))
+                    {
+                        continue;
+                    }
+
+                    ProtocolVersion version = TlsUtilities.ReadVersion(record, 1);
+                    if (mDiscoveredPeerVersion != null && !mDiscoveredPeerVersion.Equals(version))
+                    {
+                        continue;
+                    }
+
+                    byte[] plaintext = recordEpoch.Cipher.DecodeCiphertext(
+                        GetMacSequenceNumber(recordEpoch.Epoch, seq), type, record, RECORD_HEADER_LENGTH,
+                        received - RECORD_HEADER_LENGTH);
+
+                    recordEpoch.ReplayWindow.ReportAuthenticated(seq);
+
+                    if (plaintext.Length > this.mPlaintextLimit)
+                    {
+                        continue;
+                    }
+
+                    if (mDiscoveredPeerVersion == null)
+                    {
+                        mDiscoveredPeerVersion = version;
+                    }
+
+                    switch (type)
+                    {
+                    case ContentType.alert:
+                    {
+                        if (plaintext.Length == 2)
+                        {
+                            byte alertLevel = plaintext[0];
+                            byte alertDescription = plaintext[1];
+
+                            mPeer.NotifyAlertReceived(alertLevel, alertDescription);
+
+                            if (alertLevel == AlertLevel.fatal)
+                            {
+                                Fail(alertDescription);
+                                throw new TlsFatalAlert(alertDescription);
+                            }
+
+                            // TODO Can close_notify be a fatal alert?
+                            if (alertDescription == AlertDescription.close_notify)
+                            {
+                                CloseTransport();
+                            }
+                        }
+
+                        continue;
+                    }
+                    case ContentType.application_data:
+                    {
+                        if (mInHandshake)
+                        {
+                            // TODO Consider buffering application data for new epoch that arrives
+                            // out-of-order with the Finished message
+                            continue;
+                        }
+                        break;
+                    }
+                    case ContentType.change_cipher_spec:
+                    {
+                        // Implicitly receive change_cipher_spec and change to pending cipher state
+
+                        for (int i = 0; i < plaintext.Length; ++i)
+                        {
+                            byte message = TlsUtilities.ReadUint8(plaintext, i);
+                            if (message != ChangeCipherSpec.change_cipher_spec)
+                            {
+                                continue;
+                            }
+
+                            if (mPendingEpoch != null)
+                            {
+                                mReadEpoch = mPendingEpoch;
+                            }
+                        }
+
+                        continue;
+                    }
+                    case ContentType.handshake:
+                    {
+                        if (!mInHandshake)
+                        {
+                            if (mRetransmit != null)
+                            {
+                                mRetransmit.ReceivedHandshakeRecord(epoch, plaintext, 0, plaintext.Length);
+                            }
+
+                            // TODO Consider support for HelloRequest
+                            continue;
+                        }
+                        break;
+                    }
+                    case ContentType.heartbeat:
+                    {
+                        // TODO[RFC 6520]
+                        continue;
+                    }
+                    }
+
+                    /*
+                     * NOTE: If we receive any non-handshake data in the new epoch implies the peer has
+                     * received our final flight.
+                     */
+                    if (!mInHandshake && mRetransmit != null)
+                    {
+                        this.mRetransmit = null;
+                        this.mRetransmitEpoch = null;
+                    }
+
+                    Array.Copy(plaintext, 0, buf, off, plaintext.Length);
+                    return plaintext.Length;
+                }
+                catch (IOException e)
+                {
+                    // NOTE: Assume this is a timeout for the moment
+                    throw e;
+                }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            byte contentType = ContentType.application_data;
+
+            if (this.mInHandshake || this.mWriteEpoch == this.mRetransmitEpoch)
+            {
+                contentType = ContentType.handshake;
+
+                byte handshakeType = TlsUtilities.ReadUint8(buf, off);
+                if (handshakeType == HandshakeType.finished)
+                {
+                    DtlsEpoch nextEpoch = null;
+                    if (this.mInHandshake)
+                    {
+                        nextEpoch = mPendingEpoch;
+                    }
+                    else if (this.mWriteEpoch == this.mRetransmitEpoch)
+                    {
+                        nextEpoch = mCurrentEpoch;
+                    }
+
+                    if (nextEpoch == null)
+                    {
+                        // TODO
+                        throw new InvalidOperationException();
+                    }
+
+                    // Implicitly send change_cipher_spec and change to pending cipher state
+
+                    // TODO Send change_cipher_spec and finished records in single datagram?
+                    byte[] data = new byte[]{ 1 };
+                    SendRecord(ContentType.change_cipher_spec, data, 0, data.Length);
+
+                    mWriteEpoch = nextEpoch;
+                }
+            }
+
+            SendRecord(contentType, buf, off, len);
+        }
+
+        public virtual void Close()
+        {
+            if (!mClosed)
+            {
+                if (mInHandshake)
+                {
+                    Warn(AlertDescription.user_canceled, "User canceled handshake");
+                }
+                CloseTransport();
+            }
+        }
+
+        internal virtual void Fail(byte alertDescription)
+        {
+            if (!mClosed)
+            {
+                try
+                {
+                    RaiseAlert(AlertLevel.fatal, alertDescription, null, null);
+                }
+                catch (Exception)
+                {
+                    // Ignore
+                }
+
+                mFailed = true;
+
+                CloseTransport();
+            }
+        }
+
+        internal virtual void Warn(byte alertDescription, string message)
+        {
+            RaiseAlert(AlertLevel.warning, alertDescription, message, null);
+        }
+
+        private void CloseTransport()
+        {
+            if (!mClosed)
+            {
+                /*
+                 * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is
+                 * required to send a close_notify alert before closing the write side of the
+                 * connection. The other party MUST respond with a close_notify alert of its own and
+                 * close down the connection immediately, discarding any pending writes.
+                 */
+
+                try
+                {
+                    if (!mFailed)
+                    {
+                        Warn(AlertDescription.close_notify, null);
+                    }
+                    mTransport.Close();
+                }
+                catch (Exception)
+                {
+                    // Ignore
+                }
+
+                mClosed = true;
+            }
+        }
+
+        private void RaiseAlert(byte alertLevel, byte alertDescription, string message, Exception cause)
+        {
+            mPeer.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+            byte[] error = new byte[2];
+            error[0] = (byte)alertLevel;
+            error[1] = (byte)alertDescription;
+
+            SendRecord(ContentType.alert, error, 0, 2);
+        }
+
+        private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis)
+        {
+            if (mRecordQueue.Available > 0)
+            {
+                int length = 0;
+                if (mRecordQueue.Available >= RECORD_HEADER_LENGTH)
+                {
+                    byte[] lengthBytes = new byte[2];
+                    mRecordQueue.Read(lengthBytes, 0, 2, 11);
+                    length = TlsUtilities.ReadUint16(lengthBytes, 0);
+                }
+
+                int received = System.Math.Min(mRecordQueue.Available, RECORD_HEADER_LENGTH + length);
+                mRecordQueue.RemoveData(buf, off, received, 0);
+                return received;
+            }
+
+            {
+                int received = mTransport.Receive(buf, off, len, waitMillis);
+                if (received >= RECORD_HEADER_LENGTH)
+                {
+                    int fragmentLength = TlsUtilities.ReadUint16(buf, off + 11);
+                    int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
+                    if (received > recordLength)
+                    {
+                        mRecordQueue.AddData(buf, off + recordLength, received - recordLength);
+                        received = recordLength;
+                    }
+                }
+                return received;
+            }
+        }
+
+        private void SendRecord(byte contentType, byte[] buf, int off, int len)
+        {
+            if (len > this.mPlaintextLimit)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            /*
+             * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (len < 1 && contentType != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            int recordEpoch = mWriteEpoch.Epoch;
+            long recordSequenceNumber = mWriteEpoch.AllocateSequenceNumber();
+
+            byte[] ciphertext = mWriteEpoch.Cipher.EncodePlaintext(
+                GetMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len);
+
+            // TODO Check the ciphertext length?
+
+            byte[] record = new byte[ciphertext.Length + RECORD_HEADER_LENGTH];
+            TlsUtilities.WriteUint8(contentType, record, 0);
+            ProtocolVersion version = mDiscoveredPeerVersion != null ? mDiscoveredPeerVersion : mContext.ClientVersion;
+            TlsUtilities.WriteVersion(version, record, 1);
+            TlsUtilities.WriteUint16(recordEpoch, record, 3);
+            TlsUtilities.WriteUint48(recordSequenceNumber, record, 5);
+            TlsUtilities.WriteUint16(ciphertext.Length, record, 11);
+            Array.Copy(ciphertext, 0, record, RECORD_HEADER_LENGTH, ciphertext.Length);
+
+            mTransport.Send(record, 0, record.Length);
+        }
+
+        private static long GetMacSequenceNumber(int epoch, long sequence_number)
+        {
+            return ((epoch & 0xFFFFFFFFL) << 48) | sequence_number;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsReliableHandshake.cs b/crypto/src/crypto/tls/DtlsReliableHandshake.cs
new file mode 100644
index 000000000..bf9e61d03
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsReliableHandshake.cs
@@ -0,0 +1,443 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    internal class DtlsReliableHandshake
+    {
+        private const int MAX_RECEIVE_AHEAD = 10;
+
+        private readonly DtlsRecordLayer mRecordLayer;
+
+        private TlsHandshakeHash mHandshakeHash;
+
+        private IDictionary mCurrentInboundFlight = Platform.CreateHashtable();
+        private IDictionary mPreviousInboundFlight = null;
+        private IList mOutboundFlight = Platform.CreateArrayList();
+        private bool mSending = true;
+
+        private int mMessageSeq = 0, mNextReceiveSeq = 0;
+
+        internal DtlsReliableHandshake(TlsContext context, DtlsRecordLayer transport)
+        {
+            this.mRecordLayer = transport;
+            this.mHandshakeHash = new DeferredHash();
+            this.mHandshakeHash.Init(context);
+        }
+
+        internal void NotifyHelloComplete()
+        {
+            this.mHandshakeHash = mHandshakeHash.NotifyPrfDetermined();
+        }
+
+        internal TlsHandshakeHash HandshakeHash
+        {
+            get { return mHandshakeHash; }
+        }
+
+        internal TlsHandshakeHash PrepareToFinish()
+        {
+            TlsHandshakeHash result = mHandshakeHash;
+            this.mHandshakeHash = mHandshakeHash.StopTracking();
+            return result;
+        }
+
+        internal void SendMessage(byte msg_type, byte[] body)
+        {
+            TlsUtilities.CheckUint24(body.Length);
+
+            if (!mSending)
+            {
+                CheckInboundFlight();
+                mSending = true;
+                mOutboundFlight.Clear();
+            }
+
+            Message message = new Message(mMessageSeq++, msg_type, body);
+
+            mOutboundFlight.Add(message);
+
+            WriteMessage(message);
+            UpdateHandshakeMessagesDigest(message);
+        }
+
+        internal byte[] ReceiveMessageBody(byte msg_type)
+        {
+            Message message = ReceiveMessage();
+            if (message.Type != msg_type)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            return message.Body;
+        }
+
+        internal Message ReceiveMessage()
+        {
+            if (mSending)
+            {
+                mSending = false;
+                PrepareInboundFlight();
+            }
+
+            // Check if we already have the next message waiting
+            {
+                DtlsReassembler next = (DtlsReassembler)mCurrentInboundFlight[mNextReceiveSeq];
+                if (next != null)
+                {
+                    byte[] body = next.GetBodyIfComplete();
+                    if (body != null)
+                    {
+                        mPreviousInboundFlight = null;
+                        return UpdateHandshakeMessagesDigest(new Message(mNextReceiveSeq++, next.MsgType, body));
+                    }
+                }
+            }
+
+            byte[] buf = null;
+
+            // TODO Check the conditions under which we should reset this
+            int readTimeoutMillis = 1000;
+
+            for (;;)
+            {
+                int receiveLimit = mRecordLayer.GetReceiveLimit();
+                if (buf == null || buf.Length < receiveLimit)
+                {
+                    buf = new byte[receiveLimit];
+                }
+
+                // TODO Handle records containing multiple handshake messages
+
+                try
+                {
+                    for (; ; )
+                    {
+                        int Received = mRecordLayer.Receive(buf, 0, receiveLimit, readTimeoutMillis);
+                        if (Received < 0)
+                        {
+                            break;
+                        }
+                        if (Received < 12)
+                        {
+                            continue;
+                        }
+                        int fragment_length = TlsUtilities.ReadUint24(buf, 9);
+                        if (Received != (fragment_length + 12))
+                        {
+                            continue;
+                        }
+                        int seq = TlsUtilities.ReadUint16(buf, 4);
+                        if (seq > (mNextReceiveSeq + MAX_RECEIVE_AHEAD))
+                        {
+                            continue;
+                        }
+                        byte msg_type = TlsUtilities.ReadUint8(buf, 0);
+                        int length = TlsUtilities.ReadUint24(buf, 1);
+                        int fragment_offset = TlsUtilities.ReadUint24(buf, 6);
+                        if (fragment_offset + fragment_length > length)
+                        {
+                            continue;
+                        }
+
+                        if (seq < mNextReceiveSeq)
+                        {
+                            /*
+                             * NOTE: If we Receive the previous flight of incoming messages in full
+                             * again, retransmit our last flight
+                             */
+                            if (mPreviousInboundFlight != null)
+                            {
+                                DtlsReassembler reassembler = (DtlsReassembler)mPreviousInboundFlight[seq];
+                                if (reassembler != null)
+                                {
+                                    reassembler.ContributeFragment(msg_type, length, buf, 12, fragment_offset,
+                                        fragment_length);
+
+                                    if (CheckAll(mPreviousInboundFlight))
+                                    {
+                                        ResendOutboundFlight();
+
+                                        /*
+                                         * TODO[DTLS] implementations SHOULD back off handshake packet
+                                         * size during the retransmit backoff.
+                                         */
+                                        readTimeoutMillis = System.Math.Min(readTimeoutMillis * 2, 60000);
+
+                                        ResetAll(mPreviousInboundFlight);
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            DtlsReassembler reassembler = (DtlsReassembler)mCurrentInboundFlight[seq];
+                            if (reassembler == null)
+                            {
+                                reassembler = new DtlsReassembler(msg_type, length);
+                                mCurrentInboundFlight[seq] = reassembler;
+                            }
+
+                            reassembler.ContributeFragment(msg_type, length, buf, 12, fragment_offset, fragment_length);
+
+                            if (seq == mNextReceiveSeq)
+                            {
+                                byte[] body = reassembler.GetBodyIfComplete();
+                                if (body != null)
+                                {
+                                    mPreviousInboundFlight = null;
+                                    return UpdateHandshakeMessagesDigest(new Message(mNextReceiveSeq++,
+                                        reassembler.MsgType, body));
+                                }
+                            }
+                        }
+                    }
+                }
+                catch (IOException)
+                {
+                    // NOTE: Assume this is a timeout for the moment
+                }
+
+                ResendOutboundFlight();
+
+                /*
+                 * TODO[DTLS] implementations SHOULD back off handshake packet size during the
+                 * retransmit backoff.
+                 */
+                readTimeoutMillis = System.Math.Min(readTimeoutMillis * 2, 60000);
+            }
+        }
+
+        internal void Finish()
+        {
+            DtlsHandshakeRetransmit retransmit = null;
+            if (!mSending)
+            {
+                CheckInboundFlight();
+            }
+            else if (mCurrentInboundFlight != null)
+            {
+                /*
+                 * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP],
+                 * when in the FINISHED state, the node that transmits the last flight (the server in an
+                 * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit
+                 * of the peer's last flight with a retransmit of the last flight.
+                 */
+                retransmit = new Retransmit(this);
+            }
+
+            mRecordLayer.HandshakeSuccessful(retransmit);
+        }
+
+        internal void ResetHandshakeMessagesDigest()
+        {
+            mHandshakeHash.Reset();
+        }
+
+        private void HandleRetransmittedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+        {
+            /*
+             * TODO Need to handle the case where the previous inbound flight contains
+             * messages from two epochs.
+             */
+            if (len < 12)
+                return;
+            int fragment_length = TlsUtilities.ReadUint24(buf, off + 9);
+            if (len != (fragment_length + 12))
+                return;
+            int seq = TlsUtilities.ReadUint16(buf, off + 4);
+            if (seq >= mNextReceiveSeq)
+                return;
+
+            byte msg_type = TlsUtilities.ReadUint8(buf, off);
+
+            // TODO This is a hack that only works until we try to support renegotiation
+            int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0;
+            if (epoch != expectedEpoch)
+                return;
+
+            int length = TlsUtilities.ReadUint24(buf, off + 1);
+            int fragment_offset = TlsUtilities.ReadUint24(buf, off + 6);
+            if (fragment_offset + fragment_length > length)
+                return;
+
+            DtlsReassembler reassembler = (DtlsReassembler)mCurrentInboundFlight[seq];
+            if (reassembler != null)
+            {
+                reassembler.ContributeFragment(msg_type, length, buf, off + 12, fragment_offset,
+                    fragment_length);
+                if (CheckAll(mCurrentInboundFlight))
+                {
+                    ResendOutboundFlight();
+                    ResetAll(mCurrentInboundFlight);
+                }
+            }
+        }
+
+        /**
+         * Check that there are no "extra" messages left in the current inbound flight
+         */
+        private void CheckInboundFlight()
+        {
+            foreach (int key in mCurrentInboundFlight.Keys)
+            {
+                if (key >= mNextReceiveSeq)
+                {
+                    // TODO Should this be considered an error?
+                }
+            }
+        }
+
+        private void PrepareInboundFlight()
+        {
+            ResetAll(mCurrentInboundFlight);
+            mPreviousInboundFlight = mCurrentInboundFlight;
+            mCurrentInboundFlight = Platform.CreateHashtable();
+        }
+
+        private void ResendOutboundFlight()
+        {
+            mRecordLayer.ResetWriteEpoch();
+            for (int i = 0; i < mOutboundFlight.Count; ++i)
+            {
+                WriteMessage((Message)mOutboundFlight[i]);
+            }
+        }
+
+        private Message UpdateHandshakeMessagesDigest(Message message)
+        {
+            if (message.Type != HandshakeType.hello_request)
+            {
+                byte[] body = message.Body;
+                byte[] buf = new byte[12];
+                TlsUtilities.WriteUint8(message.Type, buf, 0);
+                TlsUtilities.WriteUint24(body.Length, buf, 1);
+                TlsUtilities.WriteUint16(message.Seq, buf, 4);
+                TlsUtilities.WriteUint24(0, buf, 6);
+                TlsUtilities.WriteUint24(body.Length, buf, 9);
+                mHandshakeHash.BlockUpdate(buf, 0, buf.Length);
+                mHandshakeHash.BlockUpdate(body, 0, body.Length);
+            }
+            return message;
+        }
+
+        private void WriteMessage(Message message)
+        {
+            int sendLimit = mRecordLayer.GetSendLimit();
+            int fragmentLimit = sendLimit - 12;
+
+            // TODO Support a higher minimum fragment size?
+            if (fragmentLimit < 1)
+            {
+                // TODO Should we be throwing an exception here?
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int length = message.Body.Length;
+
+            // NOTE: Must still send a fragment if body is empty
+            int fragment_offset = 0;
+            do
+            {
+                int fragment_length = System.Math.Min(length - fragment_offset, fragmentLimit);
+                WriteHandshakeFragment(message, fragment_offset, fragment_length);
+                fragment_offset += fragment_length;
+            }
+            while (fragment_offset < length);
+        }
+
+        private void WriteHandshakeFragment(Message message, int fragment_offset, int fragment_length)
+        {
+            RecordLayerBuffer fragment = new RecordLayerBuffer(12 + fragment_length);
+            TlsUtilities.WriteUint8(message.Type, fragment);
+            TlsUtilities.WriteUint24(message.Body.Length, fragment);
+            TlsUtilities.WriteUint16(message.Seq, fragment);
+            TlsUtilities.WriteUint24(fragment_offset, fragment);
+            TlsUtilities.WriteUint24(fragment_length, fragment);
+            fragment.Write(message.Body, fragment_offset, fragment_length);
+
+            fragment.SendToRecordLayer(mRecordLayer);
+        }
+
+        private static bool CheckAll(IDictionary inboundFlight)
+        {
+            foreach (DtlsReassembler r in inboundFlight.Values)
+            {
+                if (r.GetBodyIfComplete() == null)
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private static void ResetAll(IDictionary inboundFlight)
+        {
+            foreach (DtlsReassembler r in inboundFlight.Values)
+            {
+                r.Reset();
+            }
+        }
+
+        internal class Message
+        {
+            private readonly int mMessageSeq;
+            private readonly byte mMsgType;
+            private readonly byte[] mBody;
+
+            internal Message(int message_seq, byte msg_type, byte[] body)
+            {
+                this.mMessageSeq = message_seq;
+                this.mMsgType = msg_type;
+                this.mBody = body;
+            }
+
+            public int Seq
+            {
+                get { return mMessageSeq; }
+            }
+
+            public byte Type
+            {
+                get { return mMsgType; }
+            }
+
+            public byte[] Body
+            {
+                get { return mBody; }
+            }
+        }
+
+        internal class RecordLayerBuffer
+            :   MemoryStream
+        {
+            internal RecordLayerBuffer(int size)
+                :   base(size)
+            {
+            }
+
+            internal void SendToRecordLayer(DtlsRecordLayer recordLayer)
+            {
+                recordLayer.Send(GetBuffer(), 0, (int)Length);
+                this.Close();
+            }
+        }
+
+        internal class Retransmit
+            :   DtlsHandshakeRetransmit
+        {
+            private readonly DtlsReliableHandshake mOuter;
+
+            internal Retransmit(DtlsReliableHandshake outer)
+            {
+                this.mOuter = outer;
+            }
+
+            public void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+            {
+                mOuter.HandleRetransmittedHandshakeRecord(epoch, buf, off, len);
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsReplayWindow.cs b/crypto/src/crypto/tls/DtlsReplayWindow.cs
new file mode 100644
index 000000000..ea18e805e
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsReplayWindow.cs
@@ -0,0 +1,85 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    /**
+     * RFC 4347 4.1.2.5 Anti-replay
+     * <p/>
+     * Support fast rejection of duplicate records by maintaining a sliding receive window
+     */
+    internal class DtlsReplayWindow
+    {
+        private const long VALID_SEQ_MASK = 0x0000FFFFFFFFFFFFL;
+
+        private const long WINDOW_SIZE = 64L;
+
+        private long mLatestConfirmedSeq = -1;
+        private long mBitmap = 0;
+
+        /**
+         * Check whether a received record with the given sequence number should be rejected as a duplicate.
+         *
+         * @param seq the 48-bit DTLSPlainText.sequence_number field of a received record.
+         * @return true if the record should be discarded without further processing.
+         */
+        internal bool ShouldDiscard(long seq)
+        {
+            if ((seq & VALID_SEQ_MASK) != seq)
+                return true;
+
+            if (seq <= mLatestConfirmedSeq)
+            {
+                long diff = mLatestConfirmedSeq - seq;
+                if (diff >= WINDOW_SIZE)
+                    return true;
+                if ((mBitmap & (1L << (int)diff)) != 0)
+                    return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Report that a received record with the given sequence number passed authentication checks.
+         *
+         * @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record.
+         */
+        internal void ReportAuthenticated(long seq)
+        {
+            if ((seq & VALID_SEQ_MASK) != seq)
+                throw new ArgumentException("out of range", "seq");
+
+            if (seq <= mLatestConfirmedSeq)
+            {
+                long diff = mLatestConfirmedSeq - seq;
+                if (diff < WINDOW_SIZE)
+                {
+                    mBitmap |= (1L << (int)diff);
+                }
+            }
+            else
+            {
+                long diff = seq - mLatestConfirmedSeq;
+                if (diff >= WINDOW_SIZE)
+                {
+                    mBitmap = 1;
+                }
+                else
+                {
+                    mBitmap <<= (int)diff;
+                    mBitmap |= 1;
+                }
+                mLatestConfirmedSeq = seq;
+            }
+        }
+
+        /**
+         * When a new epoch begins, sequence numbers begin again at 0
+         */
+        internal void Reset()
+        {
+            mLatestConfirmedSeq = -1;
+            mBitmap = 0;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsServerProtocol.cs b/crypto/src/crypto/tls/DtlsServerProtocol.cs
new file mode 100644
index 000000000..3335a9f36
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsServerProtocol.cs
@@ -0,0 +1,642 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class DtlsServerProtocol
+        :   DtlsProtocol
+    {
+        protected bool mVerifyRequests = true;
+
+        public DtlsServerProtocol(SecureRandom secureRandom)
+            :   base(secureRandom)
+        {
+        }
+
+        public virtual bool VerifyRequests
+        {
+            get { return mVerifyRequests; }
+            set { this.mVerifyRequests = value; }
+        }
+
+        public virtual DtlsTransport Accept(TlsServer server, DatagramTransport transport)
+        {
+            if (server == null)
+                throw new ArgumentNullException("server");
+            if (transport == null)
+                throw new ArgumentNullException("transport");
+
+            SecurityParameters securityParameters = new SecurityParameters();
+            securityParameters.entity = ConnectionEnd.server;
+
+            ServerHandshakeState state = new ServerHandshakeState();
+            state.server = server;
+            state.serverContext = new TlsServerContextImpl(mSecureRandom, securityParameters);
+
+            securityParameters.serverRandom = TlsProtocol.CreateRandomBlock(server.ShouldUseGmtUnixTime(),
+                state.serverContext.NonceRandomGenerator);
+
+            server.Init(state.serverContext);
+
+            DtlsRecordLayer recordLayer = new DtlsRecordLayer(transport, state.serverContext, server, ContentType.handshake);
+
+            // TODO Need to handle sending of HelloVerifyRequest without entering a full connection
+
+            try
+            {
+                return ServerHandshake(state, recordLayer);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                recordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (IOException e)
+            {
+                recordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        internal virtual DtlsTransport ServerHandshake(ServerHandshakeState state, DtlsRecordLayer recordLayer)
+        {
+            SecurityParameters securityParameters = state.serverContext.SecurityParameters;
+            DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.serverContext, recordLayer);
+
+            DtlsReliableHandshake.Message clientMessage = handshake.ReceiveMessage();
+
+            {
+                // NOTE: After receiving a record from the client, we discover the record layer version
+                ProtocolVersion client_version = recordLayer.DiscoveredPeerVersion;
+                // TODO Read RFCs for guidance on the expected record layer version number
+                state.serverContext.SetClientVersion(client_version);
+            }
+
+            if (clientMessage.Type == HandshakeType.client_hello)
+            {
+                ProcessClientHello(state, clientMessage.Body);
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            {
+                byte[] serverHelloBody = GenerateServerHello(state);
+    
+                if (state.maxFragmentLength >= 0)
+                {
+                    int plainTextLimit = 1 << (8 + state.maxFragmentLength);
+                    recordLayer.SetPlaintextLimit(plainTextLimit);
+                }
+    
+                securityParameters.cipherSuite = state.selectedCipherSuite;
+                securityParameters.compressionAlgorithm = (byte)state.selectedCompressionMethod;
+                securityParameters.prfAlgorithm = TlsProtocol.GetPrfAlgorithm(state.serverContext,
+                    state.selectedCipherSuite);
+
+                /*
+                 * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length
+                 * has a verify_data_length equal to 12. This includes all existing cipher suites.
+                 */
+                securityParameters.verifyDataLength = 12;
+    
+                handshake.SendMessage(HandshakeType.server_hello, serverHelloBody);
+            }
+
+            handshake.NotifyHelloComplete();
+
+            IList serverSupplementalData = state.server.GetServerSupplementalData();
+            if (serverSupplementalData != null)
+            {
+                byte[] supplementalDataBody = GenerateSupplementalData(serverSupplementalData);
+                handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+            }
+
+            state.keyExchange = state.server.GetKeyExchange();
+            state.keyExchange.Init(state.serverContext);
+
+            state.serverCredentials = state.server.GetCredentials();
+
+            Certificate serverCertificate = null;
+
+            if (state.serverCredentials == null)
+            {
+                state.keyExchange.SkipServerCredentials();
+            }
+            else
+            {
+                state.keyExchange.ProcessServerCredentials(state.serverCredentials);
+
+                serverCertificate = state.serverCredentials.Certificate;
+                byte[] certificateBody = GenerateCertificate(serverCertificate);
+                handshake.SendMessage(HandshakeType.certificate, certificateBody);
+            }
+
+            // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+            if (serverCertificate == null || serverCertificate.IsEmpty)
+            {
+                state.allowCertificateStatus = false;
+            }
+
+            if (state.allowCertificateStatus)
+            {
+                CertificateStatus certificateStatus = state.server.GetCertificateStatus();
+                if (certificateStatus != null)
+                {
+                    byte[] certificateStatusBody = GenerateCertificateStatus(state, certificateStatus);
+                    handshake.SendMessage(HandshakeType.certificate_status, certificateStatusBody);
+                }
+            }
+
+            byte[] serverKeyExchange = state.keyExchange.GenerateServerKeyExchange();
+            if (serverKeyExchange != null)
+            {
+                handshake.SendMessage(HandshakeType.server_key_exchange, serverKeyExchange);
+            }
+
+            if (state.serverCredentials != null)
+            {
+                state.certificateRequest = state.server.GetCertificateRequest();
+                if (state.certificateRequest != null)
+                {
+                    state.keyExchange.ValidateCertificateRequest(state.certificateRequest);
+
+                    byte[] certificateRequestBody = GenerateCertificateRequest(state, state.certificateRequest);
+                    handshake.SendMessage(HandshakeType.certificate_request, certificateRequestBody);
+
+                    TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash,
+                        state.certificateRequest.SupportedSignatureAlgorithms);
+                }
+            }
+
+            handshake.SendMessage(HandshakeType.server_hello_done, TlsUtilities.EmptyBytes);
+
+            handshake.HandshakeHash.SealHashAlgorithms();
+
+            clientMessage = handshake.ReceiveMessage();
+
+            if (clientMessage.Type == HandshakeType.supplemental_data)
+            {
+                ProcessClientSupplementalData(state, clientMessage.Body);
+                clientMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                state.server.ProcessClientSupplementalData(null);
+            }
+
+            if (state.certificateRequest == null)
+            {
+                state.keyExchange.SkipClientCredentials();
+            }
+            else
+            {
+                if (clientMessage.Type == HandshakeType.certificate)
+                {
+                    ProcessClientCertificate(state, clientMessage.Body);
+                    clientMessage = handshake.ReceiveMessage();
+                }
+                else
+                {
+                    if (TlsUtilities.IsTlsV12(state.serverContext))
+                    {
+                        /*
+                         * RFC 5246 If no suitable certificate is available, the client MUST send a
+                         * certificate message containing no certificates.
+                         * 
+                         * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                         */
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    NotifyClientCertificate(state, Certificate.EmptyChain);
+                }
+            }
+
+            if (clientMessage.Type == HandshakeType.client_key_exchange)
+            {
+                ProcessClientKeyExchange(state, clientMessage.Body);
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            TlsHandshakeHash prepareFinishHash = handshake.PrepareToFinish();
+            securityParameters.sessionHash = TlsProtocol.GetCurrentPrfHash(state.serverContext, prepareFinishHash, null);
+
+            TlsProtocol.EstablishMasterSecret(state.serverContext, state.keyExchange);
+            recordLayer.InitPendingEpoch(state.server.GetCipher());
+
+            /*
+             * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing
+             * capability (i.e., all certificates except those containing fixed Diffie-Hellman
+             * parameters).
+             */
+            if (ExpectCertificateVerifyMessage(state))
+            {
+                byte[] certificateVerifyBody = handshake.ReceiveMessageBody(HandshakeType.certificate_verify);
+                ProcessCertificateVerify(state, certificateVerifyBody, prepareFinishHash);
+            }
+
+            // NOTE: Calculated exclusive of the actual Finished message from the client
+            byte[] expectedClientVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext, ExporterLabel.client_finished,
+                TlsProtocol.GetCurrentPrfHash(state.serverContext, handshake.HandshakeHash, null));
+            ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), expectedClientVerifyData);
+
+            if (state.expectSessionTicket)
+            {
+                NewSessionTicket newSessionTicket = state.server.GetNewSessionTicket();
+                byte[] newSessionTicketBody = GenerateNewSessionTicket(state, newSessionTicket);
+                handshake.SendMessage(HandshakeType.session_ticket, newSessionTicketBody);
+            }
+
+            // NOTE: Calculated exclusive of the Finished message itself
+            byte[] serverVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext, ExporterLabel.server_finished,
+                TlsProtocol.GetCurrentPrfHash(state.serverContext, handshake.HandshakeHash, null));
+            handshake.SendMessage(HandshakeType.finished, serverVerifyData);
+
+            handshake.Finish();
+
+            state.server.NotifyHandshakeComplete();
+
+            return new DtlsTransport(recordLayer);
+        }
+
+        protected virtual byte[] GenerateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificateRequest.Encode(buf);
+            return buf.ToArray();
+        }
+
+        protected virtual byte[] GenerateCertificateStatus(ServerHandshakeState state, CertificateStatus certificateStatus)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificateStatus.Encode(buf);
+            return buf.ToArray();
+        }
+
+        protected virtual byte[] GenerateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket)
+        {
+            MemoryStream buf = new MemoryStream();
+            newSessionTicket.Encode(buf);
+            return buf.ToArray();
+        }
+
+        protected virtual byte[] GenerateServerHello(ServerHandshakeState state)
+        {
+            SecurityParameters securityParameters = state.serverContext.SecurityParameters;
+
+            MemoryStream buf = new MemoryStream();
+
+            ProtocolVersion server_version = state.server.GetServerVersion();
+            if (!server_version.IsEqualOrEarlierVersionOf(state.serverContext.ClientVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            // TODO Read RFCs for guidance on the expected record layer version number
+            // recordStream.setReadVersion(server_version);
+            // recordStream.setWriteVersion(server_version);
+            // recordStream.setRestrictReadVersion(true);
+            state.serverContext.SetServerVersion(server_version);
+
+            TlsUtilities.WriteVersion(state.serverContext.ServerVersion, buf);
+
+            buf.Write(securityParameters.ServerRandom, 0, securityParameters.ServerRandom.Length);
+
+            /*
+             * The server may return an empty session_id to indicate that the session will not be cached
+             * and therefore cannot be resumed.
+             */
+            TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, buf);
+
+            state.selectedCipherSuite = state.server.GetSelectedCipherSuite();
+            if (!Arrays.Contains(state.offeredCipherSuites, state.selectedCipherSuite)
+                || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+                || CipherSuite.IsScsv(state.selectedCipherSuite)
+                || !TlsUtilities.IsValidCipherSuiteForVersion(state.selectedCipherSuite, server_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            ValidateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.internal_error);
+
+            state.selectedCompressionMethod = state.server.GetSelectedCompressionMethod();
+            if (!Arrays.Contains(state.offeredCompressionMethods, (byte)state.selectedCompressionMethod))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsUtilities.WriteUint16(state.selectedCipherSuite, buf);
+            TlsUtilities.WriteUint8((byte)state.selectedCompressionMethod, buf);
+
+            state.serverExtensions = state.server.GetServerExtensions();
+
+            /*
+             * RFC 5746 3.6. Server Behavior: Initial Handshake
+             */
+            if (state.secure_renegotiation)
+            {
+                byte[] renegExtData = TlsUtilities.GetExtensionData(state.serverExtensions, ExtensionType.renegotiation_info);
+                bool noRenegExt = (null == renegExtData);
+
+                if (noRenegExt)
+                {
+                    /*
+                     * Note that sending a "renegotiation_info" extension in response to a ClientHello
+                     * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+                     * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed
+                     * because the client is signaling its willingness to receive the extension via the
+                     * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                     */
+
+                    /*
+                     * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+                     * "renegotiation_info" extension in the ServerHello message.
+                     */
+                    state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(state.serverExtensions);
+                    state.serverExtensions[ExtensionType.renegotiation_info] = TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes);
+                }
+            }
+
+            if (securityParameters.extendedMasterSecret)
+            {
+                state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(state.serverExtensions);
+                TlsExtensionsUtilities.AddExtendedMasterSecretExtension(state.serverExtensions);
+            }
+
+            if (state.serverExtensions != null)
+            {
+                securityParameters.encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(state.serverExtensions);
+
+                state.maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.clientExtensions, state.serverExtensions,
+                    AlertDescription.internal_error);
+
+                securityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(state.serverExtensions);
+
+                state.allowCertificateStatus = TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions,
+                    ExtensionType.status_request, AlertDescription.internal_error);
+
+                state.expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions,
+                    ExtensionType.session_ticket, AlertDescription.internal_error);
+
+                TlsProtocol.WriteExtensions(buf, state.serverExtensions);
+            }
+
+            return buf.ToArray();
+        }
+
+        protected virtual void NotifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate)
+        {
+            if (state.certificateRequest == null)
+                throw new InvalidOperationException();
+
+            if (state.clientCertificate != null)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            state.clientCertificate = clientCertificate;
+
+            if (clientCertificate.IsEmpty)
+            {
+                state.keyExchange.SkipClientCredentials();
+            }
+            else
+            {
+
+                /*
+                 * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request
+                 * message was non-empty, one of the certificates in the certificate chain SHOULD be
+                 * issued by one of the listed CAs.
+                 */
+
+                state.clientCertificateType = TlsUtilities.GetClientCertificateType(clientCertificate,
+                    state.serverCredentials.Certificate);
+
+                state.keyExchange.ProcessClientCertificate(clientCertificate);
+            }
+
+            /*
+             * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its
+             * discretion either continue the handshake without client authentication, or respond with a
+             * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+             * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+             * discretion either continue the handshake (considering the client unauthenticated) or send
+             * a fatal alert.
+             */
+            state.server.NotifyClientCertificate(clientCertificate);
+        }
+
+        protected virtual void ProcessClientCertificate(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            Certificate clientCertificate = Certificate.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            NotifyClientCertificate(state, clientCertificate);
+        }
+
+        protected virtual void ProcessCertificateVerify(ServerHandshakeState state, byte[] body, TlsHandshakeHash prepareFinishHash)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            TlsServerContextImpl context = state.serverContext;
+            DigitallySigned clientCertificateVerify = DigitallySigned.Parse(context, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            // Verify the CertificateVerify message contains a correct signature.
+            bool verified = false;
+            try
+            {
+                byte[] hash;
+                if (TlsUtilities.IsTlsV12(context))
+                {
+                    hash = prepareFinishHash.GetFinalHash(clientCertificateVerify.Algorithm.Hash);
+                }
+                else
+                {
+                    hash = context.SecurityParameters.SessionHash;
+                }
+
+                X509CertificateStructure x509Cert = state.clientCertificate.GetCertificateAt(0);
+                SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo;
+                AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo);
+
+                TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)state.clientCertificateType);
+                tlsSigner.Init(context);
+                verified = tlsSigner.VerifyRawSignature(clientCertificateVerify.Algorithm,
+                    clientCertificateVerify.Signature, publicKey, hash);
+            }
+            catch (Exception)
+            {
+            }
+
+            if (!verified)
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+        }
+
+        protected virtual void ProcessClientHello(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            // TODO Read RFCs for guidance on the expected record layer version number
+            ProtocolVersion client_version = TlsUtilities.ReadVersion(buf);
+            if (!client_version.IsDtls)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            /*
+             * Read the client random
+             */
+            byte[] client_random = TlsUtilities.ReadFully(32, buf);
+
+            byte[] sessionID = TlsUtilities.ReadOpaque8(buf);
+            if (sessionID.Length > 32)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347
+            byte[] cookie = TlsUtilities.ReadOpaque8(buf);
+
+            int cipher_suites_length = TlsUtilities.ReadUint16(buf);
+            if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
+            {
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            /*
+             * NOTE: "If the session_id field is not empty (implying a session resumption request) this
+             * vector must include at least the cipher_suite from that session."
+             */
+            state.offeredCipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, buf);
+
+            int compression_methods_length = TlsUtilities.ReadUint8(buf);
+            if (compression_methods_length < 1)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            state.offeredCompressionMethods = TlsUtilities.ReadUint8Array(compression_methods_length, buf);
+
+            /*
+             * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+             * extensions appearing in the client hello, and send a server hello containing no
+             * extensions.
+             */
+            state.clientExtensions = TlsProtocol.ReadExtensions(buf);
+
+            TlsServerContextImpl context = state.serverContext;
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(state.clientExtensions);
+
+            context.SetClientVersion(client_version);
+
+            state.server.NotifyClientVersion(client_version);
+            state.server.NotifyFallback(Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV));
+
+            securityParameters.clientRandom = client_random;
+
+            state.server.NotifyOfferedCipherSuites(state.offeredCipherSuites);
+            state.server.NotifyOfferedCompressionMethods(state.offeredCompressionMethods);
+
+            /*
+             * RFC 5746 3.6. Server Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+                 * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+                 * ClientHello. Including both is NOT RECOMMENDED.
+                 */
+
+                /*
+                 * When a ClientHello is received, the server MUST check if it includes the
+                 * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag
+                 * to TRUE.
+                 */
+                if (Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+                {
+                    state.secure_renegotiation = true;
+                }
+
+                /*
+                 * The server MUST check if the "renegotiation_info" extension is included in the
+                 * ClientHello.
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions, ExtensionType.renegotiation_info);
+                if (renegExtData != null)
+                {
+                    /*
+                     * If the extension is present, set secure_renegotiation flag to TRUE. The
+                     * server MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake.
+                     */
+                    state.secure_renegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(renegExtData, TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+            }
+
+            state.server.NotifySecureRenegotiation(state.secure_renegotiation);
+
+            if (state.clientExtensions != null)
+            {
+                state.server.ProcessClientExtensions(state.clientExtensions);
+            }
+        }
+
+        protected virtual void ProcessClientKeyExchange(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            state.keyExchange.ProcessClientKeyExchange(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+        }
+
+        protected virtual void ProcessClientSupplementalData(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            IList clientSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf);
+            state.server.ProcessClientSupplementalData(clientSupplementalData);
+        }
+
+        protected virtual bool ExpectCertificateVerifyMessage(ServerHandshakeState state)
+        {
+            return state.clientCertificateType >= 0 && TlsUtilities.HasSigningCapability((byte)state.clientCertificateType);
+        }
+
+        protected internal class ServerHandshakeState
+        {
+            internal TlsServer server = null;
+            internal TlsServerContextImpl serverContext = null;
+            internal int[] offeredCipherSuites;
+            internal byte[] offeredCompressionMethods;
+            internal IDictionary clientExtensions;
+            internal int selectedCipherSuite = -1;
+            internal short selectedCompressionMethod = -1;
+            internal bool secure_renegotiation = false;
+            internal short maxFragmentLength = -1;
+            internal bool allowCertificateStatus = false;
+            internal bool expectSessionTicket = false;
+            internal IDictionary serverExtensions = null;
+            internal TlsKeyExchange keyExchange = null;
+            internal TlsCredentials serverCredentials = null;
+            internal CertificateRequest certificateRequest = null;
+            internal short clientCertificateType = -1;
+            internal Certificate clientCertificate = null;
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/DtlsTransport.cs b/crypto/src/crypto/tls/DtlsTransport.cs
new file mode 100644
index 000000000..5c607336b
--- /dev/null
+++ b/crypto/src/crypto/tls/DtlsTransport.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class DtlsTransport
+        :   DatagramTransport
+    {
+        private readonly DtlsRecordLayer mRecordLayer;
+
+        internal DtlsTransport(DtlsRecordLayer recordLayer)
+        {
+            this.mRecordLayer = recordLayer;
+        }
+
+        public virtual int GetReceiveLimit()
+        {
+            return mRecordLayer.GetReceiveLimit();
+        }
+
+        public virtual int GetSendLimit()
+        {
+            return mRecordLayer.GetSendLimit();
+        }
+
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            try
+            {
+                return mRecordLayer.Receive(buf, off, len, waitMillis);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                mRecordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (IOException e)
+            {
+                mRecordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                mRecordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            try
+            {
+                mRecordLayer.Send(buf, off, len);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                mRecordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (IOException e)
+            {
+                mRecordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                mRecordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        public virtual void Close()
+        {
+            mRecordLayer.Close();
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/PskTlsClient.cs b/crypto/src/crypto/tls/PskTlsClient.cs
index 6063572a0..1f4b0865c 100644
--- a/crypto/src/crypto/tls/PskTlsClient.cs
+++ b/crypto/src/crypto/tls/PskTlsClient.cs
@@ -3,7 +3,7 @@ using System.Collections;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
-    public abstract class PskTlsClient
+    public class PskTlsClient
         :   AbstractTlsClient
     {
         protected TlsPskIdentity mPskIdentity;
@@ -25,8 +25,8 @@ namespace Org.BouncyCastle.Crypto.Tls
             {
                 CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
                 CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
-                CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
-                CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA
+                CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
+                CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA
             };
         }
 
@@ -124,6 +124,15 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
         }
 
+        public override TlsAuthentication GetAuthentication()
+        {
+            /*
+             * Note: This method is not called unless a server certificate is sent, which may be the
+             * case e.g. for RSA_PSK key exchange.
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
         public override TlsCipher GetCipher()
         {
             switch (mSelectedCipherSuite)
@@ -254,7 +263,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         protected virtual TlsKeyExchange CreatePskKeyExchange(int keyExchange)
         {
-            return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mPskIdentity, null, mNamedCurves,
+            return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mPskIdentity, null, null, mNamedCurves,
                 mClientECPointFormats, mServerECPointFormats);
         }
     }
diff --git a/crypto/src/crypto/tls/PskTlsServer.cs b/crypto/src/crypto/tls/PskTlsServer.cs
new file mode 100644
index 000000000..bdb8b74a5
--- /dev/null
+++ b/crypto/src/crypto/tls/PskTlsServer.cs
@@ -0,0 +1,347 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class PskTlsServer
+        :   AbstractTlsServer
+    {
+        protected TlsPskIdentityManager mPskIdentityManager;
+
+        public PskTlsServer(TlsPskIdentityManager pskIdentityManager)
+            :   this(new DefaultTlsCipherFactory(), pskIdentityManager)
+        {
+        }
+
+        public PskTlsServer(TlsCipherFactory cipherFactory, TlsPskIdentityManager pskIdentityManager)
+            :   base(cipherFactory)
+        {
+            this.mPskIdentityManager = pskIdentityManager;
+        }
+
+        protected virtual TlsEncryptionCredentials GetRsaEncryptionCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected virtual DHParameters GetDHParameters()
+        {
+            return DHStandardGroups.rfc5114_1024_160;
+        }
+
+        protected override int[] GetCipherSuites()
+        {
+            return new int[]
+            {
+                CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
+                CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
+                CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
+                CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA
+            };
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            switch (mSelectedCipherSuite)
+            {
+            case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+
+            case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+
+            case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1:
+                return null;
+
+            case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1:
+                return GetRsaEncryptionCredentials();
+
+            default:
+                /* Note: internal error here; selected a key exchange we don't implement! */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsKeyExchange GetKeyExchange()
+        {
+            switch (mSelectedCipherSuite)
+            {
+            case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+                return CreatePskKeyExchange(KeyExchangeAlgorithm.DHE_PSK);
+
+            case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+                return CreatePskKeyExchange(KeyExchangeAlgorithm.ECDHE_PSK);
+
+            case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1:
+                return CreatePskKeyExchange(KeyExchangeAlgorithm.PSK);
+
+            case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1:
+                return CreatePskKeyExchange(KeyExchangeAlgorithm.RSA_PSK);
+
+            default:
+                /*
+                 * Note: internal error here; the TlsProtocol implementation verifies that the
+                 * server-selected cipher suite was in the list of client-offered cipher suites, so if
+                 * we now can't produce an implementation, we shouldn't have offered it!
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsCipher GetCipher()
+        {
+            switch (mSelectedCipherSuite)
+            {
+            case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.cls_3DES_EDE_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_CBC, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_CBC, MacAlgorithm.hmac_sha384);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_GCM, MacAlgorithm.cls_null);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.ESTREAM_SALSA20, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha1);
+                    
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha256);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha384);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.RC4_128, MacAlgorithm.hmac_sha1);
+
+            case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1:
+            case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1:
+                return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.SALSA20, MacAlgorithm.hmac_sha1);
+
+            default:
+                /*
+                 * Note: internal error here; the TlsProtocol implementation verifies that the
+                 * server-selected cipher suite was in the list of client-offered cipher suites, so if
+                 * we now can't produce an implementation, we shouldn't have offered it!
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        protected TlsKeyExchange CreatePskKeyExchange(int keyExchange)
+        {
+            return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, null, mPskIdentityManager,
+                GetDHParameters(), mNamedCurves, mClientECPointFormats, mServerECPointFormats);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/SecurityParameters.cs b/crypto/src/crypto/tls/SecurityParameters.cs
index 12bb59f22..0f48ee23e 100644
--- a/crypto/src/crypto/tls/SecurityParameters.cs
+++ b/crypto/src/crypto/tls/SecurityParameters.cs
@@ -15,6 +15,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         internal byte[] clientRandom = null;
         internal byte[] serverRandom = null;
         internal byte[] sessionHash = null;
+        internal byte[] pskIdentity = null;
 
         // TODO Keep these internal, since it's maybe not the ideal place for them
         internal short maxFragmentLength = -1;
@@ -87,5 +88,10 @@ namespace Org.BouncyCastle.Crypto.Tls
         {
             get { return sessionHash; }
         }
+
+        public virtual byte[] PskIdentity
+        {
+            get { return pskIdentity; }
+        }
     }
 }
diff --git a/crypto/src/crypto/tls/ServerDHParams.cs b/crypto/src/crypto/tls/ServerDHParams.cs
index 381858854..b09262771 100644
--- a/crypto/src/crypto/tls/ServerDHParams.cs
+++ b/crypto/src/crypto/tls/ServerDHParams.cs
@@ -54,7 +54,8 @@ namespace Org.BouncyCastle.Crypto.Tls
             BigInteger g = TlsDHUtilities.ReadDHParameter(input);
             BigInteger Ys = TlsDHUtilities.ReadDHParameter(input);
 
-            return new ServerDHParams(new DHPublicKeyParameters(Ys, new DHParameters(p, g)));
+            return new ServerDHParams(
+                TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Ys, new DHParameters(p, g))));
         }
     }
 }
diff --git a/crypto/src/crypto/tls/SessionParameters.cs b/crypto/src/crypto/tls/SessionParameters.cs
index c4616ac71..b17e931d7 100644
--- a/crypto/src/crypto/tls/SessionParameters.cs
+++ b/crypto/src/crypto/tls/SessionParameters.cs
@@ -14,6 +14,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             private short mCompressionAlgorithm = -1;
             private byte[] mMasterSecret = null;
             private Certificate mPeerCertificate = null;
+            private byte[] mPskIdentity = null;
             private byte[] mEncodedServerExtensions = null;
 
             public Builder()
@@ -26,7 +27,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                 Validate(this.mCompressionAlgorithm >= 0, "compressionAlgorithm");
                 Validate(this.mMasterSecret != null, "masterSecret");
                 return new SessionParameters(mCipherSuite, (byte)mCompressionAlgorithm, mMasterSecret, mPeerCertificate,
-                    mEncodedServerExtensions);
+                    mPskIdentity, mEncodedServerExtensions);
             }
 
             public Builder SetCipherSuite(int cipherSuite)
@@ -53,6 +54,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 return this;
             }
 
+            public Builder SetPskIdentity(byte[] pskIdentity)
+            {
+                this.mPskIdentity = pskIdentity;
+                return this;
+            }
+
             public Builder SetServerExtensions(IDictionary serverExtensions)
             {
                 if (serverExtensions == null)
@@ -79,15 +86,17 @@ namespace Org.BouncyCastle.Crypto.Tls
         private byte mCompressionAlgorithm;
         private byte[] mMasterSecret;
         private Certificate mPeerCertificate;
+        private byte[] mPskIdentity;
         private byte[] mEncodedServerExtensions;
 
         private SessionParameters(int cipherSuite, byte compressionAlgorithm, byte[] masterSecret,
-            Certificate peerCertificate, byte[] encodedServerExtensions)
+            Certificate peerCertificate, byte[] pskIdentity, byte[] encodedServerExtensions)
         {
             this.mCipherSuite = cipherSuite;
             this.mCompressionAlgorithm = compressionAlgorithm;
             this.mMasterSecret = Arrays.Clone(masterSecret);
             this.mPeerCertificate = peerCertificate;
+            this.mPskIdentity = Arrays.Clone(pskIdentity);
             this.mEncodedServerExtensions = encodedServerExtensions;
         }
 
@@ -102,7 +111,7 @@ namespace Org.BouncyCastle.Crypto.Tls
         public SessionParameters Copy()
         {
             return new SessionParameters(mCipherSuite, mCompressionAlgorithm, mMasterSecret, mPeerCertificate,
-                mEncodedServerExtensions);
+                mPskIdentity, mEncodedServerExtensions);
         }
 
         public int CipherSuite
@@ -125,6 +134,11 @@ namespace Org.BouncyCastle.Crypto.Tls
             get { return mPeerCertificate; }
         }
 
+        public byte[] PskIdentity
+        {
+            get { return mPskIdentity; }
+        }
+
         public IDictionary ReadServerExtensions()
         {
             if (mEncodedServerExtensions == null)
diff --git a/crypto/src/crypto/tls/TlsBlockCipher.cs b/crypto/src/crypto/tls/TlsBlockCipher.cs
index 82c0318b2..d81b881fc 100644
--- a/crypto/src/crypto/tls/TlsBlockCipher.cs
+++ b/crypto/src/crypto/tls/TlsBlockCipher.cs
@@ -262,10 +262,19 @@ namespace Org.BouncyCastle.Crypto.Tls
                 byte[] receivedMac = Arrays.CopyOfRange(ciphertext, end - macSize, end);
                 byte[] calculatedMac = mReadMac.CalculateMac(seqNo, type, ciphertext, offset, len - macSize);
 
-                bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac);
-
-                if (badMac)
+                bool badMacEtm = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac);
+                if (badMacEtm)
+                {
+                    /*
+                     * RFC 7366 3. The MAC SHALL be evaluated before any further processing such as
+                     * decryption is performed, and if the MAC verification fails, then processing SHALL
+                     * terminate immediately. For TLS, a fatal bad_record_mac MUST be generated [2]. For
+                     * DTLS, the record MUST be discarded, and a fatal bad_record_mac MAY be generated
+                     * [4]. This immediate response to a bad MAC eliminates any timing channels that may
+                     * be available through the use of manipulated packet data.
+                     */
                     throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+                }
             }
 
             if (useExplicitIV)
@@ -283,6 +292,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             // If there's anything wrong with the padding, this will return zero
             int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMac ? 0 : macSize);
+            bool badMac = (totalPad == 0);
 
             int dec_output_length = blocks_length - totalPad;
 
@@ -295,14 +305,12 @@ namespace Org.BouncyCastle.Crypto.Tls
                 byte[] calculatedMac = mReadMac.CalculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen,
                     blocks_length - macSize, randomData);
 
-                bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac);
-
-                if (badMac || totalPad == 0)
-                {
-                    throw new TlsFatalAlert(AlertDescription.bad_record_mac);
-                }
+                badMac |= !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac);
             }
 
+            if (badMac)
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+
             return Arrays.CopyOfRange(ciphertext, offset, offset + dec_output_length);
         }
 
diff --git a/crypto/src/crypto/tls/TlsClient.cs b/crypto/src/crypto/tls/TlsClient.cs
index cd5dfad13..116f6a779 100644
--- a/crypto/src/crypto/tls/TlsClient.cs
+++ b/crypto/src/crypto/tls/TlsClient.cs
@@ -27,6 +27,8 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         ProtocolVersion ClientVersion { get; }
 
+        bool IsFallback { get; }
+
         /// <summary>
         /// Get the list of cipher suites that this client supports.
         /// </summary>
diff --git a/crypto/src/crypto/tls/TlsClientProtocol.cs b/crypto/src/crypto/tls/TlsClientProtocol.cs
index 9fe50add8..19e7d71aa 100644
--- a/crypto/src/crypto/tls/TlsClientProtocol.cs
+++ b/crypto/src/crypto/tls/TlsClientProtocol.cs
@@ -607,7 +607,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             int selectedCipherSuite = TlsUtilities.ReadUint16(buf);
             if (!Arrays.Contains(this.mOfferedCipherSuites, selectedCipherSuite)
                 || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
-                || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+                || CipherSuite.IsScsv(selectedCipherSuite)
                 || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version))
             {
                 throw new TlsFatalAlert(AlertDescription.illegal_parameter);
@@ -815,6 +815,8 @@ namespace Org.BouncyCastle.Crypto.Tls
                 }
             }
 
+            bool fallback = this.mTlsClient.IsFallback;
+
             this.mOfferedCipherSuites = this.mTlsClient.GetCipherSuites();
 
             this.mOfferedCompressionMethods = this.mTlsClient.GetCompressionMethods();
@@ -850,9 +852,9 @@ namespace Org.BouncyCastle.Crypto.Tls
                 byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, ExtensionType.renegotiation_info);
                 bool noRenegExt = (null == renegExtData);
 
-                bool noSCSV = !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                bool noRenegScsv = !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
 
-                if (noRenegExt && noSCSV)
+                if (noRenegExt && noRenegScsv)
                 {
                     // TODO Consider whether to default to a client extension instead
     //                this.mClientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(this.mClientExtensions);
@@ -860,6 +862,17 @@ namespace Org.BouncyCastle.Crypto.Tls
                     this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
                 }
 
+                /*
+                 * draft-ietf-tls-downgrade-scsv-00 4. If a client sends a ClientHello.client_version
+                 * containing a lower value than the latest (highest-valued) version supported by the
+                 * client, it SHOULD include the TLS_FALLBACK_SCSV cipher suite value in
+                 * ClientHello.cipher_suites.
+                 */
+                if (fallback && !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV))
+                {
+                    this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV);
+                }
+
                 TlsUtilities.WriteUint16ArrayWithUint16Length(mOfferedCipherSuites, message);
             }
 
diff --git a/crypto/src/crypto/tls/TlsECDheKeyExchange.cs b/crypto/src/crypto/tls/TlsECDheKeyExchange.cs
index 0644bd44d..b99db0c18 100644
--- a/crypto/src/crypto/tls/TlsECDheKeyExchange.cs
+++ b/crypto/src/crypto/tls/TlsECDheKeyExchange.cs
@@ -34,73 +34,10 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         public override byte[] GenerateServerKeyExchange()
         {
-            /*
-             * First we try to find a supported named curve from the client's list.
-             */
-            int namedCurve = -1;
-            if (mNamedCurves == null)
-            {
-                // TODO Let the peer choose the default named curve
-                namedCurve = NamedCurve.secp256r1;
-            }
-            else
-            {
-                for (int i = 0; i < mNamedCurves.Length; ++i)
-                {
-                    int entry = mNamedCurves[i];
-                    if (NamedCurve.IsValid(entry) && TlsEccUtilities.IsSupportedNamedCurve(entry))
-                    {
-                        namedCurve = entry;
-                        break;
-                    }
-                }
-            }
-
-            ECDomainParameters curve_params = null;
-            if (namedCurve >= 0)
-            {
-                curve_params = TlsEccUtilities.GetParametersForNamedCurve(namedCurve);
-            }
-            else
-            {
-                /*
-                 * If no named curves are suitable, check if the client supports explicit curves.
-                 */
-                if (Arrays.Contains(mNamedCurves, NamedCurve.arbitrary_explicit_prime_curves))
-                {
-                    curve_params = TlsEccUtilities.GetParametersForNamedCurve(NamedCurve.secp256r1);
-                }
-                else if (Arrays.Contains(mNamedCurves, NamedCurve.arbitrary_explicit_char2_curves))
-                {
-                    curve_params = TlsEccUtilities.GetParametersForNamedCurve(NamedCurve.sect283r1);
-                }
-            }
-
-            if (curve_params == null)
-            {
-                /*
-                 * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find
-                 * a suitable curve.
-                 */
-                throw new TlsFatalAlert(AlertDescription.internal_error);
-            }
-
-            AsymmetricCipherKeyPair kp = TlsEccUtilities.GenerateECKeyPair(context.SecureRandom, curve_params);
-            this.mECAgreePrivateKey = (ECPrivateKeyParameters)kp.Private;
-
             DigestInputBuffer buf = new DigestInputBuffer();
 
-            if (namedCurve < 0)
-            {
-                TlsEccUtilities.WriteExplicitECParameters(mClientECPointFormats, curve_params, buf);
-            }
-            else
-            {
-                TlsEccUtilities.WriteNamedECParameters(namedCurve, buf);
-            }
-
-            ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters)kp.Public;
-            TlsEccUtilities.WriteECPoint(mClientECPointFormats, ecPublicKey.Q, buf);
+            this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(context.SecureRandom, mNamedCurves,
+                mClientECPointFormats, buf);
 
             /*
              * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
diff --git a/crypto/src/crypto/tls/TlsEccUtilities.cs b/crypto/src/crypto/tls/TlsEccUtilities.cs
index 64c3c1593..e938b1685 100644
--- a/crypto/src/crypto/tls/TlsEccUtilities.cs
+++ b/crypto/src/crypto/tls/TlsEccUtilities.cs
@@ -246,6 +246,14 @@ namespace Org.BouncyCastle.Crypto.Tls
             case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
 
             /*
+             * RFC 7251
+             */
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
+
+            /*
              * draft-agl-tls-chacha20poly1305-04
              */
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
@@ -427,6 +435,69 @@ namespace Org.BouncyCastle.Crypto.Tls
             return (ECPrivateKeyParameters)kp.Private;
         }
 
+        // TODO Refactor around ServerECDHParams before making this public
+        internal static ECPrivateKeyParameters GenerateEphemeralServerKeyExchange(SecureRandom random, int[] namedCurves,
+            byte[] ecPointFormats, Stream output)
+        {
+            /* First we try to find a supported named curve from the client's list. */
+            int namedCurve = -1;
+            if (namedCurves == null)
+            {
+                // TODO Let the peer choose the default named curve
+                namedCurve = NamedCurve.secp256r1;
+            }
+            else
+            {
+                for (int i = 0; i < namedCurves.Length; ++i)
+                {
+                    int entry = namedCurves[i];
+                    if (NamedCurve.IsValid(entry) && IsSupportedNamedCurve(entry))
+                    {
+                        namedCurve = entry;
+                        break;
+                    }
+                }
+            }
+
+            ECDomainParameters ecParams = null;
+            if (namedCurve >= 0)
+            {
+                ecParams = GetParametersForNamedCurve(namedCurve);
+            }
+            else
+            {
+                /* If no named curves are suitable, check if the client supports explicit curves. */
+                if (Arrays.Contains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves))
+                {
+                    ecParams = GetParametersForNamedCurve(NamedCurve.secp256r1);
+                }
+                else if (Arrays.Contains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves))
+                {
+                    ecParams = GetParametersForNamedCurve(NamedCurve.sect283r1);
+                }
+            }
+
+            if (ecParams == null)
+            {
+                /*
+                 * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find
+                 * a suitable curve.
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            if (namedCurve < 0)
+            {
+                WriteExplicitECParameters(ecPointFormats, ecParams, output);
+            }
+            else
+            {
+                WriteNamedECParameters(namedCurve, output);
+            }
+
+            return GenerateEphemeralClientKeyExchange(random, ecPointFormats, ecParams, output);
+        }
+
         public static ECPublicKeyParameters ValidateECPublicKey(ECPublicKeyParameters key)
         {
             // TODO Check RFC 4492 for validation
diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs
index 8ba156952..09838a717 100644
--- a/crypto/src/crypto/tls/TlsProtocol.cs
+++ b/crypto/src/crypto/tls/TlsProtocol.cs
@@ -956,7 +956,11 @@ namespace Org.BouncyCastle.Crypto.Tls
             case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
             case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
diff --git a/crypto/src/crypto/tls/TlsPskIdentityManager.cs b/crypto/src/crypto/tls/TlsPskIdentityManager.cs
new file mode 100644
index 000000000..a72c2299c
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsPskIdentityManager.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public interface TlsPskIdentityManager
+    {
+        byte[] GetHint();
+
+        byte[] GetPsk(byte[] identity);
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsPskKeyExchange.cs b/crypto/src/crypto/tls/TlsPskKeyExchange.cs
index cd13e3438..a8d0867ef 100644
--- a/crypto/src/crypto/tls/TlsPskKeyExchange.cs
+++ b/crypto/src/crypto/tls/TlsPskKeyExchange.cs
@@ -4,7 +4,10 @@ using System.IO;
 
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.Tls
 {
@@ -13,22 +16,29 @@ namespace Org.BouncyCastle.Crypto.Tls
         :   AbstractTlsKeyExchange
     {
         protected TlsPskIdentity mPskIdentity;
+        protected TlsPskIdentityManager mPskIdentityManager;
+
         protected DHParameters mDHParameters;
         protected int[] mNamedCurves;
         protected byte[] mClientECPointFormats, mServerECPointFormats;
 
         protected byte[] mPskIdentityHint = null;
+        protected byte[] mPsk = null;
 
         protected DHPrivateKeyParameters mDHAgreePrivateKey = null;
         protected DHPublicKeyParameters mDHAgreePublicKey = null;
 
+        protected ECPrivateKeyParameters mECAgreePrivateKey = null;
+        protected ECPublicKeyParameters mECAgreePublicKey = null;
+
         protected AsymmetricKeyParameter mServerPublicKey = null;
         protected RsaKeyParameters mRsaServerPublicKey = null;
         protected TlsEncryptionCredentials mServerCredentials = null;
         protected byte[] mPremasterSecret;
 
         public TlsPskKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, TlsPskIdentity pskIdentity,
-            DHParameters dhParameters, int[] namedCurves, byte[] clientECPointFormats, byte[] serverECPointFormats)
+            TlsPskIdentityManager pskIdentityManager, DHParameters dhParameters, int[] namedCurves,
+            byte[] clientECPointFormats, byte[] serverECPointFormats)
             :   base(keyExchange, supportedSignatureAlgorithms)
         {
             switch (keyExchange)
@@ -43,6 +53,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
 
             this.mPskIdentity = pskIdentity;
+            this.mPskIdentityManager = pskIdentityManager;
             this.mDHParameters = dhParameters;
             this.mNamedCurves = namedCurves;
             this.mClientECPointFormats = clientECPointFormats;
@@ -67,8 +78,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 
         public override byte[] GenerateServerKeyExchange()
         {
-            // TODO[RFC 4279] Need a server-side PSK API to determine hint and resolve identities to keys
-            this.mPskIdentityHint = null;
+            this.mPskIdentityHint = mPskIdentityManager.GetHint();
 
             if (this.mPskIdentityHint == null && !RequiresServerKeyExchange)
                 return null;
@@ -94,7 +104,8 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
             else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
             {
-                // TODO[RFC 5489]
+                this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(context.SecureRandom,
+                    mNamedCurves, mClientECPointFormats, buf);
             }
 
             return buf.ToArray();
@@ -157,7 +168,12 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
             else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
             {
-                // TODO[RFC 5489]
+                ECDomainParameters ecParams = TlsEccUtilities.ReadECParameters(mNamedCurves, mClientECPointFormats, input);
+
+                byte[] point = TlsUtilities.ReadOpaque8(input);
+
+                this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey(
+                    mClientECPointFormats, ecParams, point));
             }
         }
 
@@ -183,9 +199,17 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
 
             byte[] psk_identity = mPskIdentity.GetPskIdentity();
+            if (psk_identity == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.mPsk = mPskIdentity.GetPsk();
+            if (mPsk == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
 
             TlsUtilities.WriteOpaque16(psk_identity, output);
 
+            context.SecurityParameters.pskIdentity = psk_identity;
+
             if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK)
             {
                 this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralClientKeyExchange(context.SecureRandom,
@@ -193,8 +217,8 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
             else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
             {
-                // TODO[RFC 5489]
-                throw new TlsFatalAlert(AlertDescription.internal_error);
+                this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralClientKeyExchange(context.SecureRandom,
+                    mServerECPointFormats, mECAgreePublicKey.Parameters, output);
             }
             else if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK)
             {
@@ -203,14 +227,59 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
         }
 
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            byte[] psk_identity = TlsUtilities.ReadOpaque16(input);
+
+            this.mPsk = mPskIdentityManager.GetPsk(psk_identity);
+            if (mPsk == null)
+                throw new TlsFatalAlert(AlertDescription.unknown_psk_identity);
+
+            context.SecurityParameters.pskIdentity = psk_identity;
+
+            if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK)
+            {
+                BigInteger Yc = TlsDHUtilities.ReadDHParameter(input);
+
+                this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Yc, mDHParameters));
+            }
+            else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+            {
+                byte[] point = TlsUtilities.ReadOpaque8(input);
+
+                ECDomainParameters curve_params = this.mECAgreePrivateKey.Parameters;
+
+                this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey(
+                    mServerECPointFormats, curve_params, point));
+            }
+            else if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK)
+            {
+                byte[] encryptedPreMasterSecret;
+                if (TlsUtilities.IsSsl(context))
+                {
+                    // TODO Do any SSLv3 clients actually include the length?
+                    encryptedPreMasterSecret = Streams.ReadAll(input);
+                }
+                else
+                {
+                    encryptedPreMasterSecret = TlsUtilities.ReadOpaque16(input);
+                }
+
+                this.mPremasterSecret = mServerCredentials.DecryptPreMasterSecret(encryptedPreMasterSecret);
+            }
+        }
+
         public override byte[] GeneratePremasterSecret()
         {
-            byte[] psk = mPskIdentity.GetPsk();
-            byte[] other_secret = GenerateOtherSecret(psk.Length);
+            byte[] other_secret = GenerateOtherSecret(mPsk.Length);
 
-            MemoryStream buf = new MemoryStream(4 + other_secret.Length + psk.Length);
+            MemoryStream buf = new MemoryStream(4 + other_secret.Length + mPsk.Length);
             TlsUtilities.WriteOpaque16(other_secret, buf);
-            TlsUtilities.WriteOpaque16(psk, buf);
+            TlsUtilities.WriteOpaque16(mPsk, buf);
+
+            Arrays.Fill(mPsk, (byte)0);
+            this.mPsk = null;
+
             return buf.ToArray();
         }
 
@@ -228,7 +297,11 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
             {
-                // TODO[RFC 5489]
+                if (mECAgreePrivateKey != null)
+                {
+                    return TlsEccUtilities.CalculateECDHBasicAgreement(mECAgreePublicKey, mECAgreePrivateKey);
+                }
+
                 throw new TlsFatalAlert(AlertDescription.internal_error);
             }
 
diff --git a/crypto/src/crypto/tls/TlsServer.cs b/crypto/src/crypto/tls/TlsServer.cs
index 93e62b9ac..e791f93a9 100644
--- a/crypto/src/crypto/tls/TlsServer.cs
+++ b/crypto/src/crypto/tls/TlsServer.cs
@@ -13,6 +13,9 @@ namespace Org.BouncyCastle.Crypto.Tls
         void NotifyClientVersion(ProtocolVersion clientVersion);
 
         /// <exception cref="IOException"></exception>
+        void NotifyFallback(bool isFallback);
+
+        /// <exception cref="IOException"></exception>
         void NotifyOfferedCipherSuites(int[] offeredCipherSuites);
 
         /// <exception cref="IOException"></exception>
diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs
index 165d6a147..b1fb830b6 100644
--- a/crypto/src/crypto/tls/TlsServerProtocol.cs
+++ b/crypto/src/crypto/tls/TlsServerProtocol.cs
@@ -453,6 +453,8 @@ namespace Org.BouncyCastle.Crypto.Tls
         protected virtual void ReceiveClientHelloMessage(MemoryStream buf)
         {
             ProtocolVersion client_version = TlsUtilities.ReadVersion(buf);
+            mRecordStream.SetWriteVersion(client_version);
+
             if (client_version.IsDtls)
                 throw new TlsFatalAlert(AlertDescription.illegal_parameter);
 
@@ -499,6 +501,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             ContextAdmin.SetClientVersion(client_version);
 
             mTlsServer.NotifyClientVersion(client_version);
+            mTlsServer.NotifyFallback(Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV));
 
             mSecurityParameters.clientRandom = client_random;
 
@@ -626,7 +629,7 @@ namespace Org.BouncyCastle.Crypto.Tls
             int selectedCipherSuite = mTlsServer.GetSelectedCipherSuite();
             if (!Arrays.Contains(mOfferedCipherSuites, selectedCipherSuite)
                 || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
-                || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+                || CipherSuite.IsScsv(selectedCipherSuite)
                 || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version))
             {
                 throw new TlsFatalAlert(AlertDescription.internal_error);
diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs
index d571e5900..485ecb760 100644
--- a/crypto/src/crypto/tls/TlsUtilities.cs
+++ b/crypto/src/crypto/tls/TlsUtilities.cs
@@ -1252,11 +1252,13 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
             case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
             case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
             case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
                 return EncryptionAlgorithm.AES_128_CCM;
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
@@ -1313,11 +1315,13 @@ namespace Org.BouncyCastle.Crypto.Tls
 
             case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
             case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
             case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
             case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
                 return EncryptionAlgorithm.AES_256_CCM;
 
             case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
@@ -1554,8 +1558,12 @@ namespace Org.BouncyCastle.Crypto.Tls
             case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
             case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
             case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
diff --git a/crypto/src/math/ec/ECAlgorithms.cs b/crypto/src/math/ec/ECAlgorithms.cs
index 3c911b173..a1349a9e0 100644
--- a/crypto/src/math/ec/ECAlgorithms.cs
+++ b/crypto/src/math/ec/ECAlgorithms.cs
@@ -117,6 +117,11 @@ namespace Org.BouncyCastle.Math.EC
 
         public static void MontgomeryTrick(ECFieldElement[] zs, int off, int len)
         {
+            MontgomeryTrick(zs, off, len, null);
+        }
+
+        public static void MontgomeryTrick(ECFieldElement[] zs, int off, int len, ECFieldElement scale)
+        {
             /*
              * Uses the "Montgomery Trick" to invert many field elements, with only a single actual
              * field inversion. See e.g. the paper:
@@ -133,7 +138,14 @@ namespace Org.BouncyCastle.Math.EC
                 c[i] = c[i - 1].Multiply(zs[off + i]);
             }
 
-            ECFieldElement u = c[--i].Invert();
+            --i;
+
+            if (scale != null)
+            {
+                c[i] = c[i].Multiply(scale);
+            }
+
+            ECFieldElement u = c[i].Invert();
 
             while (i > 0)
             {
diff --git a/crypto/src/math/ec/ECCurve.cs b/crypto/src/math/ec/ECCurve.cs
index eaa3e0c3d..339d37f7c 100644
--- a/crypto/src/math/ec/ECCurve.cs
+++ b/crypto/src/math/ec/ECCurve.cs
@@ -221,26 +221,56 @@ namespace Org.BouncyCastle.Math.EC
          */
         public virtual void NormalizeAll(ECPoint[] points)
         {
-            CheckPoints(points);
+            NormalizeAll(points, 0, points.Length, null);
+        }
+
+        /**
+         * Normalization ensures that any projective coordinate is 1, and therefore that the x, y
+         * coordinates reflect those of the equivalent point in an affine coordinate system. Where more
+         * than one point is to be normalized, this method will generally be more efficient than
+         * normalizing each point separately. An (optional) z-scaling factor can be applied; effectively
+         * each z coordinate is scaled by this value prior to normalization (but only one
+         * actual multiplication is needed).
+         * 
+         * @param points
+         *            An array of points that will be updated in place with their normalized versions,
+         *            where necessary
+         * @param off
+         *            The start of the range of points to normalize
+         * @param len
+         *            The length of the range of points to normalize
+         * @param iso
+         *            The (optional) z-scaling factor - can be null
+         */
+        public virtual void NormalizeAll(ECPoint[] points, int off, int len, ECFieldElement iso)
+        {
+            CheckPoints(points, off, len);
 
-            if (this.CoordinateSystem == ECCurve.COORD_AFFINE)
+            switch (this.CoordinateSystem)
             {
-                return;
+                case ECCurve.COORD_AFFINE:
+                case ECCurve.COORD_LAMBDA_AFFINE:
+                {
+                    if (iso != null)
+                        throw new ArgumentException("not valid for affine coordinates", "iso");
+
+                    return;
+                }
             }
 
             /*
              * Figure out which of the points actually need to be normalized
              */
-            ECFieldElement[] zs = new ECFieldElement[points.Length];
-            int[] indices = new int[points.Length];
+            ECFieldElement[] zs = new ECFieldElement[len];
+            int[] indices = new int[len];
             int count = 0;
-            for (int i = 0; i < points.Length; ++i)
+            for (int i = 0; i < len; ++i)
             {
-                ECPoint p = points[i];
-                if (null != p && !p.IsNormalized())
+                ECPoint p = points[off + i];
+                if (null != p && (iso != null || !p.IsNormalized()))
                 {
                     zs[count] = p.GetZCoord(0);
-                    indices[count++] = i;
+                    indices[count++] = off + i;
                 }
             }
 
@@ -249,7 +279,7 @@ namespace Org.BouncyCastle.Math.EC
                 return;
             }
 
-            ECAlgorithms.MontgomeryTrick(zs, 0, count);
+            ECAlgorithms.MontgomeryTrick(zs, 0, count, iso);
 
             for (int j = 0; j < count; ++j)
             {
@@ -298,12 +328,19 @@ namespace Org.BouncyCastle.Math.EC
 
         protected virtual void CheckPoints(ECPoint[] points)
         {
+            CheckPoints(points, 0, points.Length);
+        }
+
+        protected virtual void CheckPoints(ECPoint[] points, int off, int len)
+        {
             if (points == null)
                 throw new ArgumentNullException("points");
+            if (off < 0 || len < 0 || (off > (points.Length - len)))
+                throw new ArgumentException("invalid range specified", "points");
 
-            for (int i = 0; i < points.Length; ++i)
+            for (int i = 0; i < len; ++i)
             {
-                ECPoint point = points[i];
+                ECPoint point = points[off + i];
                 if (null != point && this != point.Curve)
                     throw new ArgumentException("entries must be null or on this curve", "points");
             }
diff --git a/crypto/src/math/ec/multiplier/WNafUtilities.cs b/crypto/src/math/ec/multiplier/WNafUtilities.cs
index 865b9073e..5491297d7 100644
--- a/crypto/src/math/ec/multiplier/WNafUtilities.cs
+++ b/crypto/src/math/ec/multiplier/WNafUtilities.cs
@@ -10,6 +10,7 @@ namespace Org.BouncyCastle.Math.EC.Multiplier
 
         private static readonly byte[] EMPTY_BYTES = new byte[0];
         private static readonly int[] EMPTY_INTS = new int[0];
+        private static readonly ECPoint[] EMPTY_POINTS = new ECPoint[0];
 
         public static int[] GenerateCompactNaf(BigInteger k)
         {
@@ -368,46 +369,100 @@ namespace Org.BouncyCastle.Math.EC.Multiplier
         {
             ECCurve c = p.Curve;
             WNafPreCompInfo wnafPreCompInfo = GetWNafPreCompInfo(c.GetPreCompInfo(p, PRECOMP_NAME));
-            
+
+            int iniPreCompLen = 0, reqPreCompLen = 1 << System.Math.Max(0, width - 2);
+
             ECPoint[] preComp = wnafPreCompInfo.PreComp;
             if (preComp == null)
             {
-                preComp = new ECPoint[]{ p };
+                preComp = EMPTY_POINTS;
+            }
+            else
+            {
+                iniPreCompLen = preComp.Length;
             }
 
-            int preCompLen = preComp.Length;
-            int reqPreCompLen = 1 << System.Math.Max(0, width - 2);
-
-            if (preCompLen < reqPreCompLen)
+            if (iniPreCompLen < reqPreCompLen)
             {
                 preComp = ResizeTable(preComp, reqPreCompLen);
-                if (reqPreCompLen == 2)
+
+                if (reqPreCompLen == 1)
                 {
-                    preComp[1] = preComp[0].ThreeTimes();
+                    preComp[0] = p.Normalize();
                 }
                 else
                 {
-                    ECPoint twiceP = wnafPreCompInfo.Twice;
-                    if (twiceP == null)
+                    int curPreCompLen = iniPreCompLen;
+                    if (curPreCompLen == 0)
                     {
-                        twiceP = preComp[0].Twice();
-                        wnafPreCompInfo.Twice = twiceP;
+                        preComp[0] = p;
+                        curPreCompLen = 1;
                     }
 
-                    for (int i = preCompLen; i < reqPreCompLen; i++)
+                    ECFieldElement iso = null;
+
+                    if (reqPreCompLen == 2)
                     {
-                        /*
-                         * Compute the new ECPoints for the precomputation array. The values 1, 3, 5, ...,
-                         * 2^(width-1)-1 times p are computed
-                         */
-                        preComp[i] = twiceP.Add(preComp[i - 1]);
+                        preComp[1] = p.ThreeTimes();
+                    }
+                    else
+                    {
+                        ECPoint twiceP = wnafPreCompInfo.Twice, last = preComp[curPreCompLen - 1];
+                        if (twiceP == null)
+                        {
+                            twiceP = preComp[0].Twice();
+                            wnafPreCompInfo.Twice = twiceP;
+
+                            /*
+                             * For Fp curves with Jacobian projective coordinates, use a (quasi-)isomorphism
+                             * where 'twiceP' is "affine", so that the subsequent additions are cheaper. This
+                             * also requires scaling the initial point's X, Y coordinates, and reversing the
+                             * isomorphism as part of the subsequent normalization.
+                             * 
+                             *  NOTE: The correctness of this optimization depends on:
+                             *      1) additions do not use the curve's A, B coefficients.
+                             *      2) no special cases (i.e. Q +/- Q) when calculating 1P, 3P, 5P, ...
+                             */
+                            if (ECAlgorithms.IsFpCurve(c) && c.FieldSize >= 64)
+                            {
+                                switch (c.CoordinateSystem)
+                                {
+                                    case ECCurve.COORD_JACOBIAN:
+                                    case ECCurve.COORD_JACOBIAN_CHUDNOVSKY:
+                                    case ECCurve.COORD_JACOBIAN_MODIFIED:
+                                    {
+                                        iso = twiceP.GetZCoord(0);
+                                        twiceP = c.CreatePoint(twiceP.XCoord.ToBigInteger(),
+                                            twiceP.YCoord.ToBigInteger());
+
+                                        ECFieldElement iso2 = iso.Square(), iso3 = iso2.Multiply(iso);
+                                        last = last.ScaleX(iso2).ScaleY(iso3);
+
+                                        if (iniPreCompLen == 0)
+                                        {
+                                            preComp[0] = last;
+                                        }
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+
+                        while (curPreCompLen < reqPreCompLen)
+                        {
+                            /*
+                             * Compute the new ECPoints for the precomputation array. The values 1, 3,
+                             * 5, ..., 2^(width-1)-1 times p are computed
+                             */
+                            preComp[curPreCompLen++] = last = last.Add(twiceP);
+                        }
                     }
-                }
 
-                /*
-                 * Having oft-used operands in affine form makes operations faster.
-                 */
-                c.NormalizeAll(preComp);
+                    /*
+                     * Having oft-used operands in affine form makes operations faster.
+                     */
+                    c.NormalizeAll(preComp, iniPreCompLen, reqPreCompLen - iniPreCompLen, iso);
+                }
             }
 
             wnafPreCompInfo.PreComp = preComp;
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs
index b0720146c..c6b2e9e0e 100644
--- a/crypto/src/openpgp/PgpPublicKey.cs
+++ b/crypto/src/openpgp/PgpPublicKey.cs
@@ -11,22 +11,22 @@ using Org.BouncyCastle.Utilities.Collections;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>General class to handle a PGP public key object.</remarks>
+    /// <remarks>General class to handle a PGP public key object.</remarks>
     public class PgpPublicKey
     {
-		private static readonly int[] MasterKeyCertificationTypes = new int[]
-		{
-			PgpSignature.PositiveCertification,
-			PgpSignature.CasualCertification,
-			PgpSignature.NoCertification,
-			PgpSignature.DefaultCertification
-		};
-
-		private long				keyId;
+        private static readonly int[] MasterKeyCertificationTypes = new int[]
+        {
+            PgpSignature.PositiveCertification,
+            PgpSignature.CasualCertification,
+            PgpSignature.NoCertification,
+            PgpSignature.DefaultCertification
+        };
+
+        private long				keyId;
         private byte[]				fingerprint;
         private int					keyStrength;
 
-		internal PublicKeyPacket	publicPk;
+        internal PublicKeyPacket	publicPk;
         internal TrustPacket		trustPk;
         internal IList			    keySigs = Platform.CreateArrayList();
         internal IList			    ids = Platform.CreateArrayList();
@@ -34,45 +34,45 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         internal IList              idSigs = Platform.CreateArrayList();
         internal IList			    subSigs;
 
-		private void Init()
+        private void Init()
         {
             IBcpgKey key = publicPk.Key;
 
-			if (publicPk.Version <= 3)
+            if (publicPk.Version <= 3)
             {
                 RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
 
-				this.keyId = rK.Modulus.LongValue;
+                this.keyId = rK.Modulus.LongValue;
 
-				try
+                try
                 {
                     IDigest digest = DigestUtilities.GetDigest("MD5");
 
-					byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
-					digest.BlockUpdate(bytes, 0, bytes.Length);
+                    byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
+                    digest.BlockUpdate(bytes, 0, bytes.Length);
 
-					bytes = rK.PublicExponent.ToByteArrayUnsigned();
-					digest.BlockUpdate(bytes, 0, bytes.Length);
+                    bytes = rK.PublicExponent.ToByteArrayUnsigned();
+                    digest.BlockUpdate(bytes, 0, bytes.Length);
 
-					this.fingerprint = DigestUtilities.DoFinal(digest);
+                    this.fingerprint = DigestUtilities.DoFinal(digest);
                 }
-				//catch (NoSuchAlgorithmException)
-				catch (Exception e)
+                //catch (NoSuchAlgorithmException)
+                catch (Exception e)
                 {
                     throw new IOException("can't find MD5", e);
                 }
 
-				this.keyStrength = rK.Modulus.BitLength;
+                this.keyStrength = rK.Modulus.BitLength;
             }
             else
             {
                 byte[] kBytes = publicPk.GetEncodedContents();
 
-				try
+                try
                 {
                     IDigest digest = DigestUtilities.GetDigest("SHA1");
 
-					digest.Update(0x99);
+                    digest.Update(0x99);
                     digest.Update((byte)(kBytes.Length >> 8));
                     digest.Update((byte)kBytes.Length);
                     digest.BlockUpdate(kBytes, 0, kBytes.Length);
@@ -83,7 +83,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     throw new IOException("can't find SHA1", e);
                 }
 
-				this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
+                this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
                     | ((ulong)fingerprint[fingerprint.Length - 7] << 48)
                     | ((ulong)fingerprint[fingerprint.Length - 6] << 40)
                     | ((ulong)fingerprint[fingerprint.Length - 5] << 32)
@@ -92,7 +92,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     | ((ulong)fingerprint[fingerprint.Length - 2] << 8)
                     | (ulong)fingerprint[fingerprint.Length - 1]);
 
-				if (key is RsaPublicBcpgKey)
+                if (key is RsaPublicBcpgKey)
                 {
                     this.keyStrength = ((RsaPublicBcpgKey)key).Modulus.BitLength;
                 }
@@ -107,57 +107,57 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		/// <summary>
-		/// Create a PgpPublicKey from the passed in lightweight one.
-		/// </summary>
-		/// <remarks>
-		/// Note: the time passed in affects the value of the key's keyId, so you probably only want
-		/// to do this once for a lightweight key, or make sure you keep track of the time you used.
-		/// </remarks>
-		/// <param name="algorithm">Asymmetric algorithm type representing the public key.</param>
-		/// <param name="pubKey">Actual public key to associate.</param>
-		/// <param name="time">Date of creation.</param>
-		/// <exception cref="ArgumentException">If <c>pubKey</c> is not public.</exception>
-		/// <exception cref="PgpException">On key creation problem.</exception>
+        /// <summary>
+        /// Create a PgpPublicKey from the passed in lightweight one.
+        /// </summary>
+        /// <remarks>
+        /// Note: the time passed in affects the value of the key's keyId, so you probably only want
+        /// to do this once for a lightweight key, or make sure you keep track of the time you used.
+        /// </remarks>
+        /// <param name="algorithm">Asymmetric algorithm type representing the public key.</param>
+        /// <param name="pubKey">Actual public key to associate.</param>
+        /// <param name="time">Date of creation.</param>
+        /// <exception cref="ArgumentException">If <c>pubKey</c> is not public.</exception>
+        /// <exception cref="PgpException">On key creation problem.</exception>
         public PgpPublicKey(
             PublicKeyAlgorithmTag	algorithm,
             AsymmetricKeyParameter	pubKey,
             DateTime				time)
         {
-			if (pubKey.IsPrivate)
-				throw new ArgumentException("Expected a public key", "pubKey");
+            if (pubKey.IsPrivate)
+                throw new ArgumentException("Expected a public key", "pubKey");
 
-			IBcpgKey bcpgKey;
+            IBcpgKey bcpgKey;
             if (pubKey is RsaKeyParameters)
             {
                 RsaKeyParameters rK = (RsaKeyParameters) pubKey;
 
-				bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent);
+                bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent);
             }
             else if (pubKey is DsaPublicKeyParameters)
             {
                 DsaPublicKeyParameters dK = (DsaPublicKeyParameters) pubKey;
                 DsaParameters dP = dK.Parameters;
 
-				bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
+                bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
             }
             else if (pubKey is ElGamalPublicKeyParameters)
             {
                 ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
                 ElGamalParameters eS = eK.Parameters;
 
-				bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
+                bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
             }
             else
             {
                 throw new PgpException("unknown key class");
             }
 
-			this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
+            this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
             this.ids = Platform.CreateArrayList();
             this.idSigs = Platform.CreateArrayList();
 
-			try
+            try
             {
                 Init();
             }
@@ -167,7 +167,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		/// <summary>Constructor for a sub-key.</summary>
+        /// <summary>Constructor for a sub-key.</summary>
         internal PgpPublicKey(
             PublicKeyPacket	publicPk,
             TrustPacket		trustPk,
@@ -177,10 +177,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             this.trustPk = trustPk;
             this.subSigs = sigs;
 
-			Init();
+            Init();
         }
 
-		internal PgpPublicKey(
+        internal PgpPublicKey(
             PgpPublicKey	key,
             TrustPacket		trust,
             IList           subSigs)
@@ -189,19 +189,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             this.trustPk = trust;
             this.subSigs = subSigs;
 
-			this.fingerprint = key.fingerprint;
+            this.fingerprint = key.fingerprint;
             this.keyId = key.keyId;
             this.keyStrength = key.keyStrength;
         }
 
-		/// <summary>Copy constructor.</summary>
-		/// <param name="pubKey">The public key to copy.</param>
+        /// <summary>Copy constructor.</summary>
+        /// <param name="pubKey">The public key to copy.</param>
         internal PgpPublicKey(
             PgpPublicKey pubKey)
         {
             this.publicPk = pubKey.publicPk;
 
-			this.keySigs = Platform.CreateArrayList(pubKey.keySigs);
+            this.keySigs = Platform.CreateArrayList(pubKey.keySigs);
             this.ids = Platform.CreateArrayList(pubKey.ids);
             this.idTrusts = Platform.CreateArrayList(pubKey.idTrusts);
             this.idSigs = Platform.CreateArrayList(pubKey.idSigs.Count);
@@ -210,7 +210,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 this.idSigs.Add(Platform.CreateArrayList((IList)pubKey.idSigs[i]));
             }
 
-			if (pubKey.subSigs != null)
+            if (pubKey.subSigs != null)
             {
                 this.subSigs = Platform.CreateArrayList(pubKey.subSigs.Count);
                 for (int i = 0; i != pubKey.subSigs.Count; i++)
@@ -219,12 +219,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             }
 
-			this.fingerprint = pubKey.fingerprint;
+            this.fingerprint = pubKey.fingerprint;
             this.keyId = pubKey.keyId;
             this.keyStrength = pubKey.keyStrength;
         }
 
-		internal PgpPublicKey(
+        internal PgpPublicKey(
             PublicKeyPacket	publicPk,
             TrustPacket		trustPk,
             IList		    keySigs,
@@ -239,10 +239,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             this.idTrusts = idTrusts;
             this.idSigs = idSigs;
 
-			Init();
+            Init();
         }
 
-		internal PgpPublicKey(
+        internal PgpPublicKey(
             PublicKeyPacket	publicPk,
             IList           ids,
             IList           idSigs)
@@ -253,159 +253,160 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             Init();
         }
 
-		/// <summary>The version of this key.</summary>
+        /// <summary>The version of this key.</summary>
         public int Version
         {
-			get { return publicPk.Version; }
+            get { return publicPk.Version; }
         }
 
-		/// <summary>The creation time of this key.</summary>
-		public DateTime CreationTime
+        /// <summary>The creation time of this key.</summary>
+        public DateTime CreationTime
         {
-			get { return publicPk.GetTime(); }
+            get { return publicPk.GetTime(); }
         }
 
-		/// <summary>The number of valid days from creation time - zero means no expiry.</summary>
+        /// <summary>The number of valid days from creation time - zero means no expiry.</summary>
         public int ValidDays
         {
-			get
-			{
-				if (publicPk.Version > 3)
-				{
-					return (int)(GetValidSeconds() / (24 * 60 * 60));
-				}
-
-				return publicPk.ValidDays;
-			}
-        }
-
-		/// <summary>Return the trust data associated with the public key, if present.</summary>
-		/// <returns>A byte array with trust data, null otherwise.</returns>
-		public byte[] GetTrustData()
-		{
-			if (trustPk == null)
-			{
-				return null;
-			}
-
-			return trustPk.GetLevelAndTrustAmount();
-		}
-
-		/// <summary>The number of valid seconds from creation time - zero means no expiry.</summary>
-		public long GetValidSeconds()
-        {
-			if (publicPk.Version > 3)
-			{
-				if (IsMasterKey)
-				{
-					for (int i = 0; i != MasterKeyCertificationTypes.Length; i++)
-					{
-						long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]);
-
-						if (seconds >= 0)
-						{
-							return seconds;
-						}
-					}
-				}
-				else
-				{
-					long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding);
-
-					if (seconds >= 0)
-					{
-						return seconds;
-					}
-				}
-
-				return 0;
-			}
-
-			return (long) publicPk.ValidDays * 24 * 60 * 60;
-        }
-
-		private long GetExpirationTimeFromSig(
-			bool	selfSigned,
-			int		signatureType)
-		{
-			foreach (PgpSignature sig in GetSignaturesOfType(signatureType))
-			{
-				if (!selfSigned || sig.KeyId == KeyId)
-				{
-					PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets();
-
-					if (hashed != null)
-					{
-						return hashed.GetKeyExpirationTime();
-					}
-
-					return 0;
-				}
-			}
-
-			return -1;
-		}
-
-		/// <summary>The keyId associated with the public key.</summary>
+            get
+            {
+                if (publicPk.Version > 3)
+                {
+                    return (int)(GetValidSeconds() / (24 * 60 * 60));
+                }
+
+                return publicPk.ValidDays;
+            }
+        }
+
+        /// <summary>Return the trust data associated with the public key, if present.</summary>
+        /// <returns>A byte array with trust data, null otherwise.</returns>
+        public byte[] GetTrustData()
+        {
+            if (trustPk == null)
+            {
+                return null;
+            }
+
+            return Arrays.Clone(trustPk.GetLevelAndTrustAmount());
+        }
+
+        /// <summary>The number of valid seconds from creation time - zero means no expiry.</summary>
+        public long GetValidSeconds()
+        {
+            if (publicPk.Version > 3)
+            {
+                if (IsMasterKey)
+                {
+                    for (int i = 0; i != MasterKeyCertificationTypes.Length; i++)
+                    {
+                        long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]);
+
+                        if (seconds >= 0)
+                        {
+                            return seconds;
+                        }
+                    }
+                }
+                else
+                {
+                    long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding);
+
+                    if (seconds >= 0)
+                    {
+                        return seconds;
+                    }
+                }
+
+                return 0;
+            }
+
+            return (long) publicPk.ValidDays * 24 * 60 * 60;
+        }
+
+        private long GetExpirationTimeFromSig(
+            bool	selfSigned,
+            int		signatureType)
+        {
+            foreach (PgpSignature sig in GetSignaturesOfType(signatureType))
+            {
+                if (!selfSigned || sig.KeyId == KeyId)
+                {
+                    PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets();
+
+                    if (hashed != null)
+                    {
+                        return hashed.GetKeyExpirationTime();
+                    }
+
+                    return 0;
+                }
+            }
+
+            return -1;
+        }
+
+        /// <summary>The keyId associated with the public key.</summary>
         public long KeyId
         {
             get { return keyId; }
         }
 
-		/// <summary>The fingerprint of the key</summary>
+        /// <summary>The fingerprint of the key</summary>
         public byte[] GetFingerprint()
         {
-			return (byte[]) fingerprint.Clone();
+            return (byte[]) fingerprint.Clone();
         }
 
-		/// <summary>
-		/// Check if this key has an algorithm type that makes it suitable to use for encryption.
-		/// </summary>
-		/// <remarks>
-		/// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
-		/// determining the preferred use of the key.
-		/// </remarks>
-		/// <returns>
-		/// <c>true</c> if this key algorithm is suitable for encryption.
-		/// </returns>
-		public bool IsEncryptionKey
+        /// <summary>
+        /// Check if this key has an algorithm type that makes it suitable to use for encryption.
+        /// </summary>
+        /// <remarks>
+        /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
+        /// determining the preferred use of the key.
+        /// </remarks>
+        /// <returns>
+        /// <c>true</c> if this key algorithm is suitable for encryption.
+        /// </returns>
+        public bool IsEncryptionKey
         {
             get
             {
-				switch (publicPk.Algorithm)
-				{
-					case PublicKeyAlgorithmTag.ElGamalEncrypt:
-					case PublicKeyAlgorithmTag.ElGamalGeneral:
-					case PublicKeyAlgorithmTag.RsaEncrypt:
-					case PublicKeyAlgorithmTag.RsaGeneral:
-						return true;
-					default:
-						return false;
-				}
+                switch (publicPk.Algorithm)
+                {
+                    case PublicKeyAlgorithmTag.ECDH:
+                    case PublicKeyAlgorithmTag.ElGamalEncrypt:
+                    case PublicKeyAlgorithmTag.ElGamalGeneral:
+                    case PublicKeyAlgorithmTag.RsaEncrypt:
+                    case PublicKeyAlgorithmTag.RsaGeneral:
+                        return true;
+                    default:
+                        return false;
+                }
             }
         }
 
-		/// <summary>True, if this is a master key.</summary>
+        /// <summary>True, if this is a master key.</summary>
         public bool IsMasterKey
         {
             get { return subSigs == null; }
         }
 
-		/// <summary>The algorithm code associated with the public key.</summary>
+        /// <summary>The algorithm code associated with the public key.</summary>
         public PublicKeyAlgorithmTag Algorithm
         {
-			get { return publicPk.Algorithm; }
+            get { return publicPk.Algorithm; }
         }
 
-		/// <summary>The strength of the key in bits.</summary>
+        /// <summary>The strength of the key in bits.</summary>
         public int BitStrength
         {
             get { return keyStrength; }
         }
 
-		/// <summary>The public key contained in the object.</summary>
-		/// <returns>A lightweight public key.</returns>
-		/// <exception cref="PgpException">If the key algorithm is not recognised.</exception>
+        /// <summary>The public key contained in the object.</summary>
+        /// <returns>A lightweight public key.</returns>
+        /// <exception cref="PgpException">If the key algorithm is not recognised.</exception>
         public AsymmetricKeyParameter GetKey()
         {
             try
@@ -438,50 +439,50 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-		/// <summary>Allows enumeration of any user IDs associated with the key.</summary>
-		/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
+        /// <summary>Allows enumeration of any user IDs associated with the key.</summary>
+        /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
         public IEnumerable GetUserIds()
         {
             IList temp = Platform.CreateArrayList();
 
-			foreach (object o in ids)
-			{
-				if (o is string)
-				{
-					temp.Add(o);
+            foreach (object o in ids)
+            {
+                if (o is string)
+                {
+                    temp.Add(o);
                 }
             }
 
-			return new EnumerableProxy(temp);
+            return new EnumerableProxy(temp);
         }
 
-		/// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
-		/// <returns>An <c>IEnumerable</c> of <c>PgpUserAttributeSubpacketVector</c> objects.</returns>
+        /// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpUserAttributeSubpacketVector</c> objects.</returns>
         public IEnumerable GetUserAttributes()
         {
             IList temp = Platform.CreateArrayList();
 
-			foreach (object o in ids)
-			{
-				if (o is PgpUserAttributeSubpacketVector)
-				{
-					temp.Add(o);
-				}
-			}
+            foreach (object o in ids)
+            {
+                if (o is PgpUserAttributeSubpacketVector)
+                {
+                    temp.Add(o);
+                }
+            }
 
-			return new EnumerableProxy(temp);
+            return new EnumerableProxy(temp);
         }
 
-		/// <summary>Allows enumeration of any signatures associated with the passed in id.</summary>
-		/// <param name="id">The ID to be matched.</param>
-		/// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+        /// <summary>Allows enumeration of any signatures associated with the passed in id.</summary>
+        /// <param name="id">The ID to be matched.</param>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
         public IEnumerable GetSignaturesForId(
             string id)
         {
-			if (id == null)
-				throw new ArgumentNullException("id");
+            if (id == null)
+                throw new ArgumentNullException("id");
 
-			for (int i = 0; i != ids.Count; i++)
+            for (int i = 0; i != ids.Count; i++)
             {
                 if (id.Equals(ids[i]))
                 {
@@ -489,12 +490,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             }
 
-			return null;
+            return null;
         }
 
-		/// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary>
-		/// <param name="userAttributes">The vector of user attributes to be matched.</param>
-		/// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+        /// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary>
+        /// <param name="userAttributes">The vector of user attributes to be matched.</param>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
         public IEnumerable GetSignaturesForUserAttribute(
             PgpUserAttributeSubpacketVector userAttributes)
         {
@@ -506,18 +507,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             }
 
-			return null;
+            return null;
         }
 
-		/// <summary>Allows enumeration of signatures of the passed in type that are on this key.</summary>
-		/// <param name="signatureType">The type of the signature to be returned.</param>
-		/// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
+        /// <summary>Allows enumeration of signatures of the passed in type that are on this key.</summary>
+        /// <param name="signatureType">The type of the signature to be returned.</param>
+        /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns>
         public IEnumerable GetSignaturesOfType(
             int signatureType)
         {
             IList temp = Platform.CreateArrayList();
 
-			foreach (PgpSignature sig in GetSignatures())
+            foreach (PgpSignature sig in GetSignatures())
             {
                 if (sig.SignatureType == signatureType)
                 {
@@ -525,63 +526,79 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             }
 
-			return new EnumerableProxy(temp);
+            return new EnumerableProxy(temp);
         }
 
-		/// <summary>Allows enumeration of all signatures/certifications associated with this key.</summary>
-		/// <returns>An <c>IEnumerable</c> with all signatures/certifications.</returns>
+        /// <summary>Allows enumeration of all signatures/certifications associated with this key.</summary>
+        /// <returns>An <c>IEnumerable</c> with all signatures/certifications.</returns>
         public IEnumerable GetSignatures()
         {
-			IList sigs;
-			if (subSigs != null)
-			{
-				sigs = subSigs;
-			}
-			else
-			{
+            IList sigs = subSigs;
+            if (sigs == null)
+            {
                 sigs = Platform.CreateArrayList(keySigs);
 
-				foreach (ICollection extraSigs in idSigs)
-				{
+                foreach (ICollection extraSigs in idSigs)
+                {
                     CollectionUtilities.AddRange(sigs, extraSigs);
-				}
-			}
+                }
+            }
+
+            return new EnumerableProxy(sigs);
+        }
+
+        /**
+         * Return all signatures/certifications directly associated with this key (ie, not to a user id).
+         *
+         * @return an iterator (possibly empty) with all signatures/certifications.
+         */
+        public IEnumerable GetKeySignatures()
+        {
+            IList sigs = subSigs;
+            if (sigs == null)
+            {
+                sigs = Platform.CreateArrayList(keySigs);
+            }
+            return new EnumerableProxy(sigs);
+        }
 
-			return new EnumerableProxy(sigs);
+        public PublicKeyPacket PublicKeyPacket
+        {
+            get { return publicPk; }
         }
 
-		public byte[] GetEncoded()
+        public byte[] GetEncoded()
         {
             MemoryStream bOut = new MemoryStream();
             Encode(bOut);
             return bOut.ToArray();
         }
 
-		public void Encode(
+        public void Encode(
             Stream outStr)
         {
             BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
 
-			bcpgOut.WritePacket(publicPk);
+            bcpgOut.WritePacket(publicPk);
             if (trustPk != null)
             {
                 bcpgOut.WritePacket(trustPk);
             }
 
-			if (subSigs == null)    // not a sub-key
+            if (subSigs == null)    // not a sub-key
             {
-				foreach (PgpSignature keySig in keySigs)
-				{
-					keySig.Encode(bcpgOut);
-				}
+                foreach (PgpSignature keySig in keySigs)
+                {
+                    keySig.Encode(bcpgOut);
+                }
 
-				for (int i = 0; i != ids.Count; i++)
+                for (int i = 0; i != ids.Count; i++)
                 {
                     if (ids[i] is string)
                     {
                         string id = (string) ids[i];
 
-						bcpgOut.WritePacket(new UserIdPacket(id));
+                        bcpgOut.WritePacket(new UserIdPacket(id));
                     }
                     else
                     {
@@ -589,28 +606,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                         bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray()));
                     }
 
-					if (idTrusts[i] != null)
+                    if (idTrusts[i] != null)
                     {
                         bcpgOut.WritePacket((ContainedPacket)idTrusts[i]);
                     }
 
-					foreach (PgpSignature sig in (IList) idSigs[i])
-					{
-						sig.Encode(bcpgOut);
-					}
+                    foreach (PgpSignature sig in (IList) idSigs[i])
+                    {
+                        sig.Encode(bcpgOut);
+                    }
                 }
             }
             else
             {
-				foreach (PgpSignature subSig in subSigs)
-				{
-					subSig.Encode(bcpgOut);
-				}
+                foreach (PgpSignature subSig in subSigs)
+                {
+                    subSig.Encode(bcpgOut);
+                }
             }
         }
 
-		/// <summary>Check whether this (sub)key has a revocation signature on it.</summary>
-		/// <returns>True, if this (sub)key has been revoked.</returns>
+        /// <summary>Check whether this (sub)key has a revocation signature on it.</summary>
+        /// <returns>True, if this (sub)key has been revoked.</returns>
         public bool IsRevoked()
         {
             int ns = 0;
@@ -638,98 +655,98 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return revoked;
         }
 
-		/// <summary>Add a certification for an id to the given public key.</summary>
-		/// <param name="key">The key the certification is to be added to.</param>
-		/// <param name="id">The ID the certification is associated with.</param>
-		/// <param name="certification">The new certification.</param>
-		/// <returns>The re-certified key.</returns>
+        /// <summary>Add a certification for an id to the given public key.</summary>
+        /// <param name="key">The key the certification is to be added to.</param>
+        /// <param name="id">The ID the certification is associated with.</param>
+        /// <param name="certification">The new certification.</param>
+        /// <returns>The re-certified key.</returns>
         public static PgpPublicKey AddCertification(
             PgpPublicKey	key,
             string			id,
             PgpSignature	certification)
         {
-			return AddCert(key, id, certification);
-		}
-
-		/// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary>
-		/// <param name="key">The key the certification is to be added to.</param>
-		/// <param name="userAttributes">The attributes the certification is associated with.</param>
-		/// <param name="certification">The new certification.</param>
-		/// <returns>The re-certified key.</returns>
-		public static PgpPublicKey AddCertification(
-			PgpPublicKey					key,
-			PgpUserAttributeSubpacketVector	userAttributes,
-			PgpSignature					certification)
-		{
-			return AddCert(key, userAttributes, certification);
-		}
-
-		private static PgpPublicKey AddCert(
-			PgpPublicKey	key,
-			object			id,
-			PgpSignature	certification)
-		{
-			PgpPublicKey returnKey = new PgpPublicKey(key);
-			IList sigList = null;
-
-			for (int i = 0; i != returnKey.ids.Count; i++)
-			{
-				if (id.Equals(returnKey.ids[i]))
-				{
-					sigList = (IList) returnKey.idSigs[i];
-				}
-			}
-
-			if (sigList != null)
-			{
-				sigList.Add(certification);
-			}
-			else
-			{
-				sigList = Platform.CreateArrayList();
-				sigList.Add(certification);
-				returnKey.ids.Add(id);
-				returnKey.idTrusts.Add(null);
-				returnKey.idSigs.Add(sigList);
-			}
-
-			return returnKey;
-		}
-
-		/// <summary>
-		/// Remove any certifications associated with a user attribute subpacket on a key.
-		/// </summary>
-		/// <param name="key">The key the certifications are to be removed from.</param>
-		/// <param name="userAttributes">The attributes to be removed.</param>
-		/// <returns>
-		/// The re-certified key, or null if the user attribute subpacket was not found on the key.
-		/// </returns>
-		public static PgpPublicKey RemoveCertification(
-			PgpPublicKey					key,
-			PgpUserAttributeSubpacketVector	userAttributes)
-		{
-			return RemoveCert(key, userAttributes);
-		}
-
-		/// <summary>Remove any certifications associated with a given ID on a key.</summary>
-		/// <param name="key">The key the certifications are to be removed from.</param>
-		/// <param name="id">The ID that is to be removed.</param>
-		/// <returns>The re-certified key, or null if the ID was not found on the key.</returns>
+            return AddCert(key, id, certification);
+        }
+
+        /// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary>
+        /// <param name="key">The key the certification is to be added to.</param>
+        /// <param name="userAttributes">The attributes the certification is associated with.</param>
+        /// <param name="certification">The new certification.</param>
+        /// <returns>The re-certified key.</returns>
+        public static PgpPublicKey AddCertification(
+            PgpPublicKey					key,
+            PgpUserAttributeSubpacketVector	userAttributes,
+            PgpSignature					certification)
+        {
+            return AddCert(key, userAttributes, certification);
+        }
+
+        private static PgpPublicKey AddCert(
+            PgpPublicKey	key,
+            object			id,
+            PgpSignature	certification)
+        {
+            PgpPublicKey returnKey = new PgpPublicKey(key);
+            IList sigList = null;
+
+            for (int i = 0; i != returnKey.ids.Count; i++)
+            {
+                if (id.Equals(returnKey.ids[i]))
+                {
+                    sigList = (IList) returnKey.idSigs[i];
+                }
+            }
+
+            if (sigList != null)
+            {
+                sigList.Add(certification);
+            }
+            else
+            {
+                sigList = Platform.CreateArrayList();
+                sigList.Add(certification);
+                returnKey.ids.Add(id);
+                returnKey.idTrusts.Add(null);
+                returnKey.idSigs.Add(sigList);
+            }
+
+            return returnKey;
+        }
+
+        /// <summary>
+        /// Remove any certifications associated with a user attribute subpacket on a key.
+        /// </summary>
+        /// <param name="key">The key the certifications are to be removed from.</param>
+        /// <param name="userAttributes">The attributes to be removed.</param>
+        /// <returns>
+        /// The re-certified key, or null if the user attribute subpacket was not found on the key.
+        /// </returns>
+        public static PgpPublicKey RemoveCertification(
+            PgpPublicKey					key,
+            PgpUserAttributeSubpacketVector	userAttributes)
+        {
+            return RemoveCert(key, userAttributes);
+        }
+
+        /// <summary>Remove any certifications associated with a given ID on a key.</summary>
+        /// <param name="key">The key the certifications are to be removed from.</param>
+        /// <param name="id">The ID that is to be removed.</param>
+        /// <returns>The re-certified key, or null if the ID was not found on the key.</returns>
         public static PgpPublicKey RemoveCertification(
             PgpPublicKey	key,
             string			id)
         {
-			return RemoveCert(key, id);
-		}
+            return RemoveCert(key, id);
+        }
 
-		private static PgpPublicKey RemoveCert(
-			PgpPublicKey	key,
-			object			id)
-		{
-			PgpPublicKey returnKey = new PgpPublicKey(key);
+        private static PgpPublicKey RemoveCert(
+            PgpPublicKey	key,
+            object			id)
+        {
+            PgpPublicKey returnKey = new PgpPublicKey(key);
             bool found = false;
 
-			for (int i = 0; i < returnKey.ids.Count; i++)
+            for (int i = 0; i < returnKey.ids.Count; i++)
             {
                 if (id.Equals(returnKey.ids[i]))
                 {
@@ -740,64 +757,64 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             }
 
-			return found ? returnKey : null;
+            return found ? returnKey : null;
         }
 
-		/// <summary>Remove a certification associated with a given ID on a key.</summary>
-		/// <param name="key">The key the certifications are to be removed from.</param>
-		/// <param name="id">The ID that the certfication is to be removed from.</param>
-		/// <param name="certification">The certfication to be removed.</param>
-		/// <returns>The re-certified key, or null if the certification was not found.</returns>
+        /// <summary>Remove a certification associated with a given ID on a key.</summary>
+        /// <param name="key">The key the certifications are to be removed from.</param>
+        /// <param name="id">The ID that the certfication is to be removed from.</param>
+        /// <param name="certification">The certfication to be removed.</param>
+        /// <returns>The re-certified key, or null if the certification was not found.</returns>
         public static PgpPublicKey RemoveCertification(
             PgpPublicKey	key,
             string			id,
             PgpSignature	certification)
         {
-			return RemoveCert(key, id, certification);
-		}
-
-		/// <summary>Remove a certification associated with a given user attributes on a key.</summary>
-		/// <param name="key">The key the certifications are to be removed from.</param>
-		/// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param>
-		/// <param name="certification">The certification to be removed.</param>
-		/// <returns>The re-certified key, or null if the certification was not found.</returns>
-		public static PgpPublicKey RemoveCertification(
-			PgpPublicKey					key,
-			PgpUserAttributeSubpacketVector	userAttributes,
-			PgpSignature					certification)
-		{
-			return RemoveCert(key, userAttributes, certification);
-		}
-
-		private static PgpPublicKey RemoveCert(
-			PgpPublicKey	key,
-			object			id,
-			PgpSignature	certification)
-		{
-			PgpPublicKey returnKey = new PgpPublicKey(key);
+            return RemoveCert(key, id, certification);
+        }
+
+        /// <summary>Remove a certification associated with a given user attributes on a key.</summary>
+        /// <param name="key">The key the certifications are to be removed from.</param>
+        /// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param>
+        /// <param name="certification">The certification to be removed.</param>
+        /// <returns>The re-certified key, or null if the certification was not found.</returns>
+        public static PgpPublicKey RemoveCertification(
+            PgpPublicKey					key,
+            PgpUserAttributeSubpacketVector	userAttributes,
+            PgpSignature					certification)
+        {
+            return RemoveCert(key, userAttributes, certification);
+        }
+
+        private static PgpPublicKey RemoveCert(
+            PgpPublicKey	key,
+            object			id,
+            PgpSignature	certification)
+        {
+            PgpPublicKey returnKey = new PgpPublicKey(key);
             bool found = false;
 
-			for (int i = 0; i < returnKey.ids.Count; i++)
+            for (int i = 0; i < returnKey.ids.Count; i++)
             {
                 if (id.Equals(returnKey.ids[i]))
                 {
                     IList certs = (IList) returnKey.idSigs[i];
                     found = certs.Contains(certification);
 
-					if (found)
-					{
-						certs.Remove(certification);
-					}
+                    if (found)
+                    {
+                        certs.Remove(certification);
+                    }
                 }
             }
 
-			return found ? returnKey : null;
+            return found ? returnKey : null;
         }
 
-		/// <summary>Add a revocation or some other key certification to a key.</summary>
-		/// <param name="key">The key the revocation is to be added to.</param>
-		/// <param name="certification">The key signature to be added.</param>
-		/// <returns>The new changed public key object.</returns>
+        /// <summary>Add a revocation or some other key certification to a key.</summary>
+        /// <param name="key">The key the revocation is to be added to.</param>
+        /// <param name="certification">The key signature to be added.</param>
+        /// <returns>The new changed public key object.</returns>
         public static PgpPublicKey AddCertification(
             PgpPublicKey	key,
             PgpSignature	certification)
@@ -817,9 +834,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 }
             }
 
-			PgpPublicKey returnKey = new PgpPublicKey(key);
+            PgpPublicKey returnKey = new PgpPublicKey(key);
 
-			if (returnKey.subSigs != null)
+            if (returnKey.subSigs != null)
             {
                 returnKey.subSigs.Add(certification);
             }
@@ -828,63 +845,63 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 returnKey.keySigs.Add(certification);
             }
 
-			return returnKey;
+            return returnKey;
         }
 
-		/// <summary>Remove a certification from the key.</summary>
-		/// <param name="key">The key the certifications are to be removed from.</param>
-		/// <param name="certification">The certfication to be removed.</param>
-		/// <returns>The modified key, null if the certification was not found.</returns>
-		public static PgpPublicKey RemoveCertification(
-			PgpPublicKey	key,
-			PgpSignature	certification)
-		{
-			PgpPublicKey returnKey = new PgpPublicKey(key);
-			IList sigs = returnKey.subSigs != null
-				?	returnKey.subSigs
-				:	returnKey.keySigs;
+        /// <summary>Remove a certification from the key.</summary>
+        /// <param name="key">The key the certifications are to be removed from.</param>
+        /// <param name="certification">The certfication to be removed.</param>
+        /// <returns>The modified key, null if the certification was not found.</returns>
+        public static PgpPublicKey RemoveCertification(
+            PgpPublicKey	key,
+            PgpSignature	certification)
+        {
+            PgpPublicKey returnKey = new PgpPublicKey(key);
+            IList sigs = returnKey.subSigs != null
+                ?	returnKey.subSigs
+                :	returnKey.keySigs;
 
 //			bool found = sigs.Remove(certification);
-			int pos = sigs.IndexOf(certification);
-			bool found = pos >= 0;
-
-			if (found)
-			{
-				sigs.RemoveAt(pos);
-			}
-			else
-			{
-				foreach (String id in key.GetUserIds())
-				{
-					foreach (object sig in key.GetSignaturesForId(id))
-					{
-						// TODO Is this the right type of equality test?
-						if (certification == sig)
-						{
-							found = true;
-							returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
-						}
-					}
-				}
-
-				if (!found)
-				{
-					foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
-					{
-						foreach (object sig in key.GetSignaturesForUserAttribute(id))
-						{
-							// TODO Is this the right type of equality test?
-							if (certification == sig)
-							{
-								found = true;
-								returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
-							}
-						}
-					}
-				}
-			}
-
-			return returnKey;
-		}
-	}
+            int pos = sigs.IndexOf(certification);
+            bool found = pos >= 0;
+
+            if (found)
+            {
+                sigs.RemoveAt(pos);
+            }
+            else
+            {
+                foreach (String id in key.GetUserIds())
+                {
+                    foreach (object sig in key.GetSignaturesForId(id))
+                    {
+                        // TODO Is this the right type of equality test?
+                        if (certification == sig)
+                        {
+                            found = true;
+                            returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
+                        }
+                    }
+                }
+
+                if (!found)
+                {
+                    foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
+                    {
+                        foreach (object sig in key.GetSignaturesForUserAttribute(id))
+                        {
+                            // TODO Is this the right type of equality test?
+                            if (certification == sig)
+                            {
+                                found = true;
+                                returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
+                            }
+                        }
+                    }
+                }
+            }
+
+            return returnKey;
+        }
+    }
 }
diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs
index 0cf113f65..c1aea50d6 100644
--- a/crypto/src/security/SignerUtilities.cs
+++ b/crypto/src/security/SignerUtilities.cs
@@ -261,10 +261,10 @@ namespace Org.BouncyCastle.Security
         }
 
         /// <summary>
-        /// Returns a ObjectIdentifier for a give encoding.
+        /// Returns an ObjectIdentifier for a given encoding.
         /// </summary>
         /// <param name="mechanism">A string representation of the encoding.</param>
-        /// <returns>A DerObjectIdentifier, null if the Oid is not available.</returns>
+        /// <returns>A DerObjectIdentifier, null if the OID is not available.</returns>
         // TODO Don't really want to support this
         public static DerObjectIdentifier GetObjectIdentifier(
             string mechanism)
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 27fd18d6d..8614baead 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -339,6 +339,11 @@ namespace Org.BouncyCastle.Utilities
             return data == null ? null : (int[])data.Clone();
         }
 
+        internal static uint[] Clone(uint[] data)
+        {
+            return data == null ? null : (uint[])data.Clone();
+        }
+
         public static long[] Clone(long[] data)
         {
             return data == null ? null : (long[])data.Clone();