1 //------------------------------------------------------------------------------
2 // <copyright file="_NtlmClient.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.Collections;
9 using System.Security.Authentication.ExtendedProtection;
10 using System.Security.Permissions;
11 using System.Globalization;
13 internal class NtlmClient : ISessionAuthenticationModule {
15 internal const string AuthType = "NTLM";
16 internal static string Signature = AuthType.ToLower(CultureInfo.InvariantCulture);
17 internal static int SignatureSize = Signature.Length;
19 public Authorization Authenticate(string challenge, WebRequest webRequest, ICredentials credentials) {
20 GlobalLog.Print("NtlmClient::Authenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " calling DoAuthenticate()");
21 return DoAuthenticate(challenge, webRequest, credentials, false);
24 private Authorization DoAuthenticate(string challenge, WebRequest webRequest, ICredentials credentials, bool preAuthenticate) {
25 GlobalLog.Print("NtlmClient::DoAuthenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " preAuthenticate:" + preAuthenticate.ToString());
27 GlobalLog.Assert(credentials != null, "NtlmClient::DoAuthenticate()|credentials == null");
28 if (credentials == null) {
32 HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
34 GlobalLog.Assert(httpWebRequest != null, "NtlmClient::DoAuthenticate()|httpWebRequest == null");
35 GlobalLog.Assert(httpWebRequest.ChallengedUri != null, "NtlmClient::DoAuthenticate()|httpWebRequest.ChallengedUri == null");
37 NTAuthentication authSession = null;
38 string incoming = null;
40 if (!preAuthenticate) {
41 int index = AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
46 int blobBegin = index + SignatureSize;
49 // there may be multiple challenges. If the next character after the
50 // package name is not a comma then it is challenge data
52 if (challenge.Length > blobBegin && challenge[blobBegin] != ',') {
59 if (index >= 0 && challenge.Length > blobBegin)
61 // Strip other modules information in case of multiple challenges
62 // i.e do not take ", NTLM" as part of the following Negotiate blob
63 // Negotiate TlRMTVNTUAACAAAADgAOADgAAAA1wo ... MAbwBmAHQALgBjAG8AbQAAAAAA,NTLM
64 index = challenge.IndexOf(',', blobBegin);
66 incoming = challenge.Substring(blobBegin, index - blobBegin);
68 incoming = challenge.Substring(blobBegin);
72 authSession = httpWebRequest.CurrentAuthenticationState.GetSecurityContext(this);
73 GlobalLog.Print("NtlmClient::DoAuthenticate() key:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " retrieved authSession:" + ValidationHelper.HashString(authSession));
76 if (authSession==null) {
77 NetworkCredential NC = credentials.GetCredential(httpWebRequest.ChallengedUri, Signature);
78 GlobalLog.Print("NtlmClient::DoAuthenticate() GetCredential() returns:" + ValidationHelper.ToString(NC));
80 string username = string.Empty;
81 if (NC == null || (!(NC is SystemNetworkCredential) && (username = NC.InternalGetUserName()).Length == 0))
86 ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
87 if (policy != null && !policy.ShouldSendCredential(httpWebRequest.ChallengedUri, httpWebRequest, NC, this))
90 SpnToken spn = httpWebRequest.CurrentAuthenticationState.GetComputeSpn(httpWebRequest);
91 GlobalLog.Print("NtlmClient::Authenticate() ChallengedSpn:" + ValidationHelper.ToString(spn));
93 ChannelBinding binding = null;
94 if (httpWebRequest.CurrentAuthenticationState.TransportContext != null)
96 binding = httpWebRequest.CurrentAuthenticationState.TransportContext.GetChannelBinding(ChannelBindingKind.Endpoint);
100 new NTAuthentication(
107 GlobalLog.Print("NtlmClient::DoAuthenticate() setting SecurityContext for:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " to authSession:" + ValidationHelper.HashString(authSession));
108 httpWebRequest.CurrentAuthenticationState.SetSecurityContext(authSession, this);
111 string clientResponse = authSession.GetOutgoingBlob(incoming);
112 if (clientResponse==null) {
116 bool canShareConnection = httpWebRequest.UnsafeOrProxyAuthenticatedConnectionSharing;
117 if (canShareConnection) {
118 httpWebRequest.LockConnection = true;
121 // this is the first leg of an NTLM handshake,
122 // set the NtlmKeepAlive override *STRICTLY* only in this case.
123 httpWebRequest.NtlmKeepAlive = incoming==null;
125 return AuthenticationManager.GetGroupAuthorization(this, AuthType + " " + clientResponse, authSession.IsCompleted, authSession, canShareConnection, false);
128 public bool CanPreAuthenticate {
134 public Authorization PreAuthenticate(WebRequest webRequest, ICredentials credentials) {
135 GlobalLog.Print("NtlmClient::PreAuthenticate() webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " calling DoAuthenticate()");
136 return DoAuthenticate(null, webRequest, credentials, true);
139 public string AuthenticationType {
146 // called when getting the final blob on the 200 OK from the server
148 public bool Update(string challenge, WebRequest webRequest) {
149 GlobalLog.Print("NtlmClient::Update(): " + challenge);
151 HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
153 GlobalLog.Assert(httpWebRequest != null, "NtlmClient::Update()|httpWebRequest == null");
154 GlobalLog.Assert(httpWebRequest.ChallengedUri != null, "NtlmClient::Update()|httpWebRequest.ChallengedUri == null");
157 // try to retrieve the state of the ongoing handshake
159 NTAuthentication authSession = httpWebRequest.CurrentAuthenticationState.GetSecurityContext(this);
160 GlobalLog.Print("NtlmClient::Update() key:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " retrieved authSession:" + ValidationHelper.HashString(authSession));
162 if (authSession==null) {
163 GlobalLog.Print("NtlmClient::Update() null session returning true");
167 GlobalLog.Print("NtlmClient::Update() authSession.IsCompleted:" + authSession.IsCompleted.ToString());
169 if (!authSession.IsCompleted && httpWebRequest.CurrentAuthenticationState.StatusCodeMatch==httpWebRequest.ResponseStatusCode) {
170 GlobalLog.Print("NtlmClient::Update() still handshaking (based on status code) returning false");
174 ClearSession(httpWebRequest);
176 // now possibly close the ConnectionGroup after authentication is done.
177 if (!httpWebRequest.UnsafeOrProxyAuthenticatedConnectionSharing) {
178 GlobalLog.Print("NtlmClient::Update() releasing ConnectionGroup:" + httpWebRequest.GetConnectionGroupLine());
179 httpWebRequest.ServicePoint.ReleaseConnectionGroup(httpWebRequest.GetConnectionGroupLine());
182 // Extract the CBT we used and cache it for future requests that want to do preauth
183 httpWebRequest.ServicePoint.SetCachedChannelBinding(httpWebRequest.ChallengedUri, authSession.ChannelBinding);
185 GlobalLog.Print("NtlmClient::Update() session removed and ConnectionGorup released returning true");
189 public void ClearSession(WebRequest webRequest) {
190 HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
191 GlobalLog.Assert(httpWebRequest != null, "NtlmClient::ClearSession()|httpWebRequest == null");
192 httpWebRequest.CurrentAuthenticationState.ClearSession();
195 public bool CanUseDefaultCredentials {
201 }; // class NtlmClient
204 } // namespace System.Net