X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FMono.Security%2FMono.Security.Protocol.Tls%2FSslClientStream.cs;h=03d37d79ba2474339461c06d6b2a0acac31c26f8;hb=89d0ba3968d36576553e0f483b0c69465f94e8ae;hp=63b80542d573129f526a0410863442518eee505c;hpb=1241791257b81da018ef858a4ea0e9bc88179c38;p=mono.git diff --git a/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslClientStream.cs b/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslClientStream.cs index 63b80542d57..03d37d79ba2 100644 --- a/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslClientStream.cs +++ b/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslClientStream.cs @@ -1,26 +1,26 @@ -/* Transport Security Layer (TLS) - * Copyright (c) 2003-2004 Carlos Guzman Alvarez - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ +// Transport Security Layer (TLS) +// Copyright (c) 2003-2004 Carlos Guzman Alvarez + +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// using System; using System.Collections; @@ -29,6 +29,7 @@ using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Threading; using Mono.Security.Protocol.Tls.Handshake; @@ -52,7 +53,7 @@ namespace Mono.Security.Protocol.Tls #endregion - public class SslClientStream : Stream, IDisposable + public class SslClientStream : SslStreamBase { #region Internal Events @@ -62,176 +63,24 @@ namespace Mono.Security.Protocol.Tls #endregion - #region Fields - - private Stream innerStream; - private BufferedStream inputBuffer; - private ClientContext context; - private ClientRecordProtocol protocol; - private bool ownsStream; - private bool disposed; - private bool checkCertRevocationStatus; - private object read; - private object write; - - #endregion - #region Properties - public override bool CanRead + // required by HttpsClientStream for proxy support + internal Stream InputBuffer { - get { return this.innerStream.CanRead; } + get { return base.inputBuffer; } } - public override bool CanSeek + public X509CertificateCollection ClientCertificates { - get { return false; } + get { return this.context.ClientSettings.Certificates; } } - public override bool CanWrite - { - get { return this.innerStream.CanWrite; } - } - - public override long Length - { - get { throw new NotSupportedException(); } - } - - public override long Position - { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - - #endregion - - #region Security Properties - - public bool CheckCertRevocationStatus - { - get { return this.checkCertRevocationStatus ; } - set { this.checkCertRevocationStatus = value; } - } - - public CipherAlgorithmType CipherAlgorithm - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.Cipher.CipherAlgorithmType; - } - - return CipherAlgorithmType.None; - } - } - - public int CipherStrength - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.Cipher.EffectiveKeyBits; - } - - return 0; - } - } - - public X509CertificateCollection ClientCertificates - { - get { return this.context.ClientSettings.Certificates;} - } - - public HashAlgorithmType HashAlgorithm - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.Cipher.HashAlgorithmType; - } - - return HashAlgorithmType.None; - } - } - - public int HashStrength - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.Cipher.HashSize * 8; - } - - return 0; - } - } - - public int KeyExchangeStrength - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.ServerSettings.Certificates[0].RSA.KeySize; - } - - return 0; - } - } - - public ExchangeAlgorithmType KeyExchangeAlgorithm - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.Cipher.ExchangeAlgorithmType; - } - - return ExchangeAlgorithmType.None; - } - } - - public SecurityProtocolType SecurityProtocol - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - return this.context.SecurityProtocol; - } - - return 0; - } - } - - public X509Certificate SelectedClientCertificate + public X509Certificate SelectedClientCertificate { get { return this.context.ClientSettings.ClientCertificate; } } - public X509Certificate ServerCertificate - { - get - { - if (this.context.HandshakeState == HandshakeState.Finished) - { - if (this.context.ServerSettings.Certificates != null && - this.context.ServerSettings.Certificates.Count > 0) - { - return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData); - } - } - - return null; - } - } - #endregion #region Callback Properties @@ -248,12 +97,12 @@ namespace Mono.Security.Protocol.Tls set { this.ClientCertSelection = value; } } - public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate + public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate { get { return this.PrivateKeySelection; } set { this.PrivateKeySelection = value; } } - + #endregion #region Constructors @@ -304,16 +153,9 @@ namespace Mono.Security.Protocol.Tls string targetHost, bool ownsStream, SecurityProtocolType securityProtocolType, - X509CertificateCollection clientCertificates) + X509CertificateCollection clientCertificates): + base(stream, ownsStream) { - if (stream == null) - { - throw new ArgumentNullException("stream is null."); - } - if (!stream.CanRead || !stream.CanWrite) - { - throw new ArgumentNullException("stream is not both readable and writable."); - } if (targetHost == null || targetHost.Length == 0) { throw new ArgumentNullException("targetHost is null or an empty string."); @@ -325,12 +167,7 @@ namespace Mono.Security.Protocol.Tls targetHost, clientCertificates); - this.inputBuffer = new BufferedStream(new MemoryStream()); - this.innerStream = stream; - this.ownsStream = ownsStream; - this.read = String.Empty; - this.write = String.Empty; - this.protocol = new ClientRecordProtocol(innerStream, context); + this.protocol = new ClientRecordProtocol(innerStream, (ClientContext)this.context); } #endregion @@ -339,433 +176,218 @@ namespace Mono.Security.Protocol.Tls ~SslClientStream() { - this.Dispose(false); + base.Dispose(false); } #endregion - #region IDisposable Methods + #region IDisposable Methods - void IDisposable.Dispose() + protected override void Dispose(bool disposing) { - this.Dispose(true); - GC.SuppressFinalize(this); - } + base.Dispose(disposing); - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) + if (disposing) { - if (disposing) - { - if (this.innerStream != null) - { - if (this.context.HandshakeState == HandshakeState.Finished && - !this.context.ConnectionEnd) - { - // Write close notify - this.protocol.SendAlert(AlertDescription.CloseNotify); - } - - if (this.ownsStream) - { - // Close inner stream - this.innerStream.Close(); - } - } - this.ownsStream = false; - this.innerStream = null; - this.ClientCertSelection = null; - this.ServerCertValidation = null; - this.PrivateKeySelection = null; - } - - this.disposed = true; + this.ServerCertValidation = null; + this.ClientCertSelection = null; + this.PrivateKeySelection = null; } } #endregion - #region Methods + #region Handshake Methods - public override IAsyncResult BeginRead( - byte[] buffer, - int offset, - int count, - AsyncCallback callback, - object state) - { - this.checkDisposed(); - - if (buffer == null) - { - throw new ArgumentNullException("buffer is a null reference."); - } - if (offset < 0) - { - throw new ArgumentOutOfRangeException("offset is less than 0."); - } - if (offset > buffer.Length) - { - throw new ArgumentOutOfRangeException("offset is greater than the length of buffer."); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException("count is less than 0."); - } - if (count > (buffer.Length - offset)) - { - throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter."); - } + /* + Client Server - lock (this) - { - if (this.context.HandshakeState == HandshakeState.None) - { - this.NegotiateHandshake(); - } - } + ClientHello --------> + ServerHello + Certificate* + ServerKeyExchange* + CertificateRequest* + <-------- ServerHelloDone + Certificate* + ClientKeyExchange + CertificateVerify* + [ChangeCipherSpec] + Finished --------> + [ChangeCipherSpec] + <-------- Finished + Application Data <-------> Application Data - IAsyncResult asyncResult; + Fig. 1 - Message flow for a full handshake + */ - lock (this.read) + internal override IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state) + { + try { - try + if (this.context.HandshakeState != HandshakeState.None) { - // If actual buffer is full readed reset it - if (this.inputBuffer.Position == this.inputBuffer.Length && - this.inputBuffer.Length > 0) - { - this.resetBuffer(); - } - - if (!this.context.ConnectionEnd) - { - // Check if we have space in the middle buffer - // if not Read next TLS record and update the inputBuffer - while ((this.inputBuffer.Length - this.inputBuffer.Position) < count) - { - // Read next record and write it into the inputBuffer - long position = this.inputBuffer.Position; - byte[] record = this.protocol.ReceiveRecord(); - - if (record != null && record.Length > 0) - { - // Write new data to the inputBuffer - this.inputBuffer.Seek(0, SeekOrigin.End); - this.inputBuffer.Write(record, 0, record.Length); - - // Restore buffer position - this.inputBuffer.Seek(position, SeekOrigin.Begin); - } - else - { - if (record == null) - { - break; - } - } - - // TODO: Review if we need to check the Length - // property of the innerStream for other types - // of streams, to check that there are data available - // for read - if (this.innerStream is NetworkStream && - !((NetworkStream)this.innerStream).DataAvailable) - { - break; - } - } - } - - asyncResult = this.inputBuffer.BeginRead( - buffer, offset, count, callback, state); + this.context.Clear(); } - catch (TlsException ex) - { - this.protocol.SendAlert(ex.Alert); - this.Close(); - throw new IOException("The authentication or decryption has failed."); - } - catch (Exception) - { - throw new IOException("IO exception during read."); - } - } + // Obtain supported cipher suites + this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol); - return asyncResult; - } + // Set handshake state + this.context.HandshakeState = HandshakeState.Started; - public override IAsyncResult BeginWrite( - byte[] buffer, - int offset, - int count, - AsyncCallback callback, - object state) - { - this.checkDisposed(); - - if (buffer == null) - { - throw new ArgumentNullException("buffer is a null reference."); + // Send client hello + return this.protocol.BeginSendRecord(HandshakeType.ClientHello, callback, state); } - if (offset < 0) + catch (TlsException ex) { - throw new ArgumentOutOfRangeException("offset is less than 0."); + this.protocol.SendAlert(ex.Alert); + + throw new IOException("The authentication or decryption has failed.", ex); } - if (offset > buffer.Length) + catch (Exception ex) { - throw new ArgumentOutOfRangeException("offset is greater than the length of buffer."); + this.protocol.SendAlert(AlertDescription.InternalError); + + throw new IOException("The authentication or decryption has failed.", ex); } - if (count < 0) - { - throw new ArgumentOutOfRangeException("count is less than 0."); + } + + private void SafeReceiveRecord (Stream s) + { + byte[] record = this.protocol.ReceiveRecord (s); + if ((record == null) || (record.Length == 0)) { + throw new TlsException ( + AlertDescription.HandshakeFailiure, + "The server stopped the handshake."); } - if (count > (buffer.Length - offset)) + } + + internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult) + { + this.protocol.EndSendRecord(asyncResult); + + // Read server response + while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone) { - throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter."); + // Read next record + SafeReceiveRecord (this.innerStream); + + // special case for abbreviated handshake where no ServerHelloDone is sent from the server + if (this.context.AbbreviatedHandshake && (this.context.LastHandshakeMsg == HandshakeType.ServerHello)) + break; } - lock (this) + // the handshake is much easier if we can reuse a previous session settings + if (this.context.AbbreviatedHandshake) { - if (this.context.HandshakeState == HandshakeState.None) + ClientSessionCache.SetContextFromCache (this.context); + this.context.Negotiating.Cipher.ComputeKeys (); + this.context.Negotiating.Cipher.InitializeCipher (); + + // Send Cipher Spec protocol + this.protocol.SendChangeCipherSpec (); + + // Read record until server finished is received + while (this.context.HandshakeState != HandshakeState.Finished) { - this.NegotiateHandshake(); + // If all goes well this will process messages: + // Change Cipher Spec + // Server finished + SafeReceiveRecord (this.innerStream); } - } - IAsyncResult asyncResult; - - lock (this.write) + // Send Finished message + this.protocol.SendRecord (HandshakeType.Finished); + } + else { - try + // Send client certificate if requested + // even if the server ask for it it _may_ still be optional + bool clientCertificate = this.context.ServerSettings.CertificateRequest; + + // NOTE: sadly SSL3 and TLS1 differs in how they handle this and + // the current design doesn't allow a very cute way to handle + // SSL3 alert warning for NoCertificate (41). + if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3) { - // Send the buffer as a TLS record - - byte[] record = this.protocol.EncodeRecord( - ContentType.ApplicationData, buffer, offset, count); - - asyncResult = this.innerStream.BeginWrite( - record, 0, record.Length, callback, state); + clientCertificate = ((this.context.ClientSettings.Certificates != null) && + (this.context.ClientSettings.Certificates.Count > 0)); + // this works well with OpenSSL (but only for SSL3) } - catch (TlsException ex) - { - this.protocol.SendAlert(ex.Alert); - this.Close(); - throw new IOException("The authentication or decryption has failed."); - } - catch (Exception) + if (clientCertificate) { - throw new IOException("IO exception during Write."); + this.protocol.SendRecord(HandshakeType.Certificate); } - } - return asyncResult; - } + // Send Client Key Exchange + this.protocol.SendRecord(HandshakeType.ClientKeyExchange); - public override int EndRead(IAsyncResult asyncResult) - { - this.checkDisposed(); + // Now initialize session cipher with the generated keys + this.context.Negotiating.Cipher.InitializeCipher(); - if (asyncResult == null) - { - throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead."); - } + // Send certificate verify if requested (optional) + if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null)) + { + this.protocol.SendRecord(HandshakeType.CertificateVerify); + } - return this.inputBuffer.EndRead(asyncResult); - } + // Send Cipher Spec protocol + this.protocol.SendChangeCipherSpec (); - public override void EndWrite(IAsyncResult asyncResult) - { - this.checkDisposed(); + // Send Finished message + this.protocol.SendRecord (HandshakeType.Finished); - if (asyncResult == null) - { - throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead."); + // Read record until server finished is received + while (this.context.HandshakeState != HandshakeState.Finished) { + // If all goes well this will process messages: + // Change Cipher Spec + // Server finished + SafeReceiveRecord (this.innerStream); + } } - this.innerStream.EndWrite (asyncResult); - } - - public override void Close() - { - ((IDisposable)this).Dispose(); - } - - public override void Flush() - { - this.checkDisposed(); - - this.innerStream.Flush(); - } - - public int Read(byte[] buffer) - { - return this.Read(buffer, 0, buffer.Length); - } - - public override int Read(byte[] buffer, int offset, int count) - { - IAsyncResult res = this.BeginRead(buffer, offset, count, null, null); - - return this.EndRead(res); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public void Write(byte[] buffer) - { - this.Write(buffer, 0, buffer.Length); - } + // Reset Handshake messages information + this.context.HandshakeMessages.Reset (); - public override void Write(byte[] buffer, int offset, int count) - { - IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null); + // Clear Key Info + this.context.ClearKeyInfo(); - this.EndWrite(res); } #endregion - #region Misc Methods - - private void resetBuffer() - { - this.inputBuffer.SetLength(0); - this.inputBuffer.Position = 0; - } + #region Event Methods - private void checkDisposed() + internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates) { - if (this.disposed) + if (this.ClientCertSelection != null) { - throw new ObjectDisposedException("The SslClientStream is closed."); + return this.ClientCertSelection( + clientCertificates, + serverCertificate, + targetHost, + serverRequestedCertificates); } - } - - #endregion - - #region Handsake Methods - - /* - Client Server - - ClientHello --------> - ServerHello - Certificate* - ServerKeyExchange* - CertificateRequest* - <-------- ServerHelloDone - Certificate* - ClientKeyExchange - CertificateVerify* - [ChangeCipherSpec] - Finished --------> - [ChangeCipherSpec] - <-------- Finished - Application Data <-------> Application Data - Fig. 1 - Message flow for a full handshake - */ - - internal void NegotiateHandshake() + return null; + } + + internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors) { - lock (this) + if (this.ServerCertValidation != null) { - try - { - if (this.context.HandshakeState != HandshakeState.None) - { - this.context.Clear(); - } - - // Obtain supported cipher suites - this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol); - - // Send client hello - this.protocol.SendRecord(HandshakeType.ClientHello); - - // Read server response - while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone) - { - // Read next record - this.protocol.ReceiveRecord(); - } - - // Send client certificate if requested - if (this.context.ServerSettings.CertificateRequest) - { - this.protocol.SendRecord(HandshakeType.Certificate); - } - - // Send Client Key Exchange - this.protocol.SendRecord(HandshakeType.ClientKeyExchange); - - // Now initialize session cipher with the generated keys - this.context.Cipher.InitializeCipher(); - - // Send certificate verify if requested - if (this.context.ServerSettings.CertificateRequest) - { - this.protocol.SendRecord(HandshakeType.CertificateVerify); - } - - // Send Cipher Spec protocol - this.protocol.SendChangeCipherSpec(); - - // Read record until server finished is received - while (this.context.HandshakeState != HandshakeState.Finished) - { - // If all goes well this will process messages: - // Change Cipher Spec - // Server finished - this.protocol.ReceiveRecord(); - } - - // Clear Key Info - this.context.ClearKeyInfo(); - } - catch (TlsException ex) - { - this.protocol.SendAlert(ex.Alert); - this.Close(); - - throw new IOException("The authentication or decryption has failed."); - } - catch (Exception) - { - this.protocol.SendAlert(AlertDescription.InternalError); - this.Close(); - - throw new IOException("The authentication or decryption has failed."); - } + return this.ServerCertValidation(certificate, errors); } - } - #endregion - - #region Event Methods + return (errors != null && errors.Length == 0); + } internal virtual bool RaiseServerCertificateValidation( X509Certificate certificate, int[] certificateErrors) { - if (this.ServerCertValidation != null) - { - return this.ServerCertValidation(certificate, certificateErrors); - } - - return (certificateErrors != null && certificateErrors.Length == 0); + return base.RaiseRemoteCertificateValidation(certificate, certificateErrors); } internal X509Certificate RaiseClientCertificateSelection( @@ -774,21 +396,10 @@ namespace Mono.Security.Protocol.Tls string targetHost, X509CertificateCollection serverRequestedCertificates) { - if (this.ClientCertSelection != null) - { - return this.ClientCertSelection( - clientCertificates, - serverCertificate, - targetHost, - serverRequestedCertificates); - } - - return null; + return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates); } - internal AsymmetricAlgorithm RaisePrivateKeySelection( - X509Certificate certificate, - string targetHost) + internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost) { if (this.PrivateKeySelection != null) { @@ -798,6 +409,13 @@ namespace Mono.Security.Protocol.Tls return null; } + internal AsymmetricAlgorithm RaisePrivateKeySelection( + X509Certificate certificate, + string targetHost) + { + return base.RaiseLocalPrivateKeySelection(certificate, targetHost); + } + #endregion } -} \ No newline at end of file +}