summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2014-07-24 14:54:13 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2014-07-24 14:54:13 +0700
commitbf1886e5381d0f3674efa1f9890dfb9fc1811ab8 (patch)
treed30d93bb51b5397813da1b5e2ffe208db5cb3de6
parentAdd more variations of Check/IsValid (diff)
downloadBouncyCastle.NET-ed25519-bf1886e5381d0f3674efa1f9890dfb9fc1811ab8.tar.xz
Another round of TLS porting from Java
-rw-r--r--crypto/crypto.csproj45
-rw-r--r--crypto/src/crypto/tls/CertificateStatus.cs102
-rw-r--r--crypto/src/crypto/tls/CertificateStatusRequest.cs95
-rw-r--r--crypto/src/crypto/tls/CertificateUrl.cs124
-rw-r--r--crypto/src/crypto/tls/NewSessionTicket.cs4
-rw-r--r--crypto/src/crypto/tls/OcspStatusRequest.cs130
-rw-r--r--crypto/src/crypto/tls/SessionParameters.cs137
-rw-r--r--crypto/src/crypto/tls/TlsContext.cs45
-rw-r--r--crypto/src/crypto/tls/TlsProtocol.cs72
-rw-r--r--crypto/src/crypto/tls/TlsProtocolHandler.cs145
-rw-r--r--crypto/src/crypto/tls/TlsSession.cs15
-rw-r--r--crypto/src/crypto/tls/TlsUtilities.cs19
-rw-r--r--crypto/src/crypto/tls/UrlAndHash.cs94
13 files changed, 930 insertions, 97 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index c361d5d80..2cc76f367 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -4289,6 +4289,16 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\CertificateStatus.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\crypto\tls\CertificateStatusRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\CertificateStatusType.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4299,6 +4309,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\CertificateUrl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\ChangeCipherSpec.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4469,6 +4484,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\OcspStatusRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\PrfAlgorithm.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4494,6 +4514,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\SessionParameters.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\SignatureAlgorithm.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4574,6 +4599,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsContext.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsCredentials.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4659,6 +4689,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsProtocolHandler.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4689,6 +4724,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\TlsSession.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\TlsSigner.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -4719,6 +4759,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\crypto\tls\UrlAndHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\crypto\tls\UserMappingType.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/crypto/tls/CertificateStatus.cs b/crypto/src/crypto/tls/CertificateStatus.cs
new file mode 100644
index 000000000..0f95475b9
--- /dev/null
+++ b/crypto/src/crypto/tls/CertificateStatus.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Ocsp;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class CertificateStatus
+    {
+        protected readonly byte mStatusType;
+        protected readonly object mResponse;
+
+        public CertificateStatus(byte statusType, object response)
+        {
+            if (!IsCorrectType(statusType, response))
+                throw new ArgumentException("not an instance of the correct type", "response");
+
+            this.mStatusType = statusType;
+            this.mResponse = response;
+        }
+
+        public virtual byte StatusType
+        {
+            get { return mStatusType; }
+        }
+
+        public virtual object Response
+        {
+            get { return mResponse; }
+        }
+
+        public virtual OcspResponse GetOcspResponse()
+        {
+            if (!IsCorrectType(CertificateStatusType.ocsp, mResponse))
+                throw new InvalidOperationException("'response' is not an OcspResponse");
+
+            return (OcspResponse)mResponse;
+        }
+
+        /**
+         * Encode this {@link CertificateStatus} to a {@link Stream}.
+         * 
+         * @param output
+         *            the {@link Stream} to encode to.
+         * @throws IOException
+         */
+        public virtual void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(mStatusType, output);
+
+            switch (mStatusType)
+            {
+            case CertificateStatusType.ocsp:
+                byte[] derEncoding = ((OcspResponse)mResponse).GetEncoded(Asn1Encodable.Der);
+                TlsUtilities.WriteOpaque24(derEncoding, output);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        /**
+         * Parse a {@link CertificateStatus} from a {@link Stream}.
+         * 
+         * @param input
+         *            the {@link Stream} to parse from.
+         * @return a {@link CertificateStatus} object.
+         * @throws IOException
+         */
+        public static CertificateStatus Parse(Stream input)
+        {
+            byte status_type = TlsUtilities.ReadUint8(input);
+            object response;
+
+            switch (status_type)
+            {
+            case CertificateStatusType.ocsp:
+            {
+                byte[] derEncoding = TlsUtilities.ReadOpaque24(input);
+                response = OcspResponse.GetInstance(TlsUtilities.ReadDerObject(derEncoding));
+                break;
+            }
+            default:
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            return new CertificateStatus(status_type, response);
+        }
+
+        protected static bool IsCorrectType(byte statusType, object response)
+        {
+            switch (statusType)
+            {
+            case CertificateStatusType.ocsp:
+                return response is OcspResponse;
+            default:
+                throw new ArgumentException("unsupported value", "statusType");
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/CertificateStatusRequest.cs b/crypto/src/crypto/tls/CertificateStatusRequest.cs
new file mode 100644
index 000000000..9587d7df8
--- /dev/null
+++ b/crypto/src/crypto/tls/CertificateStatusRequest.cs
@@ -0,0 +1,95 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public class CertificateStatusRequest
+    {
+        protected readonly byte mStatusType;
+        protected readonly object mRequest;
+
+        public CertificateStatusRequest(byte statusType, Object request)
+        {
+            if (!IsCorrectType(statusType, request))
+                throw new ArgumentException("not an instance of the correct type", "request");
+
+            this.mStatusType = statusType;
+            this.mRequest = request;
+        }
+
+        public virtual byte StatusType
+        {
+            get { return mStatusType; }
+        }
+
+        public virtual object Request
+        {
+            get { return mRequest; }
+        }
+
+        public virtual OcspStatusRequest GetOcspStatusRequest()
+        {
+            if (!IsCorrectType(CertificateStatusType.ocsp, mRequest))
+                throw new InvalidOperationException("'request' is not an OCSPStatusRequest");
+
+            return (OcspStatusRequest)mRequest;
+        }
+
+        /**
+         * Encode this {@link CertificateStatusRequest} to a {@link Stream}.
+         * 
+         * @param output
+         *            the {@link Stream} to encode to.
+         * @throws IOException
+         */
+        public virtual void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(mStatusType, output);
+
+            switch (mStatusType)
+            {
+            case CertificateStatusType.ocsp:
+                ((OcspStatusRequest)mRequest).Encode(output);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        /**
+         * Parse a {@link CertificateStatusRequest} from a {@link Stream}.
+         * 
+         * @param input
+         *            the {@link Stream} to parse from.
+         * @return a {@link CertificateStatusRequest} object.
+         * @throws IOException
+         */
+        public static CertificateStatusRequest Parse(Stream input)
+        {
+            byte status_type = TlsUtilities.ReadUint8(input);
+            object result;
+
+            switch (status_type)
+            {
+            case CertificateStatusType.ocsp:
+                result = OcspStatusRequest.Parse(input);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            return new CertificateStatusRequest(status_type, result);
+        }
+
+        protected static bool IsCorrectType(byte statusType, object request)
+        {
+            switch (statusType)
+            {
+            case CertificateStatusType.ocsp:
+                return request is OcspStatusRequest;
+            default:
+                throw new ArgumentException("unsupported value", "statusType");
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/CertificateUrl.cs b/crypto/src/crypto/tls/CertificateUrl.cs
new file mode 100644
index 000000000..a951b8063
--- /dev/null
+++ b/crypto/src/crypto/tls/CertificateUrl.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    /*
+     * RFC 3546 3.3
+     */
+    public class CertificateUrl
+    {
+        protected readonly byte mType;
+        protected readonly IList mUrlAndHashList;
+
+        /**
+         * @param type
+         *            see {@link CertChainType} for valid constants.
+         * @param urlAndHashList
+         *            a {@link IList} of {@link UrlAndHash}.
+         */
+        public CertificateUrl(byte type, IList urlAndHashList)
+        {
+            if (!CertChainType.IsValid(type))
+                throw new ArgumentException("not a valid CertChainType value", "type");
+            if (urlAndHashList == null || urlAndHashList.Count < 1)
+                throw new ArgumentException("must have length > 0", "urlAndHashList");
+
+            this.mType = type;
+            this.mUrlAndHashList = urlAndHashList;
+        }
+
+        /**
+         * @return {@link CertChainType}
+         */
+        public virtual byte Type
+        {
+            get { return mType; }
+        }
+
+        /**
+         * @return an {@link IList} of {@link UrlAndHash} 
+         */
+        public virtual IList UrlAndHashList
+        {
+            get { return mUrlAndHashList; }
+        }
+
+        /**
+         * Encode this {@link CertificateUrl} to a {@link Stream}.
+         *
+         * @param output the {@link Stream} to encode to.
+         * @throws IOException
+         */
+        public virtual void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(this.mType, output);
+
+            ListBuffer16 buf = new ListBuffer16();
+            foreach (UrlAndHash urlAndHash in this.mUrlAndHashList)
+            {
+                urlAndHash.Encode(buf);
+            }
+            buf.EncodeTo(output);
+        }
+
+        /**
+         * Parse a {@link CertificateUrl} from a {@link Stream}.
+         * 
+         * @param context
+         *            the {@link TlsContext} of the current connection.
+         * @param input
+         *            the {@link Stream} to parse from.
+         * @return a {@link CertificateUrl} object.
+         * @throws IOException
+         */
+        public static CertificateUrl parse(TlsContext context, Stream input)
+        {
+            byte type = TlsUtilities.ReadUint8(input);
+            if (!CertChainType.IsValid(type))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int totalLength = TlsUtilities.ReadUint16(input);
+            if (totalLength < 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            byte[] urlAndHashListData = TlsUtilities.ReadFully(totalLength, input);
+
+            MemoryStream buf = new MemoryStream(urlAndHashListData, false);
+
+            IList url_and_hash_list = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                UrlAndHash url_and_hash = UrlAndHash.Parse(context, buf);
+                url_and_hash_list.Add(url_and_hash);
+            }
+
+            return new CertificateUrl(type, url_and_hash_list);
+        }
+
+        // TODO Could be more generally useful
+        internal class ListBuffer16
+            : MemoryStream
+        {
+            internal ListBuffer16()
+            {
+                // Reserve space for length
+                TlsUtilities.WriteUint16(0,  this);
+            }
+
+            internal void EncodeTo(Stream output)
+            {
+                // Patch actual length back in
+                long length = Length - 2;
+                TlsUtilities.CheckUint16(length);
+                this.Position = 0;
+                TlsUtilities.WriteUint16((int)length, this);
+                this.WriteTo(output);
+                this.Close();
+            }
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/NewSessionTicket.cs b/crypto/src/crypto/tls/NewSessionTicket.cs
index b75838b03..a84026b8c 100644
--- a/crypto/src/crypto/tls/NewSessionTicket.cs
+++ b/crypto/src/crypto/tls/NewSessionTicket.cs
@@ -5,8 +5,8 @@ namespace Org.BouncyCastle.Crypto.Tls
 {
     public class NewSessionTicket
     {
-        protected long mTicketLifetimeHint;
-        protected byte[] mTicket;
+        protected readonly long mTicketLifetimeHint;
+        protected readonly byte[] mTicket;
 
         public NewSessionTicket(long ticketLifetimeHint, byte[] ticket)
         {
diff --git a/crypto/src/crypto/tls/OcspStatusRequest.cs b/crypto/src/crypto/tls/OcspStatusRequest.cs
new file mode 100644
index 000000000..2dd8371e5
--- /dev/null
+++ b/crypto/src/crypto/tls/OcspStatusRequest.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Ocsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    /**
+     * RFC 3546 3.6
+     */
+    public class OcspStatusRequest
+    {
+        protected readonly IList mResponderIDList;
+        protected readonly X509Extensions mRequestExtensions;
+
+        /**
+         * @param responderIDList
+         *            an {@link IList} of {@link ResponderID}, specifying the list of trusted OCSP
+         *            responders. An empty list has the special meaning that the responders are
+         *            implicitly known to the server - e.g., by prior arrangement.
+         * @param requestExtensions
+         *            OCSP request extensions. A null value means that there are no extensions.
+         */
+        public OcspStatusRequest(IList responderIDList, X509Extensions requestExtensions)
+        {
+            this.mResponderIDList = responderIDList;
+            this.mRequestExtensions = requestExtensions;
+        }
+
+        /**
+         * @return an {@link IList} of {@link ResponderID}
+         */
+        public virtual IList ResponderIDList
+        {
+            get { return mResponderIDList; }
+        }
+
+        /**
+         * @return OCSP request extensions
+         */
+        public virtual X509Extensions RequestExtensions
+        {
+            get { return mRequestExtensions; }
+        }
+
+        /**
+         * Encode this {@link OcspStatusRequest} to a {@link Stream}.
+         * 
+         * @param output
+         *            the {@link Stream} to encode to.
+         * @throws IOException
+         */
+        public virtual void Encode(Stream output)
+        {
+            if (mResponderIDList == null || mResponderIDList.Count < 1)
+            {
+                TlsUtilities.WriteUint16(0, output);
+            }
+            else
+            {
+                MemoryStream buf = new MemoryStream();
+                for (int i = 0; i < mResponderIDList.Count; ++i)
+                {
+                    ResponderID responderID = (ResponderID)mResponderIDList[i];
+                    byte[] derEncoding = responderID.GetEncoded(Asn1Encodable.Der);
+                    TlsUtilities.WriteOpaque16(derEncoding, buf);
+                }
+                TlsUtilities.CheckUint16(buf.Length);
+                TlsUtilities.WriteUint16((int)buf.Length, output);
+                buf.WriteTo(output);
+            }
+
+            if (mRequestExtensions == null)
+            {
+                TlsUtilities.WriteUint16(0, output);
+            }
+            else
+            {
+                byte[] derEncoding = mRequestExtensions.GetEncoded(Asn1Encodable.Der);
+                TlsUtilities.CheckUint16(derEncoding.Length);
+                TlsUtilities.WriteUint16(derEncoding.Length, output);
+                output.Write(derEncoding, 0, derEncoding.Length);
+            }
+        }
+
+        /**
+         * Parse a {@link OcspStatusRequest} from a {@link Stream}.
+         * 
+         * @param input
+         *            the {@link Stream} to parse from.
+         * @return an {@link OcspStatusRequest} object.
+         * @throws IOException
+         */
+        public static OcspStatusRequest Parse(Stream input)
+        {
+            IList responderIDList = Platform.CreateArrayList();
+            {
+                int length = TlsUtilities.ReadUint16(input);
+                if (length > 0)
+                {
+                    byte[] data = TlsUtilities.ReadFully(length, input);
+                    MemoryStream buf = new MemoryStream(data, false);
+                    do
+                    {
+                        byte[] derEncoding = TlsUtilities.ReadOpaque16(buf);
+                        ResponderID responderID = ResponderID.GetInstance(TlsUtilities.ReadDerObject(derEncoding));
+                        responderIDList.Add(responderID);
+                    }
+                    while (buf.Position < buf.Length);
+                }
+            }
+
+            X509Extensions requestExtensions = null;
+            {
+                int length = TlsUtilities.ReadUint16(input);
+                if (length > 0)
+                {
+                    byte[] derEncoding = TlsUtilities.ReadFully(length, input);
+                    requestExtensions = X509Extensions.GetInstance(TlsUtilities.ReadDerObject(derEncoding));
+                }
+            }
+
+            return new OcspStatusRequest(responderIDList, requestExtensions);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/SessionParameters.cs b/crypto/src/crypto/tls/SessionParameters.cs
new file mode 100644
index 000000000..c4616ac71
--- /dev/null
+++ b/crypto/src/crypto/tls/SessionParameters.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public sealed class SessionParameters
+    {
+        public sealed class Builder
+        {
+            private int mCipherSuite = -1;
+            private short mCompressionAlgorithm = -1;
+            private byte[] mMasterSecret = null;
+            private Certificate mPeerCertificate = null;
+            private byte[] mEncodedServerExtensions = null;
+
+            public Builder()
+            {
+            }
+
+            public SessionParameters Build()
+            {
+                Validate(this.mCipherSuite >= 0, "cipherSuite");
+                Validate(this.mCompressionAlgorithm >= 0, "compressionAlgorithm");
+                Validate(this.mMasterSecret != null, "masterSecret");
+                return new SessionParameters(mCipherSuite, (byte)mCompressionAlgorithm, mMasterSecret, mPeerCertificate,
+                    mEncodedServerExtensions);
+            }
+
+            public Builder SetCipherSuite(int cipherSuite)
+            {
+                this.mCipherSuite = cipherSuite;
+                return this;
+            }
+
+            public Builder SetCompressionAlgorithm(byte compressionAlgorithm)
+            {
+                this.mCompressionAlgorithm = compressionAlgorithm;
+                return this;
+            }
+
+            public Builder SetMasterSecret(byte[] masterSecret)
+            {
+                this.mMasterSecret = masterSecret;
+                return this;
+            }
+
+            public Builder SetPeerCertificate(Certificate peerCertificate)
+            {
+                this.mPeerCertificate = peerCertificate;
+                return this;
+            }
+
+            public Builder SetServerExtensions(IDictionary serverExtensions)
+            {
+                if (serverExtensions == null)
+                {
+                    mEncodedServerExtensions = null;
+                }
+                else
+                {
+                    MemoryStream buf = new MemoryStream();
+                    TlsProtocol.WriteExtensions(buf, serverExtensions);
+                    mEncodedServerExtensions = buf.ToArray();
+                }
+                return this;
+            }
+
+            private void Validate(bool condition, string parameter)
+            {
+                if (!condition)
+                    throw new InvalidOperationException("Required session parameter '" + parameter + "' not configured");
+            }
+        }
+
+        private int mCipherSuite;
+        private byte mCompressionAlgorithm;
+        private byte[] mMasterSecret;
+        private Certificate mPeerCertificate;
+        private byte[] mEncodedServerExtensions;
+
+        private SessionParameters(int cipherSuite, byte compressionAlgorithm, byte[] masterSecret,
+            Certificate peerCertificate, byte[] encodedServerExtensions)
+        {
+            this.mCipherSuite = cipherSuite;
+            this.mCompressionAlgorithm = compressionAlgorithm;
+            this.mMasterSecret = Arrays.Clone(masterSecret);
+            this.mPeerCertificate = peerCertificate;
+            this.mEncodedServerExtensions = encodedServerExtensions;
+        }
+
+        public void Clear()
+        {
+            if (this.mMasterSecret != null)
+            {
+                Arrays.Fill(this.mMasterSecret, (byte)0);
+            }
+        }
+
+        public SessionParameters Copy()
+        {
+            return new SessionParameters(mCipherSuite, mCompressionAlgorithm, mMasterSecret, mPeerCertificate,
+                mEncodedServerExtensions);
+        }
+
+        public int CipherSuite
+        {
+            get { return mCipherSuite; }
+        }
+
+        public byte CompressionAlgorithm
+        {
+            get { return mCompressionAlgorithm; }
+        }
+
+        public byte[] MasterSecret
+        {
+            get { return mMasterSecret; }
+        }
+
+        public Certificate PeerCertificate
+        {
+            get { return mPeerCertificate; }
+        }
+
+        public IDictionary ReadServerExtensions()
+        {
+            if (mEncodedServerExtensions == null)
+                return null;
+
+            MemoryStream buf = new MemoryStream(mEncodedServerExtensions, false);
+            return TlsProtocol.ReadExtensions(buf);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsContext.cs b/crypto/src/crypto/tls/TlsContext.cs
new file mode 100644
index 000000000..d066723fc
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsContext.cs
@@ -0,0 +1,45 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Prng;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public interface TlsContext
+    {
+        IRandomGenerator NonceRandomGenerator { get; }
+
+        SecureRandom SecureRandom { get; }
+
+        SecurityParameters SecurityParameters { get; }
+
+        bool IsServer { get; }
+
+        ProtocolVersion ClientVersion { get; }
+
+        ProtocolVersion ServerVersion { get; }
+
+        /**
+         * Used to get the resumable session, if any, used by this connection. Only available after the
+         * handshake has successfully completed.
+         * 
+         * @return A {@link TlsSession} representing the resumable session used by this connection, or
+         *         null if no resumable session available.
+         * @see TlsPeer#NotifyHandshakeComplete()
+         */
+        TlsSession ResumableSession { get; }
+
+        object UserObject { get; set; }
+
+        /**
+         * Export keying material according to RFC 5705: "Keying Material Exporters for TLS".
+         *
+         * @param asciiLabel    indicates which application will use the exported keys.
+         * @param context_value allows the application using the exporter to mix its own data with the TLS PRF for
+         *                      the exporter output.
+         * @param length        the number of bytes to generate
+         * @return a pseudorandom bit string of 'length' bytes generated from the master_secret.
+         */
+        byte[] ExportKeyingMaterial(string asciiLabel, byte[] context_value, int length);
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs
new file mode 100644
index 000000000..764892d0b
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsProtocol.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public abstract class TlsProtocol
+    {
+        /**
+        * Make sure the Stream is now empty. Fail otherwise.
+        *
+        * @param is The Stream to check.
+        * @throws IOException If is is not empty.
+        */
+        protected internal static void AssertEmpty(MemoryStream buf)
+        {
+            if (buf.Position < buf.Length)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        protected internal static IDictionary ReadExtensions(MemoryStream input)
+        {
+            if (input.Position >= input.Length)
+                return null;
+
+            byte[] extBytes = TlsUtilities.ReadOpaque16(input);
+
+            AssertEmpty(input);
+
+            MemoryStream buf = new MemoryStream(extBytes, false);
+
+            // Integer -> byte[]
+            IDictionary extensions = Platform.CreateHashtable();
+
+            while (buf.Position < buf.Length)
+            {
+                int extension_type = TlsUtilities.ReadUint16(buf);
+                byte[] extension_data = TlsUtilities.ReadOpaque16(buf);
+
+                /*
+                 * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+                 */
+                if (extensions.Contains(extension_type))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                extensions.Add(extension_type, extension_data);
+            }
+
+            return extensions;
+        }
+
+        protected internal static void WriteExtensions(Stream output, IDictionary extensions)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            foreach (int extension_type in extensions.Keys)
+            {
+                byte[] extension_data = (byte[])extensions[extension_type];
+
+                TlsUtilities.CheckUint16(extension_type);
+                TlsUtilities.WriteUint16(extension_type, buf);
+                TlsUtilities.WriteOpaque16(extension_data, buf);
+            }
+
+            byte[] extBytes = buf.ToArray();
+
+            TlsUtilities.WriteOpaque16(extBytes, output);
+        }
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsProtocolHandler.cs b/crypto/src/crypto/tls/TlsProtocolHandler.cs
index d40e179aa..72bf8c5cf 100644
--- a/crypto/src/crypto/tls/TlsProtocolHandler.cs
+++ b/crypto/src/crypto/tls/TlsProtocolHandler.cs
@@ -23,6 +23,7 @@ namespace Org.BouncyCastle.Crypto.Tls
 {
     /// <remarks>An implementation of all high level protocols in TLS 1.0.</remarks>
     public class TlsProtocolHandler
+        : TlsProtocol
     {
         /*
         * Our Connection states
@@ -323,7 +324,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                              * it was one of the offered ones.
                              */
                             int selectedCipherSuite = TlsUtilities.ReadUint16(inStr);
-                            if (!ArrayContains(offeredCipherSuites, selectedCipherSuite)
+                            if (!Arrays.Contains(offeredCipherSuites, selectedCipherSuite)
                                 || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)
                             {
                                 this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
@@ -336,7 +337,7 @@ namespace Org.BouncyCastle.Crypto.Tls
                              * it was one of the offered ones.
                              */
                             byte selectedCompressionMethod = TlsUtilities.ReadUint8(inStr);
-                            if (!ArrayContains(offeredCompressionMethods, selectedCompressionMethod))
+                            if (!Arrays.Contains(offeredCompressionMethods, selectedCompressionMethod))
                             {
                                 this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
                             }
@@ -363,52 +364,59 @@ namespace Org.BouncyCastle.Crypto.Tls
                              */
 
                             // Int32 -> byte[]
-                            IDictionary serverExtensions = Platform.CreateHashtable();
+                            IDictionary serverExtensions = ReadExtensions(inStr);
 
-                            if (inStr.Position < inStr.Length)
+                            /*
+                             * 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)
                             {
-                                // Process extensions from extended server hello
-                                byte[] extBytes = TlsUtilities.ReadOpaque16(inStr);
-
-                                MemoryStream ext = new MemoryStream(extBytes, false);
-                                while (ext.Position < ext.Length)
+                                foreach (int extType in serverExtensions.Keys)
                                 {
-                                    int extType = TlsUtilities.ReadUint16(ext);
-                                    byte[] extValue = TlsUtilities.ReadOpaque16(ext);
-
-                                    // Note: RFC 5746 makes a special case for EXT_RenegotiationInfo
-                                    if (extType != ExtensionType.renegotiation_info
-                                        && !clientExtensions.Contains(extType))
-                                    {
-                                        /*
-                                         * RFC 3546 2.3
-                                         * Note that for all extension types (including those defined in
-                                         * future), the extension type MUST NOT appear in the extended server
-                                         * hello unless the same extension type appeared in the corresponding
-                                         * client hello.  Thus clients MUST abort the handshake if they receive
-                                         * an extension type in the extended server hello that they did not
-                                         * request in the associated (extended) client hello.
-                                         */
-                                        this.FailWithError(AlertLevel.fatal, AlertDescription.unsupported_extension);
-                                    }
-
-                                    if (serverExtensions.Contains(extType))
-                                    {
-                                        /*
-                                         * RFC 3546 2.3
-                                         * Also note that when multiple extensions of different types are
-                                         * present in the extended client hello or the extended server hello,
-                                         * the extensions may appear in any order. There MUST NOT be more than
-                                         * one extension of the same type.
-                                         */
-                                        this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter);
-                                    }
-
-                                    serverExtensions.Add(extType, extValue);
+                                    /*
+                                     * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a
+                                     * ClientHello containing only the SCSV is an explicit exception to the prohibition
+                                     * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is
+                                     * only allowed because the client is signaling its willingness to receive the
+                                     * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                                     */
+                                    if (ExtensionType.renegotiation_info == extType)
+                                        continue;
+
+                                    // TODO Add session resumption support
+                                    ///*
+                                    // * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore
+                                    // * extensions appearing in the client hello, and send a server hello containing no
+                                    // * extensions[.]
+                                    // */
+                                    //if (this.resumedSession)
+                                    //{
+                                    //    // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats
+                                    //    // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats
+                                    //    // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats
+                                    //    //                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                                    //}
+
+                                    /*
+                                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
+                                     * same extension type appeared in the corresponding ClientHello. If a client
+                                     * receives an extension type in ServerHello that it did not request in the
+                                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
+                                     * fatal alert.
+                                     */
+                                    if (null == TlsUtilities.GetExtensionData(clientExtensions, extType))
+                                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
                                 }
                             }
-
-                            AssertEmpty(inStr);
+                            else
+                            {
+                                // TODO Don't need this eventually...
+                                serverExtensions = Platform.CreateHashtable();
+                            }
 
                             /*
                              * RFC 5746 3.4. When a ServerHello is received, the client MUST check if it
@@ -882,17 +890,9 @@ namespace Org.BouncyCastle.Crypto.Tls
                 }
             }
 
-            // Extensions
             if (clientExtensions != null)
             {
-                MemoryStream ext = new MemoryStream();
-
-                foreach (int extType in clientExtensions.Keys)
-                {
-                    WriteExtension(ext, extType, (byte[])clientExtensions[extType]);
-                }
-
-                TlsUtilities.WriteOpaque16(ext.ToArray(), outStr);
+                WriteExtensions(outStr, clientExtensions);
             }
 
             MemoryStream bos = new MemoryStream();
@@ -1160,21 +1160,6 @@ namespace Org.BouncyCastle.Crypto.Tls
             }
         }
 
-        /**
-        * Make sure the Stream is now empty. Fail otherwise.
-        *
-        * @param is The Stream to check.
-        * @throws IOException If is is not empty.
-        */
-        protected internal static void AssertEmpty(
-            MemoryStream inStr)
-        {
-            if (inStr.Position < inStr.Length)
-            {
-                throw new TlsFatalAlert(AlertDescription.decode_error);
-            }
-        }
-
         protected static byte[] CreateRandomBlock(bool useGMTUnixTime, SecureRandom random, string asciiLabel)
         {
             /*
@@ -1208,37 +1193,11 @@ namespace Org.BouncyCastle.Crypto.Tls
             get { return closed; }
         }
 
-        private static bool ArrayContains(byte[] a, byte n)
-        {
-            for (int i = 0; i < a.Length; ++i)
-            {
-                if (a[i] == n)
-                    return true;
-            }
-            return false;
-        }
-
-        private static bool ArrayContains(int[] a, int n)
-        {
-            for (int i = 0; i < a.Length; ++i)
-            {
-                if (a[i] == n)
-                    return true;
-            }
-            return false;
-        }
-
         private static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
         {
             MemoryStream buf = new MemoryStream();
             TlsUtilities.WriteOpaque8(renegotiated_connection, buf);
             return buf.ToArray();
         }
-
-        private static void WriteExtension(Stream output, int extType, byte[] extValue)
-        {
-            TlsUtilities.WriteUint16(extType, output);
-            TlsUtilities.WriteOpaque16(extValue, output);
-        }
     }
 }
diff --git a/crypto/src/crypto/tls/TlsSession.cs b/crypto/src/crypto/tls/TlsSession.cs
new file mode 100644
index 000000000..6c229913b
--- /dev/null
+++ b/crypto/src/crypto/tls/TlsSession.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    public interface TlsSession
+    {
+        SessionParameters ExportSessionParameters();
+
+        byte[] SessionID { get; }
+
+        void Invalidate();
+
+        bool IsResumable { get; }
+    }
+}
diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs
index 08fb6f0a4..a7932c9cc 100644
--- a/crypto/src/crypto/tls/TlsUtilities.cs
+++ b/crypto/src/crypto/tls/TlsUtilities.cs
@@ -118,6 +118,21 @@ namespace Org.BouncyCastle.Crypto.Tls
             return true;
         }
 
+        public static bool IsSsl(TlsContext context)
+        {
+            return context.ServerVersion.IsSsl;
+        }
+
+        public static bool IsTlsV11(TlsContext context)
+        {
+            return ProtocolVersion.TLSv11.IsEqualOrEarlierVersionOf(context.ServerVersion.GetEquivalentTLSVersion());
+        }
+
+        public static bool IsTlsV12(TlsContext context)
+        {
+            return ProtocolVersion.TLSv12.IsEqualOrEarlierVersionOf(context.ServerVersion.GetEquivalentTLSVersion());
+        }
+
         public static void WriteUint8(byte i, Stream output)
         {
             output.WriteByte(i);
@@ -600,12 +615,12 @@ namespace Org.BouncyCastle.Crypto.Tls
             return VectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa));
         }
 
-        public static byte[] GetExtensionData(Hashtable extensions, int extensionType)
+        public static byte[] GetExtensionData(IDictionary extensions, int extensionType)
         {
             return extensions == null ? null : (byte[])extensions[extensionType];
         }
 
-        public static bool HasExpectedEmptyExtensionData(Hashtable extensions, int extensionType,
+        public static bool HasExpectedEmptyExtensionData(IDictionary extensions, int extensionType,
             byte alertDescription)
         {
             byte[] extension_data = GetExtensionData(extensions, extensionType);
diff --git a/crypto/src/crypto/tls/UrlAndHash.cs b/crypto/src/crypto/tls/UrlAndHash.cs
new file mode 100644
index 000000000..9ffd2cbf8
--- /dev/null
+++ b/crypto/src/crypto/tls/UrlAndHash.cs
@@ -0,0 +1,94 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Tls
+{
+    /**
+     * RFC 6066 5.
+     */
+    public class UrlAndHash
+    {
+        protected readonly string mUrl;
+        protected readonly byte[] mSha1Hash;
+
+        public UrlAndHash(string url, byte[] sha1Hash)
+        {
+            if (url == null || url.Length < 1 || url.Length >= (1 << 16))
+                throw new ArgumentException("must have length from 1 to (2^16 - 1)", "url");
+            if (sha1Hash != null && sha1Hash.Length != 20)
+                throw new ArgumentException("must have length == 20, if present", "sha1Hash");
+
+            this.mUrl = url;
+            this.mSha1Hash = sha1Hash;
+        }
+
+        public virtual string Url
+        {
+            get { return mUrl; }
+        }
+
+        public virtual byte[] Sha1Hash
+        {
+            get { return mSha1Hash; }
+        }
+
+        /**
+         * Encode this {@link UrlAndHash} to a {@link Stream}.
+         *
+         * @param output the {@link Stream} to encode to.
+         * @throws IOException
+         */
+        public virtual void Encode(Stream output)
+        {
+            byte[] urlEncoding = Strings.ToByteArray(this.mUrl);
+            TlsUtilities.WriteOpaque16(urlEncoding, output);
+
+            if (this.mSha1Hash == null)
+            {
+                TlsUtilities.WriteUint8(0, output);
+            }
+            else
+            {
+                TlsUtilities.WriteUint8(1, output);
+                output.Write(this.mSha1Hash, 0, this.mSha1Hash.Length);
+            }
+        }
+
+        /**
+         * Parse a {@link UrlAndHash} from a {@link Stream}.
+         * 
+         * @param context
+         *            the {@link TlsContext} of the current connection.
+         * @param input
+         *            the {@link Stream} to parse from.
+         * @return a {@link UrlAndHash} object.
+         * @throws IOException
+         */
+        public static UrlAndHash Parse(TlsContext context, Stream input)
+        {
+            byte[] urlEncoding = TlsUtilities.ReadOpaque16(input);
+            if (urlEncoding.Length < 1)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            string url = Strings.FromByteArray(urlEncoding);
+
+            byte[] sha1Hash = null;
+            byte padding = TlsUtilities.ReadUint8(input);
+            switch (padding)
+            {
+            case 0:
+                if (TlsUtilities.IsTlsV12(context))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                break;
+            case 1:
+                sha1Hash = TlsUtilities.ReadFully(20, input);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            return new UrlAndHash(url, sha1Hash);
+        }
+    }
+}