Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / whidbey / NetFxQFE / ndp / fx / src / Net / System / Net / _DigestClient.cs / 1 / _DigestClient.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace System.Net {
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Globalization;
internal class DigestClient : ISessionAuthenticationModule {
internal const string AuthType = "Digest";
internal static string Signature = AuthType.ToLower(CultureInfo.InvariantCulture);
internal static int SignatureSize = Signature.Length;
private static PrefixLookup challengeCache = new PrefixLookup();
private static readonly char[] singleSpaceArray = new char[]{' '};
// [....]: make sure WDigest fixes these bugs before we
// enable this code ("Windows OS" Product Studio database):
//
// 921024 1 Wdigest should support MD5, at least for explicit (non-default) credentials.
// 762116 2 WDigest should ignore directives that do not have a value
// 762115 3 WDigest should allow an application to retrieve the parsed domain directive
//
private static bool _WDigestAvailable;
static DigestClient() {
_WDigestAvailable = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, NegotiationInfoClass.WDigest)!=null;
}
public Authorization Authenticate(string challenge, WebRequest webRequest, ICredentials credentials) {
GlobalLog.Print("DigestClient::Authenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " calling DoAuthenticate()");
return DoAuthenticate(challenge, webRequest, credentials, false);
}
private Authorization DoAuthenticate(string challenge, WebRequest webRequest, ICredentials credentials, bool preAuthenticate) {
GlobalLog.Print("DigestClient::DoAuthenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " preAuthenticate:" + preAuthenticate.ToString());
GlobalLog.Assert(credentials != null, "DigestClient::DoAuthenticate()|credentials == null");
if (credentials==null) {
return null;
}
HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
GlobalLog.Assert(httpWebRequest != null, "DigestClient::DoAuthenticate()|httpWebRequest == null");
GlobalLog.Assert(httpWebRequest.ChallengedUri != null, "DigestClient::DoAuthenticate()|httpWebRequest.ChallengedUri == null");
// If it's default credentials, we support them on XP and up through WDigest.
NetworkCredential NC = credentials.GetCredential(httpWebRequest.ChallengedUri, DigestClient.Signature);
GlobalLog.Print("DigestClient::DoAuthenticate() GetCredential() returns:" + ValidationHelper.ToString(NC));
if (NC is SystemNetworkCredential) {
if (WDigestAvailable) {
return XPDoAuthenticate(challenge, httpWebRequest, credentials, preAuthenticate);
}
else {
return null;
}
}
HttpDigestChallenge digestChallenge;
if (!preAuthenticate) {
int index = AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
if (index < 0) {
return null;
}
digestChallenge = HttpDigest.Interpret(challenge, index, httpWebRequest);
}
else {
GlobalLog.Print("DigestClient::DoAuthenticate() looking up digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
digestChallenge = challengeCache.Lookup(httpWebRequest.ChallengedUri.AbsoluteUri) as HttpDigestChallenge;
}
if (digestChallenge==null) {
return null;
}
bool supported = CheckQOP(digestChallenge);
if (!supported) {
throw new NotSupportedException(SR.GetString(SR.net_QOPNotSupportedException, digestChallenge.QualityOfProtection));
}
if (preAuthenticate) {
GlobalLog.Print("DigestClient::DoAuthenticate() retrieved digestChallenge#" + ValidationHelper.HashString(digestChallenge) + " digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
digestChallenge = digestChallenge.CopyAndIncrementNonce();
digestChallenge.SetFromRequest(httpWebRequest);
}
if (NC==null) {
return null;
}
ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
if (policy != null && !policy.ShouldSendCredential(httpWebRequest.ChallengedUri, httpWebRequest, NC, this))
return null;
Authorization digestResponse = HttpDigest.Authenticate(digestChallenge, NC);
if (!preAuthenticate && digestResponse!=null) {
// add this to the cache of challenges so we can preauthenticate
string[] prefixes = digestChallenge.Domain==null ?
new string[]{httpWebRequest.ChallengedUri.GetParts(UriComponents.SchemeAndServer, UriFormat.UriEscaped)}
: digestChallenge.Domain.Split(singleSpaceArray);
// If Domain property is not set we associate Digest only with the server Uri namespace (used to do with whole server)
digestResponse.ProtectionRealm = digestChallenge.Domain==null ? null: prefixes;
for (int i=0; i=0) {
// find the next occurence of "auth"
index = challenge.QualityOfProtection.IndexOf(HttpDigest.SupportedQuality, index);
if (index<0) {
return false;
}
// if it's a whole word we're done
if ((index==0 || HttpDigest.ValidSeparator.IndexOf(challenge.QualityOfProtection[index - 1])>=0) &&
(index+HttpDigest.SupportedQuality.Length==challenge.QualityOfProtection.Length || HttpDigest.ValidSeparator.IndexOf(challenge.QualityOfProtection[index + HttpDigest.SupportedQuality.Length])>=0) ) {
break;
}
index += HttpDigest.SupportedQuality.Length;
}
}
return true;
}
public bool Update(string challenge, WebRequest webRequest) {
GlobalLog.Print("DigestClient::Update(): [" + challenge + "]");
HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
GlobalLog.Assert(httpWebRequest!=null, "DigestClient::Update()|httpWebRequest == null");
GlobalLog.Assert(httpWebRequest.ChallengedUri != null, "DigestClient::Update()|httpWebRequest.ChallengedUri == null");
// make sure WDigest fixes these bugs before we enable this code ("Windows OS"):
// 921024 1 WDigest should support MD5, at least for explicit (non-default) credentials.
// 762116 2 WDigest should ignore directives that do not have a value
// 762115 3 WDigest should allow an application to retrieve the parsed domain directive
if (httpWebRequest.CurrentAuthenticationState.GetSecurityContext(this) != null) {
return XPUpdate(challenge, httpWebRequest);
}
// here's how we know if the handshake is complete when we get the response back,
// (keeping in mind that we need to support stale credentials):
// !40X - complete & success
// 40X & stale=false - complete & failure
// 40X & stale=true - !complete
if (httpWebRequest.ResponseStatusCode!=httpWebRequest.CurrentAuthenticationState.StatusCodeMatch) {
GlobalLog.Print("DigestClient::Update(): no status code match. returning true");
return true;
}
int index = challenge==null ? -1 : AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
if (index < 0) {
GlobalLog.Print("DigestClient::Update(): no challenge. returning true");
return true;
}
int blobBegin = index + SignatureSize;
string incoming = null;
//
// there may be multiple challenges. If the next character after the
// package name is not a comma then it is challenge data
//
if (challenge.Length > blobBegin && challenge[blobBegin] != ',') {
++blobBegin;
}
else {
index = -1;
}
if (index >= 0 && challenge.Length > blobBegin) {
incoming = challenge.Substring(blobBegin);
}
HttpDigestChallenge digestChallenge = HttpDigest.Interpret(challenge, index, httpWebRequest);
if (digestChallenge==null) {
GlobalLog.Print("DigestClient::Update(): not a valid digest challenge. returning true");
return true;
}
GlobalLog.Print("DigestClient::Update(): returning digestChallenge.Stale:" + digestChallenge.Stale.ToString());
return !digestChallenge.Stale;
}
public bool CanUseDefaultCredentials {
get {
return WDigestAvailable;
}
}
internal static bool WDigestAvailable {
get {
return _WDigestAvailable;
}
}
public void ClearSession(WebRequest webRequest) {
HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
GlobalLog.Assert(httpWebRequest != null, "NtlmClient::ClearSession()|httpWebRequest == null");
// when we're using WDigest.dll we need to keep the NTAuthentication instance around, since it's in the
// challengeCache so remove the reference in the AuthenticationState to avoid closing it in ClearSession
#if WDIGEST_PREAUTH
httpWebRequest.CurrentAuthenticationState.SetSecurityContext(null, this);
#else
httpWebRequest.CurrentAuthenticationState.ClearSession();
#endif
}
// On Windows XP and up, WDigest.dll supports the Digest authentication scheme (in addition to
// support for HTTP client sides, it also supports HTTP server side and SASL) through SSPI.
private Authorization XPDoAuthenticate(string challenge, HttpWebRequest httpWebRequest, ICredentials credentials, bool preAuthenticate) {
GlobalLog.Print("DigestClient::XPDoAuthenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " preAuthenticate:" + preAuthenticate.ToString());
NTAuthentication authSession = null;
string incoming = null;
int index;
if (!preAuthenticate) {
index = AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
if (index < 0) {
return null;
}
authSession = httpWebRequest.CurrentAuthenticationState.GetSecurityContext(this);
GlobalLog.Print("DigestClient::XPDoAuthenticate() key:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " retrieved authSession:" + ValidationHelper.HashString(authSession));
incoming = RefineDigestChallenge(challenge, index);
}
else {
#if WDIGEST_PREAUTH
GlobalLog.Print("DigestClient::XPDoAuthenticate() looking up digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
authSession = challengeCache.Lookup(httpWebRequest.ChallengedUri.AbsoluteUri) as NTAuthentication;
if (authSession==null) {
return null;
}
#else
GlobalLog.Print("DigestClient::XPDoAuthenticate() looking up digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
HttpDigestChallenge digestChallenge = challengeCache.Lookup(httpWebRequest.ChallengedUri.AbsoluteUri) as HttpDigestChallenge;
if (digestChallenge==null) {
return null;
}
GlobalLog.Print("DigestClient::XPDoAuthenticate() retrieved digestChallenge#" + ValidationHelper.HashString(digestChallenge) + " digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
digestChallenge = digestChallenge.CopyAndIncrementNonce();
digestChallenge.SetFromRequest(httpWebRequest);
incoming = digestChallenge.ToBlob();
#endif
}
string rawUri = null;
#if WDIGEST_PREAUTH
if (authSession==null || preAuthenticate) {
// need rawUri for the call to MakeSignature
#else
if (authSession==null) {
#endif
UriComponents uriParts = 0;
if (httpWebRequest.CurrentMethod.ConnectRequest) {
uriParts = UriComponents.HostAndPort;
}
else if (httpWebRequest.UsesProxySemantics) {
uriParts = UriComponents.HttpRequestUrl;
}
else {
uriParts = UriComponents.PathAndQuery;
}
// here we use Address instead of ChallengedUri. This is because the
// Digest hash is generated using the uri as it is present on the wire
rawUri = httpWebRequest.Address.GetParts(uriParts, UriFormat.UriEscaped);
GlobalLog.Print("DigestClient::XPDoAuthenticate() rawUri:" + ValidationHelper.ToString(rawUri));
}
if (authSession==null) {
NetworkCredential NC = credentials.GetCredential(httpWebRequest.ChallengedUri, Signature);
GlobalLog.Print("DigestClient::XPDoAuthenticate() GetCredential() returns:" + ValidationHelper.ToString(NC));
if (NC == null || (!(NC is SystemNetworkCredential) && NC.InternalGetUserName().Length == 0))
{
return null;
}
ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
if (policy != null && !policy.ShouldSendCredential(httpWebRequest.ChallengedUri, httpWebRequest, NC, this))
return null;
authSession =
new NTAuthentication(
NegotiationInfoClass.WDigest,
NC,
rawUri,
httpWebRequest);
GlobalLog.Print("DigestClient::XPDoAuthenticate() setting SecurityContext for:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " to authSession:" + ValidationHelper.HashString(authSession));
httpWebRequest.CurrentAuthenticationState.SetSecurityContext(authSession, this);
}
SecurityStatus statusCode;
string clientResponse;
GlobalLog.Print("DigestClient::XPDoAuthenticate() incoming:" + ValidationHelper.ToString(incoming));
#if WDIGEST_PREAUTH
clientResponse = authSession.GetOutgoingDigestBlob(incoming, httpWebRequest.CurrentMethod.Name, rawUri, null, preAuthenticate, true, out statusCode);
#else
clientResponse = authSession.GetOutgoingDigestBlob(incoming, httpWebRequest.CurrentMethod.Name, rawUri, null, false, true, out statusCode);
#endif
GlobalLog.Print("DigestClient::XPDoAuthenticate() GetOutgoingDigestBlob(" + incoming + ") returns:" + ValidationHelper.ToString(clientResponse));
Authorization digestResponse = new Authorization(AuthType + " " + clientResponse, authSession.IsCompleted, string.Empty, authSession.IsMutualAuthFlag);
if (!preAuthenticate) {
// add this to the cache of challenges so we can preauthenticate
// use this DCR when avaialble to do this without calling HttpDigest.Interpret():
// 762115 3 WDigest should allow an application to retrieve the parsed domain directive
HttpDigestChallenge digestChallenge = HttpDigest.Interpret(incoming, -1, httpWebRequest);
string[] prefixes = digestChallenge.Domain==null ?
new string[]{httpWebRequest.ChallengedUri.GetParts(UriComponents.SchemeAndServer, UriFormat.UriEscaped)}
: digestChallenge.Domain.Split(singleSpaceArray);
// If Domain property is not set we associate Digest only with the server Uri namespace (used to do with whole server)
digestResponse.ProtectionRealm = digestChallenge.Domain==null ? null: prefixes;
for (int i=0; i= challenge.Length)
throw new ArgumentOutOfRangeException("challenge", challenge);
int blobBegin = index + SignatureSize;
//
// there may be multiple challenges. If the next character after the
// package name is not a comma then it is challenge data
//
if (challenge.Length > blobBegin && challenge[blobBegin] != ',') {
++blobBegin;
}
else {
index = -1;
}
if (index >= 0 && challenge.Length > blobBegin) {
incoming = challenge.Substring(blobBegin);
}
else
{
throw new ArgumentOutOfRangeException("challenge", challenge);
}
// now make sure there's nothing at the end of the challenge that is not part of the digest challenge
// this would happen if I have a Digest challenge followed by another challenge like ",NTLM,Negotiate"
// use this DCR when avaialble to do this without parsing:
// 762116 2 WDigest should ignore directives that do not have a value
int startingPoint = 0;
int start = startingPoint;
int offset;
bool valid = true;
string name, value;
HttpDigestChallenge digestChallenge = new HttpDigestChallenge();
for (;;) {
offset = start;
index = AuthenticationManager.SplitNoQuotes(incoming, ref offset);
GlobalLog.Print("DigestClient::XPDoAuthenticate() SplitNoQuotes() returning index:" + index + " offset:" + offset);
if (offset<0) {
break;
}
name = incoming.Substring(start, offset-start);
if (index<0) {
value = HttpDigest.unquote(incoming.Substring(offset+1));
}
else {
value = HttpDigest.unquote(incoming.Substring(offset+1, index-offset-1));
}
valid = digestChallenge.defineAttribute(name, value);
GlobalLog.Print("DigestClient::XPDoAuthenticate() defineAttribute(" + name + ", " + value + ") returns " + valid);
if (index<0 || !valid) {
break;
}
start = ++index;
}
GlobalLog.Print("DigestClient::XPDoAuthenticate() start:" + start + " offset:" + offset + " index:" + index + " valid:" + valid + " incoming.Length:" + incoming.Length + " incoming:" + incoming);
if ((!valid || offset<0) && start0;
}
else if (name.Equals(HttpDigest.DA_realm)) {
Realm = value;
}
else if (name.Equals(HttpDigest.DA_domain)) {
Domain = value;
}
else if (name.Equals(HttpDigest.DA_response)) {
}
else if (name.Equals(HttpDigest.DA_stale)) {
Stale = value.ToLower(CultureInfo.InvariantCulture).Equals("true");
}
else if (name.Equals(HttpDigest.DA_uri)) {
Uri = value;
}
else if (name.Equals(HttpDigest.DA_charset)) {
Charset = value;
}
else if (name.Equals(HttpDigest.DA_cipher)) {
// ignore
}
else if (name.Equals(HttpDigest.DA_username)) {
// ignore
}
else {
//
// the token is not recognized, this usually
// happens when there are multiple challenges
//
return false;
}
return true;
}
internal string ToBlob() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_realm, Realm, true));
if (Algorithm!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_algorithm, Algorithm, true));
}
if (Charset!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_charset, Charset, false));
}
if (Nonce!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_nonce, Nonce, true));
}
if (Uri!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_uri, Uri, true));
}
if (ClientNonce!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_cnonce, ClientNonce, true));
}
if (NonceCount>0) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_nc, NonceCount.ToString("x8", NumberFormatInfo.InvariantInfo), true));
}
if (QualityOfProtection!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_qop, QualityOfProtection, true));
}
if (Opaque!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_opaque, Opaque, true));
}
if (Domain!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_domain, Domain, true));
}
if (Stale) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_stale, "true", true));
}
return stringBuilder.ToString();
}
}
internal static class HttpDigest {
//
// these are the tokens defined by Digest
// http://www.ietf.org/rfc/rfc2831.txt
//
internal const string DA_algorithm = "algorithm";
internal const string DA_cnonce = "cnonce"; // client-nonce
internal const string DA_domain = "domain";
internal const string DA_nc = "nc"; // nonce-count
internal const string DA_nonce = "nonce";
internal const string DA_opaque = "opaque";
internal const string DA_qop = "qop"; // quality-of-protection
internal const string DA_realm = "realm";
internal const string DA_response = "response";
internal const string DA_stale = "stale";
internal const string DA_uri = "uri";
internal const string DA_username = "username";
internal const string DA_charset = "charset";
internal const string DA_cipher = "cipher";
internal const string SupportedQuality = "auth";
internal const string ValidSeparator = ", \"\'\t\r\n";
//
// consider internally caching the nonces sent to us by a server so that
// we can correctly send out nonce counts for subsequent requests
//
// used to create a random nonce
//
private static readonly RNGCryptoServiceProvider RandomGenerator = new RNGCryptoServiceProvider();
//
// this method parses the challenge and breaks it into the
// fundamental pieces that Digest defines and understands
//
internal static HttpDigestChallenge Interpret(string challenge, int startingPoint, HttpWebRequest httpWebRequest) {
HttpDigestChallenge digestChallenge = new HttpDigestChallenge();
digestChallenge.SetFromRequest(httpWebRequest);
//
// define the part of the challenge we really care about
//
startingPoint = startingPoint==-1 ? 0 : startingPoint + DigestClient.SignatureSize;
bool valid;
int start, offset, index;
string name, value;
// forst time parse looking for a charset="utf-8" directive
// not too bad, IIS 6.0, by default, sends this as the first directive.
// if the server does not send this we'll end up parsing twice.
start = startingPoint;
for (;;) {
offset = start;
index = AuthenticationManager.SplitNoQuotes(challenge, ref offset);
if (offset<0) {
break;
}
name = challenge.Substring(start, offset-start);
if (string.Compare(name, DA_charset, StringComparison.OrdinalIgnoreCase)==0) {
if (index<0) {
value = unquote(challenge.Substring(offset+1));
}
else {
value = unquote(challenge.Substring(offset+1, index-offset-1));
}
GlobalLog.Print("HttpDigest::Interpret() server provided a hint to use [" + value + "] encoding");
if (string.Compare(value, "utf-8", StringComparison.OrdinalIgnoreCase)==0) {
digestChallenge.UTF8Charset = true;
break;
}
}
if (index<0) {
break;
}
start = ++index;
}
// this time go through the directives, parse them and call defineAttribute()
start = startingPoint;
for (;;) {
offset = start;
index = AuthenticationManager.SplitNoQuotes(challenge, ref offset);
GlobalLog.Print("HttpDigest::Interpret() SplitNoQuotes() returning index:" + index.ToString() + " offset:" + offset.ToString());
if (offset<0) {
break;
}
name = challenge.Substring(start, offset-start);
if (index<0) {
value = unquote(challenge.Substring(offset+1));
}
else {
value = unquote(challenge.Substring(offset+1, index-offset-1));
}
if (digestChallenge.UTF8Charset) {
bool isAscii = true;
for (int i=0; i(char)0x7F) {
isAscii = false;
break;
}
}
if (!isAscii) {
GlobalLog.Print("HttpDigest::Interpret() UTF8 decoding required value:[" + value + "]");
byte[] bytes = new byte[value.Length];
for (int i=0; i(char)0x7F) {
GlobalLog.Print("HttpDigest::DetectCharset() found non ASCII character:[" + ((int)rawString[i]).ToString() + "] at offset i:[" + i.ToString() + "] charset:[" + charset.ToString() + "]");
// ----, but the only way we can tell if we can use default ANSI encoding is see
// in the encode/decode process there is no loss of information.
byte[] bytes = Encoding.Default.GetBytes(rawString);
string rawCopy = Encoding.Default.GetString(bytes);
charset = string.Compare(rawString, rawCopy, StringComparison.Ordinal)==0 ? Charset.ANSI : Charset.UTF8;
break;
}
}
GlobalLog.Print("HttpDigest::DetectCharset() rawString:[" + rawString + "] has charset:[" + charset.ToString() + "]");
return charset;
}
#if TRAVE
private static string Chars(string rawString) {
string returnString = "[";
for (int i=0; i0) {
returnString += ",";
}
returnString += ((int)rawString[i]).ToString();
}
return returnString + "]";
}
#endif // #if TRAVE
//
//
internal static Authorization Authenticate(HttpDigestChallenge digestChallenge, NetworkCredential NC) {
string username = NC.InternalGetUserName();
if (ValidationHelper.IsBlankString(username)) {
return null;
}
string password = NC.InternalGetPassword();
if (digestChallenge.QopPresent) {
if (digestChallenge.ClientNonce==null || digestChallenge.Stale) {
GlobalLog.Print("HttpDigest::Authenticate() QopPresent:True, need new nonce. digestChallenge.ClientNonce:" + ValidationHelper.ToString(digestChallenge.ClientNonce) + " digestChallenge.Stale:" + digestChallenge.Stale.ToString());
digestChallenge.ClientNonce = createNonce(32);
digestChallenge.NonceCount = 1;
}
else {
GlobalLog.Print("HttpDigest::Authenticate() QopPresent:True, reusing nonce. digestChallenge.NonceCount:" + digestChallenge.NonceCount.ToString());
digestChallenge.NonceCount++;
}
}
StringBuilder authorization = new StringBuilder();
//
// look at username & password, if it's not ASCII we need to attempt some
// kind of encoding because we need to calculate the hash on byte[]
//
Charset usernameCharset = DetectCharset(username);
if (!digestChallenge.UTF8Charset && usernameCharset==Charset.UTF8) {
GlobalLog.Print("HttpDigest::Authenticate() can't authenticate with UNICODE username. failing auth.");
return null;
}
Charset passwordCharset = DetectCharset(password);
if (!digestChallenge.UTF8Charset && passwordCharset==Charset.UTF8) {
GlobalLog.Print("HttpDigest::Authenticate() can't authenticate with UNICODE password. failing auth.");
return null;
}
if (digestChallenge.UTF8Charset) {
// on the wire always use UTF8 when the server supports it
authorization.Append(pair(DA_charset, "utf-8", false));
authorization.Append(",");
if (usernameCharset==Charset.UTF8) {
username = CharsetEncode(username, Charset.UTF8);
authorization.Append(pair(DA_username, username, true));
authorization.Append(",");
}
else {
authorization.Append(pair(DA_username, CharsetEncode(username, Charset.UTF8), true));
authorization.Append(",");
username = CharsetEncode(username, usernameCharset);
}
}
else {
// otherwise UTF8 is not required
username = CharsetEncode(username, usernameCharset);
authorization.Append(pair(DA_username, username, true));
authorization.Append(",");
}
password = CharsetEncode(password, passwordCharset);
// no special encoding for the realm since we're just going to echo it back (encoding must have happened on the server).
authorization.Append(pair(DA_realm, digestChallenge.Realm, true));
authorization.Append(",");
authorization.Append(pair(DA_nonce, digestChallenge.Nonce, true));
authorization.Append(",");
authorization.Append(pair(DA_uri, digestChallenge.Uri, true));
if (digestChallenge.QopPresent) {
if (digestChallenge.Algorithm!=null) {
// consider: should we default to "MD5" here? IE does
authorization.Append(",");
authorization.Append(pair(DA_algorithm, digestChallenge.Algorithm, true)); // IE sends quotes - IIS needs them
}
authorization.Append(",");
authorization.Append(pair(DA_cnonce, digestChallenge.ClientNonce, true));
authorization.Append(",");
authorization.Append(pair(DA_nc, digestChallenge.NonceCount.ToString("x8", NumberFormatInfo.InvariantInfo), false));
// RAID#47397
// send only the QualityOfProtection we're using
// since we support only "auth" that's what we will send out
authorization.Append(",");
authorization.Append(pair(DA_qop, SupportedQuality, true)); // IE sends quotes - IIS needs them
}
// warning: this must be computed here
string responseValue = HttpDigest.responseValue(digestChallenge, username, password);
if (responseValue==null) {
return null;
}
authorization.Append(",");
authorization.Append(pair(DA_response, responseValue, true)); // IE sends quotes - IIS needs them
if (digestChallenge.Opaque!=null) {
authorization.Append(",");
authorization.Append(pair(DA_opaque, digestChallenge.Opaque, true));
}
GlobalLog.Print("HttpDigest::Authenticate() digestChallenge.Stale:" + digestChallenge.Stale.ToString());
// completion is decided in Update()
Authorization finalAuthorization = new Authorization(DigestClient.AuthType + " " + authorization.ToString(), false);
return finalAuthorization;
}
internal static string unquote(string quotedString) {
return quotedString.Trim().Trim("\"".ToCharArray());
}
// Returns the string consisting of followed by
// an equal sign, followed by the in double-quotes
internal static string pair(string name, string value, bool quote) {
if (quote) {
return name + "=\"" + value + "\"";
}
return name + "=" + value;
}
//
// this method computes the response-value according to the
// rules described in RFC2831 section 2.1.2.1
//
private static string responseValue(HttpDigestChallenge challenge, string username, string password) {
string secretString = computeSecret(challenge, username, password);
if (secretString == null) {
return null;
}
// we assume auth here, since it's the only one we support, the check happened earlier
string dataString = challenge.Method + ":" + challenge.Uri;
if (dataString == null) {
return null;
}
string secret = hashString(secretString, challenge.MD5provider);
string hexMD2 = hashString(dataString, challenge.MD5provider);
string data =
challenge.Nonce + ":" +
(challenge.QopPresent ?
challenge.NonceCount.ToString("x8", NumberFormatInfo.InvariantInfo) + ":" +
challenge.ClientNonce + ":" +
SupportedQuality + ":" + // challenge.QualityOfProtection + ":" +
hexMD2
:
hexMD2);
return hashString(secret + ":" + data, challenge.MD5provider);
}
private static string computeSecret(HttpDigestChallenge challenge, string username, string password) {
if (challenge.Algorithm==null || string.Compare(challenge.Algorithm, "md5" ,StringComparison.OrdinalIgnoreCase)==0) {
return username + ":" + challenge.Realm + ":" + password;
}
else if (string.Compare(challenge.Algorithm, "md5-sess" ,StringComparison.OrdinalIgnoreCase)==0) {
return hashString(username + ":" + challenge.Realm + ":" + password, challenge.MD5provider) + ":" + challenge.Nonce + ":" + challenge.ClientNonce;
}
throw new NotSupportedException(SR.GetString(SR.net_HashAlgorithmNotSupportedException, challenge.Algorithm));
}
private static string hashString(string myString, MD5CryptoServiceProvider MD5provider) {
GlobalLog.Enter("HttpDigest::hashString", "[" + myString.Length.ToString() + ":" + myString + "]");
byte[] encodedBytes = new byte[myString.Length];
for (int i=0; i>4];
wa[dp++] = Uri.HexLowerChars[rawbytes[i]&0x0F];
}
return new string(wa);
}
/* returns a random nonce of given length */
private static string createNonce(int length) {
// we'd need less (half of that), but this makes the code much simpler
int bytesNeeded = length;
byte[] randomBytes = new byte[bytesNeeded];
char[] digits = new char[length];
RandomGenerator.GetBytes(randomBytes);
for (int i=0; i
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------------
namespace System.Net {
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Globalization;
internal class DigestClient : ISessionAuthenticationModule {
internal const string AuthType = "Digest";
internal static string Signature = AuthType.ToLower(CultureInfo.InvariantCulture);
internal static int SignatureSize = Signature.Length;
private static PrefixLookup challengeCache = new PrefixLookup();
private static readonly char[] singleSpaceArray = new char[]{' '};
// [....]: make sure WDigest fixes these bugs before we
// enable this code ("Windows OS" Product Studio database):
//
// 921024 1 Wdigest should support MD5, at least for explicit (non-default) credentials.
// 762116 2 WDigest should ignore directives that do not have a value
// 762115 3 WDigest should allow an application to retrieve the parsed domain directive
//
private static bool _WDigestAvailable;
static DigestClient() {
_WDigestAvailable = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, NegotiationInfoClass.WDigest)!=null;
}
public Authorization Authenticate(string challenge, WebRequest webRequest, ICredentials credentials) {
GlobalLog.Print("DigestClient::Authenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " calling DoAuthenticate()");
return DoAuthenticate(challenge, webRequest, credentials, false);
}
private Authorization DoAuthenticate(string challenge, WebRequest webRequest, ICredentials credentials, bool preAuthenticate) {
GlobalLog.Print("DigestClient::DoAuthenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] webRequest#" + ValidationHelper.HashString(webRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " preAuthenticate:" + preAuthenticate.ToString());
GlobalLog.Assert(credentials != null, "DigestClient::DoAuthenticate()|credentials == null");
if (credentials==null) {
return null;
}
HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
GlobalLog.Assert(httpWebRequest != null, "DigestClient::DoAuthenticate()|httpWebRequest == null");
GlobalLog.Assert(httpWebRequest.ChallengedUri != null, "DigestClient::DoAuthenticate()|httpWebRequest.ChallengedUri == null");
// If it's default credentials, we support them on XP and up through WDigest.
NetworkCredential NC = credentials.GetCredential(httpWebRequest.ChallengedUri, DigestClient.Signature);
GlobalLog.Print("DigestClient::DoAuthenticate() GetCredential() returns:" + ValidationHelper.ToString(NC));
if (NC is SystemNetworkCredential) {
if (WDigestAvailable) {
return XPDoAuthenticate(challenge, httpWebRequest, credentials, preAuthenticate);
}
else {
return null;
}
}
HttpDigestChallenge digestChallenge;
if (!preAuthenticate) {
int index = AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
if (index < 0) {
return null;
}
digestChallenge = HttpDigest.Interpret(challenge, index, httpWebRequest);
}
else {
GlobalLog.Print("DigestClient::DoAuthenticate() looking up digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
digestChallenge = challengeCache.Lookup(httpWebRequest.ChallengedUri.AbsoluteUri) as HttpDigestChallenge;
}
if (digestChallenge==null) {
return null;
}
bool supported = CheckQOP(digestChallenge);
if (!supported) {
throw new NotSupportedException(SR.GetString(SR.net_QOPNotSupportedException, digestChallenge.QualityOfProtection));
}
if (preAuthenticate) {
GlobalLog.Print("DigestClient::DoAuthenticate() retrieved digestChallenge#" + ValidationHelper.HashString(digestChallenge) + " digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
digestChallenge = digestChallenge.CopyAndIncrementNonce();
digestChallenge.SetFromRequest(httpWebRequest);
}
if (NC==null) {
return null;
}
ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
if (policy != null && !policy.ShouldSendCredential(httpWebRequest.ChallengedUri, httpWebRequest, NC, this))
return null;
Authorization digestResponse = HttpDigest.Authenticate(digestChallenge, NC);
if (!preAuthenticate && digestResponse!=null) {
// add this to the cache of challenges so we can preauthenticate
string[] prefixes = digestChallenge.Domain==null ?
new string[]{httpWebRequest.ChallengedUri.GetParts(UriComponents.SchemeAndServer, UriFormat.UriEscaped)}
: digestChallenge.Domain.Split(singleSpaceArray);
// If Domain property is not set we associate Digest only with the server Uri namespace (used to do with whole server)
digestResponse.ProtectionRealm = digestChallenge.Domain==null ? null: prefixes;
for (int i=0; i=0) {
// find the next occurence of "auth"
index = challenge.QualityOfProtection.IndexOf(HttpDigest.SupportedQuality, index);
if (index<0) {
return false;
}
// if it's a whole word we're done
if ((index==0 || HttpDigest.ValidSeparator.IndexOf(challenge.QualityOfProtection[index - 1])>=0) &&
(index+HttpDigest.SupportedQuality.Length==challenge.QualityOfProtection.Length || HttpDigest.ValidSeparator.IndexOf(challenge.QualityOfProtection[index + HttpDigest.SupportedQuality.Length])>=0) ) {
break;
}
index += HttpDigest.SupportedQuality.Length;
}
}
return true;
}
public bool Update(string challenge, WebRequest webRequest) {
GlobalLog.Print("DigestClient::Update(): [" + challenge + "]");
HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
GlobalLog.Assert(httpWebRequest!=null, "DigestClient::Update()|httpWebRequest == null");
GlobalLog.Assert(httpWebRequest.ChallengedUri != null, "DigestClient::Update()|httpWebRequest.ChallengedUri == null");
// make sure WDigest fixes these bugs before we enable this code ("Windows OS"):
// 921024 1 WDigest should support MD5, at least for explicit (non-default) credentials.
// 762116 2 WDigest should ignore directives that do not have a value
// 762115 3 WDigest should allow an application to retrieve the parsed domain directive
if (httpWebRequest.CurrentAuthenticationState.GetSecurityContext(this) != null) {
return XPUpdate(challenge, httpWebRequest);
}
// here's how we know if the handshake is complete when we get the response back,
// (keeping in mind that we need to support stale credentials):
// !40X - complete & success
// 40X & stale=false - complete & failure
// 40X & stale=true - !complete
if (httpWebRequest.ResponseStatusCode!=httpWebRequest.CurrentAuthenticationState.StatusCodeMatch) {
GlobalLog.Print("DigestClient::Update(): no status code match. returning true");
return true;
}
int index = challenge==null ? -1 : AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
if (index < 0) {
GlobalLog.Print("DigestClient::Update(): no challenge. returning true");
return true;
}
int blobBegin = index + SignatureSize;
string incoming = null;
//
// there may be multiple challenges. If the next character after the
// package name is not a comma then it is challenge data
//
if (challenge.Length > blobBegin && challenge[blobBegin] != ',') {
++blobBegin;
}
else {
index = -1;
}
if (index >= 0 && challenge.Length > blobBegin) {
incoming = challenge.Substring(blobBegin);
}
HttpDigestChallenge digestChallenge = HttpDigest.Interpret(challenge, index, httpWebRequest);
if (digestChallenge==null) {
GlobalLog.Print("DigestClient::Update(): not a valid digest challenge. returning true");
return true;
}
GlobalLog.Print("DigestClient::Update(): returning digestChallenge.Stale:" + digestChallenge.Stale.ToString());
return !digestChallenge.Stale;
}
public bool CanUseDefaultCredentials {
get {
return WDigestAvailable;
}
}
internal static bool WDigestAvailable {
get {
return _WDigestAvailable;
}
}
public void ClearSession(WebRequest webRequest) {
HttpWebRequest httpWebRequest = webRequest as HttpWebRequest;
GlobalLog.Assert(httpWebRequest != null, "NtlmClient::ClearSession()|httpWebRequest == null");
// when we're using WDigest.dll we need to keep the NTAuthentication instance around, since it's in the
// challengeCache so remove the reference in the AuthenticationState to avoid closing it in ClearSession
#if WDIGEST_PREAUTH
httpWebRequest.CurrentAuthenticationState.SetSecurityContext(null, this);
#else
httpWebRequest.CurrentAuthenticationState.ClearSession();
#endif
}
// On Windows XP and up, WDigest.dll supports the Digest authentication scheme (in addition to
// support for HTTP client sides, it also supports HTTP server side and SASL) through SSPI.
private Authorization XPDoAuthenticate(string challenge, HttpWebRequest httpWebRequest, ICredentials credentials, bool preAuthenticate) {
GlobalLog.Print("DigestClient::XPDoAuthenticate() challenge:[" + ValidationHelper.ToString(challenge) + "] httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " credentials#" + ValidationHelper.HashString(credentials) + " preAuthenticate:" + preAuthenticate.ToString());
NTAuthentication authSession = null;
string incoming = null;
int index;
if (!preAuthenticate) {
index = AuthenticationManager.FindSubstringNotInQuotes(challenge, Signature);
if (index < 0) {
return null;
}
authSession = httpWebRequest.CurrentAuthenticationState.GetSecurityContext(this);
GlobalLog.Print("DigestClient::XPDoAuthenticate() key:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " retrieved authSession:" + ValidationHelper.HashString(authSession));
incoming = RefineDigestChallenge(challenge, index);
}
else {
#if WDIGEST_PREAUTH
GlobalLog.Print("DigestClient::XPDoAuthenticate() looking up digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
authSession = challengeCache.Lookup(httpWebRequest.ChallengedUri.AbsoluteUri) as NTAuthentication;
if (authSession==null) {
return null;
}
#else
GlobalLog.Print("DigestClient::XPDoAuthenticate() looking up digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
HttpDigestChallenge digestChallenge = challengeCache.Lookup(httpWebRequest.ChallengedUri.AbsoluteUri) as HttpDigestChallenge;
if (digestChallenge==null) {
return null;
}
GlobalLog.Print("DigestClient::XPDoAuthenticate() retrieved digestChallenge#" + ValidationHelper.HashString(digestChallenge) + " digestChallenge for prefix:" + httpWebRequest.ChallengedUri.AbsoluteUri);
digestChallenge = digestChallenge.CopyAndIncrementNonce();
digestChallenge.SetFromRequest(httpWebRequest);
incoming = digestChallenge.ToBlob();
#endif
}
string rawUri = null;
#if WDIGEST_PREAUTH
if (authSession==null || preAuthenticate) {
// need rawUri for the call to MakeSignature
#else
if (authSession==null) {
#endif
UriComponents uriParts = 0;
if (httpWebRequest.CurrentMethod.ConnectRequest) {
uriParts = UriComponents.HostAndPort;
}
else if (httpWebRequest.UsesProxySemantics) {
uriParts = UriComponents.HttpRequestUrl;
}
else {
uriParts = UriComponents.PathAndQuery;
}
// here we use Address instead of ChallengedUri. This is because the
// Digest hash is generated using the uri as it is present on the wire
rawUri = httpWebRequest.Address.GetParts(uriParts, UriFormat.UriEscaped);
GlobalLog.Print("DigestClient::XPDoAuthenticate() rawUri:" + ValidationHelper.ToString(rawUri));
}
if (authSession==null) {
NetworkCredential NC = credentials.GetCredential(httpWebRequest.ChallengedUri, Signature);
GlobalLog.Print("DigestClient::XPDoAuthenticate() GetCredential() returns:" + ValidationHelper.ToString(NC));
if (NC == null || (!(NC is SystemNetworkCredential) && NC.InternalGetUserName().Length == 0))
{
return null;
}
ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
if (policy != null && !policy.ShouldSendCredential(httpWebRequest.ChallengedUri, httpWebRequest, NC, this))
return null;
authSession =
new NTAuthentication(
NegotiationInfoClass.WDigest,
NC,
rawUri,
httpWebRequest);
GlobalLog.Print("DigestClient::XPDoAuthenticate() setting SecurityContext for:" + ValidationHelper.HashString(httpWebRequest.CurrentAuthenticationState) + " to authSession:" + ValidationHelper.HashString(authSession));
httpWebRequest.CurrentAuthenticationState.SetSecurityContext(authSession, this);
}
SecurityStatus statusCode;
string clientResponse;
GlobalLog.Print("DigestClient::XPDoAuthenticate() incoming:" + ValidationHelper.ToString(incoming));
#if WDIGEST_PREAUTH
clientResponse = authSession.GetOutgoingDigestBlob(incoming, httpWebRequest.CurrentMethod.Name, rawUri, null, preAuthenticate, true, out statusCode);
#else
clientResponse = authSession.GetOutgoingDigestBlob(incoming, httpWebRequest.CurrentMethod.Name, rawUri, null, false, true, out statusCode);
#endif
GlobalLog.Print("DigestClient::XPDoAuthenticate() GetOutgoingDigestBlob(" + incoming + ") returns:" + ValidationHelper.ToString(clientResponse));
Authorization digestResponse = new Authorization(AuthType + " " + clientResponse, authSession.IsCompleted, string.Empty, authSession.IsMutualAuthFlag);
if (!preAuthenticate) {
// add this to the cache of challenges so we can preauthenticate
// use this DCR when avaialble to do this without calling HttpDigest.Interpret():
// 762115 3 WDigest should allow an application to retrieve the parsed domain directive
HttpDigestChallenge digestChallenge = HttpDigest.Interpret(incoming, -1, httpWebRequest);
string[] prefixes = digestChallenge.Domain==null ?
new string[]{httpWebRequest.ChallengedUri.GetParts(UriComponents.SchemeAndServer, UriFormat.UriEscaped)}
: digestChallenge.Domain.Split(singleSpaceArray);
// If Domain property is not set we associate Digest only with the server Uri namespace (used to do with whole server)
digestResponse.ProtectionRealm = digestChallenge.Domain==null ? null: prefixes;
for (int i=0; i= challenge.Length)
throw new ArgumentOutOfRangeException("challenge", challenge);
int blobBegin = index + SignatureSize;
//
// there may be multiple challenges. If the next character after the
// package name is not a comma then it is challenge data
//
if (challenge.Length > blobBegin && challenge[blobBegin] != ',') {
++blobBegin;
}
else {
index = -1;
}
if (index >= 0 && challenge.Length > blobBegin) {
incoming = challenge.Substring(blobBegin);
}
else
{
throw new ArgumentOutOfRangeException("challenge", challenge);
}
// now make sure there's nothing at the end of the challenge that is not part of the digest challenge
// this would happen if I have a Digest challenge followed by another challenge like ",NTLM,Negotiate"
// use this DCR when avaialble to do this without parsing:
// 762116 2 WDigest should ignore directives that do not have a value
int startingPoint = 0;
int start = startingPoint;
int offset;
bool valid = true;
string name, value;
HttpDigestChallenge digestChallenge = new HttpDigestChallenge();
for (;;) {
offset = start;
index = AuthenticationManager.SplitNoQuotes(incoming, ref offset);
GlobalLog.Print("DigestClient::XPDoAuthenticate() SplitNoQuotes() returning index:" + index + " offset:" + offset);
if (offset<0) {
break;
}
name = incoming.Substring(start, offset-start);
if (index<0) {
value = HttpDigest.unquote(incoming.Substring(offset+1));
}
else {
value = HttpDigest.unquote(incoming.Substring(offset+1, index-offset-1));
}
valid = digestChallenge.defineAttribute(name, value);
GlobalLog.Print("DigestClient::XPDoAuthenticate() defineAttribute(" + name + ", " + value + ") returns " + valid);
if (index<0 || !valid) {
break;
}
start = ++index;
}
GlobalLog.Print("DigestClient::XPDoAuthenticate() start:" + start + " offset:" + offset + " index:" + index + " valid:" + valid + " incoming.Length:" + incoming.Length + " incoming:" + incoming);
if ((!valid || offset<0) && start0;
}
else if (name.Equals(HttpDigest.DA_realm)) {
Realm = value;
}
else if (name.Equals(HttpDigest.DA_domain)) {
Domain = value;
}
else if (name.Equals(HttpDigest.DA_response)) {
}
else if (name.Equals(HttpDigest.DA_stale)) {
Stale = value.ToLower(CultureInfo.InvariantCulture).Equals("true");
}
else if (name.Equals(HttpDigest.DA_uri)) {
Uri = value;
}
else if (name.Equals(HttpDigest.DA_charset)) {
Charset = value;
}
else if (name.Equals(HttpDigest.DA_cipher)) {
// ignore
}
else if (name.Equals(HttpDigest.DA_username)) {
// ignore
}
else {
//
// the token is not recognized, this usually
// happens when there are multiple challenges
//
return false;
}
return true;
}
internal string ToBlob() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_realm, Realm, true));
if (Algorithm!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_algorithm, Algorithm, true));
}
if (Charset!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_charset, Charset, false));
}
if (Nonce!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_nonce, Nonce, true));
}
if (Uri!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_uri, Uri, true));
}
if (ClientNonce!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_cnonce, ClientNonce, true));
}
if (NonceCount>0) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_nc, NonceCount.ToString("x8", NumberFormatInfo.InvariantInfo), true));
}
if (QualityOfProtection!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_qop, QualityOfProtection, true));
}
if (Opaque!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_opaque, Opaque, true));
}
if (Domain!=null) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_domain, Domain, true));
}
if (Stale) {
stringBuilder.Append(",");
stringBuilder.Append(HttpDigest.pair(HttpDigest.DA_stale, "true", true));
}
return stringBuilder.ToString();
}
}
internal static class HttpDigest {
//
// these are the tokens defined by Digest
// http://www.ietf.org/rfc/rfc2831.txt
//
internal const string DA_algorithm = "algorithm";
internal const string DA_cnonce = "cnonce"; // client-nonce
internal const string DA_domain = "domain";
internal const string DA_nc = "nc"; // nonce-count
internal const string DA_nonce = "nonce";
internal const string DA_opaque = "opaque";
internal const string DA_qop = "qop"; // quality-of-protection
internal const string DA_realm = "realm";
internal const string DA_response = "response";
internal const string DA_stale = "stale";
internal const string DA_uri = "uri";
internal const string DA_username = "username";
internal const string DA_charset = "charset";
internal const string DA_cipher = "cipher";
internal const string SupportedQuality = "auth";
internal const string ValidSeparator = ", \"\'\t\r\n";
//
// consider internally caching the nonces sent to us by a server so that
// we can correctly send out nonce counts for subsequent requests
//
// used to create a random nonce
//
private static readonly RNGCryptoServiceProvider RandomGenerator = new RNGCryptoServiceProvider();
//
// this method parses the challenge and breaks it into the
// fundamental pieces that Digest defines and understands
//
internal static HttpDigestChallenge Interpret(string challenge, int startingPoint, HttpWebRequest httpWebRequest) {
HttpDigestChallenge digestChallenge = new HttpDigestChallenge();
digestChallenge.SetFromRequest(httpWebRequest);
//
// define the part of the challenge we really care about
//
startingPoint = startingPoint==-1 ? 0 : startingPoint + DigestClient.SignatureSize;
bool valid;
int start, offset, index;
string name, value;
// forst time parse looking for a charset="utf-8" directive
// not too bad, IIS 6.0, by default, sends this as the first directive.
// if the server does not send this we'll end up parsing twice.
start = startingPoint;
for (;;) {
offset = start;
index = AuthenticationManager.SplitNoQuotes(challenge, ref offset);
if (offset<0) {
break;
}
name = challenge.Substring(start, offset-start);
if (string.Compare(name, DA_charset, StringComparison.OrdinalIgnoreCase)==0) {
if (index<0) {
value = unquote(challenge.Substring(offset+1));
}
else {
value = unquote(challenge.Substring(offset+1, index-offset-1));
}
GlobalLog.Print("HttpDigest::Interpret() server provided a hint to use [" + value + "] encoding");
if (string.Compare(value, "utf-8", StringComparison.OrdinalIgnoreCase)==0) {
digestChallenge.UTF8Charset = true;
break;
}
}
if (index<0) {
break;
}
start = ++index;
}
// this time go through the directives, parse them and call defineAttribute()
start = startingPoint;
for (;;) {
offset = start;
index = AuthenticationManager.SplitNoQuotes(challenge, ref offset);
GlobalLog.Print("HttpDigest::Interpret() SplitNoQuotes() returning index:" + index.ToString() + " offset:" + offset.ToString());
if (offset<0) {
break;
}
name = challenge.Substring(start, offset-start);
if (index<0) {
value = unquote(challenge.Substring(offset+1));
}
else {
value = unquote(challenge.Substring(offset+1, index-offset-1));
}
if (digestChallenge.UTF8Charset) {
bool isAscii = true;
for (int i=0; i(char)0x7F) {
isAscii = false;
break;
}
}
if (!isAscii) {
GlobalLog.Print("HttpDigest::Interpret() UTF8 decoding required value:[" + value + "]");
byte[] bytes = new byte[value.Length];
for (int i=0; i(char)0x7F) {
GlobalLog.Print("HttpDigest::DetectCharset() found non ASCII character:[" + ((int)rawString[i]).ToString() + "] at offset i:[" + i.ToString() + "] charset:[" + charset.ToString() + "]");
// ----, but the only way we can tell if we can use default ANSI encoding is see
// in the encode/decode process there is no loss of information.
byte[] bytes = Encoding.Default.GetBytes(rawString);
string rawCopy = Encoding.Default.GetString(bytes);
charset = string.Compare(rawString, rawCopy, StringComparison.Ordinal)==0 ? Charset.ANSI : Charset.UTF8;
break;
}
}
GlobalLog.Print("HttpDigest::DetectCharset() rawString:[" + rawString + "] has charset:[" + charset.ToString() + "]");
return charset;
}
#if TRAVE
private static string Chars(string rawString) {
string returnString = "[";
for (int i=0; i0) {
returnString += ",";
}
returnString += ((int)rawString[i]).ToString();
}
return returnString + "]";
}
#endif // #if TRAVE
//
//
internal static Authorization Authenticate(HttpDigestChallenge digestChallenge, NetworkCredential NC) {
string username = NC.InternalGetUserName();
if (ValidationHelper.IsBlankString(username)) {
return null;
}
string password = NC.InternalGetPassword();
if (digestChallenge.QopPresent) {
if (digestChallenge.ClientNonce==null || digestChallenge.Stale) {
GlobalLog.Print("HttpDigest::Authenticate() QopPresent:True, need new nonce. digestChallenge.ClientNonce:" + ValidationHelper.ToString(digestChallenge.ClientNonce) + " digestChallenge.Stale:" + digestChallenge.Stale.ToString());
digestChallenge.ClientNonce = createNonce(32);
digestChallenge.NonceCount = 1;
}
else {
GlobalLog.Print("HttpDigest::Authenticate() QopPresent:True, reusing nonce. digestChallenge.NonceCount:" + digestChallenge.NonceCount.ToString());
digestChallenge.NonceCount++;
}
}
StringBuilder authorization = new StringBuilder();
//
// look at username & password, if it's not ASCII we need to attempt some
// kind of encoding because we need to calculate the hash on byte[]
//
Charset usernameCharset = DetectCharset(username);
if (!digestChallenge.UTF8Charset && usernameCharset==Charset.UTF8) {
GlobalLog.Print("HttpDigest::Authenticate() can't authenticate with UNICODE username. failing auth.");
return null;
}
Charset passwordCharset = DetectCharset(password);
if (!digestChallenge.UTF8Charset && passwordCharset==Charset.UTF8) {
GlobalLog.Print("HttpDigest::Authenticate() can't authenticate with UNICODE password. failing auth.");
return null;
}
if (digestChallenge.UTF8Charset) {
// on the wire always use UTF8 when the server supports it
authorization.Append(pair(DA_charset, "utf-8", false));
authorization.Append(",");
if (usernameCharset==Charset.UTF8) {
username = CharsetEncode(username, Charset.UTF8);
authorization.Append(pair(DA_username, username, true));
authorization.Append(",");
}
else {
authorization.Append(pair(DA_username, CharsetEncode(username, Charset.UTF8), true));
authorization.Append(",");
username = CharsetEncode(username, usernameCharset);
}
}
else {
// otherwise UTF8 is not required
username = CharsetEncode(username, usernameCharset);
authorization.Append(pair(DA_username, username, true));
authorization.Append(",");
}
password = CharsetEncode(password, passwordCharset);
// no special encoding for the realm since we're just going to echo it back (encoding must have happened on the server).
authorization.Append(pair(DA_realm, digestChallenge.Realm, true));
authorization.Append(",");
authorization.Append(pair(DA_nonce, digestChallenge.Nonce, true));
authorization.Append(",");
authorization.Append(pair(DA_uri, digestChallenge.Uri, true));
if (digestChallenge.QopPresent) {
if (digestChallenge.Algorithm!=null) {
// consider: should we default to "MD5" here? IE does
authorization.Append(",");
authorization.Append(pair(DA_algorithm, digestChallenge.Algorithm, true)); // IE sends quotes - IIS needs them
}
authorization.Append(",");
authorization.Append(pair(DA_cnonce, digestChallenge.ClientNonce, true));
authorization.Append(",");
authorization.Append(pair(DA_nc, digestChallenge.NonceCount.ToString("x8", NumberFormatInfo.InvariantInfo), false));
// RAID#47397
// send only the QualityOfProtection we're using
// since we support only "auth" that's what we will send out
authorization.Append(",");
authorization.Append(pair(DA_qop, SupportedQuality, true)); // IE sends quotes - IIS needs them
}
// warning: this must be computed here
string responseValue = HttpDigest.responseValue(digestChallenge, username, password);
if (responseValue==null) {
return null;
}
authorization.Append(",");
authorization.Append(pair(DA_response, responseValue, true)); // IE sends quotes - IIS needs them
if (digestChallenge.Opaque!=null) {
authorization.Append(",");
authorization.Append(pair(DA_opaque, digestChallenge.Opaque, true));
}
GlobalLog.Print("HttpDigest::Authenticate() digestChallenge.Stale:" + digestChallenge.Stale.ToString());
// completion is decided in Update()
Authorization finalAuthorization = new Authorization(DigestClient.AuthType + " " + authorization.ToString(), false);
return finalAuthorization;
}
internal static string unquote(string quotedString) {
return quotedString.Trim().Trim("\"".ToCharArray());
}
// Returns the string consisting of followed by
// an equal sign, followed by the in double-quotes
internal static string pair(string name, string value, bool quote) {
if (quote) {
return name + "=\"" + value + "\"";
}
return name + "=" + value;
}
//
// this method computes the response-value according to the
// rules described in RFC2831 section 2.1.2.1
//
private static string responseValue(HttpDigestChallenge challenge, string username, string password) {
string secretString = computeSecret(challenge, username, password);
if (secretString == null) {
return null;
}
// we assume auth here, since it's the only one we support, the check happened earlier
string dataString = challenge.Method + ":" + challenge.Uri;
if (dataString == null) {
return null;
}
string secret = hashString(secretString, challenge.MD5provider);
string hexMD2 = hashString(dataString, challenge.MD5provider);
string data =
challenge.Nonce + ":" +
(challenge.QopPresent ?
challenge.NonceCount.ToString("x8", NumberFormatInfo.InvariantInfo) + ":" +
challenge.ClientNonce + ":" +
SupportedQuality + ":" + // challenge.QualityOfProtection + ":" +
hexMD2
:
hexMD2);
return hashString(secret + ":" + data, challenge.MD5provider);
}
private static string computeSecret(HttpDigestChallenge challenge, string username, string password) {
if (challenge.Algorithm==null || string.Compare(challenge.Algorithm, "md5" ,StringComparison.OrdinalIgnoreCase)==0) {
return username + ":" + challenge.Realm + ":" + password;
}
else if (string.Compare(challenge.Algorithm, "md5-sess" ,StringComparison.OrdinalIgnoreCase)==0) {
return hashString(username + ":" + challenge.Realm + ":" + password, challenge.MD5provider) + ":" + challenge.Nonce + ":" + challenge.ClientNonce;
}
throw new NotSupportedException(SR.GetString(SR.net_HashAlgorithmNotSupportedException, challenge.Algorithm));
}
private static string hashString(string myString, MD5CryptoServiceProvider MD5provider) {
GlobalLog.Enter("HttpDigest::hashString", "[" + myString.Length.ToString() + ":" + myString + "]");
byte[] encodedBytes = new byte[myString.Length];
for (int i=0; i>4];
wa[dp++] = Uri.HexLowerChars[rawbytes[i]&0x0F];
}
return new string(wa);
}
/* returns a random nonce of given length */
private static string createNonce(int length) {
// we'd need less (half of that), but this makes the code much simpler
int bytesNeeded = length;
byte[] randomBytes = new byte[bytesNeeded];
char[] digits = new char[length];
RandomGenerator.GetBytes(randomBytes);
for (int i=0; i
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TemplateKey.cs
- BasicViewGenerator.cs
- PermissionAttributes.cs
- Scanner.cs
- SHA512.cs
- TabControl.cs
- InputLanguageManager.cs
- XmlDataImplementation.cs
- SqlDataSourceFilteringEventArgs.cs
- PixelFormats.cs
- ParamArrayAttribute.cs
- VectorKeyFrameCollection.cs
- DataGrid.cs
- ScriptingScriptResourceHandlerSection.cs
- Filter.cs
- DebugView.cs
- RenderDataDrawingContext.cs
- CardSpaceShim.cs
- BaseProcessProtocolHandler.cs
- ModulesEntry.cs
- ActiveDocumentEvent.cs
- DbParameterCollectionHelper.cs
- AvTrace.cs
- NumericUpDownAccelerationCollection.cs
- ProcessHostConfigUtils.cs
- ServiceOperationInvoker.cs
- CriticalFinalizerObject.cs
- ProfilePropertySettings.cs
- RepeatButtonAutomationPeer.cs
- DataBindEngine.cs
- ClientApiGenerator.cs
- SwitchDesigner.xaml.cs
- DeclaredTypeElement.cs
- OdbcConnection.cs
- ReceiveActivityDesigner.cs
- PowerModeChangedEventArgs.cs
- SingleStorage.cs
- ContainerSelectorGlyph.cs
- TileBrush.cs
- ContainerParaClient.cs
- TraceContextEventArgs.cs
- Int64AnimationBase.cs
- LineServicesCallbacks.cs
- BoundingRectTracker.cs
- BitmapEffectGroup.cs
- CollectionBuilder.cs
- EntityModelSchemaGenerator.cs
- IdentifierCreationService.cs
- HtmlControlAdapter.cs
- WebMessageEncodingBindingElement.cs
- SequenceQuery.cs
- BulletedListEventArgs.cs
- Quad.cs
- DataListItemCollection.cs
- PrinterUnitConvert.cs
- TemplateNodeContextMenu.cs
- _ListenerAsyncResult.cs
- XPathNodeIterator.cs
- FrameworkContentElementAutomationPeer.cs
- VirtualizingPanel.cs
- DesignTimeTemplateParser.cs
- ResourcePart.cs
- _SslSessionsCache.cs
- EasingFunctionBase.cs
- NativeMethods.cs
- ShaderEffect.cs
- DataGridViewButtonCell.cs
- ObjectView.cs
- NotFiniteNumberException.cs
- IndexerHelper.cs
- FlowDocumentView.cs
- Directory.cs
- JapaneseCalendar.cs
- InternalDispatchObject.cs
- ConnectionPoolRegistry.cs
- BaseDataListPage.cs
- ColumnMapVisitor.cs
- OracleString.cs
- SrgsRuleRef.cs
- ServerValidateEventArgs.cs
- DebugHandleTracker.cs
- Base64Encoding.cs
- EdmRelationshipRoleAttribute.cs
- NativeRecognizer.cs
- PropertyChangedEventArgs.cs
- PageEventArgs.cs
- ValidatedControlConverter.cs
- DrawingBrush.cs
- TransportChannelFactory.cs
- DataProviderNameConverter.cs
- ProgressBarBrushConverter.cs
- _AutoWebProxyScriptHelper.cs
- CompositionDesigner.cs
- StorageRoot.cs
- SiteMapNodeItem.cs
- EntityModelSchemaGenerator.cs
- Parallel.cs
- embossbitmapeffect.cs
- ActivityMarkupSerializationProvider.cs
- ResourcePool.cs