Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Net / System / Net / SecureProtocols / _SslState.cs / 1305376 / _SslState.cs
/*++ Copyright (c) 2000 Microsoft Corporation Module Name: _SslState.cs Abstract: Author: Mauro Ottaviani 07-Nov-2001 Arthur Bierer 16-Nov-2001 Alexei Vopilov 26-Jun-2002 Revision History: 22-Aug-2003 Adopted for new Ssl feature design. 15-Sept-2003 Implemented concurent rehanshake --*/ namespace System.Net.Security { using System; using System.IO; using System.Threading; using System.Security.Cryptography.X509Certificates; using System.Collections; using System.Runtime.InteropServices; using System.Globalization; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; using System.ComponentModel; internal class SslState { static int UniqueNameInteger = 123; static AsyncProtocolCallback _PartialFrameCallback = new AsyncProtocolCallback(PartialFrameCallback); static AsyncProtocolCallback _ReadFrameCallback = new AsyncProtocolCallback(ReadFrameCallback); static AsyncCallback _WriteCallback = new AsyncCallback(WriteCallback); private RemoteCertValidationCallback _CertValidationDelegate; private LocalCertSelectionCallback _CertSelectionDelegate; private bool _CanRetryAuthentication; // for now it's always false. private Stream _InnerStream; private _SslStream _SecureStream; private FixedSizeReader _Reader; private int _NestedAuth; private SecureChannel _Context; private bool _HandshakeCompleted; private bool _CertValidationFailed; private SecurityStatus _SecurityStatus; private Exception _Exception; enum CachedSessionStatus: byte { Unknown = 0, IsNotCached = 1, IsCached = 2, Renegotiated = 3 } private CachedSessionStatus _CachedSession; // This block is used by rehandshake code to buffer data decryptred with the old key private byte[] _QueuedReadData; private int _QueuedReadCount; private bool _PendingReHandshake; private const int _ConstMaxQueuedReadBytes = 1024 * 128; // // This block is used to rule the >>re-handshakes<< that are concurent with read/write io requests // private const int LockNone = 0; private const int LockWrite = 1; private const int LockHandshake = 2; private const int LockPendingWrite = 3; private const int LockRead = 4; private const int LockPendingRead = 6; private int _LockWriteState; private object _QueuedWriteStateRequest; private int _LockReadState; private object _QueuedReadStateRequest; // This is a perf trick ONLY for HTTPS TlsStream. // private bool _ForceBufferingLastHandshakePayload; private byte[] _LastPayload; private readonly EncryptionPolicy _EncryptionPolicy; // // This .ctor is only for internal TlsStream class, used only from client side ssl and it also has some special requirements. // internal SslState(Stream innerStream, bool isHTTP, EncryptionPolicy encryptionPolicy): this(innerStream, null, null, encryptionPolicy) { _ForceBufferingLastHandshakePayload = isHTTP; } // // The public Client and Server classes enforce the parameters rules before // calling into this .ctor. // internal SslState(Stream innerStream, RemoteCertValidationCallback certValidationCallback, LocalCertSelectionCallback certSelectionCallback, EncryptionPolicy encryptionPolicy) { _InnerStream = innerStream; _Reader = new FixedSizeReader(innerStream); _CertValidationDelegate = certValidationCallback; _CertSelectionDelegate = certSelectionCallback; _EncryptionPolicy = encryptionPolicy; } // // // internal void ValidateCreateContext(bool isServer, string targetHost, SslProtocols enabledSslProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool remoteCertRequired, bool checkCertRevocationStatus) { ValidateCreateContext( isServer, targetHost, enabledSslProtocols, serverCertificate, clientCertificates, remoteCertRequired, checkCertRevocationStatus, !isServer); } internal void ValidateCreateContext(bool isServer, string targetHost, SslProtocols enabledSslProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool remoteCertRequired, bool checkCertRevocationStatus, bool checkCertName) { // // We don;t support SSL alerts right now, hence any exception is fatal and cannot be retried // Consider: make use if SSL alerts to re-[....] SslStream on both sides for retrying. // if (_Exception != null && !_CanRetryAuthentication) { throw _Exception; } if (Context != null && Context.IsValidContext) { throw new InvalidOperationException(SR.GetString(SR.net_auth_reauth)); } if (Context != null && IsServer != isServer) { throw new InvalidOperationException(SR.GetString(SR.net_auth_client_server)); } if (targetHost == null) { throw new ArgumentNullException("targetHost"); } if (isServer ) { enabledSslProtocols &= (SslProtocols)SchProtocols.ServerMask; if (serverCertificate == null) throw new ArgumentNullException("serverCertificate"); } else { enabledSslProtocols &= (SslProtocols)SchProtocols.ClientMask; } if ((int)enabledSslProtocols == 0) { throw new ArgumentException(SR.GetString(SR.net_invalid_enum, "SslProtocolType"), "sslProtocolType"); } if (clientCertificates == null) { clientCertificates = new X509CertificateCollection(); } if (targetHost.Length == 0) { targetHost = "?" + Interlocked.Increment(ref UniqueNameInteger).ToString(NumberFormatInfo.InvariantInfo); } _Exception = null; try { _Context = new SecureChannel(targetHost, isServer, (SchProtocols)((int)enabledSslProtocols), serverCertificate, clientCertificates, remoteCertRequired, checkCertName, checkCertRevocationStatus, _EncryptionPolicy, _CertSelectionDelegate); } catch (Win32Exception e) { throw new AuthenticationException(SR.GetString(SR.net_auth_SSPI), e); } } // // General informational properties // // // internal bool IsAuthenticated { get { return _Context != null && _Context.IsValidContext && _Exception == null && HandshakeCompleted; } } // // // internal bool IsMutuallyAuthenticated { get { return IsAuthenticated && (Context.IsServer? Context.LocalServerCertificate: Context.LocalClientCertificate) != null && Context.IsRemoteCertificateAvailable; /* does not work: Context.IsMutualAuthFlag;*/ } } // internal bool RemoteCertRequired { get { return Context == null || Context.RemoteCertRequired; } } // internal bool IsServer { get { return Context != null && Context.IsServer; } } // // SSL related properties // internal void SetCertValidationDelegate(RemoteCertValidationCallback certValidationCallback) { _CertValidationDelegate = certValidationCallback; } // // This will return selected local cert for both client/server streams // internal X509Certificate LocalCertificate { get { CheckThrow(true); return InternalLocalCertificate; } } // // Used directly from ServicePoint for client side only internal X509Certificate InternalLocalCertificate { get { return Context.IsServer? Context.LocalServerCertificate : Context.LocalClientCertificate; } } // internal ChannelBinding GetChannelBinding(ChannelBindingKind kind) { return (Context == null) ? null : Context.GetChannelBinding(kind); } // internal bool CheckCertRevocationStatus { get { return Context != null && Context.CheckCertRevocationStatus; } } // internal SecurityStatus LastSecurityStatus { get {return _SecurityStatus;} } // internal bool IsCertValidationFailed { get { return _CertValidationFailed; } } // internal bool DataAvailable { get { return IsAuthenticated && (SecureStream.DataAvailable || _QueuedReadCount != 0); } } // internal CipherAlgorithmType CipherAlgorithm { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return CipherAlgorithmType.None; } return (CipherAlgorithmType)info.DataCipherAlg; } } // internal int CipherStrength { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return 0; } return info.DataKeySize; } } // internal HashAlgorithmType HashAlgorithm { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return (HashAlgorithmType)0; } return (HashAlgorithmType)info.DataHashAlg; } } // internal int HashStrength { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return 0; } return info.DataHashKeySize; } } // internal ExchangeAlgorithmType KeyExchangeAlgorithm { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return (ExchangeAlgorithmType)0; } return (ExchangeAlgorithmType)info.KeyExchangeAlg; } } // internal int KeyExchangeStrength { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return 0; } return info.KeyExchKeySize; } } // internal SslProtocols SslProtocol { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return SslProtocols.None; } SslProtocols proto = (SslProtocols)info.Protocol; // restore client/server bits so the result maps exaclty on published constants if ((proto & SslProtocols.Ssl2) != 0) { proto |= SslProtocols.Ssl2; } if ((proto & SslProtocols.Ssl3) != 0) { proto |= SslProtocols.Ssl3; } if ((proto & SslProtocols.Tls) != 0) { proto |= SslProtocols.Tls; } return proto; } } // // // internal Stream InnerStream { get { return _InnerStream; } } // // // internal _SslStream SecureStream { get{ CheckThrow(true); if (_SecureStream == null) { Interlocked.CompareExchange<_SslStream>(ref _SecureStream, new _SslStream(this), null); } return _SecureStream; } } // internal int HeaderSize { get { return Context.HeaderSize; } } // internal int MaxDataSize { get { return Context.MaxDataSize; } } internal byte[] LastPayload { get { return _LastPayload; } } internal void LastPayloadConsumed() { _LastPayload = null; } // private Exception SetException(Exception e) { if (_Exception == null) { _Exception = e; } if (_Exception != null && Context != null) { Context.Close(); } return _Exception; } // private bool HandshakeCompleted { get { return _HandshakeCompleted; } } // // // private SecureChannel Context { get { return _Context; } } // // Methods // // // internal void CheckThrow(bool authSucessCheck) { if (_Exception != null) { throw _Exception; } if (authSucessCheck && !IsAuthenticated) { throw new InvalidOperationException(SR.GetString(SR.net_auth_noauth)); } } // internal void Flush() { InnerStream.Flush(); } // // This is to not depend on GC&SafeHandle class if the context is not needed anymore. // internal void Close() { _Exception = new ObjectDisposedException("SslStream"); if (Context != null) { Context.Close(); } } // // // internal SecurityStatus EncryptData(byte[] buffer, int offset, int count, ref byte[] outBuffer, out int outSize) { CheckThrow(true); return Context.Encrypt(buffer, offset, count, ref outBuffer, out outSize); } // // // internal SecurityStatus DecryptData(byte[] buffer, ref int offset, ref int count) { CheckThrow(true); return PrivateDecryptData(buffer, ref offset, ref count); } // // private SecurityStatus PrivateDecryptData(byte[] buffer, ref int offset, ref int count) { return Context.Decrypt(buffer, ref offset, ref count); } // // // Called by re-handshake if found data decrypted with the old key // private Exception EnqueueOldKeyDecryptedData(byte[] buffer, int offset, int count) { lock (this) { if (_QueuedReadCount + count > _ConstMaxQueuedReadBytes) { return new IOException(SR.GetString(SR.net_auth_ignored_reauth, _ConstMaxQueuedReadBytes.ToString(NumberFormatInfo.CurrentInfo))); } if (count !=0) { // This is inefficient yet simple and that should be a rare case of receiving data encrypted with "old" key _QueuedReadData = EnsureBufferSize(_QueuedReadData, _QueuedReadCount, _QueuedReadCount + count); Buffer.BlockCopy(buffer, offset, _QueuedReadData, _QueuedReadCount, count); _QueuedReadCount+=count; FinishHandshakeRead(LockHandshake); } } return null; } // // When re-handshaking the "old" key decrypted data are queued until the handshake is done // When stream calls for decryption we will feed it queued data left from "old" encryption key. // // Must be called under the lock in case concurent handshake is going // internal int CheckOldKeyDecryptedData(byte[] buffer, int offset, int count) { CheckThrow(true); if (_QueuedReadData != null) { // This is inefficient yet simple and should be a REALLY rare case. int toCopy = Math.Min(_QueuedReadCount, count); Buffer.BlockCopy(_QueuedReadData, 0, buffer, offset, toCopy); _QueuedReadCount -= toCopy; if (_QueuedReadCount == 0) { _QueuedReadData = null; } else { Buffer.BlockCopy(_QueuedReadData, toCopy, _QueuedReadData, 0, _QueuedReadCount); } return toCopy; } return -1; } // // This method assumes that a SSPI context is already in a good shape. // For example it is either a fresh context or already authenticated context that needs renegotiation. // internal void ProcessAuthentication(LazyAsyncResult lazyResult) { if (Interlocked.Exchange(ref _NestedAuth, 1) == 1) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidnestedcall, lazyResult==null?"BeginAuthenticate":"Authenticate", "authenticate")); } try { CheckThrow(false); AsyncProtocolRequest asyncRequest = null; if (lazyResult != null) { asyncRequest = new AsyncProtocolRequest(lazyResult); asyncRequest.Buffer = null; #if DEBUG lazyResult._DebugAsyncChain = asyncRequest; #endif } // A Win 9x trick to discover and avoid cached sessions _CachedSession = CachedSessionStatus.Unknown; ForceAuthentication(Context.IsServer, null, asyncRequest); // Not aync so the connection is completed at this point if (lazyResult == null && Logging.On) { Logging.PrintInfo(Logging.Web, SR.GetString(SR.net_log_sspi_selected_cipher_suite, "ProcessAuthentication", SslProtocol, CipherAlgorithm, CipherStrength, HashAlgorithm, HashStrength, KeyExchangeAlgorithm, KeyExchangeStrength)); } } finally { if (lazyResult == null || _Exception != null) { _NestedAuth = 0; } } } // // This is used to reply on re-handshake when received SEC_I_RENEGOTIATE on Read() // internal void ReplyOnReAuthentication(byte[] buffer) { lock (this) { // Note we are already inside the read, so checking for already going concurent handshake _LockReadState = LockHandshake; if (_PendingReHandshake) { // A concurent handshake is pending, resume FinishRead(buffer); return; } } // Start rehandshake from here // forcing async mode. The caller will queue another Read as soon as we return using its preferred // calling convention, which will be woken up when the handshake completes. The callback is just // to capture any SocketErrors that happen during the handshake so they can be surfaced from the Read. AsyncProtocolRequest asyncRequest = new AsyncProtocolRequest(new LazyAsyncResult(this, null, new AsyncCallback(RehandshakeCompleteCallback))); // Buffer contains a result from DecryptMessage that will be passed to ISC/ASC asyncRequest.Buffer = buffer; ForceAuthentication(false, buffer, asyncRequest); } // // This method attempts to start authentication. // Incoming buffer is either null or is the result of "renegotiate" decrypted message // If write is in progress the method will either wait or be put on hold // private void ForceAuthentication(bool receiveFirst, byte[] buffer, AsyncProtocolRequest asyncRequest) { if (CheckEnqueueHandshake(buffer, asyncRequest)) { // Async handshake is enqueued and will resume later return; } // Either [....] handshake is ready to go or async handshake won the ---- over write. // This will tell that we don't know the framing yet (what SSL version is) _Framing = Framing.None; try { if (receiveFirst) { // Listen for a client blob StartReceiveBlob(buffer, asyncRequest); } else { // we start with the first blob StartSendBlob(buffer, (buffer == null? 0: buffer.Length), asyncRequest); } } catch (Exception e) { // Failed auth, reset the framing if any. _Framing = Framing.None; _HandshakeCompleted = false; if (SetException(e) == e) { throw; } else { throw _Exception; } } finally { if (_Exception != null) { // This either a failed handshake // Release waiting IO if any. Presumably it should not throw. // Otheriwse application may receive not expected type of the exception. FinishHandshake(null, null); } } } // // // internal void EndProcessAuthentication(IAsyncResult result) { if (result == null) { throw new ArgumentNullException("asyncResult"); } LazyAsyncResult lazyResult = result as LazyAsyncResult; if (lazyResult == null) { throw new ArgumentException(SR.GetString(SR.net_io_async_result, result.GetType().FullName), "asyncResult"); } if (Interlocked.Exchange(ref _NestedAuth, 0) == 0) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndAuthenticate")); } InternalEndProcessAuthentication(lazyResult); // Connection is completed at this point if (Logging.On) { Logging.PrintInfo(Logging.Web, SR.GetString(SR.net_log_sspi_selected_cipher_suite, "EndProcessAuthentication", SslProtocol, CipherAlgorithm, CipherStrength, HashAlgorithm, HashStrength, KeyExchangeAlgorithm, KeyExchangeStrength)); } } // // // internal void InternalEndProcessAuthentication(LazyAsyncResult lazyResult) { // No "artificial" timeouts implemented so far, InnerStream controls that. lazyResult.InternalWaitForCompletion(); Exception e = lazyResult.Result as Exception; if (e != null) { // Failed auth, reset the framing if any. _Framing = Framing.None; _HandshakeCompleted = false; throw SetException(e); } } // // Client side starts here, but server also loops through this method // private void StartSendBlob(byte[] incoming, int count, AsyncProtocolRequest asyncRequest) { ProtocolToken message = Context.NextMessage(incoming, 0, count); _SecurityStatus = message.Status; if (message.Size != 0) { if (Context.IsServer && _CachedSession == CachedSessionStatus.Unknown) { // //[Schannel] If the first call to ASC returns a token less than 200 bytes, // then it's a reconnect (a handshake based on a cache entry) // _CachedSession = message.Size < 200? CachedSessionStatus.IsCached: CachedSessionStatus.IsNotCached; } if (_Framing == Framing.Unified) { _Framing = DetectFraming(message.Payload, message.Payload.Length); } // Even if we are comleted, there could be a blob for sending. // ONLY for TlsStream we want to delay it if the underlined stream is a NetworkStream that is subject to Nagle algorithm // if ( message.Done && _ForceBufferingLastHandshakePayload && InnerStream.GetType() == typeof(NetworkStream) && !_PendingReHandshake && !CheckWin9xCachedSession()) { _LastPayload = message.Payload; } else { if (asyncRequest == null) { InnerStream.Write(message.Payload, 0, message.Size); } else { asyncRequest.AsyncState = message; IAsyncResult ar = InnerStream.BeginWrite(message.Payload, 0, message.Size, _WriteCallback, asyncRequest); if (!ar.CompletedSynchronously) { #if DEBUG asyncRequest._DebugAsyncChain = ar; #endif return; } InnerStream.EndWrite(ar); } } } CheckCompletionBeforeNextReceive(message, asyncRequest); } // // This will check and logically complete / fail the auth handshake // private void CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest) { if (message.Failed) { StartSendAuthResetSignal(null, asyncRequest, new AuthenticationException(SR.GetString(SR.net_auth_SSPI), message.GetException())); return; } else if (message.Done && !_PendingReHandshake) { if (CheckWin9xCachedSession()) { // This will tell that a decrypt must be called first // If it's not a renegotiate reply status, then decrypted data must be buffered // until rehandshake is done _PendingReHandshake = true; // Allow this to happen only once Win9xSessionRestarted(); ForceAuthentication(false, null, asyncRequest); return; } if (!CompleteHandshake()) { StartSendAuthResetSignal(null, asyncRequest, new AuthenticationException(SR.GetString(SR.net_ssl_io_cert_validation), null)); return; } // Release waiting IO if any. Presumably it should not throw. // Otheriwse application may get not expected type of the exception. FinishHandshake(null, asyncRequest); return; } StartReceiveBlob(message.Payload, asyncRequest); } // // Server side starts here, but client also loops through this method // private void StartReceiveBlob(byte[] buffer, AsyncProtocolRequest asyncRequest) { if (_PendingReHandshake) { if (CheckEnqueueHandshakeRead(ref buffer, asyncRequest)) { return; } if (!_PendingReHandshake) { // read fed us renegotiate proceed to the next step ProcessReceivedBlob(buffer, buffer.Length, asyncRequest); return; } } //This is first server read buffer = EnsureBufferSize(buffer, 0, Context.HeaderSize); int readBytes = 0; if (asyncRequest == null) { readBytes = _Reader.ReadPacket(buffer, 0, Context.HeaderSize); } else { asyncRequest.SetNextRequest(buffer, 0, Context.HeaderSize, _PartialFrameCallback); _Reader.AsyncReadPacket(asyncRequest); if (!asyncRequest.MustCompleteSynchronously) { return; } readBytes = asyncRequest.Result; } StartReadFrame(buffer, readBytes, asyncRequest); } // private void StartReadFrame(byte[] buffer, int readBytes, AsyncProtocolRequest asyncRequest) { if (readBytes == 0) { // EOF, throw throw new IOException(SR.GetString(SR.net_auth_eof)); } if (_Framing == Framing.None) { // figure out the Framing _Framing = DetectFraming(buffer, readBytes); } int restBytes = GetRemainingFrameSize(buffer, readBytes); if (restBytes < 0) { throw new IOException(SR.GetString(SR.net_ssl_io_frame)); } if (restBytes == 0) { // EOF received throw new AuthenticationException(SR.GetString(SR.net_auth_eof), null); } buffer = EnsureBufferSize(buffer, readBytes, readBytes + restBytes); if (asyncRequest == null) { restBytes = _Reader.ReadPacket(buffer, readBytes, restBytes); } else { asyncRequest.SetNextRequest(buffer, readBytes, restBytes, _ReadFrameCallback); _Reader.AsyncReadPacket(asyncRequest); if (!asyncRequest.MustCompleteSynchronously) { return; } restBytes = asyncRequest.Result; if (restBytes == 0) { //eof, fail readBytes = 0; } } ProcessReceivedBlob(buffer, readBytes + restBytes, asyncRequest); } // // // private void ProcessReceivedBlob(byte[] buffer, int count, AsyncProtocolRequest asyncRequest) { if (count == 0) { // EOF received throw new AuthenticationException(SR.GetString(SR.net_auth_eof), null); } if (_PendingReHandshake) { int offset = 0; SecurityStatus status = PrivateDecryptData(buffer, ref offset, ref count); if (status == SecurityStatus.OK) { Exception e = EnqueueOldKeyDecryptedData(buffer, offset, count); if (e != null) { StartSendAuthResetSignal(null, asyncRequest, e); return; } // Again, forget about framing we can get a new one at any time _Framing = Framing.None; StartReceiveBlob(buffer, asyncRequest); return; } else if (status != SecurityStatus.Renegotiate) { // fail re-handshake ProtocolToken message = new ProtocolToken(null, status); StartSendAuthResetSignal(null, asyncRequest, new AuthenticationException(SR.GetString(SR.net_auth_SSPI), message.GetException())); return; } // Got it, now we should expect only handshake messages _PendingReHandshake = false; if (offset != 0) { Buffer.BlockCopy(buffer, offset, buffer, 0, count); } } StartSendBlob(buffer, count, asyncRequest); } // // This is to reset auth state on remote side. // If this write succeeds we will allow auth retrying. // private void StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, Exception exception) { if (message == null || message.Size == 0) { // // we don't have an alert to send so cannot retry and fail prematurely. // throw exception; } if (asyncRequest == null) { InnerStream.Write(message.Payload, 0, message.Size); } else { asyncRequest.AsyncState = exception; IAsyncResult ar = InnerStream.BeginWrite(message.Payload, 0, message.Size, _WriteCallback, asyncRequest); if (!ar.CompletedSynchronously) { return; } InnerStream.EndWrite(ar); } throw exception; } // // // private bool CheckWin9xCachedSession() { if (ComNetOS.IsWin9x && _CachedSession == CachedSessionStatus.IsCached && Context.IsServer && Context.RemoteCertRequired) { X509Certificate2 remoteClientCert = null; X509Certificate2Collection dummyStore; try { remoteClientCert = Context.GetRemoteCertificate(out dummyStore); if (remoteClientCert == null) return true; } finally { if (remoteClientCert != null) remoteClientCert.Reset(); } } return false; } private void Win9xSessionRestarted() { _CachedSession = CachedSessionStatus.Renegotiated; } // - Loads the channel parameters // - Optionally Verifies the Remote Certificate // - Sets HandshakeCompleted flag // - Sets the guarding event if other thread is waiting for // handshake completino // // - Returns false if failed to verify the Remote Cert // private bool CompleteHandshake() { GlobalLog.Enter("CompleteHandshake"); Context.ProcessHandshakeSuccess(); if (!Context.VerifyRemoteCertificate(_CertValidationDelegate)) { _HandshakeCompleted = false; _CertValidationFailed = true; GlobalLog.Leave("CompleteHandshake", false); return false; } _CertValidationFailed = false; _HandshakeCompleted = true; GlobalLog.Leave("CompleteHandshake", true); return true; } // // // private static void WriteCallback(IAsyncResult transportResult) { if (transportResult.CompletedSynchronously) { return; } AsyncProtocolRequest asyncRequest; SslState sslState; #if DEBUG try { #endif asyncRequest = (AsyncProtocolRequest) transportResult.AsyncState; sslState = (SslState) asyncRequest.AsyncObject; #if DEBUG } catch (Exception exception) { if (!NclUtilities.IsFatal(exception)){ GlobalLog.Assert("SslState::WriteCallback", "Exception while decoding context. type:" + exception.GetType().ToString() + " message:" + exception.Message); } throw; } #endif // Async completion try { sslState.InnerStream.EndWrite(transportResult); //special case for an error notification object asyncState = asyncRequest.AsyncState; Exception exception = asyncState as Exception; if (exception != null) { throw exception; } sslState.CheckCompletionBeforeNextReceive((ProtocolToken) asyncState, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslState.FinishHandshake(e, asyncRequest); } } // // // private static void PartialFrameCallback(AsyncProtocolRequest asyncRequest) { GlobalLog.Print("SslState::PartialFrameCallback()"); // Async ONLY completion SslState sslState = (SslState)asyncRequest.AsyncObject; try { sslState.StartReadFrame(asyncRequest.Buffer, asyncRequest.Result, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslState.FinishHandshake(e, asyncRequest); } } // // private static void ReadFrameCallback(AsyncProtocolRequest asyncRequest) { GlobalLog.Print("SslState::ReadFrameCallback()"); // Async ONLY completion SslState sslState = (SslState)asyncRequest.AsyncObject; try { if (asyncRequest.Result == 0) { //eof, will fail asyncRequest.Offset = 0; } sslState.ProcessReceivedBlob(asyncRequest.Buffer, asyncRequest.Offset + asyncRequest.Result, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslState.FinishHandshake(e, asyncRequest); } } // // private bool CheckEnqueueHandshakeRead(ref byte[] buffer, AsyncProtocolRequest request) { LazyAsyncResult lazyResult = null; lock (this) { if (_LockReadState == LockPendingRead) { // we own the whole process and will never let read to take over until completed. return false; } int lockState = Interlocked.Exchange(ref _LockReadState, LockHandshake); if (lockState != LockRead) { // we came first return false; } if (request != null) { // Request queued _QueuedReadStateRequest = request; return true; } lazyResult = new LazyAsyncResult(null, null,/*must be */ null); _QueuedReadStateRequest = lazyResult; } // need to exit from lock before waiting lazyResult.InternalWaitForCompletion(); buffer = (byte[])lazyResult.Result; return false; } // // lock is redundant here still included for clarity // private void FinishHandshakeRead(int newState) { lock (this) { int lockState = Interlocked.Exchange(ref _LockReadState, newState); if (lockState != LockPendingRead) { return; } _LockReadState = LockRead; object obj = _QueuedReadStateRequest; if (obj == null) { // other thread did not get under the lock yet return; } _QueuedReadStateRequest = null; if (obj is LazyAsyncResult) { ((LazyAsyncResult)obj).InvokeCallback(); } else { ThreadPool.QueueUserWorkItem(new WaitCallback(CompleteRequestWaitCallback), obj); } } } // // -1 - proceed // 0 - queued // X - some bytes are ready, no need for IO internal int CheckEnqueueRead(byte[] buffer, int offset, int count, AsyncProtocolRequest request) { int lockState = Interlocked.CompareExchange(ref _LockReadState, LockRead, LockNone); if (lockState != LockHandshake) { // proceed, no concurent handshake is going so no need for a lock. return CheckOldKeyDecryptedData(buffer, offset, count); } LazyAsyncResult lazyResult = null; lock (this) { int result = CheckOldKeyDecryptedData(buffer, offset, count); if (result != -1) { return result; } //check again under the lock if (_LockReadState != LockHandshake) { // other thread has finished before we grabbed the lock _LockReadState = LockRead; return -1; } _LockReadState = LockPendingRead; if (request != null) { // Request queued _QueuedReadStateRequest = request; return 0; } lazyResult = new LazyAsyncResult(null, null, /*must be */ null); _QueuedReadStateRequest = lazyResult; } // need to exit from lock before waiting lazyResult.InternalWaitForCompletion(); lock (this) { return CheckOldKeyDecryptedData(buffer, offset, count); } } // // // internal void FinishRead(byte[] renegotiateBuffer) { int lockState = Interlocked.CompareExchange(ref _LockReadState, LockNone, LockRead); if (lockState != LockHandshake) { return; } lock (this) { LazyAsyncResult ar = _QueuedReadStateRequest as LazyAsyncResult; if (ar != null) { _QueuedReadStateRequest = null; ar.InvokeCallback(renegotiateBuffer); } else { AsyncProtocolRequest request = (AsyncProtocolRequest) _QueuedReadStateRequest; request.Buffer = renegotiateBuffer; _QueuedReadStateRequest = null; ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncResumeHandshakeRead), request); } } } // // // true - operation queued // false - operation can proceed internal bool CheckEnqueueWrite(AsyncProtocolRequest asyncRequest) { // cleare previous request _QueuedWriteStateRequest = null; int lockState = Interlocked.CompareExchange(ref _LockWriteState, LockWrite, LockNone); if (lockState != LockHandshake) { // proceed with write return false; } LazyAsyncResult lazyResult = null; lock (this) { if (_LockWriteState == LockWrite) { // handshake has completed before we grabbed the lock CheckThrow(true); return false; } _LockWriteState = LockPendingWrite; // still pending, wait or enqueue if (asyncRequest != null) { _QueuedWriteStateRequest = asyncRequest; return true; } lazyResult = new LazyAsyncResult(null, null, /*must be */null); _QueuedWriteStateRequest = lazyResult; } // need to exit from lock before waiting lazyResult.InternalWaitForCompletion(); CheckThrow(true); return false; } // internal void FinishWrite() { int lockState = Interlocked.CompareExchange(ref _LockWriteState, LockNone, LockWrite); if (lockState != LockHandshake) { return; } lock (this) { object obj = _QueuedWriteStateRequest; if (obj == null) { // a repeated call return; } _QueuedWriteStateRequest = null; if (obj is LazyAsyncResult) { // [....] handshake is waiting on other thread. ((LazyAsyncResult)obj).InvokeCallback(); } else { // async handshake is pending, start it on other thread // Consider: we could start it in on this thread but that will delay THIS write completion ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncResumeHandshake), obj); } } } // true - operation queued // false - operation can proceed private bool CheckEnqueueHandshake(byte[] buffer, AsyncProtocolRequest asyncRequest) { LazyAsyncResult lazyResult = null; lock (this) { if (_LockWriteState == LockPendingWrite) { return false; } int lockState = Interlocked.Exchange(ref _LockWriteState, LockHandshake); if (lockState != LockWrite) { // proceed with handshake return false; } if (asyncRequest != null) { asyncRequest.Buffer = buffer; _QueuedWriteStateRequest = asyncRequest; return true; } lazyResult = new LazyAsyncResult(null, null, /*must be*/null); _QueuedWriteStateRequest = lazyResult; } lazyResult.InternalWaitForCompletion(); return false; } // // private void FinishHandshake(Exception e, AsyncProtocolRequest asyncRequest) { try { lock (this) { if (e != null) { SetException(e); } // Release read if any FinishHandshakeRead(LockNone); // If there is a pending write we want to keep it's lock state int lockState = Interlocked.CompareExchange(ref _LockWriteState, LockNone, LockHandshake); if (lockState != LockPendingWrite) { return; } _LockWriteState = LockWrite; object obj = _QueuedWriteStateRequest; if (obj == null) { //We finished before Write has grabbed the lock return; } _QueuedWriteStateRequest = null; if (obj is LazyAsyncResult) { // [....] write is waiting on other thread. ((LazyAsyncResult)obj).InvokeCallback(); } else { // Async write is pending, start it on other thread. // Consider: we could start it in on this thread but that will delay THIS handshake completion ThreadPool.QueueUserWorkItem(new WaitCallback(CompleteRequestWaitCallback), obj); } } } finally { if (asyncRequest != null) { if (e != null) { asyncRequest.CompleteWithError(e); } else { asyncRequest.CompleteUser(); } } } } // // private static byte[] EnsureBufferSize(byte[] buffer, int copyCount, int size) { if (buffer == null || buffer.Length < size) { byte[] saved = buffer; buffer = new byte[size]; if (saved != null && copyCount != 0) { Buffer.BlockCopy(saved, 0, buffer, 0, copyCount); } } return buffer; } // // // private enum Framing { None = 0, BeforeSSL3, SinceSSL3, Unified, Invalid } // This is set on the first packet to figure out the framing style private Framing _Framing = Framing.None; //SSL3/TLS protocol frames definitions private enum FrameType: byte { ChangeCipherSpec = 20, Alert = 21, Handshake = 22, AppData = 23 } // We need at least 5 bytes to determine what we have. private Framing DetectFraming(byte[] bytes, int length) { /* PCTv1.0 Hello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * PCT1_CLIENT_HELLO (must be equal) * PCT1_CLIENT_VERSION_MSB (if version greater than PCTv1) * PCT1_CLIENT_VERSION_LSB (if version greater than PCTv1) * * ... PCT hello ... */ /* Microsft Unihello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 Compatible Hello ... */ /* SSLv2 CLIENT_HELLO starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 CLIENT_HELLO ... */ /* SSLv2 SERVER_HELLO starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_SERVER_HELLO (must be equal) * SSL2_SESSION_ID_HIT (ignore) * SSL2_CERTIFICATE_TYPE (ignore) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 SERVER_HELLO ... */ /* SSLv3 Type 2 Hello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv3) * * ... SSLv2 Compatible Hello ... */ /* SSLv3 Type 3 Hello starts with * 22 (HANDSHAKE MESSAGE) * VERSION MSB * VERSION LSB * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * HS TYPE (CLIENT_HELLO) * 3 bytes HS record length * HS Version * HS Version */ /* SSLv2 message codes * SSL_MT_ERROR 0 * SSL_MT_CLIENT_HELLO 1 * SSL_MT_CLIENT_MASTER_KEY 2 * SSL_MT_CLIENT_FINISHED 3 * SSL_MT_SERVER_HELLO 4 * SSL_MT_SERVER_VERIFY 5 * SSL_MT_SERVER_FINISHED 6 * SSL_MT_REQUEST_CERTIFICATE 7 * SSL_MT_CLIENT_CERTIFICATE 8 */ int version=-1; GlobalLog.Assert((bytes != null && bytes.Length > 0), "SslState::DetectFraming()|Header buffer is not allocated will boom shortly."); // If the first byte is SSL3 HandShake, then check if we have a // SSLv3 Type3 client hello if(bytes[0] == (byte)FrameType.Handshake || bytes[0] == (byte)FrameType.AppData) { if (length < 3) { return Framing.Invalid; } #if TRAVE if (bytes[1] != 3) GlobalLog.Print("WARNING: SslState::DetectFraming() SSL protocol is > 3, trying SSL3 framing in retail = " + bytes[1].ToString("x", NumberFormatInfo.InvariantInfo)); #endif version = (bytes[1]<<8) | bytes[2]; if (version < 0x300 || version >= 0x500) { return Framing.Invalid; } // // This is an SSL3 Framing // return Framing.SinceSSL3; } #if TRAVE if ((bytes[0] & 0x80) == 0) // We have a three-byte header format GlobalLog.Print("WARNING: SslState::DetectFraming() SSL v <=2 HELLO has no high bit set for 3 bytes header, we are broken, received byte = " + bytes[0].ToString("x", NumberFormatInfo.InvariantInfo)); #endif if (length < 3) { return Framing.Invalid; } if (bytes[2] > 8) return Framing.Invalid; if (bytes[2] == 0x1) // SSL_MT_CLIENT_HELLO { if (length >= 5) version = (bytes[3]<<8) | bytes[4]; } else if (bytes[2] == 0x4) // SSL_MT_SERVER_HELLO { if (length >= 7) version = (bytes[5]<<8) | bytes[6]; } if (version != -1) { // If this is the first packet, the client may start with an SSL2 packet // but stating that the version is 3.x, so check the full range. // For the subsequent packets we assume that an SSL2 packet should have a 2.x version. if (_Framing == Framing.None) { if (version != 0x0002 && (version < 0x200 || version >= 0x500)) return Framing.Invalid; } else { if (version != 0x0002) return Framing.Invalid; } } // When server has replied the framing is already fixed depending on the prior client packet if (!Context.IsServer || _Framing == Framing.Unified) { return Framing.BeforeSSL3; } return Framing.Unified; //this will use Ssl2 just for this frame } // // This is called from SslStream class too internal int GetRemainingFrameSize(byte[] buffer, int dataSize) { GlobalLog.Enter("GetRemainingFrameSize", "dataSize = " + dataSize); int payloadSize = -1; switch (_Framing) { case Framing.Unified: case Framing.BeforeSSL3: if(dataSize < 2) { throw new System.IO.IOException(SR.GetString(SR.net_ssl_io_frame)); } // Note: Cannot detect version mismatch for <= SSL2 if((buffer[0] & 0x80) != 0) { // two bytes payloadSize = (((buffer[0] & 0x7f) << 8) | buffer[1]) + 2; payloadSize -= dataSize; } else { //three bytes payloadSize = (((buffer[0] & 0x3f) << 8) | buffer[1]) + 3; payloadSize -= dataSize; } break; case Framing.SinceSSL3: if(dataSize < 5) { throw new System.IO.IOException(SR.GetString(SR.net_ssl_io_frame)); } payloadSize = ((buffer[3]<<8) | buffer[4]) + 5; payloadSize -= dataSize; break; default: break; } GlobalLog.Leave("GetRemainingFrameSize", payloadSize); return payloadSize; } // // Called with no user stack // private void AsyncResumeHandshake(object state) { AsyncProtocolRequest request = state as AsyncProtocolRequest; ForceAuthentication(Context.IsServer, request.Buffer, request); } // // Called with no user stack // private void AsyncResumeHandshakeRead(object state) { AsyncProtocolRequest asyncRequest = (AsyncProtocolRequest)state; try { if (_PendingReHandshake) { // resume as read a blob StartReceiveBlob(asyncRequest.Buffer, asyncRequest); } else { // resume as process the blob ProcessReceivedBlob(asyncRequest.Buffer, asyncRequest.Buffer == null? 0: asyncRequest.Buffer.Length, asyncRequest); } } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } FinishHandshake(e, asyncRequest); } } // // Called with no user stack // private void CompleteRequestWaitCallback(object state) { AsyncProtocolRequest request = (AsyncProtocolRequest)state; // Force async completion if (request.MustCompleteSynchronously) throw new InternalException(); request.CompleteRequest(0); } private void RehandshakeCompleteCallback(IAsyncResult result) { LazyAsyncResult lazyAsyncResult = (LazyAsyncResult)result; GlobalLog.Assert(lazyAsyncResult != null, "SslState::RehandshakeCompleteCallback()|result is null!"); GlobalLog.Assert(lazyAsyncResult.InternalPeekCompleted, "SslState::RehandshakeCompleteCallback()|result is not completed!"); // If the rehandshake succeeded, FinishHandshake has already been called; if there was a SocketException // during the handshake, this gets called directly from FixedSizeReader, and we need to call // FinishHandshake to wake up the Read that triggered this rehandshake so the error gets back to the caller Exception exception = lazyAsyncResult.InternalWaitForCompletion() as Exception; if (exception != null) { // We may be calling FinishHandshake reentrantly, as FinishHandshake can call // asyncRequest.CompleteWithError, which will result in this method being called. // This is not a problem because: // // 1. We pass null as the asyncRequest parameter, so this second call to FinishHandshake won't loop // back here. // // 2. _QueuedWriteStateRequest and _QueuedReadStateRequest are set to null after the first call, // so, we won't invoke their callbacks again. // // 3. SetException won't overwrite an already-set _Exception. // // 4. There are three possibilites for _LockReadState and _LockWriteState: // // a. They were set back to None by the first call to FinishHandshake, and this will set them to // None again: a no-op. // // b. They were set to None by the first call to FinishHandshake, but as soon as the lock was given // up, another thread took a read/write lock. Calling FinishHandshake again will set them back // to None, but that's fine because that thread will be throwing _Exception before it actually // does any reading or writing and setting them back to None in a catch block anyways. // // c. If there is a Read/Write going on another thread, and the second FinishHandshake clears its // read/write lock, it's fine because no other Read/Write can look at the lock until the current // one gives up _SslStream._NestedRead/Write, and no handshake will look at the lock because // handshakes are only triggered in response to successful reads (which won't happen once // _Exception is set). FinishHandshake(exception, null); } } #if TRAVE [System.Diagnostics.Conditional("TRAVE")] internal void Debug() { GlobalLog.Print("_HandshakeCompleted: " + _HandshakeCompleted); GlobalLog.Print("_CertValidationFailed: " + _CertValidationFailed); GlobalLog.Print("_Context: "); _Context.Debug(); } #endif } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. /*++ Copyright (c) 2000 Microsoft Corporation Module Name: _SslState.cs Abstract: Author: Mauro Ottaviani 07-Nov-2001 Arthur Bierer 16-Nov-2001 Alexei Vopilov 26-Jun-2002 Revision History: 22-Aug-2003 Adopted for new Ssl feature design. 15-Sept-2003 Implemented concurent rehanshake --*/ namespace System.Net.Security { using System; using System.IO; using System.Threading; using System.Security.Cryptography.X509Certificates; using System.Collections; using System.Runtime.InteropServices; using System.Globalization; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; using System.ComponentModel; internal class SslState { static int UniqueNameInteger = 123; static AsyncProtocolCallback _PartialFrameCallback = new AsyncProtocolCallback(PartialFrameCallback); static AsyncProtocolCallback _ReadFrameCallback = new AsyncProtocolCallback(ReadFrameCallback); static AsyncCallback _WriteCallback = new AsyncCallback(WriteCallback); private RemoteCertValidationCallback _CertValidationDelegate; private LocalCertSelectionCallback _CertSelectionDelegate; private bool _CanRetryAuthentication; // for now it's always false. private Stream _InnerStream; private _SslStream _SecureStream; private FixedSizeReader _Reader; private int _NestedAuth; private SecureChannel _Context; private bool _HandshakeCompleted; private bool _CertValidationFailed; private SecurityStatus _SecurityStatus; private Exception _Exception; enum CachedSessionStatus: byte { Unknown = 0, IsNotCached = 1, IsCached = 2, Renegotiated = 3 } private CachedSessionStatus _CachedSession; // This block is used by rehandshake code to buffer data decryptred with the old key private byte[] _QueuedReadData; private int _QueuedReadCount; private bool _PendingReHandshake; private const int _ConstMaxQueuedReadBytes = 1024 * 128; // // This block is used to rule the >>re-handshakes<< that are concurent with read/write io requests // private const int LockNone = 0; private const int LockWrite = 1; private const int LockHandshake = 2; private const int LockPendingWrite = 3; private const int LockRead = 4; private const int LockPendingRead = 6; private int _LockWriteState; private object _QueuedWriteStateRequest; private int _LockReadState; private object _QueuedReadStateRequest; // This is a perf trick ONLY for HTTPS TlsStream. // private bool _ForceBufferingLastHandshakePayload; private byte[] _LastPayload; private readonly EncryptionPolicy _EncryptionPolicy; // // This .ctor is only for internal TlsStream class, used only from client side ssl and it also has some special requirements. // internal SslState(Stream innerStream, bool isHTTP, EncryptionPolicy encryptionPolicy): this(innerStream, null, null, encryptionPolicy) { _ForceBufferingLastHandshakePayload = isHTTP; } // // The public Client and Server classes enforce the parameters rules before // calling into this .ctor. // internal SslState(Stream innerStream, RemoteCertValidationCallback certValidationCallback, LocalCertSelectionCallback certSelectionCallback, EncryptionPolicy encryptionPolicy) { _InnerStream = innerStream; _Reader = new FixedSizeReader(innerStream); _CertValidationDelegate = certValidationCallback; _CertSelectionDelegate = certSelectionCallback; _EncryptionPolicy = encryptionPolicy; } // // // internal void ValidateCreateContext(bool isServer, string targetHost, SslProtocols enabledSslProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool remoteCertRequired, bool checkCertRevocationStatus) { ValidateCreateContext( isServer, targetHost, enabledSslProtocols, serverCertificate, clientCertificates, remoteCertRequired, checkCertRevocationStatus, !isServer); } internal void ValidateCreateContext(bool isServer, string targetHost, SslProtocols enabledSslProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool remoteCertRequired, bool checkCertRevocationStatus, bool checkCertName) { // // We don;t support SSL alerts right now, hence any exception is fatal and cannot be retried // Consider: make use if SSL alerts to re-[....] SslStream on both sides for retrying. // if (_Exception != null && !_CanRetryAuthentication) { throw _Exception; } if (Context != null && Context.IsValidContext) { throw new InvalidOperationException(SR.GetString(SR.net_auth_reauth)); } if (Context != null && IsServer != isServer) { throw new InvalidOperationException(SR.GetString(SR.net_auth_client_server)); } if (targetHost == null) { throw new ArgumentNullException("targetHost"); } if (isServer ) { enabledSslProtocols &= (SslProtocols)SchProtocols.ServerMask; if (serverCertificate == null) throw new ArgumentNullException("serverCertificate"); } else { enabledSslProtocols &= (SslProtocols)SchProtocols.ClientMask; } if ((int)enabledSslProtocols == 0) { throw new ArgumentException(SR.GetString(SR.net_invalid_enum, "SslProtocolType"), "sslProtocolType"); } if (clientCertificates == null) { clientCertificates = new X509CertificateCollection(); } if (targetHost.Length == 0) { targetHost = "?" + Interlocked.Increment(ref UniqueNameInteger).ToString(NumberFormatInfo.InvariantInfo); } _Exception = null; try { _Context = new SecureChannel(targetHost, isServer, (SchProtocols)((int)enabledSslProtocols), serverCertificate, clientCertificates, remoteCertRequired, checkCertName, checkCertRevocationStatus, _EncryptionPolicy, _CertSelectionDelegate); } catch (Win32Exception e) { throw new AuthenticationException(SR.GetString(SR.net_auth_SSPI), e); } } // // General informational properties // // // internal bool IsAuthenticated { get { return _Context != null && _Context.IsValidContext && _Exception == null && HandshakeCompleted; } } // // // internal bool IsMutuallyAuthenticated { get { return IsAuthenticated && (Context.IsServer? Context.LocalServerCertificate: Context.LocalClientCertificate) != null && Context.IsRemoteCertificateAvailable; /* does not work: Context.IsMutualAuthFlag;*/ } } // internal bool RemoteCertRequired { get { return Context == null || Context.RemoteCertRequired; } } // internal bool IsServer { get { return Context != null && Context.IsServer; } } // // SSL related properties // internal void SetCertValidationDelegate(RemoteCertValidationCallback certValidationCallback) { _CertValidationDelegate = certValidationCallback; } // // This will return selected local cert for both client/server streams // internal X509Certificate LocalCertificate { get { CheckThrow(true); return InternalLocalCertificate; } } // // Used directly from ServicePoint for client side only internal X509Certificate InternalLocalCertificate { get { return Context.IsServer? Context.LocalServerCertificate : Context.LocalClientCertificate; } } // internal ChannelBinding GetChannelBinding(ChannelBindingKind kind) { return (Context == null) ? null : Context.GetChannelBinding(kind); } // internal bool CheckCertRevocationStatus { get { return Context != null && Context.CheckCertRevocationStatus; } } // internal SecurityStatus LastSecurityStatus { get {return _SecurityStatus;} } // internal bool IsCertValidationFailed { get { return _CertValidationFailed; } } // internal bool DataAvailable { get { return IsAuthenticated && (SecureStream.DataAvailable || _QueuedReadCount != 0); } } // internal CipherAlgorithmType CipherAlgorithm { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return CipherAlgorithmType.None; } return (CipherAlgorithmType)info.DataCipherAlg; } } // internal int CipherStrength { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return 0; } return info.DataKeySize; } } // internal HashAlgorithmType HashAlgorithm { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return (HashAlgorithmType)0; } return (HashAlgorithmType)info.DataHashAlg; } } // internal int HashStrength { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return 0; } return info.DataHashKeySize; } } // internal ExchangeAlgorithmType KeyExchangeAlgorithm { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return (ExchangeAlgorithmType)0; } return (ExchangeAlgorithmType)info.KeyExchangeAlg; } } // internal int KeyExchangeStrength { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return 0; } return info.KeyExchKeySize; } } // internal SslProtocols SslProtocol { get { CheckThrow(true); SslConnectionInfo info = Context.ConnectionInfo; if (info == null) { return SslProtocols.None; } SslProtocols proto = (SslProtocols)info.Protocol; // restore client/server bits so the result maps exaclty on published constants if ((proto & SslProtocols.Ssl2) != 0) { proto |= SslProtocols.Ssl2; } if ((proto & SslProtocols.Ssl3) != 0) { proto |= SslProtocols.Ssl3; } if ((proto & SslProtocols.Tls) != 0) { proto |= SslProtocols.Tls; } return proto; } } // // // internal Stream InnerStream { get { return _InnerStream; } } // // // internal _SslStream SecureStream { get{ CheckThrow(true); if (_SecureStream == null) { Interlocked.CompareExchange<_SslStream>(ref _SecureStream, new _SslStream(this), null); } return _SecureStream; } } // internal int HeaderSize { get { return Context.HeaderSize; } } // internal int MaxDataSize { get { return Context.MaxDataSize; } } internal byte[] LastPayload { get { return _LastPayload; } } internal void LastPayloadConsumed() { _LastPayload = null; } // private Exception SetException(Exception e) { if (_Exception == null) { _Exception = e; } if (_Exception != null && Context != null) { Context.Close(); } return _Exception; } // private bool HandshakeCompleted { get { return _HandshakeCompleted; } } // // // private SecureChannel Context { get { return _Context; } } // // Methods // // // internal void CheckThrow(bool authSucessCheck) { if (_Exception != null) { throw _Exception; } if (authSucessCheck && !IsAuthenticated) { throw new InvalidOperationException(SR.GetString(SR.net_auth_noauth)); } } // internal void Flush() { InnerStream.Flush(); } // // This is to not depend on GC&SafeHandle class if the context is not needed anymore. // internal void Close() { _Exception = new ObjectDisposedException("SslStream"); if (Context != null) { Context.Close(); } } // // // internal SecurityStatus EncryptData(byte[] buffer, int offset, int count, ref byte[] outBuffer, out int outSize) { CheckThrow(true); return Context.Encrypt(buffer, offset, count, ref outBuffer, out outSize); } // // // internal SecurityStatus DecryptData(byte[] buffer, ref int offset, ref int count) { CheckThrow(true); return PrivateDecryptData(buffer, ref offset, ref count); } // // private SecurityStatus PrivateDecryptData(byte[] buffer, ref int offset, ref int count) { return Context.Decrypt(buffer, ref offset, ref count); } // // // Called by re-handshake if found data decrypted with the old key // private Exception EnqueueOldKeyDecryptedData(byte[] buffer, int offset, int count) { lock (this) { if (_QueuedReadCount + count > _ConstMaxQueuedReadBytes) { return new IOException(SR.GetString(SR.net_auth_ignored_reauth, _ConstMaxQueuedReadBytes.ToString(NumberFormatInfo.CurrentInfo))); } if (count !=0) { // This is inefficient yet simple and that should be a rare case of receiving data encrypted with "old" key _QueuedReadData = EnsureBufferSize(_QueuedReadData, _QueuedReadCount, _QueuedReadCount + count); Buffer.BlockCopy(buffer, offset, _QueuedReadData, _QueuedReadCount, count); _QueuedReadCount+=count; FinishHandshakeRead(LockHandshake); } } return null; } // // When re-handshaking the "old" key decrypted data are queued until the handshake is done // When stream calls for decryption we will feed it queued data left from "old" encryption key. // // Must be called under the lock in case concurent handshake is going // internal int CheckOldKeyDecryptedData(byte[] buffer, int offset, int count) { CheckThrow(true); if (_QueuedReadData != null) { // This is inefficient yet simple and should be a REALLY rare case. int toCopy = Math.Min(_QueuedReadCount, count); Buffer.BlockCopy(_QueuedReadData, 0, buffer, offset, toCopy); _QueuedReadCount -= toCopy; if (_QueuedReadCount == 0) { _QueuedReadData = null; } else { Buffer.BlockCopy(_QueuedReadData, toCopy, _QueuedReadData, 0, _QueuedReadCount); } return toCopy; } return -1; } // // This method assumes that a SSPI context is already in a good shape. // For example it is either a fresh context or already authenticated context that needs renegotiation. // internal void ProcessAuthentication(LazyAsyncResult lazyResult) { if (Interlocked.Exchange(ref _NestedAuth, 1) == 1) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidnestedcall, lazyResult==null?"BeginAuthenticate":"Authenticate", "authenticate")); } try { CheckThrow(false); AsyncProtocolRequest asyncRequest = null; if (lazyResult != null) { asyncRequest = new AsyncProtocolRequest(lazyResult); asyncRequest.Buffer = null; #if DEBUG lazyResult._DebugAsyncChain = asyncRequest; #endif } // A Win 9x trick to discover and avoid cached sessions _CachedSession = CachedSessionStatus.Unknown; ForceAuthentication(Context.IsServer, null, asyncRequest); // Not aync so the connection is completed at this point if (lazyResult == null && Logging.On) { Logging.PrintInfo(Logging.Web, SR.GetString(SR.net_log_sspi_selected_cipher_suite, "ProcessAuthentication", SslProtocol, CipherAlgorithm, CipherStrength, HashAlgorithm, HashStrength, KeyExchangeAlgorithm, KeyExchangeStrength)); } } finally { if (lazyResult == null || _Exception != null) { _NestedAuth = 0; } } } // // This is used to reply on re-handshake when received SEC_I_RENEGOTIATE on Read() // internal void ReplyOnReAuthentication(byte[] buffer) { lock (this) { // Note we are already inside the read, so checking for already going concurent handshake _LockReadState = LockHandshake; if (_PendingReHandshake) { // A concurent handshake is pending, resume FinishRead(buffer); return; } } // Start rehandshake from here // forcing async mode. The caller will queue another Read as soon as we return using its preferred // calling convention, which will be woken up when the handshake completes. The callback is just // to capture any SocketErrors that happen during the handshake so they can be surfaced from the Read. AsyncProtocolRequest asyncRequest = new AsyncProtocolRequest(new LazyAsyncResult(this, null, new AsyncCallback(RehandshakeCompleteCallback))); // Buffer contains a result from DecryptMessage that will be passed to ISC/ASC asyncRequest.Buffer = buffer; ForceAuthentication(false, buffer, asyncRequest); } // // This method attempts to start authentication. // Incoming buffer is either null or is the result of "renegotiate" decrypted message // If write is in progress the method will either wait or be put on hold // private void ForceAuthentication(bool receiveFirst, byte[] buffer, AsyncProtocolRequest asyncRequest) { if (CheckEnqueueHandshake(buffer, asyncRequest)) { // Async handshake is enqueued and will resume later return; } // Either [....] handshake is ready to go or async handshake won the ---- over write. // This will tell that we don't know the framing yet (what SSL version is) _Framing = Framing.None; try { if (receiveFirst) { // Listen for a client blob StartReceiveBlob(buffer, asyncRequest); } else { // we start with the first blob StartSendBlob(buffer, (buffer == null? 0: buffer.Length), asyncRequest); } } catch (Exception e) { // Failed auth, reset the framing if any. _Framing = Framing.None; _HandshakeCompleted = false; if (SetException(e) == e) { throw; } else { throw _Exception; } } finally { if (_Exception != null) { // This either a failed handshake // Release waiting IO if any. Presumably it should not throw. // Otheriwse application may receive not expected type of the exception. FinishHandshake(null, null); } } } // // // internal void EndProcessAuthentication(IAsyncResult result) { if (result == null) { throw new ArgumentNullException("asyncResult"); } LazyAsyncResult lazyResult = result as LazyAsyncResult; if (lazyResult == null) { throw new ArgumentException(SR.GetString(SR.net_io_async_result, result.GetType().FullName), "asyncResult"); } if (Interlocked.Exchange(ref _NestedAuth, 0) == 0) { throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndAuthenticate")); } InternalEndProcessAuthentication(lazyResult); // Connection is completed at this point if (Logging.On) { Logging.PrintInfo(Logging.Web, SR.GetString(SR.net_log_sspi_selected_cipher_suite, "EndProcessAuthentication", SslProtocol, CipherAlgorithm, CipherStrength, HashAlgorithm, HashStrength, KeyExchangeAlgorithm, KeyExchangeStrength)); } } // // // internal void InternalEndProcessAuthentication(LazyAsyncResult lazyResult) { // No "artificial" timeouts implemented so far, InnerStream controls that. lazyResult.InternalWaitForCompletion(); Exception e = lazyResult.Result as Exception; if (e != null) { // Failed auth, reset the framing if any. _Framing = Framing.None; _HandshakeCompleted = false; throw SetException(e); } } // // Client side starts here, but server also loops through this method // private void StartSendBlob(byte[] incoming, int count, AsyncProtocolRequest asyncRequest) { ProtocolToken message = Context.NextMessage(incoming, 0, count); _SecurityStatus = message.Status; if (message.Size != 0) { if (Context.IsServer && _CachedSession == CachedSessionStatus.Unknown) { // //[Schannel] If the first call to ASC returns a token less than 200 bytes, // then it's a reconnect (a handshake based on a cache entry) // _CachedSession = message.Size < 200? CachedSessionStatus.IsCached: CachedSessionStatus.IsNotCached; } if (_Framing == Framing.Unified) { _Framing = DetectFraming(message.Payload, message.Payload.Length); } // Even if we are comleted, there could be a blob for sending. // ONLY for TlsStream we want to delay it if the underlined stream is a NetworkStream that is subject to Nagle algorithm // if ( message.Done && _ForceBufferingLastHandshakePayload && InnerStream.GetType() == typeof(NetworkStream) && !_PendingReHandshake && !CheckWin9xCachedSession()) { _LastPayload = message.Payload; } else { if (asyncRequest == null) { InnerStream.Write(message.Payload, 0, message.Size); } else { asyncRequest.AsyncState = message; IAsyncResult ar = InnerStream.BeginWrite(message.Payload, 0, message.Size, _WriteCallback, asyncRequest); if (!ar.CompletedSynchronously) { #if DEBUG asyncRequest._DebugAsyncChain = ar; #endif return; } InnerStream.EndWrite(ar); } } } CheckCompletionBeforeNextReceive(message, asyncRequest); } // // This will check and logically complete / fail the auth handshake // private void CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest) { if (message.Failed) { StartSendAuthResetSignal(null, asyncRequest, new AuthenticationException(SR.GetString(SR.net_auth_SSPI), message.GetException())); return; } else if (message.Done && !_PendingReHandshake) { if (CheckWin9xCachedSession()) { // This will tell that a decrypt must be called first // If it's not a renegotiate reply status, then decrypted data must be buffered // until rehandshake is done _PendingReHandshake = true; // Allow this to happen only once Win9xSessionRestarted(); ForceAuthentication(false, null, asyncRequest); return; } if (!CompleteHandshake()) { StartSendAuthResetSignal(null, asyncRequest, new AuthenticationException(SR.GetString(SR.net_ssl_io_cert_validation), null)); return; } // Release waiting IO if any. Presumably it should not throw. // Otheriwse application may get not expected type of the exception. FinishHandshake(null, asyncRequest); return; } StartReceiveBlob(message.Payload, asyncRequest); } // // Server side starts here, but client also loops through this method // private void StartReceiveBlob(byte[] buffer, AsyncProtocolRequest asyncRequest) { if (_PendingReHandshake) { if (CheckEnqueueHandshakeRead(ref buffer, asyncRequest)) { return; } if (!_PendingReHandshake) { // read fed us renegotiate proceed to the next step ProcessReceivedBlob(buffer, buffer.Length, asyncRequest); return; } } //This is first server read buffer = EnsureBufferSize(buffer, 0, Context.HeaderSize); int readBytes = 0; if (asyncRequest == null) { readBytes = _Reader.ReadPacket(buffer, 0, Context.HeaderSize); } else { asyncRequest.SetNextRequest(buffer, 0, Context.HeaderSize, _PartialFrameCallback); _Reader.AsyncReadPacket(asyncRequest); if (!asyncRequest.MustCompleteSynchronously) { return; } readBytes = asyncRequest.Result; } StartReadFrame(buffer, readBytes, asyncRequest); } // private void StartReadFrame(byte[] buffer, int readBytes, AsyncProtocolRequest asyncRequest) { if (readBytes == 0) { // EOF, throw throw new IOException(SR.GetString(SR.net_auth_eof)); } if (_Framing == Framing.None) { // figure out the Framing _Framing = DetectFraming(buffer, readBytes); } int restBytes = GetRemainingFrameSize(buffer, readBytes); if (restBytes < 0) { throw new IOException(SR.GetString(SR.net_ssl_io_frame)); } if (restBytes == 0) { // EOF received throw new AuthenticationException(SR.GetString(SR.net_auth_eof), null); } buffer = EnsureBufferSize(buffer, readBytes, readBytes + restBytes); if (asyncRequest == null) { restBytes = _Reader.ReadPacket(buffer, readBytes, restBytes); } else { asyncRequest.SetNextRequest(buffer, readBytes, restBytes, _ReadFrameCallback); _Reader.AsyncReadPacket(asyncRequest); if (!asyncRequest.MustCompleteSynchronously) { return; } restBytes = asyncRequest.Result; if (restBytes == 0) { //eof, fail readBytes = 0; } } ProcessReceivedBlob(buffer, readBytes + restBytes, asyncRequest); } // // // private void ProcessReceivedBlob(byte[] buffer, int count, AsyncProtocolRequest asyncRequest) { if (count == 0) { // EOF received throw new AuthenticationException(SR.GetString(SR.net_auth_eof), null); } if (_PendingReHandshake) { int offset = 0; SecurityStatus status = PrivateDecryptData(buffer, ref offset, ref count); if (status == SecurityStatus.OK) { Exception e = EnqueueOldKeyDecryptedData(buffer, offset, count); if (e != null) { StartSendAuthResetSignal(null, asyncRequest, e); return; } // Again, forget about framing we can get a new one at any time _Framing = Framing.None; StartReceiveBlob(buffer, asyncRequest); return; } else if (status != SecurityStatus.Renegotiate) { // fail re-handshake ProtocolToken message = new ProtocolToken(null, status); StartSendAuthResetSignal(null, asyncRequest, new AuthenticationException(SR.GetString(SR.net_auth_SSPI), message.GetException())); return; } // Got it, now we should expect only handshake messages _PendingReHandshake = false; if (offset != 0) { Buffer.BlockCopy(buffer, offset, buffer, 0, count); } } StartSendBlob(buffer, count, asyncRequest); } // // This is to reset auth state on remote side. // If this write succeeds we will allow auth retrying. // private void StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, Exception exception) { if (message == null || message.Size == 0) { // // we don't have an alert to send so cannot retry and fail prematurely. // throw exception; } if (asyncRequest == null) { InnerStream.Write(message.Payload, 0, message.Size); } else { asyncRequest.AsyncState = exception; IAsyncResult ar = InnerStream.BeginWrite(message.Payload, 0, message.Size, _WriteCallback, asyncRequest); if (!ar.CompletedSynchronously) { return; } InnerStream.EndWrite(ar); } throw exception; } // // // private bool CheckWin9xCachedSession() { if (ComNetOS.IsWin9x && _CachedSession == CachedSessionStatus.IsCached && Context.IsServer && Context.RemoteCertRequired) { X509Certificate2 remoteClientCert = null; X509Certificate2Collection dummyStore; try { remoteClientCert = Context.GetRemoteCertificate(out dummyStore); if (remoteClientCert == null) return true; } finally { if (remoteClientCert != null) remoteClientCert.Reset(); } } return false; } private void Win9xSessionRestarted() { _CachedSession = CachedSessionStatus.Renegotiated; } // - Loads the channel parameters // - Optionally Verifies the Remote Certificate // - Sets HandshakeCompleted flag // - Sets the guarding event if other thread is waiting for // handshake completino // // - Returns false if failed to verify the Remote Cert // private bool CompleteHandshake() { GlobalLog.Enter("CompleteHandshake"); Context.ProcessHandshakeSuccess(); if (!Context.VerifyRemoteCertificate(_CertValidationDelegate)) { _HandshakeCompleted = false; _CertValidationFailed = true; GlobalLog.Leave("CompleteHandshake", false); return false; } _CertValidationFailed = false; _HandshakeCompleted = true; GlobalLog.Leave("CompleteHandshake", true); return true; } // // // private static void WriteCallback(IAsyncResult transportResult) { if (transportResult.CompletedSynchronously) { return; } AsyncProtocolRequest asyncRequest; SslState sslState; #if DEBUG try { #endif asyncRequest = (AsyncProtocolRequest) transportResult.AsyncState; sslState = (SslState) asyncRequest.AsyncObject; #if DEBUG } catch (Exception exception) { if (!NclUtilities.IsFatal(exception)){ GlobalLog.Assert("SslState::WriteCallback", "Exception while decoding context. type:" + exception.GetType().ToString() + " message:" + exception.Message); } throw; } #endif // Async completion try { sslState.InnerStream.EndWrite(transportResult); //special case for an error notification object asyncState = asyncRequest.AsyncState; Exception exception = asyncState as Exception; if (exception != null) { throw exception; } sslState.CheckCompletionBeforeNextReceive((ProtocolToken) asyncState, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslState.FinishHandshake(e, asyncRequest); } } // // // private static void PartialFrameCallback(AsyncProtocolRequest asyncRequest) { GlobalLog.Print("SslState::PartialFrameCallback()"); // Async ONLY completion SslState sslState = (SslState)asyncRequest.AsyncObject; try { sslState.StartReadFrame(asyncRequest.Buffer, asyncRequest.Result, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslState.FinishHandshake(e, asyncRequest); } } // // private static void ReadFrameCallback(AsyncProtocolRequest asyncRequest) { GlobalLog.Print("SslState::ReadFrameCallback()"); // Async ONLY completion SslState sslState = (SslState)asyncRequest.AsyncObject; try { if (asyncRequest.Result == 0) { //eof, will fail asyncRequest.Offset = 0; } sslState.ProcessReceivedBlob(asyncRequest.Buffer, asyncRequest.Offset + asyncRequest.Result, asyncRequest); } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } sslState.FinishHandshake(e, asyncRequest); } } // // private bool CheckEnqueueHandshakeRead(ref byte[] buffer, AsyncProtocolRequest request) { LazyAsyncResult lazyResult = null; lock (this) { if (_LockReadState == LockPendingRead) { // we own the whole process and will never let read to take over until completed. return false; } int lockState = Interlocked.Exchange(ref _LockReadState, LockHandshake); if (lockState != LockRead) { // we came first return false; } if (request != null) { // Request queued _QueuedReadStateRequest = request; return true; } lazyResult = new LazyAsyncResult(null, null,/*must be */ null); _QueuedReadStateRequest = lazyResult; } // need to exit from lock before waiting lazyResult.InternalWaitForCompletion(); buffer = (byte[])lazyResult.Result; return false; } // // lock is redundant here still included for clarity // private void FinishHandshakeRead(int newState) { lock (this) { int lockState = Interlocked.Exchange(ref _LockReadState, newState); if (lockState != LockPendingRead) { return; } _LockReadState = LockRead; object obj = _QueuedReadStateRequest; if (obj == null) { // other thread did not get under the lock yet return; } _QueuedReadStateRequest = null; if (obj is LazyAsyncResult) { ((LazyAsyncResult)obj).InvokeCallback(); } else { ThreadPool.QueueUserWorkItem(new WaitCallback(CompleteRequestWaitCallback), obj); } } } // // -1 - proceed // 0 - queued // X - some bytes are ready, no need for IO internal int CheckEnqueueRead(byte[] buffer, int offset, int count, AsyncProtocolRequest request) { int lockState = Interlocked.CompareExchange(ref _LockReadState, LockRead, LockNone); if (lockState != LockHandshake) { // proceed, no concurent handshake is going so no need for a lock. return CheckOldKeyDecryptedData(buffer, offset, count); } LazyAsyncResult lazyResult = null; lock (this) { int result = CheckOldKeyDecryptedData(buffer, offset, count); if (result != -1) { return result; } //check again under the lock if (_LockReadState != LockHandshake) { // other thread has finished before we grabbed the lock _LockReadState = LockRead; return -1; } _LockReadState = LockPendingRead; if (request != null) { // Request queued _QueuedReadStateRequest = request; return 0; } lazyResult = new LazyAsyncResult(null, null, /*must be */ null); _QueuedReadStateRequest = lazyResult; } // need to exit from lock before waiting lazyResult.InternalWaitForCompletion(); lock (this) { return CheckOldKeyDecryptedData(buffer, offset, count); } } // // // internal void FinishRead(byte[] renegotiateBuffer) { int lockState = Interlocked.CompareExchange(ref _LockReadState, LockNone, LockRead); if (lockState != LockHandshake) { return; } lock (this) { LazyAsyncResult ar = _QueuedReadStateRequest as LazyAsyncResult; if (ar != null) { _QueuedReadStateRequest = null; ar.InvokeCallback(renegotiateBuffer); } else { AsyncProtocolRequest request = (AsyncProtocolRequest) _QueuedReadStateRequest; request.Buffer = renegotiateBuffer; _QueuedReadStateRequest = null; ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncResumeHandshakeRead), request); } } } // // // true - operation queued // false - operation can proceed internal bool CheckEnqueueWrite(AsyncProtocolRequest asyncRequest) { // cleare previous request _QueuedWriteStateRequest = null; int lockState = Interlocked.CompareExchange(ref _LockWriteState, LockWrite, LockNone); if (lockState != LockHandshake) { // proceed with write return false; } LazyAsyncResult lazyResult = null; lock (this) { if (_LockWriteState == LockWrite) { // handshake has completed before we grabbed the lock CheckThrow(true); return false; } _LockWriteState = LockPendingWrite; // still pending, wait or enqueue if (asyncRequest != null) { _QueuedWriteStateRequest = asyncRequest; return true; } lazyResult = new LazyAsyncResult(null, null, /*must be */null); _QueuedWriteStateRequest = lazyResult; } // need to exit from lock before waiting lazyResult.InternalWaitForCompletion(); CheckThrow(true); return false; } // internal void FinishWrite() { int lockState = Interlocked.CompareExchange(ref _LockWriteState, LockNone, LockWrite); if (lockState != LockHandshake) { return; } lock (this) { object obj = _QueuedWriteStateRequest; if (obj == null) { // a repeated call return; } _QueuedWriteStateRequest = null; if (obj is LazyAsyncResult) { // [....] handshake is waiting on other thread. ((LazyAsyncResult)obj).InvokeCallback(); } else { // async handshake is pending, start it on other thread // Consider: we could start it in on this thread but that will delay THIS write completion ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncResumeHandshake), obj); } } } // true - operation queued // false - operation can proceed private bool CheckEnqueueHandshake(byte[] buffer, AsyncProtocolRequest asyncRequest) { LazyAsyncResult lazyResult = null; lock (this) { if (_LockWriteState == LockPendingWrite) { return false; } int lockState = Interlocked.Exchange(ref _LockWriteState, LockHandshake); if (lockState != LockWrite) { // proceed with handshake return false; } if (asyncRequest != null) { asyncRequest.Buffer = buffer; _QueuedWriteStateRequest = asyncRequest; return true; } lazyResult = new LazyAsyncResult(null, null, /*must be*/null); _QueuedWriteStateRequest = lazyResult; } lazyResult.InternalWaitForCompletion(); return false; } // // private void FinishHandshake(Exception e, AsyncProtocolRequest asyncRequest) { try { lock (this) { if (e != null) { SetException(e); } // Release read if any FinishHandshakeRead(LockNone); // If there is a pending write we want to keep it's lock state int lockState = Interlocked.CompareExchange(ref _LockWriteState, LockNone, LockHandshake); if (lockState != LockPendingWrite) { return; } _LockWriteState = LockWrite; object obj = _QueuedWriteStateRequest; if (obj == null) { //We finished before Write has grabbed the lock return; } _QueuedWriteStateRequest = null; if (obj is LazyAsyncResult) { // [....] write is waiting on other thread. ((LazyAsyncResult)obj).InvokeCallback(); } else { // Async write is pending, start it on other thread. // Consider: we could start it in on this thread but that will delay THIS handshake completion ThreadPool.QueueUserWorkItem(new WaitCallback(CompleteRequestWaitCallback), obj); } } } finally { if (asyncRequest != null) { if (e != null) { asyncRequest.CompleteWithError(e); } else { asyncRequest.CompleteUser(); } } } } // // private static byte[] EnsureBufferSize(byte[] buffer, int copyCount, int size) { if (buffer == null || buffer.Length < size) { byte[] saved = buffer; buffer = new byte[size]; if (saved != null && copyCount != 0) { Buffer.BlockCopy(saved, 0, buffer, 0, copyCount); } } return buffer; } // // // private enum Framing { None = 0, BeforeSSL3, SinceSSL3, Unified, Invalid } // This is set on the first packet to figure out the framing style private Framing _Framing = Framing.None; //SSL3/TLS protocol frames definitions private enum FrameType: byte { ChangeCipherSpec = 20, Alert = 21, Handshake = 22, AppData = 23 } // We need at least 5 bytes to determine what we have. private Framing DetectFraming(byte[] bytes, int length) { /* PCTv1.0 Hello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * PCT1_CLIENT_HELLO (must be equal) * PCT1_CLIENT_VERSION_MSB (if version greater than PCTv1) * PCT1_CLIENT_VERSION_LSB (if version greater than PCTv1) * * ... PCT hello ... */ /* Microsft Unihello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 Compatible Hello ... */ /* SSLv2 CLIENT_HELLO starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 CLIENT_HELLO ... */ /* SSLv2 SERVER_HELLO starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_SERVER_HELLO (must be equal) * SSL2_SESSION_ID_HIT (ignore) * SSL2_CERTIFICATE_TYPE (ignore) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv2) ( or v3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv2) ( or v3) * * ... SSLv2 SERVER_HELLO ... */ /* SSLv3 Type 2 Hello starts with * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * SSL2_CLIENT_HELLO (must be equal) * SSL2_CLIENT_VERSION_MSB (if version greater than SSLv3) * SSL2_CLIENT_VERSION_LSB (if version greater than SSLv3) * * ... SSLv2 Compatible Hello ... */ /* SSLv3 Type 3 Hello starts with * 22 (HANDSHAKE MESSAGE) * VERSION MSB * VERSION LSB * RECORD_LENGTH_MSB (ignore) * RECORD_LENGTH_LSB (ignore) * HS TYPE (CLIENT_HELLO) * 3 bytes HS record length * HS Version * HS Version */ /* SSLv2 message codes * SSL_MT_ERROR 0 * SSL_MT_CLIENT_HELLO 1 * SSL_MT_CLIENT_MASTER_KEY 2 * SSL_MT_CLIENT_FINISHED 3 * SSL_MT_SERVER_HELLO 4 * SSL_MT_SERVER_VERIFY 5 * SSL_MT_SERVER_FINISHED 6 * SSL_MT_REQUEST_CERTIFICATE 7 * SSL_MT_CLIENT_CERTIFICATE 8 */ int version=-1; GlobalLog.Assert((bytes != null && bytes.Length > 0), "SslState::DetectFraming()|Header buffer is not allocated will boom shortly."); // If the first byte is SSL3 HandShake, then check if we have a // SSLv3 Type3 client hello if(bytes[0] == (byte)FrameType.Handshake || bytes[0] == (byte)FrameType.AppData) { if (length < 3) { return Framing.Invalid; } #if TRAVE if (bytes[1] != 3) GlobalLog.Print("WARNING: SslState::DetectFraming() SSL protocol is > 3, trying SSL3 framing in retail = " + bytes[1].ToString("x", NumberFormatInfo.InvariantInfo)); #endif version = (bytes[1]<<8) | bytes[2]; if (version < 0x300 || version >= 0x500) { return Framing.Invalid; } // // This is an SSL3 Framing // return Framing.SinceSSL3; } #if TRAVE if ((bytes[0] & 0x80) == 0) // We have a three-byte header format GlobalLog.Print("WARNING: SslState::DetectFraming() SSL v <=2 HELLO has no high bit set for 3 bytes header, we are broken, received byte = " + bytes[0].ToString("x", NumberFormatInfo.InvariantInfo)); #endif if (length < 3) { return Framing.Invalid; } if (bytes[2] > 8) return Framing.Invalid; if (bytes[2] == 0x1) // SSL_MT_CLIENT_HELLO { if (length >= 5) version = (bytes[3]<<8) | bytes[4]; } else if (bytes[2] == 0x4) // SSL_MT_SERVER_HELLO { if (length >= 7) version = (bytes[5]<<8) | bytes[6]; } if (version != -1) { // If this is the first packet, the client may start with an SSL2 packet // but stating that the version is 3.x, so check the full range. // For the subsequent packets we assume that an SSL2 packet should have a 2.x version. if (_Framing == Framing.None) { if (version != 0x0002 && (version < 0x200 || version >= 0x500)) return Framing.Invalid; } else { if (version != 0x0002) return Framing.Invalid; } } // When server has replied the framing is already fixed depending on the prior client packet if (!Context.IsServer || _Framing == Framing.Unified) { return Framing.BeforeSSL3; } return Framing.Unified; //this will use Ssl2 just for this frame } // // This is called from SslStream class too internal int GetRemainingFrameSize(byte[] buffer, int dataSize) { GlobalLog.Enter("GetRemainingFrameSize", "dataSize = " + dataSize); int payloadSize = -1; switch (_Framing) { case Framing.Unified: case Framing.BeforeSSL3: if(dataSize < 2) { throw new System.IO.IOException(SR.GetString(SR.net_ssl_io_frame)); } // Note: Cannot detect version mismatch for <= SSL2 if((buffer[0] & 0x80) != 0) { // two bytes payloadSize = (((buffer[0] & 0x7f) << 8) | buffer[1]) + 2; payloadSize -= dataSize; } else { //three bytes payloadSize = (((buffer[0] & 0x3f) << 8) | buffer[1]) + 3; payloadSize -= dataSize; } break; case Framing.SinceSSL3: if(dataSize < 5) { throw new System.IO.IOException(SR.GetString(SR.net_ssl_io_frame)); } payloadSize = ((buffer[3]<<8) | buffer[4]) + 5; payloadSize -= dataSize; break; default: break; } GlobalLog.Leave("GetRemainingFrameSize", payloadSize); return payloadSize; } // // Called with no user stack // private void AsyncResumeHandshake(object state) { AsyncProtocolRequest request = state as AsyncProtocolRequest; ForceAuthentication(Context.IsServer, request.Buffer, request); } // // Called with no user stack // private void AsyncResumeHandshakeRead(object state) { AsyncProtocolRequest asyncRequest = (AsyncProtocolRequest)state; try { if (_PendingReHandshake) { // resume as read a blob StartReceiveBlob(asyncRequest.Buffer, asyncRequest); } else { // resume as process the blob ProcessReceivedBlob(asyncRequest.Buffer, asyncRequest.Buffer == null? 0: asyncRequest.Buffer.Length, asyncRequest); } } catch (Exception e) { if (asyncRequest.IsUserCompleted) { // This will throw on a worker thread. throw; } FinishHandshake(e, asyncRequest); } } // // Called with no user stack // private void CompleteRequestWaitCallback(object state) { AsyncProtocolRequest request = (AsyncProtocolRequest)state; // Force async completion if (request.MustCompleteSynchronously) throw new InternalException(); request.CompleteRequest(0); } private void RehandshakeCompleteCallback(IAsyncResult result) { LazyAsyncResult lazyAsyncResult = (LazyAsyncResult)result; GlobalLog.Assert(lazyAsyncResult != null, "SslState::RehandshakeCompleteCallback()|result is null!"); GlobalLog.Assert(lazyAsyncResult.InternalPeekCompleted, "SslState::RehandshakeCompleteCallback()|result is not completed!"); // If the rehandshake succeeded, FinishHandshake has already been called; if there was a SocketException // during the handshake, this gets called directly from FixedSizeReader, and we need to call // FinishHandshake to wake up the Read that triggered this rehandshake so the error gets back to the caller Exception exception = lazyAsyncResult.InternalWaitForCompletion() as Exception; if (exception != null) { // We may be calling FinishHandshake reentrantly, as FinishHandshake can call // asyncRequest.CompleteWithError, which will result in this method being called. // This is not a problem because: // // 1. We pass null as the asyncRequest parameter, so this second call to FinishHandshake won't loop // back here. // // 2. _QueuedWriteStateRequest and _QueuedReadStateRequest are set to null after the first call, // so, we won't invoke their callbacks again. // // 3. SetException won't overwrite an already-set _Exception. // // 4. There are three possibilites for _LockReadState and _LockWriteState: // // a. They were set back to None by the first call to FinishHandshake, and this will set them to // None again: a no-op. // // b. They were set to None by the first call to FinishHandshake, but as soon as the lock was given // up, another thread took a read/write lock. Calling FinishHandshake again will set them back // to None, but that's fine because that thread will be throwing _Exception before it actually // does any reading or writing and setting them back to None in a catch block anyways. // // c. If there is a Read/Write going on another thread, and the second FinishHandshake clears its // read/write lock, it's fine because no other Read/Write can look at the lock until the current // one gives up _SslStream._NestedRead/Write, and no handshake will look at the lock because // handshakes are only triggered in response to successful reads (which won't happen once // _Exception is set). FinishHandshake(exception, null); } } #if TRAVE [System.Diagnostics.Conditional("TRAVE")] internal void Debug() { GlobalLog.Print("_HandshakeCompleted: " + _HandshakeCompleted); GlobalLog.Print("_CertValidationFailed: " + _CertValidationFailed); GlobalLog.Print("_Context: "); _Context.Debug(); } #endif } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- Unit.cs
- CalendarDay.cs
- HMACRIPEMD160.cs
- HtmlControlPersistable.cs
- QueryContinueDragEvent.cs
- ControllableStoryboardAction.cs
- loginstatus.cs
- TypeDescriptionProvider.cs
- RectValueSerializer.cs
- EventManager.cs
- SerialReceived.cs
- Internal.cs
- PersonalizationAdministration.cs
- QilChoice.cs
- EncoderReplacementFallback.cs
- PropertyManager.cs
- SchemaMapping.cs
- XmlSchemaValidator.cs
- MatrixTransform3D.cs
- XAMLParseException.cs
- Object.cs
- DebugView.cs
- ApplicationSettingsBase.cs
- basemetadatamappingvisitor.cs
- IdentityNotMappedException.cs
- FreezableDefaultValueFactory.cs
- MouseBinding.cs
- IndexerNameAttribute.cs
- SqlFunctionAttribute.cs
- WebBrowser.cs
- ListViewGroup.cs
- DataStreamFromComStream.cs
- ResourceDescriptionAttribute.cs
- SectionRecord.cs
- NumberFormatter.cs
- DataSource.cs
- SkewTransform.cs
- DataBinder.cs
- ConfigurationPropertyCollection.cs
- OrderPreservingPipeliningSpoolingTask.cs
- DataControlButton.cs
- Subordinate.cs
- WebPartConnectionsEventArgs.cs
- GlobalProxySelection.cs
- ProfilePropertyNameValidator.cs
- DataReaderContainer.cs
- SQLBytes.cs
- XmlSchemaAppInfo.cs
- HostedHttpTransportManager.cs
- HelpKeywordAttribute.cs
- CopyAction.cs
- StateElement.cs
- XamlStyleSerializer.cs
- AppDomainAttributes.cs
- NonVisualControlAttribute.cs
- OdbcInfoMessageEvent.cs
- TabletDeviceInfo.cs
- ErrorActivity.cs
- ClientSettingsStore.cs
- HttpCapabilitiesSectionHandler.cs
- WriteableBitmap.cs
- RSAPKCS1KeyExchangeFormatter.cs
- XmlNamespaceMapping.cs
- WebPartsSection.cs
- XmlCharacterData.cs
- InputScope.cs
- UnsafeNativeMethods.cs
- RawStylusInput.cs
- StylesEditorDialog.cs
- ComEventsSink.cs
- HttpResponse.cs
- ToolStripDropDown.cs
- DataGridRelationshipRow.cs
- BuildProviderUtils.cs
- SrgsElementFactoryCompiler.cs
- IResourceProvider.cs
- OutputCacheModule.cs
- Subtract.cs
- TcpConnectionPoolSettingsElement.cs
- DataBindingExpressionBuilder.cs
- CodeTypeReference.cs
- UriSchemeKeyedCollection.cs
- WeakEventManager.cs
- PolicyUnit.cs
- WebPartAddingEventArgs.cs
- Win32Native.cs
- DetailsViewUpdateEventArgs.cs
- CalendarTable.cs
- DataGridPageChangedEventArgs.cs
- GenericQueueSurrogate.cs
- ComponentCommands.cs
- EventRecord.cs
- CompositionTarget.cs
- GlobalizationSection.cs
- MatrixAnimationUsingPath.cs
- DesignerResources.cs
- LocatorPartList.cs
- SerialErrors.cs
- CompilationSection.cs
- HttpApplicationStateWrapper.cs