diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2014-07-24 14:54:13 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2014-07-24 14:54:13 +0700 |
commit | bf1886e5381d0f3674efa1f9890dfb9fc1811ab8 (patch) | |
tree | d30d93bb51b5397813da1b5e2ffe208db5cb3de6 | |
parent | Add more variations of Check/IsValid (diff) | |
download | BouncyCastle.NET-ed25519-bf1886e5381d0f3674efa1f9890dfb9fc1811ab8.tar.xz |
Another round of TLS porting from Java
-rw-r--r-- | crypto/crypto.csproj | 45 | ||||
-rw-r--r-- | crypto/src/crypto/tls/CertificateStatus.cs | 102 | ||||
-rw-r--r-- | crypto/src/crypto/tls/CertificateStatusRequest.cs | 95 | ||||
-rw-r--r-- | crypto/src/crypto/tls/CertificateUrl.cs | 124 | ||||
-rw-r--r-- | crypto/src/crypto/tls/NewSessionTicket.cs | 4 | ||||
-rw-r--r-- | crypto/src/crypto/tls/OcspStatusRequest.cs | 130 | ||||
-rw-r--r-- | crypto/src/crypto/tls/SessionParameters.cs | 137 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsContext.cs | 45 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsProtocol.cs | 72 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsProtocolHandler.cs | 145 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsSession.cs | 15 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsUtilities.cs | 19 | ||||
-rw-r--r-- | crypto/src/crypto/tls/UrlAndHash.cs | 94 |
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); + } + } +} |