//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Net { using System.Runtime.InteropServices; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Threading; using System.Globalization; using System.Security.Authentication.ExtendedProtection; using System.Security.Principal; using System.Security.Permissions; using System.Net.Security; // #define ISC_REQ_DELEGATE 0x00000001 // #define ISC_REQ_MUTUAL_AUTH 0x00000002 // #define ISC_REQ_REPLAY_DETECT 0x00000004 // #define ISC_REQ_SEQUENCE_DETECT 0x00000008 // #define ISC_REQ_CONFIDENTIALITY 0x00000010 // #define ISC_REQ_USE_SESSION_KEY 0x00000020 // #define ISC_REQ_PROMPT_FOR_CREDS 0x00000040 // #define ISC_REQ_USE_SUPPLIED_CREDS 0x00000080 // #define ISC_REQ_ALLOCATE_MEMORY 0x00000100 // #define ISC_REQ_USE_DCE_STYLE 0x00000200 // #define ISC_REQ_DATAGRAM 0x00000400 // #define ISC_REQ_CONNECTION 0x00000800 // #define ISC_REQ_CALL_LEVEL 0x00001000 // #define ISC_REQ_FRAGMENT_SUPPLIED 0x00002000 // #define ISC_REQ_EXTENDED_ERROR 0x00004000 // #define ISC_REQ_STREAM 0x00008000 // #define ISC_REQ_INTEGRITY 0x00010000 // #define ISC_REQ_IDENTIFY 0x00020000 // #define ISC_REQ_NULL_SESSION 0x00040000 // #define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000 // #define ISC_REQ_RESERVED1 0x00100000 // #define ISC_REQ_FRAGMENT_TO_FIT 0x00200000 // #define ISC_REQ_HTTP 0x10000000 // Win7 SP1 + // #define ISC_REQ_UNVERIFIED_TARGET_NAME 0x20000000 // #define ASC_REQ_DELEGATE 0x00000001 // #define ASC_REQ_MUTUAL_AUTH 0x00000002 // #define ASC_REQ_REPLAY_DETECT 0x00000004 // #define ASC_REQ_SEQUENCE_DETECT 0x00000008 // #define ASC_REQ_CONFIDENTIALITY 0x00000010 // #define ASC_REQ_USE_SESSION_KEY 0x00000020 // #define ASC_REQ_ALLOCATE_MEMORY 0x00000100 // #define ASC_REQ_USE_DCE_STYLE 0x00000200 // #define ASC_REQ_DATAGRAM 0x00000400 // #define ASC_REQ_CONNECTION 0x00000800 // #define ASC_REQ_CALL_LEVEL 0x00001000 // #define ASC_REQ_EXTENDED_ERROR 0x00008000 // #define ASC_REQ_STREAM 0x00010000 // #define ASC_REQ_INTEGRITY 0x00020000 // #define ASC_REQ_LICENSING 0x00040000 // #define ASC_REQ_IDENTIFY 0x00080000 // #define ASC_REQ_ALLOW_NULL_SESSION 0x00100000 // #define ASC_REQ_ALLOW_NON_USER_LOGONS 0x00200000 // #define ASC_REQ_ALLOW_CONTEXT_REPLAY 0x00400000 // #define ASC_REQ_FRAGMENT_TO_FIT 0x00800000 // #define ASC_REQ_FRAGMENT_SUPPLIED 0x00002000 // #define ASC_REQ_NO_TOKEN 0x01000000 // #define ASC_REQ_HTTP 0x10000000 [Flags] internal enum ContextFlags { Zero = 0, // The server in the transport application can // build new security contexts impersonating the // client that will be accepted by other servers // as the client's contexts. Delegate = 0x00000001, // The communicating parties must authenticate // their identities to each other. Without MutualAuth, // the client authenticates its identity to the server. // With MutualAuth, the server also must authenticate // its identity to the client. MutualAuth = 0x00000002, // The security package detects replayed packets and // notifies the caller if a packet has been replayed. // The use of this flag implies all of the conditions // specified by the Integrity flag. ReplayDetect = 0x00000004, // The context must be allowed to detect out-of-order // delivery of packets later through the message support // functions. Use of this flag implies all of the // conditions specified by the Integrity flag. SequenceDetect = 0x00000008, // The context must protect data while in transit. // Confidentiality is supported for NTLM with Microsoft // Windows NT version 4.0, SP4 and later and with the // Kerberos protocol in Microsoft Windows 2000 and later. Confidentiality = 0x00000010, UseSessionKey = 0x00000020, AllocateMemory = 0x00000100, // Connection semantics must be used. Connection = 0x00000800, // Client applications requiring extended error messages specify the // ISC_REQ_EXTENDED_ERROR flag when calling the InitializeSecurityContext // Server applications requiring extended error messages set // the ASC_REQ_EXTENDED_ERROR flag when calling AcceptSecurityContext. InitExtendedError = 0x00004000, AcceptExtendedError = 0x00008000, // A transport application requests stream semantics // by setting the ISC_REQ_STREAM and ASC_REQ_STREAM // flags in the calls to the InitializeSecurityContext // and AcceptSecurityContext functions InitStream = 0x00008000, AcceptStream = 0x00010000, // Buffer integrity can be verified; however, replayed // and out-of-sequence messages will not be detected InitIntegrity = 0x00010000, // ISC_REQ_INTEGRITY AcceptIntegrity = 0x00020000, // ASC_REQ_INTEGRITY InitManualCredValidation = 0x00080000, // ISC_REQ_MANUAL_CRED_VALIDATION InitUseSuppliedCreds = 0x00000080, // ISC_REQ_USE_SUPPLIED_CREDS InitIdentify = 0x00020000, // ISC_REQ_IDENTIFY AcceptIdentify = 0x00080000, // ASC_REQ_IDENTIFY ProxyBindings = 0x04000000, // ASC_REQ_PROXY_BINDINGS AllowMissingBindings = 0x10000000, // ASC_REQ_ALLOW_MISSING_BINDINGS UnverifiedTargetName = 0x20000000, // ISC_REQ_UNVERIFIED_TARGET_NAME } #if MONO_NOT_IMPLEMENTED internal class NTAuthentication { static private int s_UniqueGroupId = 1; static private ContextCallback s_InitializeCallback = new ContextCallback(InitializeCallback); private bool m_IsServer; private SafeFreeCredentials m_CredentialsHandle; private SafeDeleteContext m_SecurityContext; private string m_Spn; private string m_ClientSpecifiedSpn; private int m_TokenSize; private ContextFlags m_RequestedContextFlags; private ContextFlags m_ContextFlags; private string m_UniqueUserId; private bool m_IsCompleted; private string m_ProtocolName; private SecSizes m_Sizes; private string m_LastProtocolName; private string m_Package; private ChannelBinding m_ChannelBinding; // // Properties // internal string UniqueUserId { get { return m_UniqueUserId; } } // The semantic of this propoerty is "Don't call me again". // It can be completed either with success or error // The latest case is signalled by IsValidContext==false internal bool IsCompleted { get { return m_IsCompleted; } } internal bool IsValidContext { get { return !(m_SecurityContext == null || m_SecurityContext.IsInvalid); } } internal string AssociatedName { get { if (!(IsValidContext && IsCompleted)) throw new Win32Exception((int)SecurityStatus.InvalidHandle); string name = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, m_SecurityContext, ContextAttribute.Names) as string; GlobalLog.Print("NTAuthentication: The context is associated with [" + name + "]"); return name; } } internal bool IsConfidentialityFlag { get { return (m_ContextFlags & ContextFlags.Confidentiality) != 0; } } internal bool IsIntegrityFlag { get { return (m_ContextFlags & (m_IsServer?ContextFlags.AcceptIntegrity:ContextFlags.InitIntegrity)) != 0; } } internal bool IsMutualAuthFlag { get { return (m_ContextFlags & ContextFlags.MutualAuth) != 0; } } internal bool IsDelegationFlag { get { return (m_ContextFlags & ContextFlags.Delegate) != 0; } } internal bool IsIdentifyFlag { get { return (m_ContextFlags & (m_IsServer?ContextFlags.AcceptIdentify:ContextFlags.InitIdentify)) != 0; } } internal string Spn { get { return m_Spn; } } internal string ClientSpecifiedSpn { get { if (m_ClientSpecifiedSpn == null) { m_ClientSpecifiedSpn = GetClientSpecifiedSpn(); } return m_ClientSpecifiedSpn; } } internal bool OSSupportsExtendedProtection { get { GlobalLog.Assert(IsCompleted && IsValidContext, "NTAuthentication#{0}::OSSupportsExtendedProtection|The context is not completed or invalid.", ValidationHelper.HashString(this)); int errorCode; SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, m_SecurityContext, ContextAttribute.ClientSpecifiedSpn, out errorCode); // We consider any error other than Unsupported to mean that the underlying OS // supports extended protection. Most likely it will be TargetUnknown. return ((SecurityStatus)errorCode != SecurityStatus.Unsupported); } } // // True indicates this instance is for Server and will use AcceptSecurityContext SSPI API // internal bool IsServer { get { return m_IsServer; } } // internal bool IsKerberos { get { if (m_LastProtocolName == null) m_LastProtocolName = ProtocolName; return (object) m_LastProtocolName == (object) NegotiationInfoClass.Kerberos; } } internal bool IsNTLM { get { if (m_LastProtocolName == null) m_LastProtocolName = ProtocolName; return (object) m_LastProtocolName == (object) NegotiationInfoClass.NTLM; } } internal string Package { get { return m_Package; } } internal string ProtocolName { get { // NB: May return string.Empty if the auth is not done yet or failed if (m_ProtocolName==null) { NegotiationInfoClass negotiationInfo = null; if (IsValidContext) { negotiationInfo = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, m_SecurityContext, ContextAttribute.NegotiationInfo) as NegotiationInfoClass; if (IsCompleted) { if (negotiationInfo != null) { //cache it only when it's completed m_ProtocolName = negotiationInfo.AuthenticationPackage; } } } return negotiationInfo == null? string.Empty: negotiationInfo.AuthenticationPackage; } return m_ProtocolName; } } internal SecSizes Sizes { get { GlobalLog.Assert(IsCompleted && IsValidContext, "NTAuthentication#{0}::MaxDataSize|The context is not completed or invalid.", ValidationHelper.HashString(this)); if (m_Sizes == null) { m_Sizes = SSPIWrapper.QueryContextAttributes( GlobalSSPI.SSPIAuth, m_SecurityContext, ContextAttribute.Sizes ) as SecSizes; } return m_Sizes; } } internal ChannelBinding ChannelBinding { get { return m_ChannelBinding; } } // // .Ctors // // // Use only for client HTTP authentication // internal NTAuthentication(string package, NetworkCredential networkCredential, SpnToken spnToken, WebRequest request, ChannelBinding channelBinding) : this(false, package, networkCredential, spnToken.Spn, GetHttpContextFlags(request, spnToken.IsTrusted), request.GetWritingContext(), channelBinding) { // // In order to prevent a race condition where one request could // steal a connection from another request, before a handshake is // complete, we create a new Group for each authentication request. // if (package == NtlmClient.AuthType || package == NegotiateClient.AuthType) { m_UniqueUserId = (Interlocked.Increment(ref s_UniqueGroupId)).ToString(NumberFormatInfo.InvariantInfo) + m_UniqueUserId; } } // private static ContextFlags GetHttpContextFlags(WebRequest request, bool trustedSpn) { ContextFlags contextFlags = ContextFlags.Connection; if (request.ImpersonationLevel == TokenImpersonationLevel.Anonymous) throw new NotSupportedException(SR.GetString(SR.net_auth_no_anonymous_support)); else if(request.ImpersonationLevel == TokenImpersonationLevel.Identification) contextFlags |= ContextFlags.InitIdentify; else if(request.ImpersonationLevel == TokenImpersonationLevel.Delegation) contextFlags |= ContextFlags.Delegate; if (request.AuthenticationLevel == AuthenticationLevel.MutualAuthRequested || request.AuthenticationLevel == AuthenticationLevel.MutualAuthRequired) contextFlags |= ContextFlags.MutualAuth; // CBT: If the SPN came from an untrusted source we should tell the server by setting this flag if (!trustedSpn && ComNetOS.IsWin7Sp1orLater) contextFlags |= ContextFlags.UnverifiedTargetName; return contextFlags; } // // This constructor is for a general (non-HTTP) authentication handshake using SSPI // Works for both client and server sides. // // Security: we may need to impersonate on user behalf as to temporarily restore original thread token. [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)] internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string spn, ContextFlags requestedContextFlags, ContextAwareResult context, ChannelBinding channelBinding) { // // check if we're using DefaultCredentials // if (credential is SystemNetworkCredential) { // #if DEBUG GlobalLog.Assert(context == null || context.IdentityRequested, "NTAuthentication#{0}::.ctor|Authentication required when it wasn't expected. (Maybe Credentials was changed on another thread?)", ValidationHelper.HashString(this)); #endif WindowsIdentity w = context == null ? null : context.Identity; try { IDisposable ctx = w == null ? null : w.Impersonate(); if (ctx != null) { using (ctx) { Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding); } } else { ExecutionContext x = context == null ? null : context.ContextCopy; if (x == null) { Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding); } else { ExecutionContext.Run(x, s_InitializeCallback, new InitializeCallbackContext(this, isServer, package, credential, spn, requestedContextFlags, channelBinding)); } } } catch { // Prevent the impersonation from leaking to upstack exception filters. throw; } } else { Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding); } } // // This overload does not attmept 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, ContextFlags requestedContextFlags, ChannelBinding channelBinding) { Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding); } // // This overload always uses the default credentials for the process. // [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.ControlPrincipal)] internal NTAuthentication(bool isServer, string package, string spn, ContextFlags requestedContextFlags, ChannelBinding channelBinding) { try { using (WindowsIdentity.Impersonate(IntPtr.Zero)) { Initialize(isServer, package, SystemNetworkCredential.defaultCredential, spn, requestedContextFlags, channelBinding); } } catch { // Avoid exception filter attacks. throw; } } private class InitializeCallbackContext { internal InitializeCallbackContext(NTAuthentication thisPtr, bool isServer, string package, NetworkCredential credential, string spn, ContextFlags requestedContextFlags, ChannelBinding channelBinding) { this.thisPtr = thisPtr; this.isServer = isServer; this.package = package; this.credential = credential; this.spn = spn; this.requestedContextFlags = requestedContextFlags; this.channelBinding = channelBinding; } internal readonly NTAuthentication thisPtr; internal readonly bool isServer; internal readonly string package; internal readonly NetworkCredential credential; internal readonly string spn; internal readonly ContextFlags requestedContextFlags; internal readonly ChannelBinding channelBinding; } private static void InitializeCallback(object state) { InitializeCallbackContext context = (InitializeCallbackContext)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, ContextFlags requestedContextFlags, ChannelBinding channelBinding) { GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::.ctor() package:" + ValidationHelper.ToString(package) + " spn:" + ValidationHelper.ToString(spn) + " flags :" + requestedContextFlags.ToString()); m_TokenSize = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, package, true).MaxToken; m_IsServer = isServer; m_Spn = spn; m_SecurityContext = null; m_RequestedContextFlags = requestedContextFlags; m_Package = package; m_ChannelBinding = channelBinding; GlobalLog.Print("Peer SPN-> '" + m_Spn + "'"); // // check if we're using DefaultCredentials // if (credential is SystemNetworkCredential) { GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::.ctor(): using DefaultCredentials"); m_CredentialsHandle = SSPIWrapper.AcquireDefaultCredential( GlobalSSPI.SSPIAuth, package, (m_IsServer? CredentialUse.Inbound: CredentialUse.Outbound)); m_UniqueUserId = "/S"; // save off for unique connection marking ONLY used by HTTP client } else if (ComNetOS.IsWin7orLater) { unsafe { SafeSspiAuthDataHandle authData = null; try { SecurityStatus result = UnsafeNclNativeMethods.SspiHelper.SspiEncodeStringsAsAuthIdentity( credential.InternalGetUserName(), credential.InternalGetDomain(), credential.InternalGetPassword(), out authData); if (result != SecurityStatus.OK) { if (Logging.On) Logging.PrintError(Logging.Web, SR.GetString(SR.net_log_operation_failed_with_error, "SspiEncodeStringsAsAuthIdentity()", String.Format(CultureInfo.CurrentCulture, "0x{0:X}", (int)result))); throw new Win32Exception((int)result); } m_CredentialsHandle = SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPIAuth, package, (m_IsServer ? CredentialUse.Inbound : CredentialUse.Outbound), ref authData); } finally { if (authData != null) { authData.Close(); } } } } else { // // we're not using DefaultCredentials, we need a // AuthIdentity struct to contain credentials // SECREVIEW: // we'll save username/domain in temp strings, to avoid decrypting multiple times. // password is only used once // string username = credential.InternalGetUserName(); string domain = credential.InternalGetDomain(); // ATTN: // NetworkCredential class does not differentiate between null and "" but SSPI packages treat these cases differently // For NTLM we want to keep "" for Wdigest.Dll we should use null. AuthIdentity authIdentity = new AuthIdentity(username, credential.InternalGetPassword(), (object)package == (object)NegotiationInfoClass.WDigest && (domain == null || domain.Length == 0)? null: domain); m_UniqueUserId = domain + "/" + username + "/U"; // save off for unique connection marking ONLY used by HTTP client GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::.ctor(): using authIdentity:" + authIdentity.ToString()); m_CredentialsHandle = SSPIWrapper.AcquireCredentialsHandle( GlobalSSPI.SSPIAuth, package, (m_IsServer? CredentialUse.Inbound: CredentialUse.Outbound), ref authIdentity ); } } // // Methods // // This will return an client token when conducted authentication on server side' // This token can be used ofr impersanation // We use it to create a WindowsIdentity and hand it out to the server app. internal SafeCloseHandle GetContextToken(out SecurityStatus status) { GlobalLog.Assert(IsCompleted && IsValidContext, "NTAuthentication#{0}::GetContextToken|Should be called only when completed with success, currently is not!", ValidationHelper.HashString(this)); GlobalLog.Assert(IsServer, "NTAuthentication#{0}::GetContextToken|The method must not be called by the client side!", ValidationHelper.HashString(this)); if (!IsValidContext) { throw new Win32Exception((int)SecurityStatus.InvalidHandle); } SafeCloseHandle token = null; status = (SecurityStatus) SSPIWrapper.QuerySecurityContextToken( GlobalSSPI.SSPIAuth, m_SecurityContext, out token); return token; } internal SafeCloseHandle GetContextToken() { SecurityStatus status; SafeCloseHandle token = GetContextToken(out status); if (status != SecurityStatus.OK) { throw new Win32Exception((int)status); } return token; } internal void CloseContext() { if (m_SecurityContext != null && !m_SecurityContext.IsClosed) m_SecurityContext.Close(); } // // NTAuth::GetOutgoingBlob() // Created: 12-01-1999: L.M. // Description: // Accepts a base64 encoded incoming security blob and returns // a base 64 encoded outgoing security blob // // This method is for HttpWebRequest usage only as it has semantic bound to it internal string GetOutgoingBlob(string incomingBlob) { GlobalLog.Enter("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", incomingBlob); byte[] decodedIncomingBlob = null; if (incomingBlob != null && incomingBlob.Length > 0) { decodedIncomingBlob = Convert.FromBase64String(incomingBlob); } byte[] decodedOutgoingBlob = null; if ((IsValidContext || IsCompleted) && decodedIncomingBlob == null) { // we tried auth previously, now we got a null blob, we're done. this happens // with Kerberos & valid credentials on the domain but no ACLs on the resource GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob() null blob AND m_SecurityContext#" + ValidationHelper.HashString(m_SecurityContext) + "::Handle:[0x" + m_SecurityContext.ToString() + "]"); m_IsCompleted = true; } else { SecurityStatus statusCode; #if TRAVE try { #endif decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, true, out statusCode); #if TRAVE } catch (Exception exception) { if (NclUtilities.IsFatal(exception)) throw; GlobalLog.LeaveException("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", exception); throw; } #endif } string outgoingBlob = null; if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) { outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob); } //This is only for HttpWebRequest that does not need security context anymore if (IsCompleted) { string name = ProtocolName; // cache the only info needed from a completed context before closing it CloseContext(); } GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", outgoingBlob); return outgoingBlob; } // NTAuth::GetOutgoingBlob() // Created: 12-01-1999: L.M. // Description: // Accepts an incoming binary security blob and returns // an outgoing binary security blob internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out SecurityStatus statusCode) { GlobalLog.Enter("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", ((incomingBlob == null) ? "0" : incomingBlob.Length.ToString(NumberFormatInfo.InvariantInfo)) + " bytes"); List list = new List(2); if (incomingBlob != null) { list.Add(new SecurityBuffer(incomingBlob, BufferType.Token)); } if (m_ChannelBinding != null) { list.Add(new SecurityBuffer(m_ChannelBinding)); } SecurityBuffer[] inSecurityBufferArray = null; if (list.Count > 0) { inSecurityBufferArray = list.ToArray(); } SecurityBuffer outSecurityBuffer = new SecurityBuffer(m_TokenSize, BufferType.Token); bool firstTime = m_SecurityContext == null; try { if (!m_IsServer) { // client session statusCode = (SecurityStatus)SSPIWrapper.InitializeSecurityContext( GlobalSSPI.SSPIAuth, m_CredentialsHandle, ref m_SecurityContext, m_Spn, m_RequestedContextFlags, Endianness.Network, inSecurityBufferArray, outSecurityBuffer, ref m_ContextFlags); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob() SSPIWrapper.InitializeSecurityContext() returns statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); if (statusCode == SecurityStatus.CompleteNeeded) { SecurityBuffer[] inSecurityBuffers = new SecurityBuffer[1]; inSecurityBuffers[0] = outSecurityBuffer; statusCode = (SecurityStatus) SSPIWrapper.CompleteAuthToken( GlobalSSPI.SSPIAuth, ref m_SecurityContext, inSecurityBuffers ); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() SSPIWrapper.CompleteAuthToken() returns statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); outSecurityBuffer.token = null; } } else { // server session statusCode = (SecurityStatus)SSPIWrapper.AcceptSecurityContext( GlobalSSPI.SSPIAuth, m_CredentialsHandle, ref m_SecurityContext, m_RequestedContextFlags, Endianness.Network, inSecurityBufferArray, outSecurityBuffer, ref m_ContextFlags); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob() SSPIWrapper.AcceptSecurityContext() returns statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); } } finally { // // Assuming the ISC or ASC has referenced the credential on the first successful call, // we want to decrement the effective ref count by "disposing" it. // The real dispose will happen when the security context is closed. // Note if the first call was not successfull the handle is physically destroyed here // if (firstTime && m_CredentialsHandle != null) m_CredentialsHandle.Close(); } if (((int) statusCode & unchecked((int) 0x80000000)) != 0) { CloseContext(); m_IsCompleted = true; if (throwOnError) { Win32Exception exception = new Win32Exception((int) statusCode); GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", "Win32Exception:" + exception); throw exception; } GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", "null statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); return null; } else if (firstTime && m_CredentialsHandle != null) { // cache until it is pushed out by newly incoming handles SSPIHandleCache.CacheCredential(m_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 == SecurityStatus.OK) { // we're sucessfully done GlobalLog.Assert(statusCode == SecurityStatus.OK, "NTAuthentication#{0}::GetOutgoingBlob()|statusCode:[0x{1:x8}] ({2}) m_SecurityContext#{3}::Handle:[{4}] [STATUS != OK]", ValidationHelper.HashString(this), (int)statusCode, statusCode, ValidationHelper.HashString(m_SecurityContext), ValidationHelper.ToString(m_SecurityContext)); m_IsCompleted = true; } else { // we need to continue GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob() need continue statusCode:[0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + "] (" + statusCode.ToString() + ") m_SecurityContext#" + ValidationHelper.HashString(m_SecurityContext) + "::Handle:" + ValidationHelper.ToString(m_SecurityContext) + "]"); } // GlobalLog.Print("out token = " + outSecurityBuffer.ToString()); // GlobalLog.Dump(outSecurityBuffer.token); GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingBlob", "IsCompleted:" + IsCompleted.ToString()); return outSecurityBuffer.token; } // for Server side (IIS 6.0) see: \\netindex\Sources\inetsrv\iis\iisrearc\iisplus\ulw3\digestprovider.cxx // for Client side (HTTP.SYS) see: \\netindex\Sources\net\http\sys\ucauth.c internal string GetOutgoingDigestBlob(string incomingBlob, string requestMethod, string requestedUri, string realm, bool isClientPreAuth, bool throwOnError, out SecurityStatus statusCode) { GlobalLog.Enter("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob", incomingBlob); // second time call with 3 incoming buffers to select HTTP client. // we should get back a SecurityStatus.OK and a non null outgoingBlob. SecurityBuffer[] inSecurityBuffers = null; SecurityBuffer outSecurityBuffer = new SecurityBuffer(m_TokenSize, isClientPreAuth ? BufferType.Parameters : BufferType.Token); bool firstTime = m_SecurityContext == null; try { if (!m_IsServer) { // client session if (!isClientPreAuth) { if (incomingBlob != null) { List list = new List(5); list.Add(new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(incomingBlob), BufferType.Token)); list.Add(new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(requestMethod), BufferType.Parameters)); list.Add(new SecurityBuffer(null, BufferType.Parameters)); list.Add(new SecurityBuffer(Encoding.Unicode.GetBytes(m_Spn), BufferType.TargetHost)); if (m_ChannelBinding != null) { list.Add(new SecurityBuffer(m_ChannelBinding)); } inSecurityBuffers = list.ToArray(); } statusCode = (SecurityStatus) SSPIWrapper.InitializeSecurityContext( GlobalSSPI.SSPIAuth, m_CredentialsHandle, ref m_SecurityContext, requestedUri, // this must match the Uri in the HTTP status line for the current request m_RequestedContextFlags, Endianness.Network, inSecurityBuffers, outSecurityBuffer, ref m_ContextFlags ); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() SSPIWrapper.InitializeSecurityContext() returns statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); } else { #if WDIGEST_PREAUTH inSecurityBuffers = new SecurityBuffer[] { new SecurityBuffer(null, BufferType.Token), new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(requestMethod), BufferType.Parameters), new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(requestedUri), BufferType.Parameters), new SecurityBuffer(null, BufferType.Parameters), outSecurityBuffer, }; statusCode = (SecurityStatus) SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, m_SecurityContext, inSecurityBuffers, 0); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() SSPIWrapper.MakeSignature() returns statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); #else statusCode = SecurityStatus.OK; GlobalLog.Assert("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob()", "Invalid code path."); #endif } } else { // server session List list = new List(6); list.Add(incomingBlob == null ? new SecurityBuffer(0, BufferType.Token) : new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(incomingBlob), BufferType.Token)); list.Add(requestMethod == null ? new SecurityBuffer(0, BufferType.Parameters) : new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(requestMethod), BufferType.Parameters)); list.Add(requestedUri == null ? new SecurityBuffer(0, BufferType.Parameters) : new SecurityBuffer(WebHeaderCollection.HeaderEncoding.GetBytes(requestedUri), BufferType.Parameters)); list.Add(new SecurityBuffer(0, BufferType.Parameters)); list.Add(realm == null ? new SecurityBuffer(0, BufferType.Parameters) : new SecurityBuffer(Encoding.Unicode.GetBytes(realm), BufferType.Parameters)); if (m_ChannelBinding != null) { list.Add(new SecurityBuffer(m_ChannelBinding)); } inSecurityBuffers = list.ToArray(); statusCode = (SecurityStatus) SSPIWrapper.AcceptSecurityContext( GlobalSSPI.SSPIAuth, m_CredentialsHandle, ref m_SecurityContext, m_RequestedContextFlags, Endianness.Network, inSecurityBuffers, outSecurityBuffer, ref m_ContextFlags ); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() SSPIWrapper.AcceptSecurityContext() returns statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); if (statusCode == SecurityStatus.CompleteNeeded) { inSecurityBuffers[4] = outSecurityBuffer; statusCode = (SecurityStatus) SSPIWrapper.CompleteAuthToken( GlobalSSPI.SSPIAuth, ref m_SecurityContext, inSecurityBuffers ); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() SSPIWrapper.CompleteAuthToken() returns statusCode:0x" + ((int)statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); outSecurityBuffer.token = null; } } } finally { // // Assuming the ISC or ASC has referenced the credential on the first successful call, // we want to decrement the effective ref count by "disposing" it. // The real dispose will happen when the security context is closed. // Note if the first call was not successfull the handle is physically destroyed here // if (firstTime && m_CredentialsHandle != null) m_CredentialsHandle.Close(); } if (((int) statusCode & unchecked((int) 0x80000000)) != 0) { CloseContext(); if (throwOnError) { Win32Exception exception = new Win32Exception((int) statusCode); GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob", "Win32Exception:" + exception); throw exception; } GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob", "null statusCode:0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + " (" + statusCode.ToString() + ")"); return null; } else if (firstTime && m_CredentialsHandle != null) { // cache until it is pushed out by newly incoming handles SSPIHandleCache.CacheCredential(m_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 if (statusCode == SecurityStatus.OK) { // we're done, cleanup m_IsCompleted = true; } else { // we need to continue GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() need continue statusCode:[0x" + ((int) statusCode).ToString("x8", NumberFormatInfo.InvariantInfo) + "] (" + statusCode.ToString() + ") m_SecurityContext#" + ValidationHelper.HashString(m_SecurityContext) + "::Handle:" + ValidationHelper.ToString(m_SecurityContext) + "]"); } GlobalLog.Print("out token = " + outSecurityBuffer.ToString()); GlobalLog.Dump(outSecurityBuffer.token); GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob() IsCompleted:" + IsCompleted.ToString()); byte[] decodedOutgoingBlob = outSecurityBuffer.token; string outgoingBlob = null; if (decodedOutgoingBlob!=null && decodedOutgoingBlob.Length>0) { outgoingBlob = WebHeaderCollection.HeaderEncoding.GetString(decodedOutgoingBlob, 0, outSecurityBuffer.size); } GlobalLog.Leave("NTAuthentication#" + ValidationHelper.HashString(this) + "::GetOutgoingDigestBlob", outgoingBlob); return outgoingBlob; } 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.GetString(SR.net_io_out_range, maxCount)); } } catch(Exception e) { if (!NclUtilities.IsFatal(e)){ GlobalLog.Assert(false, "NTAuthentication#" + ValidationHelper.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(signautre), DATA and Padding SecurityBuffer[] securityBuffer = new SecurityBuffer[3]; securityBuffer[0] = new SecurityBuffer(output, 4, sizes.SecurityTrailer, BufferType.Token); securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.SecurityTrailer, count, BufferType.Data); securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.SecurityTrailer + count, sizes.BlockSize, BufferType.Padding); int errorCode; if (IsConfidentialityFlag) { errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, sequenceNumber); } else { if (IsNTLM) securityBuffer[1].type |= BufferType.ReadOnlyFlag; errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, 0); } if (errorCode != 0) { GlobalLog.Print("NTAuthentication#" + ValidationHelper.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; unchecked { output[0] = (byte)((resultSize) & 0xFF); output[1] = (byte)(((resultSize)>>8) & 0xFF); output[2] = (byte)(((resultSize)>>16) & 0xFF); output[3] = (byte)(((resultSize)>>24) & 0xFF); } return resultSize+4; } internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) { if (offset < 0 || offset > (payload == null ? 0 : payload.Length)) { GlobalLog.Assert(false, "NTAuthentication#" + ValidationHelper.HashString(this) + "::Decrypt", "Argument 'offset' out of range."); throw new ArgumentOutOfRangeException("offset"); } if (count < 0 || count > (payload == null ? 0 : payload.Length - offset)) { GlobalLog.Assert(false, "NTAuthentication#" + ValidationHelper.HashString(this) + "::Decrypt", "Argument 'count' out of range."); throw new ArgumentOutOfRangeException("count"); } if (IsNTLM) return DecryptNtlm(payload, offset, count, out newOffset, expectedSeqNumber); // // Kerberos and up // SecurityBuffer[] securityBuffer = new SecurityBuffer[2]; securityBuffer[0] = new SecurityBuffer(payload, offset, count, BufferType.Stream); securityBuffer[1] = new SecurityBuffer(0, BufferType.Data); int errorCode; if (IsConfidentialityFlag) { errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, expectedSeqNumber); } else { errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, expectedSeqNumber); } if (errorCode != 0) { GlobalLog.Print("NTAuthentication#" + ValidationHelper.HashString(this) + "::Decrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); throw new Win32Exception(errorCode); } if (securityBuffer[1].type != BufferType.Data) throw new InternalException(); newOffset = securityBuffer[1].offset; return securityBuffer[1].size; } private string GetClientSpecifiedSpn() { GlobalLog.Assert(IsValidContext && IsCompleted, "NTAuthentication: Trying to get the client SPN before handshaking is done!"); string spn = SSPIWrapper.QueryContextAttributes(GlobalSSPI.SSPIAuth, m_SecurityContext, ContextAttribute.ClientSpecifiedSpn) as string; GlobalLog.Print("NTAuthentication: The client specified SPN is [" + spn + "]"); return spn; } // private int DecryptNtlm(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) { // For the most part the arguments are verified in Encrypt(). if (count < 16) { GlobalLog.Assert(false, "NTAuthentication#" + ValidationHelper.HashString(this) + "::DecryptNtlm", "Argument 'count' out of range."); throw new ArgumentOutOfRangeException("count"); } SecurityBuffer[] securityBuffer = new SecurityBuffer[2]; securityBuffer[0] = new SecurityBuffer(payload, offset, 16, BufferType.Token); securityBuffer[1] = new SecurityBuffer(payload, offset + 16, count-16, BufferType.Data); int errorCode; BufferType realDataType = BufferType.Data; if (IsConfidentialityFlag) { errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, expectedSeqNumber); } else { realDataType |= BufferType.ReadOnlyFlag; securityBuffer[1].type = realDataType; errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, expectedSeqNumber); } if (errorCode != 0) { GlobalLog.Print("NTAuthentication#" + ValidationHelper.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; } // // VerifySignature // // Adapted from Decrypt method above as a more generic message // signature verify method for SMTP AUTH GSSAPI (SASL). // Decrypt method, used NegotiateStream, couldn't be used due // to special cases for NTLM. // // See SmtpNegotiateAuthenticationModule class for caller. // internal int VerifySignature(byte[] buffer, int offset, int count) { // validate offset within length if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) { GlobalLog.Assert( false, "NTAuthentication#" + ValidationHelper.HashString(this) + "::VerifySignature", "Argument 'offset' out of range."); throw new ArgumentOutOfRangeException("offset"); } // validate count within offset and end of buffer if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) { GlobalLog.Assert( false, "NTAuthentication#" + ValidationHelper.HashString(this) + "::VerifySignature", "Argument 'count' out of range."); throw new ArgumentOutOfRangeException("count"); } // setup security buffers for ssp call // one points at signed data // two will receive payload if signature is valid SecurityBuffer[] securityBuffer = new SecurityBuffer[2]; securityBuffer[0] = new SecurityBuffer(buffer, offset, count, BufferType.Stream); securityBuffer[1] = new SecurityBuffer(0, BufferType.Data); // call SSP function int errorCode = SSPIWrapper.VerifySignature( GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, 0); // throw if error if (errorCode != 0) { GlobalLog.Print( "NTAuthentication#" + ValidationHelper.HashString(this) + "::VerifySignature() threw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); throw new Win32Exception(errorCode); } // not sure why this is here - retained from Encrypt code above if (securityBuffer[1].type != BufferType.Data) throw new InternalException(); // return validated payload size return securityBuffer[1].size; } // // MakeSignature // // Adapted from Encrypt method above as a more generic message // signing method for SMTP AUTH GSSAPI (SASL). // Encrypt method, used for NegotiateStream, put size at head of // message. Don't need that // // See SmtpNegotiateAuthenticationModule class for caller. // internal int MakeSignature( byte[] buffer, int offset, int count, ref byte[] output) { SecSizes sizes = Sizes; // alloc new output buffer if not supplied or too small int resultSize = count + sizes.MaxSignature; if (output == null || output.Length < resultSize) { output = new byte[resultSize]; } // make a copy of user data for in-place encryption Buffer.BlockCopy(buffer, offset, output, sizes.MaxSignature, count); // setup security buffers for ssp call SecurityBuffer[] securityBuffer = new SecurityBuffer[2]; securityBuffer[0] = new SecurityBuffer(output, 0, sizes.MaxSignature, BufferType.Token); securityBuffer[1] = new SecurityBuffer(output, sizes.MaxSignature, count, BufferType.Data); // call SSP Function int errorCode = SSPIWrapper.MakeSignature( GlobalSSPI.SSPIAuth, m_SecurityContext, securityBuffer, 0); // throw if error if (errorCode != 0) { GlobalLog.Print( "NTAuthentication#" + ValidationHelper.HashString(this) + "::Encrypt() throw Error = " + errorCode.ToString("x", NumberFormatInfo.InvariantInfo)); throw new Win32Exception(errorCode); } // return signed size return securityBuffer[0].size + securityBuffer[1].size; } } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] internal struct AuthIdentity { // see SEC_WINNT_AUTH_IDENTITY_W internal string UserName; internal int UserNameLength; internal string Domain; internal int DomainLength; internal string Password; internal int PasswordLength; internal int Flags; internal AuthIdentity(string userName, string password, string domain) { UserName = userName; UserNameLength = userName==null ? 0 : userName.Length; Password = password; PasswordLength = password==null ? 0 : password.Length; Domain = domain; DomainLength = domain==null ? 0 : domain.Length; // Flags are 2 for Unicode and 1 for ANSI. We use 2 on NT and 1 on Win9x. Flags = 2; } public override string ToString() { return ValidationHelper.ToString(Domain) + "\\" + ValidationHelper.ToString(UserName); } } #endif }