X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FMono.Security%2FMono.Security.Protocol.Tls%2FSslClientStream.cs;h=e615e83e3f66a531ff09fbb82107745837c8eeba;hb=a0173a7e76ad48889ade46116e516731b170e7c5;hp=0f4e3b44e477b637d409daa0d68cb5f43c200ca9;hpb=6cfd2055426c190ca2f6a9f8ca3af2da6f6a79d0;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 0f4e3b44e47..e615e83e3f6 100644 --- a/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslClientStream.cs +++ b/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/SslClientStream.cs @@ -37,23 +37,80 @@ namespace Mono.Security.Protocol.Tls { #region Delegates - public delegate bool CertificateValidationCallback( +#if INSIDE_SYSTEM + internal +#else + public +#endif + delegate bool CertificateValidationCallback( X509Certificate certificate, int[] certificateErrors); - public delegate X509Certificate CertificateSelectionCallback( +#if INSIDE_SYSTEM + internal +#else + public +#endif + class ValidationResult { + bool trusted; + bool user_denied; + int error_code; + + public ValidationResult (bool trusted, bool user_denied, int error_code) + { + this.trusted = trusted; + this.user_denied = user_denied; + this.error_code = error_code; + } + + public bool Trusted { + get { return trusted; } + } + + public bool UserDenied { + get { return user_denied; } + } + + public int ErrorCode { + get { return error_code; } + } + } + +#if INSIDE_SYSTEM + internal +#else + public +#endif + delegate ValidationResult CertificateValidationCallback2 (Mono.Security.X509.X509CertificateCollection collection); + +#if INSIDE_SYSTEM + internal +#else + public +#endif + delegate X509Certificate CertificateSelectionCallback( X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates); - public delegate AsymmetricAlgorithm PrivateKeySelectionCallback( +#if INSIDE_SYSTEM + internal +#else + public +#endif + delegate AsymmetricAlgorithm PrivateKeySelectionCallback( X509Certificate certificate, string targetHost); #endregion - public class SslClientStream : SslStreamBase +#if INSIDE_SYSTEM + internal +#else + public +#endif + class SslClientStream : SslStreamBase { #region Internal Events @@ -105,6 +162,8 @@ namespace Mono.Security.Protocol.Tls #endregion + public event CertificateValidationCallback2 ServerCertValidation2; + #region Constructors public SslClientStream( @@ -192,6 +251,7 @@ namespace Mono.Security.Protocol.Tls this.ServerCertValidation = null; this.ClientCertSelection = null; this.PrivateKeySelection = null; + this.ServerCertValidation2 = null; } } @@ -220,95 +280,323 @@ namespace Mono.Security.Protocol.Tls Fig. 1 - Message flow for a full handshake */ - internal override IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state) + private void SafeEndReceiveRecord (IAsyncResult ar, bool ignoreEmpty = false) { - try + byte[] record = this.protocol.EndReceiveRecord (ar); + if (!ignoreEmpty && ((record == null) || (record.Length == 0))) { + throw new TlsException ( + AlertDescription.HandshakeFailiure, + "The server stopped the handshake."); + } + } + + private enum NegotiateState + { + SentClientHello, + ReceiveClientHelloResponse, + SentCipherSpec, + ReceiveCipherSpecResponse, + SentKeyExchange, + ReceiveFinishResponse, + SentFinished, + }; + + private class NegotiateAsyncResult : IAsyncResult + { + private object locker = new object (); + private AsyncCallback _userCallback; + private object _userState; + private Exception _asyncException; + private ManualResetEvent handle; + private NegotiateState _state; + private bool completed; + + public NegotiateAsyncResult(AsyncCallback userCallback, object userState, NegotiateState state) { - if (this.context.HandshakeState != HandshakeState.None) - { - this.context.Clear(); - } + _userCallback = userCallback; + _userState = userState; + _state = state; + } - // Obtain supported cipher suites - this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol); + public NegotiateState State + { + get { return _state; } + set { _state = value; } + } - // Set handshake state - this.context.HandshakeState = HandshakeState.Started; + public object AsyncState + { + get { return _userState; } + } - // Send client hello - return this.protocol.BeginSendRecord(HandshakeType.ClientHello, callback, state); + public Exception AsyncException + { + get { return _asyncException; } } - catch (TlsException ex) + + public bool CompletedWithError { - this.protocol.SendAlert(ex.Alert); + get { + if (!IsCompleted) + return false; // Perhaps throw InvalidOperationExcetion? - throw new IOException("The authentication or decryption has failed.", ex); + return null != _asyncException; + } } - catch (Exception ex) + + public WaitHandle AsyncWaitHandle { - this.protocol.SendAlert(AlertDescription.InternalError); + get { + lock (locker) { + if (handle == null) + handle = new ManualResetEvent (completed); + } + return handle; + } - throw new IOException("The authentication or decryption has failed.", ex); } - } - internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult) - { - this.protocol.EndSendRecord(asyncResult); - - // Read server response - while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone) + public bool CompletedSynchronously { - // Read next record - this.protocol.ReceiveRecord(this.innerStream); + get { return false; } } - // 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) + public bool IsCompleted { - clientCertificate = ((this.context.ClientSettings.Certificates != null) && - (this.context.ClientSettings.Certificates.Count > 0)); - // this works well with OpenSSL (but only for SSL3) + get { + lock (locker) { + return completed; + } + } } - if (clientCertificate) + public void SetComplete(Exception ex) { - this.protocol.SendRecord(HandshakeType.Certificate); - } + lock (locker) { + if (completed) + return; + + completed = true; + if (handle != null) + handle.Set (); - // Send Client Key Exchange - this.protocol.SendRecord(HandshakeType.ClientKeyExchange); + if (_userCallback != null) + _userCallback.BeginInvoke (this, null, null); - // Now initialize session cipher with the generated keys - this.context.Cipher.InitializeCipher(); + _asyncException = ex; + } + } - // Send certificate verify if requested (optional) - if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null)) + public void SetComplete() { - this.protocol.SendRecord(HandshakeType.CertificateVerify); + SetComplete(null); } + } + + internal override IAsyncResult BeginNegotiateHandshake(AsyncCallback callback, object state) + { + if (this.context.HandshakeState != HandshakeState.None) { + this.context.Clear (); + } + + // Obtain supported cipher suites + this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers (this.context.SecurityProtocol); + + // Set handshake state + this.context.HandshakeState = HandshakeState.Started; - // Send Cipher Spec protocol - this.protocol.SendChangeCipherSpec(); + NegotiateAsyncResult result = new NegotiateAsyncResult (callback, state, NegotiateState.SentClientHello); - // Read record until server finished is received - while (this.context.HandshakeState != HandshakeState.Finished) + // Begin sending the client hello + this.protocol.BeginSendRecord (HandshakeType.ClientHello, NegotiateAsyncWorker, result); + + return result; + } + + internal override void EndNegotiateHandshake (IAsyncResult result) + { + NegotiateAsyncResult negotiate = result as NegotiateAsyncResult; + + if (negotiate == null) + throw new ArgumentNullException (); + if (!negotiate.IsCompleted) + negotiate.AsyncWaitHandle.WaitOne(); + if (negotiate.CompletedWithError) + throw negotiate.AsyncException; + } + + private void NegotiateAsyncWorker (IAsyncResult result) + { + NegotiateAsyncResult negotiate = result.AsyncState as NegotiateAsyncResult; + + try { - // If all goes well this will process messages: - // Change Cipher Spec - // Server finished - this.protocol.ReceiveRecord(this.innerStream); - } + switch (negotiate.State) + { + case NegotiateState.SentClientHello: + this.protocol.EndSendRecord (result); + + // we are now ready to ready the receive the hello response. + negotiate.State = NegotiateState.ReceiveClientHelloResponse; + + // Start reading the client hello response + this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate); + break; + + case NegotiateState.ReceiveClientHelloResponse: + this.SafeEndReceiveRecord (result, true); + + if (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone && + (!this.context.AbbreviatedHandshake || this.context.LastHandshakeMsg != HandshakeType.ServerHello)) { + // Read next record (skip empty, e.g. warnings alerts) + this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate); + break; + } + + // special case for abbreviated handshake where no ServerHelloDone is sent from the server + if (this.context.AbbreviatedHandshake) { + ClientSessionCache.SetContextFromCache (this.context); + this.context.Negotiating.Cipher.ComputeKeys (); + this.context.Negotiating.Cipher.InitializeCipher (); + + negotiate.State = NegotiateState.SentCipherSpec; + + // Send Change Cipher Spec message with the current cipher + // or as plain text if this is the initial negotiation + this.protocol.BeginSendChangeCipherSpec(NegotiateAsyncWorker, negotiate); + } else { + // Send client certificate if requested + // even if the server ask for it it _may_ still be optional + bool clientCertificate = this.context.ServerSettings.CertificateRequest; + + using (var memstream = new MemoryStream()) + { + // 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) + { + clientCertificate = ((this.context.ClientSettings.Certificates != null) && + (this.context.ClientSettings.Certificates.Count > 0)); + // this works well with OpenSSL (but only for SSL3) + } + + byte[] record = null; + + if (clientCertificate) + { + record = this.protocol.EncodeHandshakeRecord(HandshakeType.Certificate); + memstream.Write(record, 0, record.Length); + } + + // Send Client Key Exchange + record = this.protocol.EncodeHandshakeRecord(HandshakeType.ClientKeyExchange); + memstream.Write(record, 0, record.Length); + + // Now initialize session cipher with the generated keys + this.context.Negotiating.Cipher.InitializeCipher(); + + // Send certificate verify if requested (optional) + if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null)) + { + record = this.protocol.EncodeHandshakeRecord(HandshakeType.CertificateVerify); + memstream.Write(record, 0, record.Length); + } + + // send the chnage cipher spec. + this.protocol.SendChangeCipherSpec(memstream); + + // Send Finished message + record = this.protocol.EncodeHandshakeRecord(HandshakeType.Finished); + memstream.Write(record, 0, record.Length); + + negotiate.State = NegotiateState.SentKeyExchange; + + // send all the records. + this.innerStream.BeginWrite (memstream.GetBuffer (), 0, (int)memstream.Length, NegotiateAsyncWorker, negotiate); + } + } + break; + + case NegotiateState.SentKeyExchange: + this.innerStream.EndWrite (result); + + negotiate.State = NegotiateState.ReceiveFinishResponse; + + this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate); + + break; + + case NegotiateState.ReceiveFinishResponse: + this.SafeEndReceiveRecord (result); + + // Read record until server finished is received + if (this.context.HandshakeState != HandshakeState.Finished) { + // If all goes well this will process messages: + // Change Cipher Spec + // Server finished + this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate); + } + else { + // Reset Handshake messages information + this.context.HandshakeMessages.Reset (); + + // Clear Key Info + this.context.ClearKeyInfo(); + + negotiate.SetComplete (); + } + break; + + + case NegotiateState.SentCipherSpec: + this.protocol.EndSendChangeCipherSpec (result); + + negotiate.State = NegotiateState.ReceiveCipherSpecResponse; + + // Start reading the cipher spec response + this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate); + break; + + case NegotiateState.ReceiveCipherSpecResponse: + this.SafeEndReceiveRecord (result, true); + + if (this.context.HandshakeState != HandshakeState.Finished) + { + this.protocol.BeginReceiveRecord (this.innerStream, NegotiateAsyncWorker, negotiate); + } + else + { + negotiate.State = NegotiateState.SentFinished; + this.protocol.BeginSendRecord(HandshakeType.Finished, NegotiateAsyncWorker, negotiate); + } + break; + + case NegotiateState.SentFinished: + this.protocol.EndSendRecord (result); + + // Reset Handshake messages information + this.context.HandshakeMessages.Reset (); + + // Clear Key Info + this.context.ClearKeyInfo(); - // Clear Key Info - this.context.ClearKeyInfo(); + negotiate.SetComplete (); + break; + } + } + catch (TlsException ex) + { + // FIXME: should the send alert also be done asynchronously here and below? + this.protocol.SendAlert(ex.Alert); + negotiate.SetComplete (new IOException("The authentication or decryption has failed.", ex)); + } + catch (Exception ex) + { + this.protocol.SendAlert(AlertDescription.InternalError); + negotiate.SetComplete (new IOException("The authentication or decryption has failed.", ex)); + } } #endregion @@ -328,7 +616,19 @@ namespace Mono.Security.Protocol.Tls return null; } - + + internal override bool HaveRemoteValidation2Callback { + get { return ServerCertValidation2 != null; } + } + + internal override ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection) + { + CertificateValidationCallback2 cb = ServerCertValidation2; + if (cb != null) + return cb (collection); + return null; + } + internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors) { if (this.ServerCertValidation != null) @@ -346,6 +646,11 @@ namespace Mono.Security.Protocol.Tls return base.RaiseRemoteCertificateValidation(certificate, certificateErrors); } + internal virtual ValidationResult RaiseServerCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection) + { + return base.RaiseRemoteCertificateValidation2 (collection); + } + internal X509Certificate RaiseClientCertificateSelection( X509CertificateCollection clientCertificates, X509Certificate serverCertificate,