diff --git a/src/Common/src/Interop/Linux/System.Net.Security.Native/SecuritySafeHandles.cs b/src/Common/src/Interop/Linux/System.Net.Security.Native/SecuritySafeHandles.cs new file mode 100644 index 000000000000..e12b127d70c3 --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Security.Native/SecuritySafeHandles.cs @@ -0,0 +1,98 @@ +// 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 Microsoft.Win32.SafeHandles; +using System.Diagnostics; + +namespace System.Net.Security +{ + internal sealed partial class SafeFreeNegoCredentials : SafeFreeCredentials + { + private readonly string _password; + + public string Password + { + get { return _password; } + } + + public SafeFreeNegoCredentials(bool ntlmOnly, string username, string password, string domain) : base(IntPtr.Zero, true) + { + _isNtlm = ntlmOnly; + _isdefault = string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password); + if (!ntlmOnly) + { + try + { + _credential = SafeGssCredHandle.Create(username, password, domain); + } + catch + { + // NTLM fallback is not possible with default credentials + if (_isdefault) + { + throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred); + } + + _isNtlm = true; + } + } + + // Even if Kerberos TGT could be obtained, we might later need + // to fall back to NTLM if service ticket cannot be fetched + _username = username; + _password = password; + _domain = domain; + } + } + + internal sealed partial class SafeDeleteNegoContext : SafeDeleteContext + { + private readonly Interop.NetNtlmNative.NtlmFlags _flags; + private Interop.HeimdalNtlm.SigningKey _serverSignKey; + private Interop.HeimdalNtlm.SealingKey _serverSealKey; + private Interop.HeimdalNtlm.SigningKey _clientSignKey; + private Interop.HeimdalNtlm.SealingKey _clientSealKey; + + public Interop.NetNtlmNative.NtlmFlags Flags + { + get { return _flags; } + } + + public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, Interop.NetNtlmNative.NtlmFlags flags) + : base(credential) + { + _flags = flags; + _isNtlm = true; + } + + public void SetKeys(byte[] sessionKey) + { + Interop.HeimdalNtlm.CreateKeys(sessionKey, out _serverSignKey, out _serverSealKey, out _clientSignKey, out _clientSealKey); + } + + public byte[] MakeClientSignature(byte[] buffer, int offset, int count) + { + Debug.Assert(_clientSignKey != null, "_clientSignKey cannot be null"); + return _clientSignKey.Sign(_clientSealKey, buffer, offset, count); + } + + public byte[] MakeServerSignature(byte[] buffer, int offset, int count) + { + Debug.Assert(_serverSignKey != null, "_serverSignKey cannot be null"); + return _serverSignKey.Sign(_serverSealKey, buffer, offset, count); + } + + public byte[] Encrypt(byte[] buffer, int offset, int count) + { + Debug.Assert(_clientSignKey != null, "_clientSealKey cannot be null"); + return _clientSealKey.SealOrUnseal(buffer, offset, count); + } + + public byte[] Decrypt(byte[] buffer, int offset, int count) + { + Debug.Assert(_serverSignKey != null, "_serverSealKey cannot be null"); + return _serverSealKey.SealOrUnseal(buffer, offset, count); + } + } +} diff --git a/src/Common/src/Interop/OSX/System.Net.Security.Native/SecuritySafeHandles.cs b/src/Common/src/Interop/OSX/System.Net.Security.Native/SecuritySafeHandles.cs new file mode 100644 index 000000000000..8509141d447f --- /dev/null +++ b/src/Common/src/Interop/OSX/System.Net.Security.Native/SecuritySafeHandles.cs @@ -0,0 +1,79 @@ +// 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.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace System.Net.Security +{ + internal sealed partial class SafeFreeNegoCredentials : SafeFreeCredentials + { + + public SafeFreeNegoCredentials(bool ntlmOnly, string username, string password, string domain) : base(IntPtr.Zero, true) + { + _isNtlm = ntlmOnly; + _isdefault = string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password); + _username = username; + _domain = domain; + _credential = SafeGssCredHandle.Create(username, password, domain); + } + } + + internal sealed partial class SafeDeleteNegoContext : SafeDeleteContext + { + private const char At = '@'; + public SafeDeleteNegoContext(SafeFreeNegoCredentials credential) + : base(credential) + { + // Try to construct target in user@domain format + string targetName = credential.UserName; + string domain = credential.Domain; + + //remove any leading and trailing whitespace + if (domain != null) + { + domain = domain.Trim(); + } + + if ((targetName.IndexOf(At) < 0) && !string.IsNullOrEmpty(domain)) + { + targetName += At + domain; + } + + try + { + _targetName = SafeGssNameHandle.CreatePrincipal(targetName); + } + catch + { + Dispose(); + throw; + } + } + + public byte[] MakeClientSignature(byte[] buffer, int offset, int count) + { + //MakeClientSignature is not supported on OSX + throw new PlatformNotSupportedException(); + } + + public byte[] MakeServerSignature(byte[] buffer, int offset, int count) + { + //MakeServerSignature is not supported on OSX + throw new PlatformNotSupportedException(); + } + + public byte[] Encrypt(byte[] buffer, int offset, int count) + { + //Encrypt is not supported on OSX + throw new PlatformNotSupportedException(); + } + + public byte[] Decrypt(byte[] buffer, int offset, int count) + { + //Decrypt is not supported on OSX + throw new PlatformNotSupportedException(); + } + } +} 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 index e4e07954e02c..0237bb657fb4 100644 --- a/src/Common/src/Interop/Unix/System.Net.Security.Native/SecuritySafeHandles.cs +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/SecuritySafeHandles.cs @@ -10,9 +10,33 @@ namespace System.Net.Security { - internal sealed class SafeFreeNegoCredentials : SafeFreeCredentials + internal sealed partial class SafeFreeNegoCredentials : SafeFreeCredentials { private SafeGssCredHandle _credential; + private bool _isdefault; + private readonly string _username; + private readonly string _domain; + private bool _isNtlm; + + public bool IsDefault + { + get { return _isdefault; } + } + + public string UserName + { + get { return _username; } + } + + public string Domain + { + get { return _domain; } + } + + public bool IsNtlm + { + get { return _isNtlm; } + } public SafeGssCredHandle GssCredential { @@ -39,10 +63,11 @@ protected override bool ReleaseHandle() } } - internal sealed class SafeDeleteNegoContext : SafeDeleteContext + internal sealed partial class SafeDeleteNegoContext : SafeDeleteContext { private SafeGssNameHandle _targetName; private SafeGssContextHandle _context; + private bool _isNtlm; public SafeGssNameHandle TargetName { @@ -54,6 +79,11 @@ public SafeGssContextHandle GssContext get { return _context; } } + public bool IsNtlm + { + get { return _isNtlm; } + } + public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetName) : base(credential) { @@ -69,10 +99,11 @@ public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetNa } } - public void SetGssContext(SafeGssContextHandle context) + public void SetGssContext(SafeGssContextHandle context, bool isNtlm) { Debug.Assert(!context.IsInvalid, "Invalid context passed to SafeDeleteNegoContext"); _context = context; + _isNtlm = isNtlm; } protected override void Dispose(bool disposing) diff --git a/src/System.Net.Security/src/Resources/Strings.resx b/src/System.Net.Security/src/Resources/Strings.resx index 3634df93afa9..1fb467cb5991 100644 --- a/src/System.Net.Security/src/Resources/Strings.resx +++ b/src/System.Net.Security/src/Resources/Strings.resx @@ -432,13 +432,16 @@ Insufficient buffer space. Required: {0} Actual: {1}. + + NTLM authentication is not possible with default credentials which are no-op. + No support for channel binding on operating systems other than Windows - - NTLM is not supported - Server implementation is not supported + + NTLM operation failed with status: {0}) + diff --git a/src/System.Net.Security/src/System.Net.Security.csproj b/src/System.Net.Security/src/System.Net.Security.csproj index 28e37dc86c3e..8cb005c4733a 100644 --- a/src/System.Net.Security/src/System.Net.Security.csproj +++ b/src/System.Net.Security/src/System.Net.Security.csproj @@ -46,8 +46,6 @@ - - @@ -151,8 +149,6 @@ - - @@ -238,8 +234,6 @@ - - @@ -348,6 +342,60 @@ + + + + + + Common\Interop\Linux\System.Net.Security.Native\SecuritySafeHandles.cs + + + Common\Interop\Linux\System.Net.Ntlm.Native\Interop.NetNtlmNative.cs + + + Common\Interop\Linux\System.Net.Ntlm.Native\Interop.NtlmException.cs + + + Common\Interop\Linux\System.Net.Ntlm.Native\Interop.Ntlm.cs + + + Common\Interop\Linux\System.Net.Ntlm.Native\Interop.NtlmBuffer.cs + + + Common\Interop\Linux\System.Net.Ntlm.Native\Interop.NtlmType3Message.cs + + + Common\Microsoft\Win32\SafeHandles\NtlmSecuritySafeHandles.cs + + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Hmac.cs + + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.cs + + + Microsoft\Win32\SafeHandles\SafeEvpMdCtxHandle.Unix.cs + + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.Cipher.cs + + + Common\Microsoft\Win32\SafeHandles\SafeEvpCipherCtxHandle.Unix.cs + + + Common\Microsoft\Win32\SafeHandles\SafeHmacCtxHandle.Unix.cs + + + + + + + + Common\Interop\OSX\System.Net.Security.Native\SecuritySafeHandles.cs + + + + diff --git a/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Linux.cs b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Linux.cs new file mode 100644 index 000000000000..e88f68229f8e --- /dev/null +++ b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Linux.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; + +namespace System.Net.Security +{ + internal static partial class NegotiateStreamPal + { + private static bool EstablishNtlmSecurityContext( + SafeFreeNegoCredentials credential, + ref SafeDeleteContext context, + string targetName, + ContextFlagsPal inFlags, + SecurityBuffer inputBuffer, + SecurityBuffer outputBuffer, + ref ContextFlagsPal outFlags) + { + bool retVal; + Interop.NetNtlmNative.NtlmFlags flags; + if (null == context) + { + flags = GetInteropNtlmFromContextFlagsPal(inFlags); + context = new SafeDeleteNegoContext(credential, flags); + outputBuffer.token = Interop.Ntlm.CreateNegotiateMessage((uint) flags); + retVal = false; + } + else + { + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext) context; + flags = negoContext.Flags; + byte[] sessionKey; + outputBuffer.token = Interop.Ntlm.CreateAuthenticateMessage( + (uint) flags, + credential.UserName, + credential.Password, + credential.Domain, + inputBuffer.token, + inputBuffer.offset, + inputBuffer.size, + out sessionKey); + negoContext.SetKeys(sessionKey); + retVal = true; + } + + outFlags = inFlags; + outputBuffer.size = outputBuffer.token.Length; + return retVal; + } + + private static ContextFlagsPal GetContextFlagsPalFromInteropNtlm(Interop.NetNtlmNative.NtlmFlags ntlmFlags) + { + ContextFlagsPal flags = ContextFlagsPal.Zero; + if ((ntlmFlags & Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_SEAL) != 0) + { + flags |= ContextFlagsPal.Confidentiality; + } + + if ((ntlmFlags & Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_SIGN) != 0) + { + // No NTLM server support so not setting AcceptIntegrity flag + flags |= ContextFlagsPal.InitIntegrity; + flags |= ContextFlagsPal.ReplayDetect | ContextFlagsPal.SequenceDetect; + } + + return flags; + } + + private static Interop.NetNtlmNative.NtlmFlags GetInteropNtlmFromContextFlagsPal(ContextFlagsPal flags) + { + Interop.NetNtlmNative.NtlmFlags ntlmFlags = Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE | Interop.NetNtlmNative.NtlmFlags.NTLMSSP_REQUEST_TARGET; + if ((flags & (ContextFlagsPal.AcceptIntegrity | ContextFlagsPal.InitIntegrity | ContextFlagsPal.Confidentiality)) != 0) + { + ntlmFlags |= Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_SIGN + | Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN + | Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_NTLM + | Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY + | Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_128 + | Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if ((flags & ContextFlagsPal.Confidentiality) != 0) + { + ntlmFlags |= Interop.NetNtlmNative.NtlmFlags.NTLMSSP_NEGOTIATE_SEAL; + } + + return ntlmFlags; + } + } +} diff --git a/src/System.Net.Security/src/System/Net/NegotiateStreamPal.OSX.cs b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.OSX.cs new file mode 100644 index 000000000000..ce6f1ffab95b --- /dev/null +++ b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.OSX.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; + +namespace System.Net.Security +{ + internal static partial class NegotiateStreamPal + { + private static bool EstablishNtlmSecurityContext( + SafeFreeNegoCredentials credential, + ref SafeDeleteContext context, + string targetName, + ContextFlagsPal inFlags, + SecurityBuffer inputBuffer, + SecurityBuffer outputBuffer, + ref ContextFlagsPal outFlags) + { + if (context == null) + { + context = new SafeDeleteNegoContext(credential); + } + + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext) context; + SafeGssContextHandle contextHandle = negoContext.GssContext; + + uint outputFlags; + Interop.NetSecurityNative.GssFlags inputFlags = ContextFlagsAdapterPal.GetInteropFromContextFlagsPal(inFlags); + bool done = Interop.GssApi.EstablishSecurityContext( + ref contextHandle, + credential.GssCredential, + true, + negoContext.TargetName, + inputFlags, + ((inputBuffer != null) ? inputBuffer.token : null), + out outputBuffer.token, + out outputFlags); + + outFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop((Interop.NetSecurityNative.GssFlags) outputFlags); + + // Save the inner context handle for further calls to NetSecurityNative + if (null == negoContext.GssContext) + { + negoContext.SetGssContext(contextHandle, true); + } + + return done; + } + } +} diff --git a/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs index c028c9e4d0dc..49bf17c99d7e 100644 --- a/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs +++ b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs @@ -22,12 +22,15 @@ namespace System.Net.Security // // This is part of the NegotiateStream PAL. // - internal static class NegotiateStreamPal + internal static partial class NegotiateStreamPal { // value should match the Windows sspicli NTE_FAIL value // defined in winerror.h private const int NTE_FAIL = unchecked((int)0x80090020); + // In case of NTLM input bytes are preceded by a signature of 16 bytes + private const int NtlmSignatureLength = 16; + internal static IIdentity GetIdentity(NTAuthentication context) { Debug.Assert(!context.IsServer, "GetIdentity: Server is not supported"); @@ -46,7 +49,8 @@ internal static string QueryContextAssociatedName(SafeDeleteContext securityCont internal static string QueryContextAuthenticationPackage(SafeDeleteContext securityContext) { - return NegotiationInfoClass.Kerberos; + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + return negoContext.IsNtlm ? NegotiationInfoClass.NTLM : NegotiationInfoClass.Kerberos; } internal static int QueryMaxTokenSize(string package) @@ -73,25 +77,26 @@ internal static SafeFreeCredentials AcquireCredentialsHandle(string package, boo } SafeFreeCredentials outCredential; - bool isNtlm = string.Equals(package, NegotiationInfoClass.NTLM); - - if (isNtlm) + bool ntlmOnly = string.Equals(package, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase); + bool isEmptyCredential = string.IsNullOrWhiteSpace(credential.UserName) || string.IsNullOrWhiteSpace(credential.Password); + if (ntlmOnly && isEmptyCredential) { - throw new PlatformNotSupportedException(SR.net_nego_ntlm_not_supported); + // NTLM authentication is not possible with default credentials which are no-op + throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred); } if (string.IsNullOrEmpty(credential.UserName) || string.IsNullOrEmpty(credential.Password)) { // 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); + outCredential = new SafeFreeNegoCredentials(false, string.Empty, string.Empty, string.Empty); } else { - outCredential = new SafeFreeNegoCredentials(credential.UserName, credential.Password, credential.Domain); + outCredential = new SafeFreeNegoCredentials(ntlmOnly, credential.UserName, credential.Password, credential.Domain); } - return outCredential; + return outCredential; } internal static SecurityStatusPal InitializeSecurityContext( @@ -109,13 +114,16 @@ internal static SecurityStatusPal InitializeSecurityContext( throw new PlatformNotSupportedException(SR.net_nego_channel_binding_not_supported); } + SecurityBuffer inSecurityBuffer = (inSecurityBufferArray != null && inSecurityBufferArray.Length != 0) + ? inSecurityBufferArray[0] + : null; + return EstablishSecurityContext( (SafeFreeNegoCredentials)credentialsHandle, ref securityContext, - false, spn, requestedContextFlags, - ((inSecurityBufferArray != null && inSecurityBufferArray.Length != 0) ? inSecurityBufferArray[0] : null), + inSecurityBuffer, outSecurityBuffer, ref contextFlags); } @@ -135,7 +143,7 @@ internal static SecurityStatusPal AcceptSecurityContext( SecurityBuffer outSecurityBuffer, ref ContextFlagsPal contextFlags) { - throw new PlatformNotSupportedException(); + throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); } internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel) @@ -163,15 +171,36 @@ internal static int Encrypt( ref byte[] output, uint sequenceNumber) { - Debug.Assert(!isNtlm, "Encrypt: NTLM is not yet supported"); - SafeDeleteNegoContext gssContext = (SafeDeleteNegoContext) securityContext; - byte[] tempOutput = Interop.GssApi.Encrypt(gssContext.GssContext, isConfidential, buffer, offset, count); - - // 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); - int resultSize = tempOutput.Length; + const int prefixLength = sizeof(uint); // Output bytes are preceded by length + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + Debug.Assert((isNtlm == negoContext.IsNtlm), "Inconsistent NTLM parameter"); + int resultSize; + if (null != negoContext.GssContext) + { + byte[] tempOutput = Interop.GssApi.Encrypt(negoContext.GssContext, isConfidential, buffer, offset, count); + output = new byte[tempOutput.Length + prefixLength]; + Array.Copy(tempOutput, 0, output, prefixLength, tempOutput.Length); + resultSize = tempOutput.Length; + } + else + { + byte[] signature = negoContext.MakeClientSignature(buffer, offset, count); + if (isConfidential) + { + byte[] cipher = negoContext.Encrypt(buffer, offset, count); + output = new byte[cipher.Length + signature.Length + prefixLength]; + Array.Copy(signature, 0, output, prefixLength, signature.Length); + Array.Copy(cipher, 0, output, signature.Length + prefixLength, cipher.Length); + } + else + { + output = new byte[count + signature.Length + prefixLength]; + Array.Copy(signature, 0, output, prefixLength, signature.Length); + Array.Copy(buffer, offset, output, signature.Length + prefixLength, count); + } + + resultSize = output.Length; + } unchecked { output[0] = (byte)((resultSize) & 0xFF); @@ -217,8 +246,24 @@ internal static int Decrypt( throw new ArgumentOutOfRangeException("count"); } - newOffset = offset; - return Interop.GssApi.Decrypt(((SafeDeleteNegoContext)securityContext).GssContext, buffer, offset, count); + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + Debug.Assert((isNtlm == negoContext.IsNtlm), "Inconsistent NTLM parameter"); + + newOffset = isNtlm ? (offset + NtlmSignatureLength) : offset; + if (!isConfidential) + { + return VerifySignature(negoContext, buffer, offset, count); + } + + if (null != negoContext.GssContext) + { + return Interop.GssApi.Decrypt(negoContext.GssContext, buffer, offset, count); + } + else + { + int tempOffset; + return DecryptNtlm(negoContext, buffer, offset, count, isConfidential, out tempOffset, sequenceNumber); + } } internal static int DecryptNtlm( @@ -230,57 +275,132 @@ internal static int DecryptNtlm( out int newOffset, uint sequenceNumber) { - throw new PlatformNotSupportedException(SR.net_nego_ntlm_not_supported); + SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)securityContext; + newOffset = offset + NtlmSignatureLength; + byte[] message = negoContext.Decrypt(buffer, newOffset, (count - NtlmSignatureLength)); + Array.Copy(message, 0, buffer, newOffset, message.Length); + return VerifySignature(negoContext, buffer, offset, count); + } + + private static int VerifySignature(SafeDeleteNegoContext negoContext, byte[] buffer, int offset, int count) + { + if (null != negoContext.GssContext) + { + return Interop.GssApi.Decrypt(negoContext.GssContext, buffer, offset, count); + } + + count -= NtlmSignatureLength; + byte[] signature = negoContext.MakeServerSignature(buffer, offset + NtlmSignatureLength, count); + for (int i = 0; i < signature.Length; i++) + { + if (buffer[offset + i] != signature[i]) + { + throw new Exception("Invalid signature"); + } + } + + return count; } 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"); + bool isNtlm; + SafeDeleteNegoContext negoContext; + SafeGssContextHandle contextHandle = null; if (context == null) { - context = new SafeDeleteNegoContext(credential, targetName); + isNtlm = credential.IsNtlm || string.IsNullOrWhiteSpace(targetName); + negoContext = isNtlm ? null : new SafeDeleteNegoContext(credential, targetName); + context = negoContext; + } + else + { + negoContext = (SafeDeleteNegoContext)context; + isNtlm = negoContext.IsNtlm; + contextHandle = negoContext.GssContext; } - SafeDeleteNegoContext negoContext = (SafeDeleteNegoContext)context; try { - Interop.NetSecurityNative.GssFlags inputFlags = ContextFlagsAdapterPal.GetInteropFromContextFlagsPal(inFlags); - uint outputFlags; - SafeGssContextHandle contextHandle = negoContext.GssContext; - bool done = Interop.GssApi.EstablishSecurityContext( - ref contextHandle, - credential.GssCredential, - isNtlm, - negoContext.TargetName, - inputFlags, - ((inputBuffer != null) ? inputBuffer.token : null), - out outputBuffer.token, - out outputFlags); + bool done = false; + if (!isNtlm) + { + Interop.NetSecurityNative.GssFlags inputFlags = + ContextFlagsAdapterPal.GetInteropFromContextFlagsPal(inFlags); + + try + { + uint outputFlags; + done = Interop.GssApi.EstablishSecurityContext( + ref contextHandle, + credential.GssCredential, + isNtlm, + negoContext.TargetName, + inputFlags, + ((inputBuffer != null) ? inputBuffer.token : null), + out outputBuffer.token, + out outputFlags); + + Debug.Assert(outputBuffer.token != null, "Unexpected null buffer returned by GssApi"); + outputBuffer.size = outputBuffer.token.Length; + outputBuffer.offset = 0; + + outFlags = + ContextFlagsAdapterPal.GetContextFlagsPalFromInterop( + (Interop.NetSecurityNative.GssFlags)outputFlags); + Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); + + // Save the inner context handle for further calls to NetSecurity + if (null == negoContext.GssContext) + { + negoContext.SetGssContext(contextHandle, false); + } + } + catch + { + // If non-default credentials are available, + // we need to try NTLM authentication + if (credential.IsDefault) + { + throw; + } + + isNtlm = true; + negoContext.Dispose(); + context = null; + + } + } + + if (isNtlm) + { + done = EstablishNtlmSecurityContext( + credential, + ref context, + targetName, + inFlags, + inputBuffer, + outputBuffer, + ref outFlags); + } Debug.Assert(outputBuffer.token != null, "Unexpected null buffer returned by GssApi"); outputBuffer.size = outputBuffer.token.Length; outputBuffer.offset = 0; - outFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop((Interop.NetSecurityNative.GssFlags)outputFlags); - Debug.Assert(negoContext.GssContext == null || contextHandle == negoContext.GssContext); - - // Save the inner context handle for further calls to NetSecurity - if (null == negoContext.GssContext) - { - negoContext.SetGssContext(contextHandle); - } - return done ? SecurityStatusPal.CompleteNeeded : SecurityStatusPal.ContinueNeeded; + return done + ? (isNtlm ? SecurityStatusPal.OK : SecurityStatusPal.CompleteNeeded) + : SecurityStatusPal.ContinueNeeded; } - catch(Exception ex) + catch (Exception ex) { //TODO (Issue #5890): Print exception until issue is fixed Debug.Write("Exception Caught. - " + ex);