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);
+ }
+ }
+}
|