diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/SecuritySafeHandles.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/SecuritySafeHandles.cs new file mode 100644 index 000000000000..eaedd736d026 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/SecuritySafeHandles.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +namespace System.Net.Security +{ + internal sealed class SafeFreeNegoCredentials : SafeFreeCredentials + { + private SafeGssCredHandle _credential; + + public SafeGssCredHandle GssCredential + { + get { return _credential; } + } + + public SafeFreeNegoCredentials(string username, string password, string domain) : base(IntPtr.Zero, true) + { + _credential = SafeGssCredHandle.Create(username, password, domain); + } + + public override bool IsInvalid + { + get { return (null == _credential); } + } + + protected override bool ReleaseHandle() + { + _credential.Dispose(); + _credential = null; + return true; + } + } + + internal sealed class SafeDeleteNegoContext : SafeDeleteContext + { + private SafeGssNameHandle _targetName; + private SafeGssContextHandle _context; + + public SafeGssNameHandle TargetName + { + get { return _targetName; } + } + + public SafeGssContextHandle GssContext + { + get { return _context; } + } + + public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetName) + : base(credential) + { + try + { + _targetName = SafeGssNameHandle.Create(targetName, false); + } + catch + { + base.ReleaseHandle(); + throw; + } + } + + public void SetGssContext(SafeGssContextHandle context) + { + Debug.Assert(!context.IsInvalid, "Invalid context passed to SafeDeleteNegoContext"); + _context = context; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (null != _context) + { + _context.Dispose(); + _context = null; + } + _targetName.Dispose(); + _targetName = null; + } + base.Dispose(disposing); + } + } +} diff --git a/src/Common/src/Interop/Unix/libssl/SecuritySafeHandles.cs b/src/Common/src/Interop/Unix/libssl/SecuritySafeHandles.cs index 2f4ae05d3699..0b4427a5f3c0 100644 --- a/src/Common/src/Interop/Unix/libssl/SecuritySafeHandles.cs +++ b/src/Common/src/Interop/Unix/libssl/SecuritySafeHandles.cs @@ -55,12 +55,19 @@ protected override bool ReleaseHandle() // Implementation of handles dependable on FreeCredentialsHandle // #if DEBUG - internal sealed class SafeFreeCredentials : DebugSafeHandle + internal abstract class SafeFreeCredentials : DebugSafeHandle { #else - internal sealed class SafeFreeCredentials : SafeHandle + internal abstract class SafeFreeCredentials : SafeHandle { #endif + protected SafeFreeCredentials(IntPtr handle, bool ownsHandle) : base(handle, ownsHandle) + { + } + } + + internal sealed class SafeFreeSslCredentials : SafeFreeCredentials + { private SafeX509Handle _certHandle; private SafeEvpPKeyHandle _certKeyHandle; private SslProtocols _protocols = SslProtocols.None; @@ -86,7 +93,7 @@ internal EncryptionPolicy Policy get { return _policy; } } - public SafeFreeCredentials(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy) + public SafeFreeSslCredentials(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy) : base(IntPtr.Zero, true) { Debug.Assert( @@ -206,13 +213,49 @@ protected override bool ReleaseHandle() } #if DEBUG - internal sealed class SafeDeleteContext : DebugSafeHandle + internal abstract class SafeDeleteContext : DebugSafeHandle { #else - internal sealed class SafeDeleteContext : SafeHandle + internal abstract class SafeDeleteContext : SafeHandle { #endif - private readonly SafeFreeCredentials _credential; + private SafeFreeCredentials _credential; + + protected SafeDeleteContext(SafeFreeCredentials credential) + : base(IntPtr.Zero, true) + { + Debug.Assert((null != credential), "Invalid credential passed to SafeDeleteContext"); + + // When a credential handle is first associated with the context we keep credential + // ref count bumped up to ensure ordered finalization. The credential properties + // are used in the SSL/NEGO data structures and should survive the lifetime of + // the SSL/NEGO context + bool ignore = false; + _credential = credential; + _credential.DangerousAddRef(ref ignore); + } + + public override bool IsInvalid + { + get { return (null == _credential); } + } + + protected override bool ReleaseHandle() + { + Debug.Assert((null != _credential), "Null credential in SafeDeleteContext"); + _credential.DangerousRelease(); + _credential = null; + return true; + } + + public override string ToString() + { + return IsInvalid ? String.Empty : handle.ToString(); + } + } + + internal sealed class SafeDeleteSslContext : SafeDeleteContext + { private readonly SafeSslHandle _sslContext; public SafeSslHandle SslContext @@ -223,18 +266,10 @@ public SafeSslHandle SslContext } } - public SafeDeleteContext(SafeFreeCredentials credential, bool isServer, bool remoteCertRequired) - : base(IntPtr.Zero, true) + public SafeDeleteSslContext(SafeFreeSslCredentials credential, bool isServer, bool remoteCertRequired) + : base(credential) { - Debug.Assert((null != credential) && !credential.IsInvalid, "Invalid credential used in SafeDeleteContext"); - - // When a credential handle is first associated with the context we keep credential - // ref count bumped up to ensure ordered finalization. The certificate handle and - // key handle are used in the SSL data structures and should survive the lifetime of - // the SSL context - bool gotCredRef = false; - _credential = credential; - _credential.DangerousAddRef(ref gotCredRef); + Debug.Assert((null != credential) && !credential.IsInvalid, "Invalid credential used in SafeDeleteSslContext"); try { @@ -248,11 +283,8 @@ public SafeDeleteContext(SafeFreeCredentials credential, bool isServer, bool rem } catch(Exception ex) { - if (gotCredRef) - { - _credential.DangerousRelease(); - } Debug.Write("Exception Caught. - " + ex); + base.ReleaseHandle(); throw; } } @@ -265,13 +297,6 @@ public override bool IsInvalid } } - protected override bool ReleaseHandle() - { - Debug.Assert((null != _credential) && !_credential.IsInvalid, "Invalid credential saved in SafeDeleteContext"); - _credential.DangerousRelease(); - return true; - } - protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/Common/src/Interop/Windows/sspicli/NegotiationInfoClass.cs b/src/Common/src/Interop/Windows/sspicli/NegotiationInfoClass.cs index 3a3149598b7a..1fcded4950d9 100644 --- a/src/Common/src/Interop/Windows/sspicli/NegotiationInfoClass.cs +++ b/src/Common/src/Interop/Windows/sspicli/NegotiationInfoClass.cs @@ -7,12 +7,8 @@ namespace System.Net { // This class is used to determine if NTLM or // Kerberos are used in the context of a Negotiate handshake - internal class NegotiationInfoClass + internal partial class NegotiationInfoClass { - internal const string NTLM = "NTLM"; - internal const string Kerberos = "Kerberos"; - internal const string WDigest = "WDigest"; - internal const string Negotiate = "Negotiate"; internal string AuthenticationPackage; internal NegotiationInfoClass(SafeHandle safeHandle, int negotiationState) diff --git a/src/System.Net.Security/src/System.Net.Security.csproj b/src/System.Net.Security/src/System.Net.Security.csproj index f525e1ff2dd3..1345462a56b1 100644 --- a/src/System.Net.Security/src/System.Net.Security.csproj +++ b/src/System.Net.Security/src/System.Net.Security.csproj @@ -48,9 +48,15 @@ + + + + + + @@ -143,10 +149,7 @@ - - - @@ -292,6 +295,21 @@ Common\Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs + + Common\Interop\Unix\System.Net.Security.Native\Interop.GssApi.cs + + + Common\Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs + + + Common\Interop\Unix\System.Net.Security.Native\SecuritySafeHandles.cs + + + Common\Microsoft\Win32\SafeHandles\GssSafeHandles.cs + + + Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurity.cs + Common\Microsoft\Win32\SafeHandles\SafeX509Handles.Unix.cs diff --git a/src/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs b/src/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs index 213567bd1887..290819784999 100644 --- a/src/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs +++ b/src/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs @@ -112,7 +112,7 @@ internal static X509Certificate2 GetRemoteCertificate(SafeDeleteContext security remoteCertificateStore = new X509Certificate2Collection(); using (SafeSharedX509StackHandle chainStack = - Interop.OpenSsl.GetPeerCertificateChain(securityContext.SslContext)) + Interop.OpenSsl.GetPeerCertificateChain(((SafeDeleteSslContext)securityContext).SslContext)) { if (!chainStack.IsInvalid) { @@ -162,7 +162,7 @@ internal static X509Certificate2 GetRemoteCertificate(SafeDeleteContext security // internal static string[] GetRequestCertificateAuthorities(SafeDeleteContext securityContext) { - using (SafeSharedX509NameStackHandle names = Interop.Ssl.SslGetClientCAList(securityContext.SslContext)) + using (SafeSharedX509NameStackHandle names = Interop.Ssl.SslGetClientCAList(((SafeDeleteSslContext)securityContext).SslContext)) { if (names.IsInvalid) { @@ -256,7 +256,7 @@ private static int QueryContextRemoteCertificate(SafeDeleteContext securityConte remoteCertContext = null; try { - SafeX509Handle remoteCertificate = Interop.OpenSsl.GetPeerCertificate(securityContext.SslContext); + SafeX509Handle remoteCertificate = Interop.OpenSsl.GetPeerCertificate(((SafeDeleteSslContext)securityContext).SslContext); // Note that cert ownership is transferred to SafeFreeCertContext remoteCertContext = new SafeFreeCertContext(remoteCertificate); return 0; diff --git a/src/System.Net.Security/src/System/Net/ContextFlagsPal.cs b/src/System.Net.Security/src/System/Net/ContextFlagsPal.cs new file mode 100644 index 000000000000..8da411e816cd --- /dev/null +++ b/src/System.Net.Security/src/System/Net/ContextFlagsPal.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace System.Net +{ + [Flags] + internal enum ContextFlagsPal + { + Zero = 0, + Delegate = 0x00000001, + MutualAuth = 0x00000002, + ReplayDetect = 0x00000004, + SequenceDetect = 0x00000008, + Confidentiality = 0x00000010, + UseSessionKey = 0x00000020, + AllocateMemory = 0x00000100, + Connection = 0x00000800, + InitExtendedError = 0x00004000, + AcceptExtendedError = 0x00008000, + InitStream = 0x00008000, + AcceptStream = 0x00010000, + InitIntegrity = 0x00010000, + AcceptIntegrity = 0x00020000, + InitManualCredValidation = 0x00080000, + InitUseSuppliedCreds = 0x00000080, + InitIdentify = 0x00020000, + AcceptIdentify = 0x00080000, + ProxyBindings = 0x04000000, + AllowMissingBindings = 0x10000000, + UnverifiedTargetName = 0x20000000, + } +} diff --git a/src/System.Net.Security/src/System/Net/NTAuthentication.cs b/src/System.Net.Security/src/System/Net/NTAuthentication.cs index bfe544f1d140..5deb98af009b 100644 --- a/src/System.Net.Security/src/System/Net/NTAuthentication.cs +++ b/src/System.Net.Security/src/System/Net/NTAuthentication.cs @@ -23,12 +23,11 @@ internal class NTAuthentication private string _clientSpecifiedSpn; private int _tokenSize; - private Interop.SspiCli.ContextFlags _requestedContextFlags; - private Interop.SspiCli.ContextFlags _contextFlags; + private ContextFlagsPal _requestedContextFlags; + private ContextFlagsPal _contextFlags; private bool _isCompleted; private string _protocolName; - private SecSizes _sizes; private string _lastProtocolName; private string _package; @@ -57,10 +56,10 @@ internal string AssociatedName { if (!(IsValidContext && IsCompleted)) { - throw new Win32Exception((int)Interop.SecurityStatus.InvalidHandle); + throw new Exception(SR.net_auth_noauth); } - string name = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.Names) as string; + string name = NegoState.QueryContextAssociatedName(_securityContext); if (GlobalLog.IsEnabled) { GlobalLog.Print("NTAuthentication: The context is associated with [" + name + "]"); @@ -73,7 +72,7 @@ internal bool IsConfidentialityFlag { get { - return (_contextFlags & Interop.SspiCli.ContextFlags.Confidentiality) != 0; + return (_contextFlags & ContextFlagsPal.Confidentiality) != 0; } } @@ -81,7 +80,7 @@ internal bool IsIntegrityFlag { get { - return (_contextFlags & (_isServer ? Interop.SspiCli.ContextFlags.AcceptIntegrity : Interop.SspiCli.ContextFlags.InitIntegrity)) != 0; + return (_contextFlags & (_isServer ? ContextFlagsPal.AcceptIntegrity : ContextFlagsPal.InitIntegrity)) != 0; } } @@ -89,7 +88,7 @@ internal bool IsMutualAuthFlag { get { - return (_contextFlags & Interop.SspiCli.ContextFlags.MutualAuth) != 0; + return (_contextFlags & ContextFlagsPal.MutualAuth) != 0; } } @@ -97,7 +96,7 @@ internal bool IsDelegationFlag { get { - return (_contextFlags & Interop.SspiCli.ContextFlags.Delegate) != 0; + return (_contextFlags & ContextFlagsPal.Delegate) != 0; } } @@ -105,7 +104,7 @@ internal bool IsIdentifyFlag { get { - return (_contextFlags & (_isServer ? Interop.SspiCli.ContextFlags.AcceptIdentify : Interop.SspiCli.ContextFlags.InitIdentify)) != 0; + return (_contextFlags & (_isServer ? ContextFlagsPal.AcceptIdentify : ContextFlagsPal.InitIdentify)) != 0; } } @@ -174,65 +173,34 @@ internal string ProtocolName // Note: May return string.Empty if the auth is not done yet or failed. if (_protocolName == null) { - NegotiationInfoClass negotiationInfo = null; + string negotiationAuthenticationPackage = null; if (IsValidContext) { - negotiationInfo = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, Interop.SspiCli.ContextAttribute.NegotiationInfo) as NegotiationInfoClass; + negotiationAuthenticationPackage = NegoState.QueryContextAuthenticationPackage(_securityContext); if (IsCompleted) { - if (negotiationInfo != null) - { - // Cache it only when it's completed. - _protocolName = negotiationInfo.AuthenticationPackage; - } + _protocolName = negotiationAuthenticationPackage; } } - - return negotiationInfo == null ? string.Empty : negotiationInfo.AuthenticationPackage; + return negotiationAuthenticationPackage ?? string.Empty; } return _protocolName; } } - internal SecSizes Sizes - { - get - { - if ((IsCompleted && IsValidContext)) - { - if (GlobalLog.IsEnabled) - { - GlobalLog.Assert("NTAuthentication#{0}::MaxDataSize|The context is not completed or invalid.", LoggingHash.HashString(this)); - } - Debug.Fail("NTAuthentication#" + LoggingHash.HashString(this) + "::MaxDataSize |The context is not completed or invalid."); - } - - if (_sizes == null) - { - _sizes = SSPIWrapper.QueryContextAttributes( - GlobalSSPI.SSPIAuth, - _securityContext, - Interop.SspiCli.ContextAttribute.Sizes - ) as SecSizes; - } - - return _sizes; - } - } - // // This overload does not attempt to impersonate because the caller either did it already or the original thread context is still preserved. // - internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string spn, Interop.SspiCli.ContextFlags requestedContextFlags, ChannelBinding channelBinding) + internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding) { Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding); } private class InitializeCallbackContext { - internal InitializeCallbackContext(NTAuthentication thisPtr, bool isServer, string package, NetworkCredential credential, string spn, Interop.SspiCli.ContextFlags requestedContextFlags, ChannelBinding channelBinding) + internal InitializeCallbackContext(NTAuthentication thisPtr, bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding) { ThisPtr = thisPtr; IsServer = isServer; @@ -248,7 +216,7 @@ internal InitializeCallbackContext(NTAuthentication thisPtr, bool isServer, stri internal readonly string Package; internal readonly NetworkCredential Credential; internal readonly string Spn; - internal readonly Interop.SspiCli.ContextFlags RequestedContextFlags; + internal readonly ContextFlagsPal RequestedContextFlags; internal readonly ChannelBinding ChannelBinding; } @@ -258,14 +226,14 @@ private static void InitializeCallback(object state) context.ThisPtr.Initialize(context.IsServer, context.Package, context.Credential, context.Spn, context.RequestedContextFlags, context.ChannelBinding); } - private void Initialize(bool isServer, string package, NetworkCredential credential, string spn, Interop.SspiCli.ContextFlags requestedContextFlags, ChannelBinding channelBinding) + private void Initialize(bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding) { if (GlobalLog.IsEnabled) { GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::.ctor() package:" + LoggingHash.ObjectToString(package) + " spn:" + LoggingHash.ObjectToString(spn) + " flags :" + requestedContextFlags.ToString()); } - _tokenSize = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, package, true).MaxToken; + _tokenSize = NegoState.QueryMaxTokenSize(package); _isServer = isServer; _spn = spn; _securityContext = null; @@ -290,55 +258,17 @@ private void Initialize(bool isServer, string package, NetworkCredential credent GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::.ctor(): using DefaultCredentials"); } - _credentialsHandle = SSPIWrapper.AcquireDefaultCredential( - GlobalSSPI.SSPIAuth, - package, - (_isServer ? Interop.SspiCli.CredentialUse.Inbound : Interop.SspiCli.CredentialUse.Outbound)); + _credentialsHandle = NegoState.AcquireDefaultCredential(package, _isServer); } else { - unsafe - { - SafeSspiAuthDataHandle authData = null; - try - { - Interop.SecurityStatus result = Interop.SspiCli.SspiEncodeStringsAsAuthIdentity( - credential.UserName, credential.Domain, - credential.Password, out authData); - - if (result != Interop.SecurityStatus.OK) - { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.PrintError( - NetEventSource.ComponentType.Security, - SR.Format( - SR.net_log_operation_failed_with_error, - "SspiEncodeStringsAsAuthIdentity()", - String.Format(CultureInfo.CurrentCulture, "0x{0:X}", (int)result))); - } - - throw new Win32Exception((int)result); - } - - _credentialsHandle = SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPIAuth, - package, (_isServer ? Interop.SspiCli.CredentialUse.Inbound : Interop.SspiCli.CredentialUse.Outbound), ref authData); - } - finally - { - if (authData != null) - { - authData.Dispose(); - } - } - } + _credentialsHandle = NegoState.AcquireCredentialsHandle(package, _isServer, credential); } } - // This will return a client token when conducted authentication on server side. - // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app. - internal SecurityContextTokenHandle GetContextToken(out Interop.SecurityStatus status) + internal SafeDeleteContext GetContext(out SecurityStatusPal status) { + status = SecurityStatusPal.OK; if ((IsCompleted && IsValidContext)) { if (GlobalLog.IsEnabled) @@ -359,28 +289,11 @@ internal SecurityContextTokenHandle GetContextToken(out Interop.SecurityStatus s if (!IsValidContext) { - throw new Win32Exception((int)Interop.SecurityStatus.InvalidHandle); - } - - SecurityContextTokenHandle token = null; - status = (Interop.SecurityStatus)SSPIWrapper.QuerySecurityContextToken( - GlobalSSPI.SSPIAuth, - _securityContext, - out token); - - return token; - } - - internal SecurityContextTokenHandle GetContextToken() - { - Interop.SecurityStatus status; - SecurityContextTokenHandle token = GetContextToken(out status); - if (status != Interop.SecurityStatus.OK) - { - throw new Win32Exception((int)status); + status = SecurityStatusPal.InvalidHandle; + return null; } - return token; + return _securityContext; } internal void CloseContext() @@ -392,7 +305,7 @@ internal void CloseContext() } // Accepts an incoming binary security blob and returns an outgoing binary security blob. - internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Interop.SecurityStatus statusCode) + internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { if (GlobalLog.IsEnabled) { @@ -425,13 +338,11 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Inte if (!_isServer) { // client session - statusCode = (Interop.SecurityStatus)SSPIWrapper.InitializeSecurityContext( - GlobalSSPI.SSPIAuth, + statusCode = NegoState.InitializeSecurityContext( _credentialsHandle, ref _securityContext, _spn, _requestedContextFlags, - Interop.SspiCli.Endianness.Network, inSecurityBufferArray, outSecurityBuffer, ref _contextFlags); @@ -441,15 +352,12 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Inte GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob() SSPIWrapper.InitializeSecurityContext() returns statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); } - if (statusCode == Interop.SecurityStatus.CompleteNeeded) + if (statusCode == SecurityStatusPal.CompleteNeeded) { var inSecurityBuffers = new SecurityBuffer[1]; inSecurityBuffers[0] = outSecurityBuffer; - statusCode = (Interop.SecurityStatus)SSPIWrapper.CompleteAuthToken( - GlobalSSPI.SSPIAuth, - ref _securityContext, - inSecurityBuffers); + statusCode = NegoState.CompleteAuthToken(ref _securityContext, inSecurityBuffers); if (GlobalLog.IsEnabled) { @@ -462,12 +370,10 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Inte else { // Server session. - statusCode = (Interop.SecurityStatus)SSPIWrapper.AcceptSecurityContext( - GlobalSSPI.SSPIAuth, + statusCode = NegoState.AcceptSecurityContext( _credentialsHandle, ref _securityContext, _requestedContextFlags, - Interop.SspiCli.Endianness.Network, inSecurityBufferArray, outSecurityBuffer, ref _contextFlags); @@ -493,13 +399,13 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Inte } - if (((int)statusCode & unchecked((int)0x80000000)) != 0) + if (NegoState.IsError(statusCode)) { CloseContext(); _isCompleted = true; if (throwOnError) { - var exception = new Win32Exception((int)statusCode); + Exception exception = NegoState.CreateExceptionFromError(statusCode); if (GlobalLog.IsEnabled) { GlobalLog.Leave("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob", "Win32Exception:" + exception); @@ -519,21 +425,18 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Inte SSPIHandleCache.CacheCredential(_credentialsHandle); } - // The return value from SSPI will tell us correctly if the - // handshake is over or not: http://msdn.microsoft.com/library/psdk/secspi/sspiref_67p0.htm - // we also have to consider the case in which SSPI formed a new context, in this case we're done as well. - if (statusCode == Interop.SecurityStatus.OK) + // The return value will tell us correctly if the handshake is over or not + if (statusCode == SecurityStatusPal.OK) { // Success. - if ((statusCode == Interop.SecurityStatus.OK)) + if (GlobalLog.IsEnabled) { - if (GlobalLog.IsEnabled) - { - GlobalLog.AssertFormat("NTAuthentication#{0}::GetOutgoingBlob()|statusCode:[0x{1:x8}] ({2}) m_SecurityContext#{3}::Handle:[{4}] [STATUS != OK]", LoggingHash.HashString(this), (int)statusCode, statusCode, LoggingHash.HashString(_securityContext), LoggingHash.ObjectToString(_securityContext)); - } - Debug.Fail("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob()|statusCode:[0x" + ((int)statusCode).ToString("x8") + "] (" + statusCode + ") m_SecurityContext#" + LoggingHash.HashString(_securityContext) + "::Handle:[" + LoggingHash.ObjectToString(_securityContext) + "] [STATUS != OK]"); + GlobalLog.AssertFormat( + "NTAuthentication#{0}::GetOutgoingBlob()|statusCode:[0x{1:x8}] ({2}) m_SecurityContext#{3}::Handle:[{4}] [STATUS != OK]", + LoggingHash.HashString(this), (int) statusCode, statusCode, + LoggingHash.HashString(_securityContext), LoggingHash.ObjectToString(_securityContext)); } - + Debug.Fail("NTAuthentication#" + LoggingHash.HashString(this) + "::GetOutgoingBlob()|statusCode:[0x" + ((int)statusCode).ToString("x8") + "] (" + statusCode + ") m_SecurityContext#" + LoggingHash.HashString(_securityContext) + "::Handle:[" + LoggingHash.ObjectToString(_securityContext) + "] [STATUS != OK]"); _isCompleted = true; } else if (GlobalLog.IsEnabled) @@ -552,86 +455,15 @@ internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out Inte internal int Encrypt(byte[] buffer, int offset, int count, ref byte[] output, uint sequenceNumber) { - SecSizes sizes = Sizes; - - try - { - int maxCount = checked(Int32.MaxValue - 4 - sizes.BlockSize - sizes.SecurityTrailer); - - if (count > maxCount || count < 0) - { - throw new ArgumentOutOfRangeException("count", SR.Format(SR.net_io_out_range, maxCount)); - } - } - catch (Exception e) - { - if (!ExceptionCheck.IsFatal(e)) - { - if (GlobalLog.IsEnabled) - { - GlobalLog.Assert("NTAuthentication#" + LoggingHash.HashString(this) + "::Encrypt", "Arguments out of range."); - } - Debug.Fail("NTAuthentication#" + LoggingHash.HashString(this) + "::Encrypt", "Arguments out of range."); - } - - throw; - } - - int resultSize = count + sizes.SecurityTrailer + sizes.BlockSize; - if (output == null || output.Length < resultSize + 4) - { - output = new byte[resultSize + 4]; - } - - // Make a copy of user data for in-place encryption. - Buffer.BlockCopy(buffer, offset, output, 4 + sizes.SecurityTrailer, count); - - // Prepare buffers TOKEN(signature), DATA and Padding. - var securityBuffer = new SecurityBuffer[3]; - securityBuffer[0] = new SecurityBuffer(output, 4, sizes.SecurityTrailer, SecurityBufferType.Token); - securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.SecurityTrailer, count, SecurityBufferType.Data); - securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.SecurityTrailer + count, sizes.BlockSize, SecurityBufferType.Padding); - - int errorCode; - if (IsConfidentialityFlag) - { - errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, _securityContext, securityBuffer, sequenceNumber); - } - else - { - if (IsNTLM) - { - securityBuffer[1].type |= SecurityBufferType.ReadOnlyFlag; - } - - errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, _securityContext, securityBuffer, 0); - } - - if (errorCode != 0) - { - if (GlobalLog.IsEnabled) - { - GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::Encrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); - } - throw new Win32Exception(errorCode); - } - - // Compacting the result. - resultSize = securityBuffer[0].size; - bool forceCopy = false; - if (resultSize != sizes.SecurityTrailer) - { - forceCopy = true; - Buffer.BlockCopy(output, securityBuffer[1].offset, output, 4 + resultSize, securityBuffer[1].size); - } - - resultSize += securityBuffer[1].size; - if (securityBuffer[2].size != 0 && (forceCopy || resultSize != (count + sizes.SecurityTrailer))) - { - Buffer.BlockCopy(output, securityBuffer[2].offset, output, 4 + resultSize, securityBuffer[2].size); - } - - resultSize += securityBuffer[2].size; + int resultSize = NegoState.Encrypt( + _securityContext, + buffer, + offset, + count, + IsConfidentialityFlag, + IsNTLM, + ref output, + sequenceNumber); unchecked { @@ -668,44 +500,7 @@ internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, u throw new ArgumentOutOfRangeException("count"); } - if (IsNTLM) - { - return DecryptNtlm(payload, offset, count, out newOffset, expectedSeqNumber); - } - - // - // Kerberos and up - // - var securityBuffer = new SecurityBuffer[2]; - securityBuffer[0] = new SecurityBuffer(payload, offset, count, SecurityBufferType.Stream); - securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.Data); - - int errorCode; - if (IsConfidentialityFlag) - { - errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, _securityContext, securityBuffer, expectedSeqNumber); - } - else - { - errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, _securityContext, securityBuffer, expectedSeqNumber); - } - - if (errorCode != 0) - { - if (GlobalLog.IsEnabled) - { - GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::Decrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); - } - throw new Win32Exception(errorCode); - } - - if (securityBuffer[1].type != SecurityBufferType.Data) - { - throw new InternalException(); - } - - newOffset = securityBuffer[1].offset; - return securityBuffer[1].size; + return NegoState.Decrypt(_securityContext, payload, offset, count, IsConfidentialityFlag, IsNTLM, out newOffset, expectedSeqNumber); } private string GetClientSpecifiedSpn() @@ -719,8 +514,7 @@ private string GetClientSpecifiedSpn() Debug.Fail("NTAuthentication: Trying to get the client SPN before handshaking is done!"); } - string spn = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, _securityContext, - Interop.SspiCli.ContextAttribute.ClientSpecifiedSpn) as string; + string spn = NegoState.QueryContextClientSpecifiedSpn(_securityContext); if (GlobalLog.IsEnabled) { @@ -743,40 +537,7 @@ private int DecryptNtlm(byte[] payload, int offset, int count, out int newOffset throw new ArgumentOutOfRangeException("count"); } - var securityBuffer = new SecurityBuffer[2]; - securityBuffer[0] = new SecurityBuffer(payload, offset, 16, SecurityBufferType.Token); - securityBuffer[1] = new SecurityBuffer(payload, offset + 16, count - 16, SecurityBufferType.Data); - - int errorCode; - SecurityBufferType realDataType = SecurityBufferType.Data; - - if (IsConfidentialityFlag) - { - errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, _securityContext, securityBuffer, expectedSeqNumber); - } - else - { - realDataType |= SecurityBufferType.ReadOnlyFlag; - securityBuffer[1].type = realDataType; - errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, _securityContext, securityBuffer, expectedSeqNumber); - } - - if (errorCode != 0) - { - if (GlobalLog.IsEnabled) - { - GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::Decrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); - } - throw new Win32Exception(errorCode); - } - - if (securityBuffer[1].type != realDataType) - { - throw new InternalException(); - } - - newOffset = securityBuffer[1].offset; - return securityBuffer[1].size; + return NegoState.DecryptNtlm(_securityContext, payload, offset, count, IsConfidentialityFlag, out newOffset, expectedSeqNumber); } } } diff --git a/src/System.Net.Security/src/System/Net/NegotiationInfoClass.cs b/src/System.Net.Security/src/System/Net/NegotiationInfoClass.cs new file mode 100644 index 000000000000..eaf5983cfa9d --- /dev/null +++ b/src/System.Net.Security/src/System/Net/NegotiationInfoClass.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Net +{ + // This class is used to determine if NTLM or + // Kerberos are used in the context of a Negotiate handshake + internal partial class NegotiationInfoClass + { + internal const string NTLM = "NTLM"; + internal const string Kerberos = "Kerberos"; + internal const string WDigest = "WDigest"; + internal const string Negotiate = "Negotiate"; + } +} diff --git a/src/System.Net.Security/src/System/Net/SecureProtocols/InternalNegoState.cs b/src/System.Net.Security/src/System/Net/SecureProtocols/InternalNegoState.cs new file mode 100644 index 000000000000..beef946762f2 --- /dev/null +++ b/src/System.Net.Security/src/System/Net/SecureProtocols/InternalNegoState.cs @@ -0,0 +1,821 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.IO; +using System.Security; +using System.Security.Principal; +using System.Threading; +using System.ComponentModel; +using System.Security.Authentication; +using System.Security.Authentication.ExtendedProtection; + +namespace System.Net.Security +{ + // + // The class maintains the state of the authentication process and the security context. + // It encapsulates security context and does the real work in authentication and + // user data encryption + // + internal partial class NegoState + { + private const int ERROR_TRUST_FAILURE = 1790; // Used to serialize protectionLevel or impersonationLevel mismatch error to the remote side. + + private static readonly byte[] s_emptyMessage = new byte[0]; + private static readonly AsyncCallback s_readCallback = new AsyncCallback(ReadCallback); + private static readonly AsyncCallback s_writeCallback = new AsyncCallback(WriteCallback); + + private Stream _innerStream; + private bool _leaveStreamOpen; + + private Exception _exception; + + private StreamFramer _framer; + private NTAuthentication _context; + + private int _nestedAuth; + + internal const int MaxReadFrameSize = 64 * 1024; + internal const int MaxWriteDataSize = 63 * 1024; // 1k for the framing and trailer that is always less as per SSPI. + + private bool _canRetryAuthentication; + private ProtectionLevel _expectedProtectionLevel; + private TokenImpersonationLevel _expectedImpersonationLevel; + private uint _writeSequenceNumber; + private uint _readSequenceNumber; + + private ExtendedProtectionPolicy _extendedProtectionPolicy; + + // SSPI does not send a server ack on successful auth. + // This is a state variable used to gracefully handle auth confirmation. + private bool _remoteOk = false; + + internal NegoState(Stream innerStream, bool leaveStreamOpen) + { + if (innerStream == null) + { + throw new ArgumentNullException("stream"); + } + + _innerStream = innerStream; + _leaveStreamOpen = leaveStreamOpen; + } + + internal static string DefaultPackage + { + get + { + return NegotiationInfoClass.Negotiate; + } + } + + internal void ValidateCreateContext( + string package, + NetworkCredential credential, + string servicePrincipalName, + ExtendedProtectionPolicy policy, + ProtectionLevel protectionLevel, + TokenImpersonationLevel impersonationLevel) + { + if (policy != null) + { + // One of these must be set if EP is turned on + if (policy.CustomChannelBinding == null && policy.CustomServiceNames == null) + { + throw new ArgumentException(SR.net_auth_must_specify_extended_protection_scheme, "policy"); + } + + _extendedProtectionPolicy = policy; + } + else + { + _extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never); + } + + ValidateCreateContext(package, true, credential, servicePrincipalName, _extendedProtectionPolicy.CustomChannelBinding, protectionLevel, impersonationLevel); + } + + internal void ValidateCreateContext( + string package, + bool isServer, + NetworkCredential credential, + string servicePrincipalName, + ChannelBinding channelBinding, + ProtectionLevel protectionLevel, + TokenImpersonationLevel impersonationLevel) + { + if (_exception != null && !_canRetryAuthentication) + { + throw _exception; + } + + if (_context != null && _context.IsValidContext) + { + throw new InvalidOperationException(SR.net_auth_reauth); + } + + if (credential == null) + { + throw new ArgumentNullException("credential"); + } + + if (servicePrincipalName == null) + { + throw new ArgumentNullException("servicePrincipalName"); + } + + ValidateImpersonationLevel(impersonationLevel); + + if (_context != null && IsServer != isServer) + { + throw new InvalidOperationException(SR.net_auth_client_server); + } + + _exception = null; + _remoteOk = false; + _framer = new StreamFramer(_innerStream); + _framer.WriteHeader.MessageId = FrameHeader.HandshakeId; + + _expectedProtectionLevel = protectionLevel; + _expectedImpersonationLevel = isServer ? impersonationLevel : TokenImpersonationLevel.None; + _writeSequenceNumber = 0; + _readSequenceNumber = 0; + + ContextFlagsPal flags = ContextFlagsPal.Connection; + + // A workaround for the client when talking to Win9x on the server side. + if (protectionLevel == ProtectionLevel.None && !isServer) + { + package = NegotiationInfoClass.NTLM; + } + else if (protectionLevel == ProtectionLevel.EncryptAndSign) + { + flags |= ContextFlagsPal.Confidentiality; + } + else if (protectionLevel == ProtectionLevel.Sign) + { + // Assuming user expects NT4 SP4 and above. + flags |= (ContextFlagsPal.ReplayDetect | ContextFlagsPal.SequenceDetect | ContextFlagsPal.InitIntegrity); + } + + if (isServer) + { + if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) + { + flags |= ContextFlagsPal.AllowMissingBindings; + } + + if (_extendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never && + _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy) + { + flags |= ContextFlagsPal.ProxyBindings; + } + } + else + { + // Server side should not request any of these flags. + if (protectionLevel != ProtectionLevel.None) + { + flags |= ContextFlagsPal.MutualAuth; + } + + if (impersonationLevel == TokenImpersonationLevel.Identification) + { + flags |= ContextFlagsPal.InitIdentify; + } + + if (impersonationLevel == TokenImpersonationLevel.Delegation) + { + flags |= ContextFlagsPal.Delegate; + } + } + + _canRetryAuthentication = false; + + try + { + _context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding); + } + catch (Exception e) + { + throw new AuthenticationException(SR.net_auth_SSPI, e); + } + } + + private Exception SetException(Exception e) + { + if (_exception == null || !(_exception is ObjectDisposedException)) + { + _exception = e; + } + + if (_exception != null && _context != null) + { + _context.CloseContext(); + } + + return _exception; + } + + internal bool IsAuthenticated + { + get + { + return _context != null && HandshakeComplete && _exception == null && _remoteOk; + } + } + + internal bool IsMutuallyAuthenticated + { + get + { + if (!IsAuthenticated) + { + return false; + } + + // Suppressing for NTLM since SSPI does not return correct value in the context flags. + if (_context.IsNTLM) + { + return false; + } + + return _context.IsMutualAuthFlag; + } + } + + internal bool IsEncrypted + { + get + { + return IsAuthenticated && _context.IsConfidentialityFlag; + } + } + + internal bool IsSigned + { + get + { + return IsAuthenticated && (_context.IsIntegrityFlag || _context.IsConfidentialityFlag); + } + } + + internal bool IsServer + { + get + { + return _context != null && _context.IsServer; + } + } + + internal bool CanGetSecureStream + { + get + { + return (_context.IsConfidentialityFlag || _context.IsIntegrityFlag); + } + } + + internal TokenImpersonationLevel AllowedImpersonation + { + get + { + CheckThrow(true); + return PrivateImpersonationLevel; + } + } + + private TokenImpersonationLevel PrivateImpersonationLevel + { + get + { + // We should suppress the delegate flag in NTLM case. + return (_context.IsDelegationFlag && _context.ProtocolName != NegotiationInfoClass.NTLM) ? TokenImpersonationLevel.Delegation + : _context.IsIdentifyFlag ? TokenImpersonationLevel.Identification + : TokenImpersonationLevel.Impersonation; + } + } + + private bool HandshakeComplete + { + get + { + return _context.IsCompleted && _context.IsValidContext; + } + } + + internal void CheckThrow(bool authSucessCheck) + { + if (_exception != null) + { + throw _exception; + } + + if (authSucessCheck && !IsAuthenticated) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + } + + // + // This is to not depend on GC&SafeHandle class if the context is not needed anymore. + // + internal void Close() + { + // Mark this instance as disposed. + _exception = new ObjectDisposedException("NegotiateStream"); + if (_context != null) + { + _context.CloseContext(); + } + } + + internal void ProcessAuthentication(LazyAsyncResult lazyResult) + { + CheckThrow(false); + if (Interlocked.Exchange(ref _nestedAuth, 1) == 1) + { + throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, lazyResult == null ? "BeginAuthenticate" : "Authenticate", "authenticate")); + } + + try + { + if (_context.IsServer) + { + // Listen for a client blob. + StartReceiveBlob(lazyResult); + } + else + { + // Start with the first blob. + StartSendBlob(null, lazyResult); + } + } + catch (Exception e) + { + // Round-trip it through SetException(). + e = SetException(e); + throw; + } + finally + { + if (lazyResult == null || _exception != null) + { + _nestedAuth = 0; + } + } + } + + internal void EndProcessAuthentication(IAsyncResult result) + { + if (result == null) + { + throw new ArgumentNullException("asyncResult"); + } + + LazyAsyncResult lazyResult = result as LazyAsyncResult; + if (lazyResult == null) + { + throw new ArgumentException(SR.Format(SR.net_io_async_result, result.GetType().FullName), "asyncResult"); + } + + if (Interlocked.Exchange(ref _nestedAuth, 0) == 0) + { + throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndAuthenticate")); + } + + // No "artificial" timeouts implemented so far, InnerStream controls that. + lazyResult.InternalWaitForCompletion(); + + Exception e = lazyResult.Result as Exception; + + if (e != null) + { + // Round-trip it through the SetException(). + e = SetException(e); + throw e; + } + } + + private bool CheckSpn() + { + if (_context.IsKerberos) + { + return true; + } + + if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.Never || + _extendedProtectionPolicy.CustomServiceNames == null) + { + return true; + } + + string clientSpn = _context.ClientSpecifiedSpn; + + if (String.IsNullOrEmpty(clientSpn)) + { + if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) + { + return true; + } + } + else + { + return _extendedProtectionPolicy.CustomServiceNames.Contains(clientSpn); + } + + return false; + } + + // + // Client side starts here, but server also loops through this method. + // + private void StartSendBlob(byte[] message, LazyAsyncResult lazyResult) + { + Exception exception = null; + if (message != s_emptyMessage) + { + message = GetOutgoingBlob(message, ref exception); + } + + if (exception != null) + { + // Signal remote side on a failed attempt. + StartSendAuthResetSignal(lazyResult, message, exception); + return; + } + + if (HandshakeComplete) + { + if (_context.IsServer && !CheckSpn()) + { + exception = new AuthenticationException(SR.net_auth_bad_client_creds_or_target_mismatch); + int statusCode = ERROR_TRUST_FAILURE; + message = new byte[8]; //sizeof(long) + + for (int i = message.Length - 1; i >= 0; --i) + { + message[i] = (byte)(statusCode & 0xFF); + statusCode = (int)((uint)statusCode >> 8); + } + + StartSendAuthResetSignal(lazyResult, message, exception); + return; + } + + if (PrivateImpersonationLevel < _expectedImpersonationLevel) + { + exception = new AuthenticationException(SR.Format(SR.net_auth_context_expectation, _expectedImpersonationLevel.ToString(), PrivateImpersonationLevel.ToString())); + int statusCode = ERROR_TRUST_FAILURE; + message = new byte[8]; //sizeof(long) + + for (int i = message.Length - 1; i >= 0; --i) + { + message[i] = (byte)(statusCode & 0xFF); + statusCode = (int)((uint)statusCode >> 8); + } + + StartSendAuthResetSignal(lazyResult, message, exception); + return; + } + + ProtectionLevel result = _context.IsConfidentialityFlag ? ProtectionLevel.EncryptAndSign : _context.IsIntegrityFlag ? ProtectionLevel.Sign : ProtectionLevel.None; + + if (result < _expectedProtectionLevel) + { + exception = new AuthenticationException(SR.Format(SR.net_auth_context_expectation, result.ToString(), _expectedProtectionLevel.ToString())); + int statusCode = ERROR_TRUST_FAILURE; + message = new byte[8]; //sizeof(long) + + for (int i = message.Length - 1; i >= 0; --i) + { + message[i] = (byte)(statusCode & 0xFF); + statusCode = (int)((uint)statusCode >> 8); + } + + StartSendAuthResetSignal(lazyResult, message, exception); + return; + } + + // Signal remote party that we are done + _framer.WriteHeader.MessageId = FrameHeader.HandshakeDoneId; + if (_context.IsServer) + { + // Server may complete now because client SSPI would not complain at this point. + _remoteOk = true; + + // However the client will wait for server to send this ACK + //Force signaling server OK to the client + if (message == null) + { + message = s_emptyMessage; + } + } + } + else if (message == null || message == s_emptyMessage) + { + throw new InternalException(); + } + + if (message != null) + { + //even if we are completed, there could be a blob for sending. + if (lazyResult == null) + { + _framer.WriteMessage(message); + } + else + { + IAsyncResult ar = _framer.BeginWriteMessage(message, s_writeCallback, lazyResult); + if (!ar.CompletedSynchronously) + { + return; + } + _framer.EndWriteMessage(ar); + } + } + CheckCompletionBeforeNextReceive(lazyResult); + } + + // + // This will check and logically complete the auth handshake. + // + private void CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult) + { + if (HandshakeComplete && _remoteOk) + { + // We are done with success. + if (lazyResult != null) + { + lazyResult.InvokeCallback(); + } + + return; + } + + StartReceiveBlob(lazyResult); + } + + // + // Server side starts here, but client also loops through this method. + // + private void StartReceiveBlob(LazyAsyncResult lazyResult) + { + byte[] message; + if (lazyResult == null) + { + message = _framer.ReadMessage(); + } + else + { + IAsyncResult ar = _framer.BeginReadMessage(s_readCallback, lazyResult); + if (!ar.CompletedSynchronously) + { + return; + } + + message = _framer.EndReadMessage(ar); + } + + ProcessReceivedBlob(message, lazyResult); + } + + private void ProcessReceivedBlob(byte[] message, LazyAsyncResult lazyResult) + { + // This is an EOF otherwise we would get at least *empty* message but not a null one. + if (message == null) + { + throw new AuthenticationException(SR.net_auth_eof, null); + } + + // Process Header information. + if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeErrId) + { + if (message.Length >= 8) // sizeof(long) + { + // Try to recover remote win32 Exception. + long error = 0; + for (int i = 0; i < 8; ++i) + { + error = (error << 8) + message[i]; + } + + ThrowCredentialException(error); + } + + throw new AuthenticationException(SR.net_auth_alert, null); + } + + if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeDoneId) + { + _remoteOk = true; + } + else if (_framer.ReadHeader.MessageId != FrameHeader.HandshakeId) + { + throw new AuthenticationException(SR.Format(SR.net_io_header_id, "MessageId", _framer.ReadHeader.MessageId, FrameHeader.HandshakeId), null); + } + + CheckCompletionBeforeNextSend(message, lazyResult); + } + + // + // This will check and logically complete the auth handshake. + // + private void CheckCompletionBeforeNextSend(byte[] message, LazyAsyncResult lazyResult) + { + //If we are done don't go into send. + if (HandshakeComplete) + { + if (!_remoteOk) + { + throw new AuthenticationException(SR.Format(SR.net_io_header_id, "MessageId", _framer.ReadHeader.MessageId, FrameHeader.HandshakeDoneId), null); + } + if (lazyResult != null) + { + lazyResult.InvokeCallback(); + } + + return; + } + + // Not yet done, get a new blob and send it if any. + StartSendBlob(message, lazyResult); + } + + // + // This is to reset auth state on the remote side. + // If this write succeeds we will allow auth retrying. + // + private void StartSendAuthResetSignal(LazyAsyncResult lazyResult, byte[] message, Exception exception) + { + _framer.WriteHeader.MessageId = FrameHeader.HandshakeErrId; + + if (IsLogonDeniedException(exception)) + { + if (IsServer) + { + exception = new InvalidCredentialException(SR.net_auth_bad_client_creds, exception); + } + else + { + exception = new InvalidCredentialException(SR.net_auth_bad_client_creds_or_target_mismatch, exception); + } + } + + if (!(exception is AuthenticationException)) + { + exception = new AuthenticationException(SR.net_auth_SSPI, exception); + } + + if (lazyResult == null) + { + _framer.WriteMessage(message); + } + else + { + lazyResult.Result = exception; + IAsyncResult ar = _framer.BeginWriteMessage(message, s_writeCallback, lazyResult); + if (!ar.CompletedSynchronously) + { + return; + } + + _framer.EndWriteMessage(ar); + } + + _canRetryAuthentication = true; + throw exception; + } + + private static void WriteCallback(IAsyncResult transportResult) + { + if ((transportResult.AsyncState is LazyAsyncResult)) + { + if (GlobalLog.IsEnabled) + { + GlobalLog.Assert("WriteCallback|State type is wrong, expected LazyAsyncResult."); + } + Debug.Fail("WriteCallback|State type is wrong, expected LazyAsyncResult."); + } + + if (transportResult.CompletedSynchronously) + { + return; + } + + LazyAsyncResult lazyResult = (LazyAsyncResult)transportResult.AsyncState; + + // Async completion. + try + { + NegoState authState = (NegoState)lazyResult.AsyncObject; + authState._framer.EndWriteMessage(transportResult); + + // Special case for an error notification. + if (lazyResult.Result is Exception) + { + authState._canRetryAuthentication = true; + throw (Exception)lazyResult.Result; + } + + authState.CheckCompletionBeforeNextReceive(lazyResult); + } + catch (Exception e) + { + if (lazyResult.InternalPeekCompleted) + { + // This will throw on a worker thread. + throw; + } + + lazyResult.InvokeCallback(e); + } + } + + private static void ReadCallback(IAsyncResult transportResult) + { + if ((transportResult.AsyncState is LazyAsyncResult)) + { + if (GlobalLog.IsEnabled) + { + GlobalLog.Assert("ReadCallback|State type is wrong, expected LazyAsyncResult."); + } + Debug.Fail("ReadCallback|State type is wrong, expected LazyAsyncResult."); + } + + if (transportResult.CompletedSynchronously) + { + return; + } + + LazyAsyncResult lazyResult = (LazyAsyncResult)transportResult.AsyncState; + + // Async completion. + try + { + NegoState authState = (NegoState)lazyResult.AsyncObject; + byte[] message = authState._framer.EndReadMessage(transportResult); + authState.ProcessReceivedBlob(message, lazyResult); + } + catch (Exception e) + { + if (lazyResult.InternalPeekCompleted) + { + // This will throw on a worker thread. + throw; + } + + lazyResult.InvokeCallback(e); + } + } + + internal static bool IsError(SecurityStatusPal status) + { + return ((int)status >= (int)SecurityStatusPal.OutOfMemory); + } + + private unsafe byte[] GetOutgoingBlob(byte[] incomingBlob, ref Exception e) + { + SecurityStatusPal statusCode; + byte[] message = _context.GetOutgoingBlob(incomingBlob, false, out statusCode); + + if (IsError(statusCode)) + { + e = CreateExceptionFromError(statusCode); + uint error = (uint)e.HResult; + + message = new byte[8]; //sizeof(long) + for (int i = message.Length - 1; i >= 0; --i) + { + message[i] = (byte)(error & 0xFF); + error = (error >> 8); + } + } + + if (message != null && message.Length == 0) + { + message = s_emptyMessage; + } + + return message; + } + + internal int EncryptData(byte[] buffer, int offset, int count, ref byte[] outBuffer) + { + CheckThrow(true); + + // SSPI seems to ignore this sequence number. + ++_writeSequenceNumber; + return _context.Encrypt(buffer, offset, count, ref outBuffer, _writeSequenceNumber); + } + + internal int DecryptData(byte[] buffer, int offset, int count, out int newOffset) + { + CheckThrow(true); + + // SSPI seems to ignore this sequence number. + ++_readSequenceNumber; + return _context.Decrypt(buffer, offset, count, out newOffset, _readSequenceNumber); + } + } +} diff --git a/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Unix.cs b/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Unix.cs index a98dc8fae706..d00d81a66711 100644 --- a/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Unix.cs +++ b/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Unix.cs @@ -6,8 +6,11 @@ using System.Security.Principal; using System.Threading; using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; +using Microsoft.Win32.SafeHandles; namespace System.Net.Security { @@ -18,140 +21,346 @@ namespace System.Net.Security // // This is part of the NegotiateStream PAL. // - internal class NegoState + internal partial class NegoState { - internal const int MaxReadFrameSize = 64 * 1024; - internal const int MaxWriteDataSize = 63 * 1024; // 1k for the framing and trailer that is always less as per SSPI. + private const int LogonDenied = unchecked((int)0x8009030C); + + private class NNSProtocolException : Exception + { + internal static readonly Exception Instance; + + // MS-NNS Protocol requires a Windows error code to be + // passed back. Hence, we always use NTE_FAIL + private const int NTE_FAIL = unchecked((int)0x80090020); + + static NNSProtocolException() + { + Instance = new NNSProtocolException(); + } + + private NNSProtocolException() : base() + { + HResult = NTE_FAIL; + } + } + + + internal IIdentity GetIdentity() + { + Debug.Assert(!_context.IsServer, "GetIdentity: Server is not supported"); + + string name = _context.Spn; + string protocol = _context.ProtocolName; + + return new GenericIdentity(name, protocol); - internal NegoState(Stream innerStream, bool leaveStreamOpen) + } + + internal static string QueryContextAssociatedName(SafeDeleteContext securityContext) { throw new PlatformNotSupportedException(); } - internal static string DefaultPackage + internal static string QueryContextAuthenticationPackage(SafeDeleteContext securityContext) { - get - { - throw new PlatformNotSupportedException(); - } + return NegotiationInfoClass.Kerberos; } - internal void ValidateCreateContext(string package, - NetworkCredential credential, - string servicePrincipalName, - ExtendedProtectionPolicy policy, - ProtectionLevel protectionLevel, - TokenImpersonationLevel impersonationLevel) + internal static object QueryContextSizes(SafeDeleteContext securityContext) { - throw new PlatformNotSupportedException(); + // This return value is never used + return null; + } + + internal static int QueryMaxTokenSize(string package) + { + // The value is unused in non-Windows code paths + return 0; } - internal void ValidateCreateContext( - string package, - bool isServer, - NetworkCredential credential, - string servicePrincipalName, - ChannelBinding channelBinding, - ProtectionLevel protectionLevel, - TokenImpersonationLevel impersonationLevel - ) + internal static string QueryContextClientSpecifiedSpn(SafeDeleteContext securityContext) { throw new PlatformNotSupportedException(); } - internal bool IsAuthenticated + internal static SafeFreeCredentials AcquireDefaultCredential(string package, bool isServer) { - get - { - throw new PlatformNotSupportedException(); - } + return AcquireCredentialsHandle(package, isServer, new NetworkCredential(string.Empty, string.Empty, string.Empty)); } - internal bool IsMutuallyAuthenticated + internal static SafeFreeCredentials AcquireCredentialsHandle(string package, bool isServer, NetworkCredential credential) { - get + if (isServer) { - throw new PlatformNotSupportedException(); + throw new NotImplementedException("AcquireCredentialsHandle: Server is not yet supported"); } - } - internal bool IsEncrypted - { - get + SafeFreeCredentials outCredential; + bool isNtlm = string.Equals(package, NegotiationInfoClass.NTLM); + + if (isNtlm) { - throw new PlatformNotSupportedException(); + throw new NotImplementedException("AcquireCredentialsHandle: NTLM is not yet supported"); } - } - internal bool IsSigned - { - get + if (string.IsNullOrEmpty(credential.UserName) || string.IsNullOrEmpty(credential.Password)) { - throw new PlatformNotSupportedException(); + // In client case, equivalent of default credentials is to use previous, + // cached Kerberos TGT to get service-specific ticket. + outCredential = new SafeFreeNegoCredentials(string.Empty, string.Empty, string.Empty); } + else + { + outCredential = new SafeFreeNegoCredentials(credential.UserName, credential.Password, credential.Domain); + } + return outCredential; + } - internal bool IsServer + internal static SecurityStatusPal InitializeSecurityContext( + SafeFreeCredentials credentialsHandle, + ref SafeDeleteContext securityContext, + string spn, + ContextFlagsPal requestedContextFlags, + SecurityBuffer[] inSecurityBufferArray, + SecurityBuffer outSecurityBuffer, + ref ContextFlagsPal contextFlags) { - get + // TODO (Issue #3718): The second buffer can contain a channel binding which is not supported + if ((null != inSecurityBufferArray) && (inSecurityBufferArray.Length > 1)) { - throw new PlatformNotSupportedException(); + throw new PlatformNotSupportedException("No support for channel binding on non-Windows"); } + + return EstablishSecurityContext( + (SafeFreeNegoCredentials)credentialsHandle, + ref securityContext, + false, + spn, + requestedContextFlags, + ((inSecurityBufferArray != null) ? inSecurityBufferArray[0] : null), + outSecurityBuffer, + ref contextFlags); + } + + internal static SecurityStatusPal CompleteAuthToken( + ref SafeDeleteContext securityContext, + SecurityBuffer[] inSecurityBufferArray) + { + return SecurityStatusPal.OK; + } + + internal static SecurityStatusPal AcceptSecurityContext( + SafeFreeCredentials credentialsHandle, + ref SafeDeleteContext securityContext, + ContextFlagsPal requestedContextFlags, + SecurityBuffer[] inSecurityBufferArray, + SecurityBuffer outSecurityBuffer, + ref ContextFlagsPal contextFlags) + { + throw new PlatformNotSupportedException(); } - internal bool CanGetSecureStream + private static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel) { - get + if (impersonationLevel != TokenImpersonationLevel.Identification) { - throw new PlatformNotSupportedException(); + throw new ArgumentOutOfRangeException("impersonationLevel", impersonationLevel.ToString(), + SR.net_auth_supported_impl_levels); } + } - internal TokenImpersonationLevel AllowedImpersonation + private static void ThrowCredentialException(long error) { - get + string message = SR.net_auth_alert; + if ((int)error == LogonDenied) { - throw new PlatformNotSupportedException(); + message = SR.net_auth_bad_client_creds; } + + if ((int)error == NegoState.ERROR_TRUST_FAILURE) + { + message = SR.net_auth_context_expectation_remote; + } + + throw new AuthenticationException(message, null); } - internal IIdentity GetIdentity() + private static bool IsLogonDeniedException(Exception exception) { - throw new PlatformNotSupportedException(); + return exception.HResult == LogonDenied; } - internal void CheckThrow(bool authSucessCheck) + internal static Exception CreateExceptionFromError(SecurityStatusPal statusCode) { - throw new PlatformNotSupportedException(); + return NNSProtocolException.Instance; } - // - // This is to not depend on GC&SafeHandle class if the context is not needed anymore. - // - internal void Close() + internal static int Encrypt( + SafeDeleteContext securityContext, + byte[] buffer, + int offset, + int count, + bool isConfidential, + bool isNtlm, + ref byte[] output, + uint sequenceNumber) { - throw new PlatformNotSupportedException(); + Debug.Assert(!isNtlm, "Encrypt: NTLM is not yet supported"); + SafeDeleteNegoContext gssContext = securityContext as SafeDeleteNegoContext; + byte[] tempOutput; + Interop.NetSecurity.Encrypt(gssContext.GssContext, isConfidential, buffer, offset, count, out tempOutput); + + // Create space for prefixing with the length + const int prefixLength = 4; + output = new byte[tempOutput.Length + prefixLength]; + Array.Copy(tempOutput, 0, output, prefixLength, tempOutput.Length); + return tempOutput.Length; } - internal void ProcessAuthentication(LazyAsyncResult lazyResult) + internal static int Decrypt( + SafeDeleteContext securityContext, + byte[] buffer, + int offset, + int count, + bool isConfidential, + bool isNtlm, + out int newOffset, + uint sequenceNumber) { - throw new PlatformNotSupportedException(); + newOffset = offset; + return Interop.NetSecurity.Decrypt(((SafeDeleteNegoContext)securityContext).GssContext, buffer, offset, count); } - internal void EndProcessAuthentication(IAsyncResult result) + internal static int DecryptNtlm( + SafeDeleteContext securityContext, + byte[] buffer, + int offset, + int count, + bool isConfidential, + out int newOffset, + uint sequenceNumber) { - throw new PlatformNotSupportedException(); + throw new NotImplementedException("DecryptNtlm: NTLM is not yet supported"); } + private static SecurityStatusPal EstablishSecurityContext( + SafeFreeNegoCredentials credential, + ref SafeDeleteContext context, + bool isNtlm, + string targetName, + ContextFlagsPal inFlags, + SecurityBuffer inputBuffer, + SecurityBuffer outputBuffer, + ref ContextFlagsPal outFlags) + { + Debug.Assert(!isNtlm, "EstablishSecurityContext: NTLM is not yet supported"); + + if (context == null) + { + context = new SafeDeleteNegoContext(credential, targetName); + } + + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)context; + try + { + Interop.NetSecurity.GssFlags inputFlags = GetInteropGssFromContextFlagsPal(inFlags); + uint outputFlags; + SafeGssContextHandle contextHandle = negoContext.GssContext; + + bool done = Interop.NetSecurity.EstablishSecurityContext( + ref contextHandle, + credential.GssCredential, + isNtlm, + negoContext.TargetName, + inputFlags, + ((inputBuffer != null) ? inputBuffer.token : null), + out outputBuffer.token, + out outputFlags); - internal int EncryptData(byte[] buffer, int offset, int count, ref byte[] outBuffer) + Debug.Assert(outputBuffer.token != null, "Unexpected null buffer returned by GssApi"); + outputBuffer.size = outputBuffer.token.Length; + outputBuffer.offset = 0; + + outFlags = GetContextFlagsPalFromInteropGss((Interop.NetSecurity.GssFlags)outputFlags); + + // Save the inner context handle for further calls to NetSecurity + if (null == negoContext.GssContext) + { + negoContext.SetGssContext(contextHandle); + } + return done ? SecurityStatusPal.CompleteNeeded : SecurityStatusPal.ContinueNeeded; + } + catch + { + return SecurityStatusPal.InternalError; + } + } + + private static ContextFlagsPal GetContextFlagsPalFromInteropGss(Interop.NetSecurity.GssFlags gssFlags) { - throw new PlatformNotSupportedException(); + ContextFlagsPal flags = ContextFlagsPal.Zero; + if ((gssFlags & Interop.NetSecurity.GssFlags.GSS_C_INTEG_FLAG) != 0) + { + flags |= (ContextFlagsPal.AcceptIntegrity | ContextFlagsPal.InitIntegrity); + } + if ((gssFlags & Interop.NetSecurity.GssFlags.GSS_C_CONF_FLAG) != 0) + { + flags |= ContextFlagsPal.Confidentiality; + } + if ((gssFlags & Interop.NetSecurity.GssFlags.GSS_C_IDENTIFY_FLAG) != 0) + { + flags |= ContextFlagsPal.InitIdentify; + } + if ((gssFlags & Interop.NetSecurity.GssFlags.GSS_C_MUTUAL_FLAG) != 0) + { + flags |= ContextFlagsPal.MutualAuth; + } + if ((gssFlags & Interop.NetSecurity.GssFlags.GSS_C_REPLAY_FLAG) != 0) + { + flags |= ContextFlagsPal.ReplayDetect; + } + if ((gssFlags & Interop.NetSecurity.GssFlags.GSS_C_SEQUENCE_FLAG) != 0) + { + flags |= ContextFlagsPal.SequenceDetect; + } + return flags; } - internal int DecryptData(byte[] buffer, int offset, int count, out int newOffset) + private static Interop.NetSecurity.GssFlags GetInteropGssFromContextFlagsPal(ContextFlagsPal flags) { - throw new PlatformNotSupportedException(); + Interop.NetSecurity.GssFlags gssFlags = (Interop.NetSecurity.GssFlags)0; + if ((flags & ContextFlagsPal.AcceptIntegrity) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_INTEG_FLAG; + } + if ((flags & ContextFlagsPal.Confidentiality) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_CONF_FLAG; + } + if ((flags & ContextFlagsPal.InitIdentify) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_IDENTIFY_FLAG; + } + if ((flags & ContextFlagsPal.InitIntegrity) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_INTEG_FLAG; + } + if ((flags & ContextFlagsPal.MutualAuth) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_MUTUAL_FLAG; + } + if ((flags & ContextFlagsPal.ReplayDetect) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_REPLAY_FLAG; + } + if ((flags & ContextFlagsPal.SequenceDetect) != 0) + { + gssFlags |= Interop.NetSecurity.GssFlags.GSS_C_SEQUENCE_FLAG; + } + return gssFlags; } + } } diff --git a/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Windows.cs b/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Windows.cs index e07415ee00f9..da0b47514f45 100644 --- a/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Windows.cs +++ b/src/System.Net.Security/src/System/Net/SecureProtocols/NegoState.Windows.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics; +using System.Globalization; using System.IO; using System.Security; using System.Security.Principal; @@ -13,869 +13,607 @@ namespace System.Net.Security { // - // The class maintains the state of the authentication process and the security context. - // It encapsulates security context and does the real work in authentication and + // The class does the real work in authentication and // user data encryption with NEGO SSPI package. // // This is part of the NegotiateStream PAL. // - internal class NegoState + internal partial class NegoState { - private const int ERROR_TRUST_FAILURE = 1790; // Used to serialize protectionLevel or impersonationLevel mismatch error to the remote side. - - private static readonly byte[] s_emptyMessage = new byte[0]; - private static readonly AsyncCallback s_readCallback = new AsyncCallback(ReadCallback); - private static readonly AsyncCallback s_writeCallback = new AsyncCallback(WriteCallback); - - private Stream _innerStream; - private bool _leaveStreamOpen; + internal IIdentity GetIdentity() + { + CheckThrow(true); - private Exception _exception; + IIdentity result = null; + string name = _context.IsServer ? _context.AssociatedName : _context.Spn; + string protocol = "NTLM"; - private StreamFramer _framer; - private NTAuthentication _context; + protocol = _context.ProtocolName; - private int _nestedAuth; + if (_context.IsServer) + { + SecurityContextTokenHandle token = null; + try + { + SecurityStatusPal status; + SafeDeleteContext securityContext = _context.GetContext(out status); + if (status != SecurityStatusPal.OK) + { + throw new Win32Exception((int)SslStreamPal.GetInteropFromSecurityStatusPal(status)); + } - internal const int MaxReadFrameSize = 64 * 1024; - internal const int MaxWriteDataSize = 63 * 1024; // 1k for the framing and trailer that is always less as per SSPI. + // This will return a client token when conducted authentication on server side. + // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app. + Interop.SecurityStatus winStatus = (Interop.SecurityStatus)SSPIWrapper.QuerySecurityContextToken( + GlobalSSPI.SSPIAuth, + securityContext, + out token); + if (winStatus != Interop.SecurityStatus.OK) + { + throw new Win32Exception((int)winStatus); + } + string authtype = _context.ProtocolName; - private bool _canRetryAuthentication; - private ProtectionLevel _expectedProtectionLevel; - private TokenImpersonationLevel _expectedImpersonationLevel; - private uint _writeSequenceNumber; - private uint _readSequenceNumber; + // TODO #5241: + // The following call was also specifying WindowsAccountType.Normal, true. + // WindowsIdentity.IsAuthenticated is no longer supported in CoreFX. + result = new WindowsIdentity(token.DangerousGetHandle(), authtype); + return result; + } + catch (SecurityException) + { + // Ignore and construct generic Identity if failed due to security problem. + } + finally + { + if (token != null) + { + token.Dispose(); + } + } + } - private ExtendedProtectionPolicy _extendedProtectionPolicy; + // On the client we don't have access to the remote side identity. + result = new GenericIdentity(name, protocol); + return result; + } - // SSPI does not send a server ack on successful auth. - // This is a state variable used to gracefully handle auth confirmation. - private bool _remoteOk = false; + internal static string QueryContextAssociatedName(SafeDeleteContext securityContext) + { + return SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.Names) as string; + } - internal NegoState(Stream innerStream, bool leaveStreamOpen) + internal static string QueryContextAuthenticationPackage(SafeDeleteContext securityContext) { - if (innerStream == null) + var negotiationInfoClass = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.NegotiationInfo) as NegotiationInfoClass; + if (negotiationInfoClass != null) { - throw new ArgumentNullException("stream"); + return negotiationInfoClass.AuthenticationPackage; } + return null; + } - _innerStream = innerStream; - _leaveStreamOpen = leaveStreamOpen; + internal static int QueryMaxTokenSize(string package) + { + return SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, package, true).MaxToken; } - internal static string DefaultPackage + internal static string QueryContextClientSpecifiedSpn(SafeDeleteContext securityContext) { - get - { - return NegotiationInfoClass.Negotiate; - } + return SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.ClientSpecifiedSpn) as string; } - internal void ValidateCreateContext( - string package, - NetworkCredential credential, - string servicePrincipalName, - ExtendedProtectionPolicy policy, - ProtectionLevel protectionLevel, - TokenImpersonationLevel impersonationLevel) + internal static SafeFreeCredentials AcquireDefaultCredential(string package, bool isServer) { - if (policy != null) + return SSPIWrapper.AcquireDefaultCredential( + GlobalSSPI.SSPIAuth, + package, + (isServer ? Interop.SspiCli.CredentialUse.Inbound : Interop.SspiCli.CredentialUse.Outbound)); + } + + internal static SafeFreeCredentials AcquireCredentialsHandle(string package, bool isServer, NetworkCredential credential) + { + unsafe { - // One of these must be set if EP is turned on - if (policy.CustomChannelBinding == null && policy.CustomServiceNames == null) + SafeSspiAuthDataHandle authData = null; + try { - throw new ArgumentException(SR.net_auth_must_specify_extended_protection_scheme, "policy"); - } + Interop.SecurityStatus result = Interop.SspiCli.SspiEncodeStringsAsAuthIdentity( + credential.UserName, credential.Domain, + credential.Password, out authData); - _extendedProtectionPolicy = policy; - } - else - { - _extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never); - } + if (result != Interop.SecurityStatus.OK) + { + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.PrintError( + NetEventSource.ComponentType.Security, + SR.Format( + SR.net_log_operation_failed_with_error, + "SspiEncodeStringsAsAuthIdentity()", + String.Format(CultureInfo.CurrentCulture, "0x{0:X}", (int)result))); + } + + throw new Win32Exception((int)result); + } - ValidateCreateContext(package, true, credential, servicePrincipalName, _extendedProtectionPolicy.CustomChannelBinding, protectionLevel, impersonationLevel); + return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPIAuth, + package, (isServer ? Interop.SspiCli.CredentialUse.Inbound : Interop.SspiCli.CredentialUse.Outbound), ref authData); + } + finally + { + if (authData != null) + { + authData.Dispose(); + } + } + } } - internal void ValidateCreateContext( - string package, - bool isServer, - NetworkCredential credential, - string servicePrincipalName, - ChannelBinding channelBinding, - ProtectionLevel protectionLevel, - TokenImpersonationLevel impersonationLevel) + internal static SecurityStatusPal InitializeSecurityContext( + SafeFreeCredentials credentialsHandle, + ref SafeDeleteContext securityContext, + string spn, + ContextFlagsPal requestedContextFlags, + SecurityBuffer[] inSecurityBufferArray, + SecurityBuffer outSecurityBuffer, + ref ContextFlagsPal contextFlags) { - if (_exception != null && !_canRetryAuthentication) - { - throw _exception; - } - - if (_context != null && _context.IsValidContext) - { - throw new InvalidOperationException(SR.net_auth_reauth); - } + Interop.SspiCli.ContextFlags outContextFlags = Interop.SspiCli.ContextFlags.Zero; + Interop.SecurityStatus winStatus = (Interop.SecurityStatus)SSPIWrapper.InitializeSecurityContext( + GlobalSSPI.SSPIAuth, + credentialsHandle, + ref securityContext, + spn, + GetInteropFromContextFlagsPal(requestedContextFlags), + Interop.SspiCli.Endianness.Network, + inSecurityBufferArray, + outSecurityBuffer, + ref outContextFlags); + + contextFlags = GetContextFlagsPalFromInterop(outContextFlags); + return SslStreamPal.GetSecurityStatusPalFromInterop(winStatus); + } - if (credential == null) - { - throw new ArgumentNullException("credential"); - } + internal static SecurityStatusPal CompleteAuthToken( + ref SafeDeleteContext securityContext, + SecurityBuffer[] inSecurityBufferArray) + { + Interop.SecurityStatus winStatus = (Interop.SecurityStatus)SSPIWrapper.CompleteAuthToken( + GlobalSSPI.SSPIAuth, + ref securityContext, + inSecurityBufferArray); + return SslStreamPal.GetSecurityStatusPalFromInterop(winStatus); + } - if (servicePrincipalName == null) - { - throw new ArgumentNullException("servicePrincipalName"); - } + internal static SecurityStatusPal AcceptSecurityContext( + SafeFreeCredentials credentialsHandle, + ref SafeDeleteContext securityContext, + ContextFlagsPal requestedContextFlags, + SecurityBuffer[] inSecurityBufferArray, + SecurityBuffer outSecurityBuffer, + ref ContextFlagsPal contextFlags) + { + Interop.SspiCli.ContextFlags outContextFlags = Interop.SspiCli.ContextFlags.Zero; + Interop.SecurityStatus winStatus = (Interop.SecurityStatus)SSPIWrapper.AcceptSecurityContext( + GlobalSSPI.SSPIAuth, + credentialsHandle, + ref securityContext, + GetInteropFromContextFlagsPal(requestedContextFlags), + Interop.SspiCli.Endianness.Network, + inSecurityBufferArray, + outSecurityBuffer, + ref outContextFlags); + + contextFlags = GetContextFlagsPalFromInterop(outContextFlags); + return SslStreamPal.GetSecurityStatusPalFromInterop(winStatus); + } + private static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel) + { if (impersonationLevel != TokenImpersonationLevel.Identification && impersonationLevel != TokenImpersonationLevel.Impersonation && impersonationLevel != TokenImpersonationLevel.Delegation) { throw new ArgumentOutOfRangeException("impersonationLevel", impersonationLevel.ToString(), SR.net_auth_supported_impl_levels); } + } - if (_context != null && IsServer != isServer) + private static void ThrowCredentialException(long error) + { + Win32Exception e = new Win32Exception((int)error); + + if (e.NativeErrorCode == (int)Interop.SecurityStatus.LogonDenied) { - throw new InvalidOperationException(SR.net_auth_client_server); + throw new InvalidCredentialException(SR.net_auth_bad_client_creds, e); } - _exception = null; - _remoteOk = false; - _framer = new StreamFramer(_innerStream); - _framer.WriteHeader.MessageId = FrameHeader.HandshakeId; + if (e.NativeErrorCode == NegoState.ERROR_TRUST_FAILURE) + { + throw new AuthenticationException(SR.net_auth_context_expectation_remote, e); + } - _expectedProtectionLevel = protectionLevel; - _expectedImpersonationLevel = isServer ? impersonationLevel : TokenImpersonationLevel.None; - _writeSequenceNumber = 0; - _readSequenceNumber = 0; + throw new AuthenticationException(SR.net_auth_alert, e); + } - Interop.SspiCli.ContextFlags flags = Interop.SspiCli.ContextFlags.Connection; + private static bool IsLogonDeniedException(Exception exception) + { + Win32Exception win32exception = exception as Win32Exception; - // A workaround for the client when talking to Win9x on the server side. - if (protectionLevel == ProtectionLevel.None && !isServer) - { - package = NegotiationInfoClass.NTLM; - } + return (win32exception != null) && (win32exception.NativeErrorCode == (int)Interop.SecurityStatus.LogonDenied); + } - else if (protectionLevel == ProtectionLevel.EncryptAndSign) - { - flags |= Interop.SspiCli.ContextFlags.Confidentiality; - } - else if (protectionLevel == ProtectionLevel.Sign) - { - // Assuming user expects NT4 SP4 and above. - flags |= Interop.SspiCli.ContextFlags.ReplayDetect | Interop.SspiCli.ContextFlags.SequenceDetect | Interop.SspiCli.ContextFlags.InitIntegrity; - } + internal static Exception CreateExceptionFromError(SecurityStatusPal statusCode) + { + return new Win32Exception((int)SslStreamPal.GetInteropFromSecurityStatusPal(statusCode)); + } + + internal static int Encrypt( + SafeDeleteContext securityContext, + byte[] buffer, + int offset, + int count, + bool isConfidential, + bool isNtlm, + ref byte[] output, + uint sequenceNumber) + { + SecSizes sizes = SSPIWrapper.QueryContextAttributes( + GlobalSSPI.SSPIAuth, + securityContext, + Interop.SspiCli.ContextAttribute.Sizes + ) as SecSizes; - if (isServer) + try { - if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) - { - flags |= Interop.SspiCli.ContextFlags.AllowMissingBindings; - } + int maxCount = checked(Int32.MaxValue - 4 - sizes.BlockSize - sizes.SecurityTrailer); - if (_extendedProtectionPolicy.PolicyEnforcement != PolicyEnforcement.Never && - _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TrustedProxy) + if (count > maxCount || count < 0) { - flags |= Interop.SspiCli.ContextFlags.ProxyBindings; + throw new ArgumentOutOfRangeException("count", SR.Format(SR.net_io_out_range, maxCount)); } } - else + catch (Exception e) { - // Server side should not request any of these flags. - if (protectionLevel != ProtectionLevel.None) - { - flags |= Interop.SspiCli.ContextFlags.MutualAuth; - } - - if (impersonationLevel == TokenImpersonationLevel.Identification) + if (!ExceptionCheck.IsFatal(e) && GlobalLog.IsEnabled) { - flags |= Interop.SspiCli.ContextFlags.InitIdentify; + GlobalLog.Assert("NTAuthentication#" + LoggingHash.HashString(securityContext) + "::Encrypt", "Arguments out of range."); } - if (impersonationLevel == TokenImpersonationLevel.Delegation) - { - flags |= Interop.SspiCli.ContextFlags.Delegate; - } + throw; } - _canRetryAuthentication = false; - - try - { - _context = new NTAuthentication(isServer, package, credential, servicePrincipalName, flags, channelBinding); - } - catch (Win32Exception e) + int resultSize = count + sizes.SecurityTrailer + sizes.BlockSize; + if (output == null || output.Length < resultSize + 4) { - throw new AuthenticationException(SR.net_auth_SSPI, e); + output = new byte[resultSize + 4]; } - } - private Exception SetException(Exception e) - { - if (_exception == null || !(_exception is ObjectDisposedException)) - { - _exception = e; - } + // Make a copy of user data for in-place encryption. + Buffer.BlockCopy(buffer, offset, output, 4 + sizes.SecurityTrailer, count); - if (_exception != null && _context != null) - { - _context.CloseContext(); - } - - return _exception; - } + // Prepare buffers TOKEN(signature), DATA and Padding. + var securityBuffer = new SecurityBuffer[3]; + securityBuffer[0] = new SecurityBuffer(output, 4, sizes.SecurityTrailer, SecurityBufferType.Token); + securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.SecurityTrailer, count, SecurityBufferType.Data); + securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.SecurityTrailer + count, sizes.BlockSize, SecurityBufferType.Padding); - internal bool IsAuthenticated - { - get + int errorCode; + if (isConfidential) { - return _context != null && HandshakeComplete && _exception == null && _remoteOk; + errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); } - } - - internal bool IsMutuallyAuthenticated - { - get + else { - if (!IsAuthenticated) + if (isNtlm) { - return false; + securityBuffer[1].type |= SecurityBufferType.ReadOnlyFlag; } - // Suppressing for NTLM since SSPI does not return correct value in the context flags. - if (_context.IsNTLM) + errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, 0); + } + + if (errorCode != 0) + { + if (GlobalLog.IsEnabled) { - return false; + GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(securityContext) + "::Encrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); } - - return _context.IsMutualAuthFlag; + throw new Win32Exception(errorCode); } - } - internal bool IsEncrypted - { - get + // Compacting the result. + resultSize = securityBuffer[0].size; + bool forceCopy = false; + if (resultSize != sizes.SecurityTrailer) { - return IsAuthenticated && _context.IsConfidentialityFlag; + forceCopy = true; + Buffer.BlockCopy(output, securityBuffer[1].offset, output, 4 + resultSize, securityBuffer[1].size); } - } - internal bool IsSigned - { - get + resultSize += securityBuffer[1].size; + if (securityBuffer[2].size != 0 && (forceCopy || resultSize != (count + sizes.SecurityTrailer))) { - return IsAuthenticated && (_context.IsIntegrityFlag || _context.IsConfidentialityFlag); + Buffer.BlockCopy(output, securityBuffer[2].offset, output, 4 + resultSize, securityBuffer[2].size); } + + resultSize += securityBuffer[2].size; + return resultSize; } - internal bool IsServer + internal static int Decrypt( + SafeDeleteContext securityContext, + byte[] buffer, + int offset, + int count, + bool isConfidential, + bool isNtlm, + out int newOffset, + uint sequenceNumber) { - get + if (isNtlm) { - return _context != null && _context.IsServer; + return DecryptNtlm(securityContext, buffer, offset, count, isConfidential, out newOffset, sequenceNumber); } - } - internal bool CanGetSecureStream - { - get + // + // Kerberos and up + // + var securityBuffer = new SecurityBuffer[2]; + securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.Stream); + securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.Data); + + int errorCode; + if (isConfidential) { - return (_context.IsConfidentialityFlag || _context.IsIntegrityFlag); + errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); } - } - - internal TokenImpersonationLevel AllowedImpersonation - { - get + else { - CheckThrow(true); - return PrivateImpersonationLevel; + errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); } - } - private TokenImpersonationLevel PrivateImpersonationLevel - { - get + if (errorCode != 0) { - // We should suppress the delegate flag in NTLM case. - return (_context.IsDelegationFlag && _context.ProtocolName != NegotiationInfoClass.NTLM) ? TokenImpersonationLevel.Delegation - : _context.IsIdentifyFlag ? TokenImpersonationLevel.Identification - : TokenImpersonationLevel.Impersonation; + if (GlobalLog.IsEnabled) + { + GlobalLog.Print("NTAuthentication#"+ "::Decrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); + } + throw new Win32Exception(errorCode); } - } - private bool HandshakeComplete - { - get + if (securityBuffer[1].type != SecurityBufferType.Data) { - return _context.IsCompleted && _context.IsValidContext; + throw new InternalException(); } + + newOffset = securityBuffer[1].offset; + return securityBuffer[1].size; } - internal IIdentity GetIdentity() + internal static int DecryptNtlm( + SafeDeleteContext securityContext, + byte[] buffer, + int offset, + int count, + bool isConfidential, + out int newOffset, + uint sequenceNumber) { - CheckThrow(true); - - IIdentity result = null; - string name = _context.IsServer ? _context.AssociatedName : _context.Spn; - string protocol = "NTLM"; + var securityBuffer = new SecurityBuffer[2]; + securityBuffer[0] = new SecurityBuffer(buffer, offset, 16, SecurityBufferType.Token); + securityBuffer[1] = new SecurityBuffer(buffer, offset + 16, count - 16, SecurityBufferType.Data); - protocol = _context.ProtocolName; + int errorCode; + SecurityBufferType realDataType = SecurityBufferType.Data; - if (_context.IsServer) + if (isConfidential) { - SecurityContextTokenHandle token = null; - try - { - token = _context.GetContextToken(); - string authtype = _context.ProtocolName; + errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } + else + { + realDataType |= SecurityBufferType.ReadOnlyFlag; + securityBuffer[1].type = realDataType; + errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } - // TODO #5241: - // The following call was also specifying WindowsAccountType.Normal, true. - // WindowsIdentity.IsAuthenticated is no longer supported in CoreFX. - result = new WindowsIdentity(token.DangerousGetHandle(), authtype); - return result; - } - catch (SecurityException) - { - // Ignore and construct generic Identity if failed due to security problem. - } - finally + if (errorCode != 0) + { + if (GlobalLog.IsEnabled) { - if (token != null) - { - token.Dispose(); - } + GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(securityContext) + "::Decrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); } + throw new Win32Exception(errorCode); } - // On the client we don't have access to the remote side identity. - result = new GenericIdentity(name, protocol); - return result; + if (securityBuffer[1].type != realDataType) + { + throw new InternalException(); + } + + newOffset = securityBuffer[1].offset; + return securityBuffer[1].size; } - internal void CheckThrow(bool authSucessCheck) + private static ContextFlagsPal GetContextFlagsPalFromInterop(Interop.SspiCli.ContextFlags win32Flags) { - if (_exception != null) + ContextFlagsPal flags = ContextFlagsPal.Zero; + if ((win32Flags & Interop.SspiCli.ContextFlags.AcceptExtendedError) != 0) { - throw _exception; + flags |= ContextFlagsPal.AcceptExtendedError; } - - if (authSucessCheck && !IsAuthenticated) + if ((win32Flags & Interop.SspiCli.ContextFlags.AcceptIdentify) != 0) { - throw new InvalidOperationException(SR.net_auth_noauth); + flags |= ContextFlagsPal.AcceptIdentify; } - } - - // - // This is to not depend on GC&SafeHandle class if the context is not needed anymore. - // - internal void Close() - { - // Mark this instance as disposed. - _exception = new ObjectDisposedException("NegotiateStream"); - if (_context != null) + if ((win32Flags & Interop.SspiCli.ContextFlags.AcceptIntegrity) != 0) { - _context.CloseContext(); + flags |= ContextFlagsPal.AcceptIntegrity; } - } - - internal void ProcessAuthentication(LazyAsyncResult lazyResult) - { - CheckThrow(false); - if (Interlocked.Exchange(ref _nestedAuth, 1) == 1) + if ((win32Flags & Interop.SspiCli.ContextFlags.AcceptStream) != 0) { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, lazyResult == null ? "BeginAuthenticate" : "Authenticate", "authenticate")); + flags |= ContextFlagsPal.AcceptStream; } - - try + if ((win32Flags & Interop.SspiCli.ContextFlags.AllocateMemory) != 0) { - if (_context.IsServer) - { - // Listen for a client blob. - StartReceiveBlob(lazyResult); - } - else - { - // Start with the first blob. - StartSendBlob(null, lazyResult); - } + flags |= ContextFlagsPal.AllocateMemory; } - catch (Exception e) + if ((win32Flags & Interop.SspiCli.ContextFlags.AllowMissingBindings) != 0) { - // Round-trip it through SetException(). - e = SetException(e); - throw; + flags |= ContextFlagsPal.AllowMissingBindings; } - finally + if ((win32Flags & Interop.SspiCli.ContextFlags.Confidentiality) != 0) { - if (lazyResult == null || _exception != null) - { - _nestedAuth = 0; - } + flags |= ContextFlagsPal.Confidentiality; } - } - - internal void EndProcessAuthentication(IAsyncResult result) - { - if (result == null) + if ((win32Flags & Interop.SspiCli.ContextFlags.Connection) != 0) { - throw new ArgumentNullException("asyncResult"); + flags |= ContextFlagsPal.Connection; } - - LazyAsyncResult lazyResult = result as LazyAsyncResult; - if (lazyResult == null) + if ((win32Flags & Interop.SspiCli.ContextFlags.Delegate) != 0) { - throw new ArgumentException(SR.Format(SR.net_io_async_result, result.GetType().FullName), "asyncResult"); + flags |= ContextFlagsPal.Delegate; } - - if (Interlocked.Exchange(ref _nestedAuth, 0) == 0) + if ((win32Flags & Interop.SspiCli.ContextFlags.InitExtendedError) != 0) { - throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, "EndAuthenticate")); + flags |= ContextFlagsPal.InitExtendedError; } - - // No "artificial" timeouts implemented so far, InnerStream controls that. - lazyResult.InternalWaitForCompletion(); - - Exception e = lazyResult.Result as Exception; - - if (e != null) + if ((win32Flags & Interop.SspiCli.ContextFlags.InitIdentify) != 0) { - // Round-trip it through the SetException(). - e = SetException(e); - throw e; + flags |= ContextFlagsPal.InitIdentify; } - } - - private bool CheckSpn() - { - if (_context.IsKerberos) + if ((win32Flags & Interop.SspiCli.ContextFlags.InitIntegrity) != 0) { - return true; + flags |= ContextFlagsPal.InitIntegrity; } - - if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.Never || - _extendedProtectionPolicy.CustomServiceNames == null) + if ((win32Flags & Interop.SspiCli.ContextFlags.InitManualCredValidation) != 0) { - return true; + flags |= ContextFlagsPal.InitManualCredValidation; } - - string clientSpn = _context.ClientSpecifiedSpn; - - if (String.IsNullOrEmpty(clientSpn)) + if ((win32Flags & Interop.SspiCli.ContextFlags.InitStream) != 0) { - if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) - { - return true; - } + flags |= ContextFlagsPal.InitStream; } - else + if ((win32Flags & Interop.SspiCli.ContextFlags.InitUseSuppliedCreds) != 0) { - return _extendedProtectionPolicy.CustomServiceNames.Contains(clientSpn); + flags |= ContextFlagsPal.InitUseSuppliedCreds; } - - return false; - } - - // - // Client side starts here, but server also loops through this method. - // - private void StartSendBlob(byte[] message, LazyAsyncResult lazyResult) - { - Win32Exception win32exception = null; - if (message != s_emptyMessage) + if ((win32Flags & Interop.SspiCli.ContextFlags.MutualAuth) != 0) { - message = GetOutgoingBlob(message, ref win32exception); + flags |= ContextFlagsPal.MutualAuth; } - - if (win32exception != null) + if ((win32Flags & Interop.SspiCli.ContextFlags.ProxyBindings) != 0) { - // Signal remote side on a failed attempt. - StartSendAuthResetSignal(lazyResult, message, win32exception); - return; + flags |= ContextFlagsPal.ProxyBindings; } - - if (HandshakeComplete) + if ((win32Flags & Interop.SspiCli.ContextFlags.ReplayDetect) != 0) { - if (_context.IsServer && !CheckSpn()) - { - Exception exception = new AuthenticationException(SR.net_auth_bad_client_creds_or_target_mismatch); - int statusCode = ERROR_TRUST_FAILURE; - message = new byte[8]; //sizeof(long) - - for (int i = message.Length - 1; i >= 0; --i) - { - message[i] = (byte)(statusCode & 0xFF); - statusCode = (int)((uint)statusCode >> 8); - } - - StartSendAuthResetSignal(lazyResult, message, exception); - return; - } - - if (PrivateImpersonationLevel < _expectedImpersonationLevel) - { - Exception exception = new AuthenticationException(SR.Format(SR.net_auth_context_expectation, _expectedImpersonationLevel.ToString(), PrivateImpersonationLevel.ToString())); - int statusCode = ERROR_TRUST_FAILURE; - message = new byte[8]; //sizeof(long) - - for (int i = message.Length - 1; i >= 0; --i) - { - message[i] = (byte)(statusCode & 0xFF); - statusCode = (int)((uint)statusCode >> 8); - } - - StartSendAuthResetSignal(lazyResult, message, exception); - return; - } - - ProtectionLevel result = _context.IsConfidentialityFlag ? ProtectionLevel.EncryptAndSign : _context.IsIntegrityFlag ? ProtectionLevel.Sign : ProtectionLevel.None; - - if (result < _expectedProtectionLevel) - { - Exception exception = new AuthenticationException(SR.Format(SR.net_auth_context_expectation, result.ToString(), _expectedProtectionLevel.ToString())); - int statusCode = ERROR_TRUST_FAILURE; - message = new byte[8]; //sizeof(long) - - for (int i = message.Length - 1; i >= 0; --i) - { - message[i] = (byte)(statusCode & 0xFF); - statusCode = (int)((uint)statusCode >> 8); - } - - StartSendAuthResetSignal(lazyResult, message, exception); - return; - } - - // Signal remote party that we are done - _framer.WriteHeader.MessageId = FrameHeader.HandshakeDoneId; - if (_context.IsServer) - { - // Server may complete now because client SSPI would not complain at this point. - _remoteOk = true; - - // However the client will wait for server to send this ACK - //Force signaling server OK to the client - if (message == null) - { - message = s_emptyMessage; - } - } + flags |= ContextFlagsPal.ReplayDetect; } - else if (message == null || message == s_emptyMessage) + if ((win32Flags & Interop.SspiCli.ContextFlags.SequenceDetect) != 0) { - throw new InternalException(); + flags |= ContextFlagsPal.SequenceDetect; } - - if (message != null) + if ((win32Flags & Interop.SspiCli.ContextFlags.UnverifiedTargetName) != 0) { - //even if we are completed, there could be a blob for sending. - if (lazyResult == null) - { - _framer.WriteMessage(message); - } - else - { - IAsyncResult ar = _framer.BeginWriteMessage(message, s_writeCallback, lazyResult); - if (!ar.CompletedSynchronously) - { - return; - } - _framer.EndWriteMessage(ar); - } + flags |= ContextFlagsPal.UnverifiedTargetName; } - CheckCompletionBeforeNextReceive(lazyResult); - } - - // - // This will check and logically complete the auth handshake. - // - private void CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult) - { - if (HandshakeComplete && _remoteOk) + if ((win32Flags & Interop.SspiCli.ContextFlags.UseSessionKey) != 0) { - // We are done with success. - if (lazyResult != null) - { - lazyResult.InvokeCallback(); - } - - return; + flags |= ContextFlagsPal.UseSessionKey; } - - StartReceiveBlob(lazyResult); + return flags; } - // - // Server side starts here, but client also loops through this method. - // - private void StartReceiveBlob(LazyAsyncResult lazyResult) + private static Interop.SspiCli.ContextFlags GetInteropFromContextFlagsPal(ContextFlagsPal flags) { - byte[] message; - if (lazyResult == null) + Interop.SspiCli.ContextFlags win32Flags = Interop.SspiCli.ContextFlags.Zero; + if ((flags & ContextFlagsPal.AcceptExtendedError) != 0) { - message = _framer.ReadMessage(); + win32Flags |= Interop.SspiCli.ContextFlags.AcceptExtendedError; } - else + if ((flags & ContextFlagsPal.AcceptIdentify) != 0) { - IAsyncResult ar = _framer.BeginReadMessage(s_readCallback, lazyResult); - if (!ar.CompletedSynchronously) - { - return; - } - - message = _framer.EndReadMessage(ar); + win32Flags |= Interop.SspiCli.ContextFlags.AcceptIdentify; } - - ProcessReceivedBlob(message, lazyResult); - } - - private void ProcessReceivedBlob(byte[] message, LazyAsyncResult lazyResult) - { - // This is an EOF otherwise we would get at least *empty* message but not a null one. - if (message == null) + if ((flags & ContextFlagsPal.AcceptIntegrity) != 0) { - throw new AuthenticationException(SR.net_auth_eof, null); + win32Flags |= Interop.SspiCli.ContextFlags.AcceptIntegrity; } - - // Process Header information. - if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeErrId) + if ((flags & ContextFlagsPal.AcceptStream) != 0) { - Win32Exception e = null; - if (message.Length >= 8) // sizeof(long) - { - // Try to recover remote win32 Exception. - long error = 0; - for (int i = 0; i < 8; ++i) - { - error = (error << 8) + message[i]; - } - - e = new Win32Exception((int)error); - } - - if (e != null) - { - if (e.NativeErrorCode == (int)Interop.SecurityStatus.LogonDenied) - { - throw new InvalidCredentialException(SR.net_auth_bad_client_creds, e); - } - - if (e.NativeErrorCode == ERROR_TRUST_FAILURE) - { - throw new AuthenticationException(SR.net_auth_context_expectation_remote, e); - } - } - - throw new AuthenticationException(SR.net_auth_alert, e); + win32Flags |= Interop.SspiCli.ContextFlags.AcceptStream; } - - if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeDoneId) + if ((flags & ContextFlagsPal.AllocateMemory) != 0) { - _remoteOk = true; + win32Flags |= Interop.SspiCli.ContextFlags.AllocateMemory; } - else if (_framer.ReadHeader.MessageId != FrameHeader.HandshakeId) + if ((flags & ContextFlagsPal.AllowMissingBindings) != 0) { - throw new AuthenticationException(SR.Format(SR.net_io_header_id, "MessageId", _framer.ReadHeader.MessageId, FrameHeader.HandshakeId), null); + win32Flags |= Interop.SspiCli.ContextFlags.AllowMissingBindings; } - - CheckCompletionBeforeNextSend(message, lazyResult); - } - - // - // This will check and logically complete the auth handshake. - // - private void CheckCompletionBeforeNextSend(byte[] message, LazyAsyncResult lazyResult) - { - //If we are done don't go into send. - if (HandshakeComplete) + if ((flags & ContextFlagsPal.Confidentiality) != 0) { - if (!_remoteOk) - { - throw new AuthenticationException(SR.Format(SR.net_io_header_id, "MessageId", _framer.ReadHeader.MessageId, FrameHeader.HandshakeDoneId), null); - } - if (lazyResult != null) - { - lazyResult.InvokeCallback(); - } - - return; + win32Flags |= Interop.SspiCli.ContextFlags.Confidentiality; } - - // Not yet done, get a new blob and send it if any. - StartSendBlob(message, lazyResult); - } - - // - // This is to reset auth state on the remote side. - // If this write succeeds we will allow auth retrying. - // - private void StartSendAuthResetSignal(LazyAsyncResult lazyResult, byte[] message, Exception exception) - { - _framer.WriteHeader.MessageId = FrameHeader.HandshakeErrId; - - Win32Exception win32exception = exception as Win32Exception; - - if (win32exception != null && win32exception.NativeErrorCode == (int)Interop.SecurityStatus.LogonDenied) + if ((flags & ContextFlagsPal.Connection) != 0) { - if (IsServer) - { - exception = new InvalidCredentialException(SR.net_auth_bad_client_creds, exception); - } - else - { - exception = new InvalidCredentialException(SR.net_auth_bad_client_creds_or_target_mismatch, exception); - } + win32Flags |= Interop.SspiCli.ContextFlags.Connection; } - - if (!(exception is AuthenticationException)) + if ((flags & ContextFlagsPal.Delegate) != 0) { - exception = new AuthenticationException(SR.net_auth_SSPI, exception); + win32Flags |= Interop.SspiCli.ContextFlags.Delegate; } - - if (lazyResult == null) + if ((flags & ContextFlagsPal.InitExtendedError) != 0) { - _framer.WriteMessage(message); + win32Flags |= Interop.SspiCli.ContextFlags.InitExtendedError; } - else + if ((flags & ContextFlagsPal.InitIdentify) != 0) { - lazyResult.Result = exception; - IAsyncResult ar = _framer.BeginWriteMessage(message, s_writeCallback, lazyResult); - if (!ar.CompletedSynchronously) - { - return; - } - - _framer.EndWriteMessage(ar); + win32Flags |= Interop.SspiCli.ContextFlags.InitIdentify; } - - _canRetryAuthentication = true; - throw exception; - } - - private static void WriteCallback(IAsyncResult transportResult) - { - if ((transportResult.AsyncState is LazyAsyncResult)) + if ((flags & ContextFlagsPal.InitIntegrity) != 0) { - if (GlobalLog.IsEnabled) - { - GlobalLog.Assert("WriteCallback|State type is wrong, expected LazyAsyncResult."); - } - Debug.Fail("WriteCallback|State type is wrong, expected LazyAsyncResult."); + win32Flags |= Interop.SspiCli.ContextFlags.InitIntegrity; } - - if (transportResult.CompletedSynchronously) + if ((flags & ContextFlagsPal.InitManualCredValidation) != 0) { - return; + win32Flags |= Interop.SspiCli.ContextFlags.InitManualCredValidation; } - - LazyAsyncResult lazyResult = (LazyAsyncResult)transportResult.AsyncState; - - // Async completion. - try + if ((flags & ContextFlagsPal.InitStream) != 0) { - NegoState authState = (NegoState)lazyResult.AsyncObject; - authState._framer.EndWriteMessage(transportResult); - - // Special case for an error notification. - if (lazyResult.Result is Exception) - { - authState._canRetryAuthentication = true; - throw (Exception)lazyResult.Result; - } - - authState.CheckCompletionBeforeNextReceive(lazyResult); + win32Flags |= Interop.SspiCli.ContextFlags.InitStream; } - catch (Exception e) + if ((flags & ContextFlagsPal.InitUseSuppliedCreds) != 0) { - if (lazyResult.InternalPeekCompleted) - { - // This will throw on a worker thread. - throw; - } - - lazyResult.InvokeCallback(e); + win32Flags |= Interop.SspiCli.ContextFlags.InitUseSuppliedCreds; } - } - - private static void ReadCallback(IAsyncResult transportResult) - { - if ((transportResult.AsyncState is LazyAsyncResult)) + if ((flags & ContextFlagsPal.MutualAuth) != 0) { - if (GlobalLog.IsEnabled) - { - GlobalLog.Assert("ReadCallback|State type is wrong, expected LazyAsyncResult."); - } - Debug.Fail("ReadCallback|State type is wrong, expected LazyAsyncResult."); + win32Flags |= Interop.SspiCli.ContextFlags.MutualAuth; } - - if (transportResult.CompletedSynchronously) + if ((flags & ContextFlagsPal.ProxyBindings) != 0) { - return; + win32Flags |= Interop.SspiCli.ContextFlags.ProxyBindings; } - - LazyAsyncResult lazyResult = (LazyAsyncResult)transportResult.AsyncState; - - // Async completion. - try + if ((flags & ContextFlagsPal.ReplayDetect) != 0) { - NegoState authState = (NegoState)lazyResult.AsyncObject; - byte[] message = authState._framer.EndReadMessage(transportResult); - authState.ProcessReceivedBlob(message, lazyResult); + win32Flags |= Interop.SspiCli.ContextFlags.ReplayDetect; } - catch (Exception e) + if ((flags & ContextFlagsPal.SequenceDetect) != 0) { - if (lazyResult.InternalPeekCompleted) - { - // This will throw on a worker thread. - throw; - } - - lazyResult.InvokeCallback(e); + win32Flags |= Interop.SspiCli.ContextFlags.SequenceDetect; } - } - - private unsafe byte[] GetOutgoingBlob(byte[] incomingBlob, ref Win32Exception e) - { - Interop.SecurityStatus statusCode; - byte[] message = _context.GetOutgoingBlob(incomingBlob, false, out statusCode); - - if (((int)statusCode & unchecked((int)0x80000000)) != 0) + if ((flags & ContextFlagsPal.UnverifiedTargetName) != 0) { - e = new System.ComponentModel.Win32Exception((int)statusCode); - - message = new byte[8]; //sizeof(long) - for (int i = message.Length - 1; i >= 0; --i) - { - message[i] = (byte)((uint)statusCode & 0xFF); - statusCode = (Interop.SecurityStatus)((uint)statusCode >> 8); - } + win32Flags |= Interop.SspiCli.ContextFlags.UnverifiedTargetName; } - - if (message != null && message.Length == 0) + if ((flags & ContextFlagsPal.UseSessionKey) != 0) { - message = s_emptyMessage; + win32Flags |= Interop.SspiCli.ContextFlags.UseSessionKey; } - - return message; - } - - internal int EncryptData(byte[] buffer, int offset, int count, ref byte[] outBuffer) - { - CheckThrow(true); - - // SSPI seems to ignore this sequence number. - ++_writeSequenceNumber; - return _context.Encrypt(buffer, offset, count, ref outBuffer, _writeSequenceNumber); - } - - internal int DecryptData(byte[] buffer, int offset, int count, out int newOffset) - { - CheckThrow(true); - - // SSPI seems to ignore this sequence number. - ++_readSequenceNumber; - return _context.Decrypt(buffer, offset, count, out newOffset, _readSequenceNumber); + return win32Flags; } } } diff --git a/src/System.Net.Security/src/System/Net/SslStreamPal.Unix.cs b/src/System.Net.Security/src/System/Net/SslStreamPal.Unix.cs index 755c0fbae642..af722050ad2f 100644 --- a/src/System.Net.Security/src/System/Net/SslStreamPal.Unix.cs +++ b/src/System.Net.Security/src/System/Net/SslStreamPal.Unix.cs @@ -44,7 +44,7 @@ public static SecurityStatusPal InitializeSecurityContext(SafeFreeCredentials cr public static SafeFreeCredentials AcquireCredentialsHandle(X509Certificate certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer) { - return new SafeFreeCredentials(certificate, protocols, policy); + return new SafeFreeSslCredentials(certificate, protocols, policy); } public static SecurityStatusPal EncryptMessage(SafeDeleteContext securityContext, byte[] buffer, int size, int headerSize, int trailerSize, out int resultSize) @@ -66,7 +66,7 @@ public static SecurityStatusPal DecryptMessage(SafeDeleteContext securityContext public static SafeFreeContextBufferChannelBinding QueryContextChannelBinding(SafeDeleteContext securityContext, ChannelBindingKind attribute) { - SafeChannelBindingHandle bindingHandle = Interop.OpenSsl.QueryChannelBinding(securityContext.SslContext, attribute); + SafeChannelBindingHandle bindingHandle = Interop.OpenSsl.QueryChannelBinding(((SafeDeleteSslContext)securityContext).SslContext, attribute); var refHandle = bindingHandle == null ? null : new SafeFreeContextBufferChannelBinding(bindingHandle); return refHandle; } @@ -78,7 +78,7 @@ public static void QueryContextStreamSizes(SafeDeleteContext securityContext, ou public static void QueryContextConnectionInfo(SafeDeleteContext securityContext, out SslConnectionInfo connectionInfo) { - connectionInfo = new SslConnectionInfo(securityContext.SslContext); + connectionInfo = new SslConnectionInfo(((SafeDeleteSslContext)securityContext).SslContext); } private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credential, ref SafeDeleteContext context, @@ -90,7 +90,7 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia { if ((null == context) || context.IsInvalid) { - context = new SafeDeleteContext(credential, isServer, remoteCertRequired); + context = new SafeDeleteSslContext(credential as SafeFreeSslCredentials, isServer, remoteCertRequired); } byte[] output = null; @@ -99,11 +99,11 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia if (null == inputBuffer) { - done = Interop.OpenSsl.DoSslHandshake(context.SslContext, null, 0, 0, out output, out outputSize); + done = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, null, 0, 0, out output, out outputSize); } else { - done = Interop.OpenSsl.DoSslHandshake(context.SslContext, inputBuffer.token, inputBuffer.offset, inputBuffer.size, out output, out outputSize); + done = Interop.OpenSsl.DoSslHandshake(((SafeDeleteSslContext)context).SslContext, inputBuffer.token, inputBuffer.offset, inputBuffer.size, out output, out outputSize); } outputBuffer.size = outputSize; @@ -116,7 +116,7 @@ private static SecurityStatusPal HandshakeInternal(SafeFreeCredentials credentia { // TODO: This Debug.Fail is triggering on Linux in many test cases #4317 // Debug.Fail("Exception Caught. - " + ex); - return SecurityStatusPal.InternalError; + return SecurityStatusPal.InternalError; } } @@ -128,7 +128,7 @@ private static SecurityStatusPal EncryptDecryptHelper(SafeDeleteContext security Interop.Ssl.SslErrorCode errorCode = Interop.Ssl.SslErrorCode.SSL_ERROR_NONE; - SafeSslHandle scHandle = securityContext.SslContext; + SafeSslHandle scHandle = ((SafeDeleteSslContext)securityContext).SslContext; resultSize = encrypt ? Interop.OpenSsl.Encrypt(scHandle, buffer, offset, size, out errorCode) : diff --git a/src/System.Net.Security/src/System/Net/SslStreamPal.Windows.cs b/src/System.Net.Security/src/System/Net/SslStreamPal.Windows.cs index 5848c17c84a5..26ebc7e50e05 100644 --- a/src/System.Net.Security/src/System/Net/SslStreamPal.Windows.cs +++ b/src/System.Net.Security/src/System/Net/SslStreamPal.Windows.cs @@ -298,12 +298,12 @@ private static SafeFreeCredentials AcquireCredentialsHandle(Interop.SspiCli.Cred } } - private static SecurityStatusPal GetSecurityStatusPalFromWin32Int(int win32SecurityStatus) + internal static SecurityStatusPal GetSecurityStatusPalFromWin32Int(int win32SecurityStatus) { return GetSecurityStatusPalFromInterop((Interop.SecurityStatus)win32SecurityStatus); } - private static SecurityStatusPal GetSecurityStatusPalFromInterop(Interop.SecurityStatus win32SecurityStatus) + internal static SecurityStatusPal GetSecurityStatusPalFromInterop(Interop.SecurityStatus win32SecurityStatus) { switch (win32SecurityStatus) { @@ -391,7 +391,7 @@ private static SecurityStatusPal GetSecurityStatusPalFromInterop(Interop.Securit } } - private static Interop.SecurityStatus GetInteropFromSecurityStatusPal(SecurityStatusPal status) + internal static Interop.SecurityStatus GetInteropFromSecurityStatusPal(SecurityStatusPal status) { switch (status) {