private byte[] DigestCalcResponse(DigestResponse result, byte[] ha1) { var md5 = new MD5Digest(); var ha2 = new byte[md5.GetDigestSize()]; var digestResponse = new byte[md5.GetDigestSize()]; // HA2 md5.BlockUpdate("AUTHENTICATE:".ToUtf8Bytes()); md5.BlockUpdate(result.DigestUri.ToUtf8Bytes()); if (result.QOP != QualityOfProtection.AuthenticationOnly) { md5.BlockUpdate(":00000000000000000000000000000000".ToUtf8Bytes()); } md5.DoFinal(ha2, 0); // The actual Digest Response md5.BlockUpdate(ha1.ToHexString().ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(result.Nonce.ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(result.NonceCountString().ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(result.CNonce.ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(GetQOPString(result.QOP).ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(ha2.ToHexString().ToUtf8Bytes()); md5.DoFinal(digestResponse); return(digestResponse); }
private static void SetDelegates(DigestResponse parameters) { if (parameters.algorithm == "MD5-sess") { HA1Method = HA1_MD5SESS_Method; } else { HA1Method = HA1_MD5_Method; } if (parameters.qop == "auth-int") { HA2Method = HA2_AuthInt_Method; } else { HA2Method = HA2_Auth_Method; } if (parameters.qop == null) { ResponseMethod = Response_UnspecifiedQoP_Method; } else { ResponseMethod = Response_SpecifiedQoP_Method; } }
public void CanWriteEscapedSecuence() { DigestResponse resp = new DigestResponse(); resp.Username = "******"er"; string expected = "username=\"us\\\"er\",nc=00000000,maxbuf=0"; Assert.AreEqual(expected, resp.ToString()); }
private static async Task <HttpResponseMessage> SendWithAuthAsync(HttpRequestMessage request, Uri authUri, ICredentials credentials, bool preAuthenticate, bool isProxyAuth, bool doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken) { if (preAuthenticate) { NetworkCredential credential = credentials.GetCredential(authUri, BasicScheme); if (credential != null) { SetBasicAuthToken(request, credential, isProxyAuth); } } HttpResponseMessage response = await InnerSendAsync(request, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { if (challenge.AuthenticationType == AuthenticationType.Digest) { var digestResponse = new DigestResponse(challenge.ChallengeData); if (await TrySetDigestAuthToken(request, challenge.Credential, digestResponse, isProxyAuth).ConfigureAwait(false)) { response.Dispose(); response = await InnerSendAsync(request, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); // Retry in case of nonce timeout in server. if (TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out string challengeData)) { digestResponse = new DigestResponse(challengeData); if (IsServerNonceStale(digestResponse) && await TrySetDigestAuthToken(request, challenge.Credential, digestResponse, isProxyAuth).ConfigureAwait(false)) { response.Dispose(); response = await InnerSendAsync(request, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); } } } } else if (challenge.AuthenticationType == AuthenticationType.Basic) { if (!preAuthenticate) { SetBasicAuthToken(request, challenge.Credential, isProxyAuth); response.Dispose(); response = await InnerSendAsync(request, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); } } } return(response); }
private byte[] CreateDigestResponse(ChallengeInfo challenge) { if ((challenge.QOP & QualityOfProtection.AuthenticationOnly) == 0) { throw new SaslException("Currently, DigestMD5Client only supports \"auth\" QOP value."); } // cipher is only used in "auth-conf", which we don't support yet. if (!challenge.Algorithm.EqualsOrdinalCI("md5-sess")) { throw new SaslException($"Invalid DIGEST-MD5 Algorithm: '{challenge.Algorithm}' - must be 'md5-sess'"); } if (!challenge.Realms.Contains(_realm)) { // Do we care? The server would reject us anyway if the Realm is invalid } var result = new DigestResponse { Username = _username, Realm = _realm, QOP = QualityOfProtection.AuthenticationOnly, Charset = "utf-8", NonceCount = 1, MaxBuf = 65536, Nonce = challenge.Nonce, DigestUri = "ldap/" + _host }; var cnonce = new byte[32]; _rng.GetBytes(cnonce); result.CNonce = Utilclass.Base64.Encode(cnonce); var ha1 = DigestCalcHa1(result); result.Response = DigestCalcResponse(result, ha1); var resultStr = result.ToString(); return(resultStr.ToUtf8Bytes()); }
public void CanWriteResponse() { DigestResponse resp = new DigestResponse(); resp.Username = "******"; resp.Realm = "nowhere.com"; resp.Nonce = "OA9BSXrbuRhWay"; resp.Cnonce = "OA9BSuZWMSpW8m"; resp.NonceCount = 16; resp.DigestUri = "acap/elwood.innosoft.com"; resp.Response = "6084c6db3fede7352c551284490fd0fc"; resp.Qop = "auth"; resp.MaxBuffer = 65536; resp.Cipher = "3des"; resp.Authzid = "user2"; resp.AuthParam = "ap"; resp.Charset = "utf-8"; string expected = "username=\"user\",realm=\"nowhere.com\",nonce=\"OA9BSXrbuRhWay\",cnonce=\"OA9BSuZWMSpW8m\",nc=00000010,qop=auth,digest-uri=\"acap/elwood.innosoft.com\",response=\"6084c6db3fede7352c551284490fd0fc\",maxbuf=65536,charset=utf-8,cipher=3des,authzid=\"user2\",auth-param=\"ap\""; Assert.AreEqual(expected, resp.ToString()); }
static void Main(string[] args) { //Simple test with wikipedia page : https://en.wikipedia.org/wiki/Digest_access_authentication DigestResponse resp = new DigestResponse { username = "******", realm = "*****@*****.**", nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093", uri = "/dir/index.html", qop = "auth", nc = "00000001", cnonce = "0a4f113b", opaque = "5ccc069c403ebaf9f0171e9517f40e41", algorithm = null, method = "GET" }; Console.WriteLine(DigestTool.GetExpectedResponse(resp, "Circle Of Life")); Console.ReadKey(); }
private byte[] DigestCalcHa1(DigestResponse result) { var md5 = new MD5Digest(); byte[] hash = new byte[md5.GetDigestSize()]; byte[] ha1 = new byte[md5.GetDigestSize()]; md5.BlockUpdate(result.Username.ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(result.Realm.ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(_password); md5.DoFinal(hash); md5.BlockUpdate(hash); md5.BlockUpdate(Colon); md5.BlockUpdate(result.Nonce.ToUtf8Bytes()); md5.BlockUpdate(Colon); md5.BlockUpdate(result.CNonce.ToUtf8Bytes()); md5.DoFinal(ha1); return(ha1); }
public static string GetDigestTokenForCredential(NetworkCredential credential, string httpMethod, string pathAndQuery, string content, DigestResponse digestResponse) { StringBuilder sb = StringBuilderCache.Acquire(); // It is mandatory for servers to implement sha-256 per RFC 7616 // Keep MD5 for backward compatibility. string algorithm; if (digestResponse.Parameters.TryGetValue(Algorithm, out algorithm)) { if (algorithm != Sha256 && algorithm != Md5 && algorithm != Sha256Sess && algorithm != MD5Sess) { return(null); } } else { algorithm = Md5; } // Check if nonce is there in challenge string nonce; if (!digestResponse.Parameters.TryGetValue(Nonce, out nonce)) { return(null); } // opaque token may or may not exist string opaque; digestResponse.Parameters.TryGetValue(Opaque, out opaque); string realm; if (!digestResponse.Parameters.TryGetValue(Realm, out realm)) { return(null); } // Add username string userhash; if (digestResponse.Parameters.TryGetValue(UserHash, out userhash) && userhash == "true") { sb.AppendKeyValue(Username, ComputeHash(credential.UserName + ":" + realm, algorithm)); sb.AppendKeyValue(UserHash, userhash, includeQuotes: false); } else { string usernameStar; if (IsInputEncoded5987(credential.UserName, out usernameStar)) { sb.AppendKeyValue(UsernameStar, usernameStar, includeQuotes: false); } else { sb.AppendKeyValue(Username, credential.UserName); } } // Add realm if (realm != string.Empty) { sb.AppendKeyValue(Realm, realm); } // Add nonce sb.AppendKeyValue(Nonce, nonce); // Add uri sb.AppendKeyValue(Uri, pathAndQuery); // Set qop, default is auth string qop = Auth; if (digestResponse.Parameters.ContainsKey(Qop)) { // Check if auth-int present in qop string int index1 = digestResponse.Parameters[Qop].IndexOf(AuthInt); if (index1 != -1) { // Get index of auth if present in qop string int index2 = digestResponse.Parameters[Qop].IndexOf(Auth); // If index2 < index1, auth option is available // If index2 == index1, check if auth option available later in string after auth-int. if (index2 == index1) { index2 = digestResponse.Parameters[Qop].IndexOf(Auth, index1 + AuthInt.Length); if (index2 == -1) { qop = AuthInt; } } } } // Set cnonce string cnonce = GetRandomAlphaNumericString(); // Calculate response string a1 = credential.UserName + ":" + realm + ":" + credential.Password; if (algorithm.IndexOf("sess") != -1) { a1 = ComputeHash(a1, algorithm) + ":" + nonce + ":" + cnonce; } string a2 = httpMethod + ":" + pathAndQuery; if (qop == AuthInt) { a2 = a2 + ":" + ComputeHash(content ?? string.Empty, algorithm); } string response = ComputeHash(ComputeHash(a1, algorithm) + ":" + nonce + ":" + DigestResponse.NonceCount + ":" + cnonce + ":" + qop + ":" + ComputeHash(a2, algorithm), algorithm); // Add response sb.AppendKeyValue(Response, response); // Add algorithm sb.AppendKeyValue(Algorithm, algorithm, includeQuotes: false); // Add opaque if (opaque != null) { sb.AppendKeyValue(Opaque, opaque); } // Add qop sb.AppendKeyValue(Qop, qop, includeQuotes: false); // Add nc sb.AppendKeyValue(NC, DigestResponse.NonceCount, includeQuotes: false); // Add cnonce sb.AppendKeyValue(CNonce, cnonce, includeComma: false); return(StringBuilderCache.GetStringAndRelease(sb)); }
public static bool IsServerNonceStale(DigestResponse digestResponse) { string stale = null; return(digestResponse.Parameters.TryGetValue(Stale, out stale) && stale == "true"); }
public async static Task <bool> TrySetDigestAuthToken(HttpRequestMessage request, ICredentials credentials, DigestResponse digestResponse, string authHeader) { NetworkCredential credential = credentials.GetCredential(request.RequestUri, Digest); if (credential == null) { return(false); } string parameter = await GetDigestTokenForCredential(credential, request, digestResponse).ConfigureAwait(false); // Any errors in obtaining parameter return false if (string.IsNullOrEmpty(parameter)) { return(false); } if (authHeader == HttpKnownHeaderNames.Authorization) { request.Headers.Authorization = new AuthenticationHeaderValue(Digest, parameter); } else if (authHeader == HttpKnownHeaderNames.ProxyAuthorization) { request.Headers.ProxyAuthorization = new AuthenticationHeaderValue(Digest, parameter); } return(true); }
public static async Task <string> GetDigestTokenForCredential(NetworkCredential credential, HttpRequestMessage request, DigestResponse digestResponse) { StringBuilder sb = StringBuilderCache.Acquire(); // It is mandatory for servers to implement sha-256 per RFC 7616 // Keep MD5 for backward compatibility. string algorithm; bool isAlgorithmSpecified = digestResponse.Parameters.TryGetValue(Algorithm, out algorithm); if (isAlgorithmSpecified) { if (!algorithm.Equals(Sha256, StringComparison.OrdinalIgnoreCase) && !algorithm.Equals(Md5, StringComparison.OrdinalIgnoreCase) && !algorithm.Equals(Sha256Sess, StringComparison.OrdinalIgnoreCase) && !algorithm.Equals(MD5Sess, StringComparison.OrdinalIgnoreCase)) { if (NetEventSource.IsEnabled) { NetEventSource.Error(digestResponse, "Algorithm not supported: {algorithm}"); } return(null); } } else { algorithm = Md5; } // Check if nonce is there in challenge string nonce; if (!digestResponse.Parameters.TryGetValue(Nonce, out nonce)) { if (NetEventSource.IsEnabled) { NetEventSource.Error(digestResponse, "Nonce missing"); } return(null); } // opaque token may or may not exist string opaque; digestResponse.Parameters.TryGetValue(Opaque, out opaque); string realm; if (!digestResponse.Parameters.TryGetValue(Realm, out realm)) { if (NetEventSource.IsEnabled) { NetEventSource.Error(digestResponse, "Realm missing"); } return(null); } // Add username string userhash; if (digestResponse.Parameters.TryGetValue(UserHash, out userhash) && userhash == "true") { sb.AppendKeyValue(Username, ComputeHash(credential.UserName + ":" + realm, algorithm)); sb.AppendKeyValue(UserHash, userhash, includeQuotes: false); } else { if (HeaderUtilities.ContainsNonAscii(credential.UserName)) { string usernameStar = HeaderUtilities.Encode5987(credential.UserName); sb.AppendKeyValue(UsernameStar, usernameStar, includeQuotes: false); } else { sb.AppendKeyValue(Username, credential.UserName); } } // Add realm if (realm != string.Empty) { sb.AppendKeyValue(Realm, realm); } // Add nonce sb.AppendKeyValue(Nonce, nonce); // Add uri sb.AppendKeyValue(Uri, request.RequestUri.PathAndQuery); // Set qop, default is auth string qop = Auth; bool isQopSpecified = digestResponse.Parameters.ContainsKey(Qop); if (isQopSpecified) { // Check if auth-int present in qop string int index1 = digestResponse.Parameters[Qop].IndexOf(AuthInt, StringComparison.Ordinal); if (index1 != -1) { // Get index of auth if present in qop string int index2 = digestResponse.Parameters[Qop].IndexOf(Auth, StringComparison.Ordinal); // If index2 < index1, auth option is available // If index2 == index1, check if auth option available later in string after auth-int. if (index2 == index1) { index2 = digestResponse.Parameters[Qop].IndexOf(Auth, index1 + AuthInt.Length, StringComparison.Ordinal); if (index2 == -1) { qop = AuthInt; } } } } // Set cnonce string cnonce = GetRandomAlphaNumericString(); // Calculate response string a1 = credential.UserName + ":" + realm + ":" + credential.Password; if (algorithm.EndsWith("sess", StringComparison.OrdinalIgnoreCase)) { a1 = ComputeHash(a1, algorithm) + ":" + nonce + ":" + cnonce; } string a2 = request.Method.Method + ":" + request.RequestUri.PathAndQuery; if (qop == AuthInt) { string content = request.Content == null ? string.Empty : await request.Content.ReadAsStringAsync().ConfigureAwait(false); a2 = a2 + ":" + ComputeHash(content, algorithm); } string response; if (isQopSpecified) { response = ComputeHash(ComputeHash(a1, algorithm) + ":" + nonce + ":" + DigestResponse.NonceCount + ":" + cnonce + ":" + qop + ":" + ComputeHash(a2, algorithm), algorithm); } else { response = ComputeHash(ComputeHash(a1, algorithm) + ":" + nonce + ":" + ComputeHash(a2, algorithm), algorithm); } // Add response sb.AppendKeyValue(Response, response, includeComma: opaque != null || isAlgorithmSpecified || isQopSpecified); // Add opaque if (opaque != null) { sb.AppendKeyValue(Opaque, opaque, includeComma: isAlgorithmSpecified || isQopSpecified); } if (isAlgorithmSpecified) { // Add algorithm sb.AppendKeyValue(Algorithm, algorithm, includeQuotes: false, includeComma: isQopSpecified); } if (isQopSpecified) { // Add qop sb.AppendKeyValue(Qop, qop, includeQuotes: false); // Add nc sb.AppendKeyValue(NC, DigestResponse.NonceCount, includeQuotes: false); // Add cnonce sb.AppendKeyValue(CNonce, cnonce, includeComma: false); } return(StringBuilderCache.GetStringAndRelease(sb)); }
private static string Response_SpecifiedQoP_Method(DigestResponse p, string HA1, string HA2) => $"{HA1}:{p.nonce}:{p.nc}:{p.cnonce}:{p.qop}:{HA2}".ToMD5Hash();
private static string HA2_AuthInt_Method(DigestResponse p) => $"{p.method}:{p.uri}:{p.entityBody.ToMD5Hash()}".ToMD5Hash();
private static async Task <bool> TrySetDigestAuthToken(HttpRequestMessage request, NetworkCredential credential, DigestResponse digestResponse, bool isProxyAuth) { string parameter = await GetDigestTokenForCredential(credential, request, digestResponse).ConfigureAwait(false); // Any errors in obtaining parameter return false and we don't proceed with auth if (string.IsNullOrEmpty(parameter)) { return(false); } var headerValue = new AuthenticationHeaderValue(DigestScheme, parameter); SetRequestAuthenticationHeaderValue(request, headerValue, isProxyAuth); return(true); }
public static string GetDigestAuthChallengeResponse(NetworkCredential credential, string httpMethod, string pathAndQuery, string content, string challengeData) { var digestResponse = new DigestResponse(challengeData); return(GetDigestTokenForCredential(credential, httpMethod, pathAndQuery, content, digestResponse)); }
public static string GetExpectedResponse(DigestResponse parameters, string password) { SetDelegates(parameters); return(ResponseMethod(parameters, HA1Method(parameters, password), HA2Method(parameters))); }
public static bool ResponseIsCorrect(DigestResponse parameters, string password) { return(string.Equals(GetExpectedResponse(parameters, password), parameters.response)); }
private static async ValueTask <HttpResponseMessage> SendWithAuthAsync(HttpRequestMessage request, Uri authUri, bool async, ICredentials credentials, bool preAuthenticate, bool isProxyAuth, bool doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken) { // If preauth is enabled and this isn't proxy auth, try to get a basic credential from the // preauth credentials cache, and if successful, set an auth header for it onto the request. // Currently we only support preauth for Basic. bool performedBasicPreauth = false; if (preAuthenticate) { Debug.Assert(pool.PreAuthCredentials != null); NetworkCredential?credential; lock (pool.PreAuthCredentials) { // Just look for basic credentials. If in the future we support preauth // for other schemes, this will need to search in order of precedence. Debug.Assert(pool.PreAuthCredentials.GetCredential(authUri, NegotiateScheme) == null); Debug.Assert(pool.PreAuthCredentials.GetCredential(authUri, NtlmScheme) == null); Debug.Assert(pool.PreAuthCredentials.GetCredential(authUri, DigestScheme) == null); credential = pool.PreAuthCredentials.GetCredential(authUri, BasicScheme); } if (credential != null) { SetBasicAuthToken(request, credential, isProxyAuth); performedBasicPreauth = true; } } HttpResponseMessage response = await InnerSendAsync(request, async, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); if (TryGetAuthenticationChallenge(response, isProxyAuth, authUri, credentials, out AuthenticationChallenge challenge)) { switch (challenge.AuthenticationType) { case AuthenticationType.Digest: var digestResponse = new DigestResponse(challenge.ChallengeData); if (await TrySetDigestAuthToken(request, challenge.Credential, digestResponse, isProxyAuth).ConfigureAwait(false)) { response.Dispose(); response = await InnerSendAsync(request, async, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); // Retry in case of nonce timeout in server. if (TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out string?challengeData)) { digestResponse = new DigestResponse(challengeData); if (IsServerNonceStale(digestResponse) && await TrySetDigestAuthToken(request, challenge.Credential, digestResponse, isProxyAuth).ConfigureAwait(false)) { response.Dispose(); response = await InnerSendAsync(request, async, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); } } } break; case AuthenticationType.Basic: if (performedBasicPreauth) { if (NetEventSource.IsEnabled) { NetEventSource.AuthenticationError(authUri, $"Pre-authentication with {(isProxyAuth ? "proxy" : "server")} failed."); } break; } response.Dispose(); SetBasicAuthToken(request, challenge.Credential, isProxyAuth); response = await InnerSendAsync(request, async, isProxyAuth, doRequestAuth, pool, cancellationToken).ConfigureAwait(false); if (preAuthenticate) { switch (response.StatusCode) { case HttpStatusCode.ProxyAuthenticationRequired: case HttpStatusCode.Unauthorized: if (NetEventSource.IsEnabled) { NetEventSource.AuthenticationError(authUri, $"Pre-authentication with {(isProxyAuth ? "proxy" : "server")} failed."); } break; default: lock (pool.PreAuthCredentials !) { try { if (NetEventSource.IsEnabled) { NetEventSource.Info(pool.PreAuthCredentials, $"Adding Basic credential to cache, uri={authUri}, username={challenge.Credential.UserName}"); } pool.PreAuthCredentials.Add(authUri, BasicScheme, challenge.Credential); } catch (ArgumentException) { // The credential already existed. if (NetEventSource.IsEnabled) { NetEventSource.Info(pool.PreAuthCredentials, $"Basic credential present in cache, uri={authUri}, username={challenge.Credential.UserName}"); } } } break; } } break; } } if (NetEventSource.IsEnabled && response.StatusCode == HttpStatusCode.Unauthorized) { NetEventSource.AuthenticationError(authUri, $"{(isProxyAuth ? "Proxy" : "Server")} authentication failed."); } return(response); }
private static async ValueTask <bool> TrySetDigestAuthToken(HttpRequestMessage request, NetworkCredential credential, DigestResponse digestResponse, bool isProxyAuth) { string?parameter = await GetDigestTokenForCredential(credential, request, digestResponse).ConfigureAwait(false); // Any errors in obtaining parameter return false and we don't proceed with auth if (string.IsNullOrEmpty(parameter)) { if (NetEventSource.IsEnabled) { NetEventSource.AuthenticationError(request.RequestUri, $"Unable to find 'Digest' authentication token when authenticating with {(isProxyAuth ? "proxy" : "server")}"); } return(false); } var headerValue = new AuthenticationHeaderValue(DigestScheme, parameter); SetRequestAuthenticationHeaderValue(request, headerValue, isProxyAuth); return(true); }
private static string HA1_MD5_Method(DigestResponse p, string password) => $"{p.username}:{p.realm}:{password}".ToMD5Hash();
private static string HA2_Auth_Method(DigestResponse p) => $"{p.method}:{p.uri}".ToMD5Hash();
private static string HA1_MD5SESS_Method(DigestResponse p, string password) => $"{HA1_MD5_Method(p, password)}:{p.nonce}:{p.cnonce}".ToMD5Hash();
public static async Task <string> GetDigestTokenForCredential(NetworkCredential credential, HttpRequestMessage request, DigestResponse digestResponse) { StringBuilder sb = StringBuilderCache.Acquire(); // It is mandatory for servers to implement sha-256 per RFC 7616 // Keep MD5 for backward compatibility. string algorithm; if (digestResponse.Parameters.TryGetValue(Algorithm, out algorithm)) { if (algorithm != Sha256 && algorithm != Md5 && algorithm != Sha256Sess && algorithm != MD5Sess) { return(null); } } else { algorithm = Md5; } // Check if nonce is there in challenge string nonce; if (!digestResponse.Parameters.TryGetValue(Nonce, out nonce)) { return(null); } string opaque; if (!digestResponse.Parameters.TryGetValue(Opaque, out opaque)) { return(null); } string realm = digestResponse.Parameters.ContainsKey(Realm) ? digestResponse.Parameters[Realm] : string.Empty; // Add username string userhash; if (digestResponse.Parameters.TryGetValue(UserHash, out userhash) && userhash == "true") { sb.AppendKeyValue(Username, ComputeHash(credential.UserName + ":" + realm, algorithm)); sb.AppendKeyValue(UserHash, userhash, includeQuotes: false); } else { string usernameStar; if (HeaderUtilities.IsInputEncoded5987(credential.UserName, out usernameStar)) { sb.AppendKeyValue(UsernameStar, usernameStar, includeQuotes: false); } else { sb.AppendKeyValue(Username, credential.UserName); } } // Add realm if (realm != string.Empty) { sb.AppendKeyValue(Realm, realm); } // If nonce is same as previous request, update nonce count. if (nonce == digestResponse.Nonce) { digestResponse.NonceCount++; } // Add nonce sb.AppendKeyValue(Nonce, nonce); digestResponse.Nonce = nonce; // Add uri sb.AppendKeyValue(Uri, request.RequestUri.PathAndQuery); // Set qop, default is auth string qop = Auth; if (digestResponse.Parameters.ContainsKey(Qop)) { // Check if auth-int present in qop string int index1 = digestResponse.Parameters[Qop].IndexOf(AuthInt); if (index1 != -1) { // Get index of auth if present in qop string int index2 = digestResponse.Parameters[Qop].IndexOf(Auth); // If index2 < index1, auth option is available // If index2 == index1, check if auth option available later in string after auth-int. if (index2 == index1) { index2 = digestResponse.Parameters[Qop].IndexOf(Auth, index1 + AuthInt.Length); if (index2 == -1) { qop = AuthInt; } } } } // Set cnonce string cnonce = GetRandomAlphaNumericString(); // Calculate response string a1 = credential.UserName + ":" + realm + ":" + credential.Password; if (algorithm == Sha256Sess || algorithm == MD5Sess) { algorithm = algorithm == Sha256Sess ? Sha256 : Md5; a1 = ComputeHash(a1, algorithm) + ":" + nonce + ":" + cnonce; } string a2 = request.Method.Method + ":" + request.RequestUri.PathAndQuery; if (qop == AuthInt) { string content = request.Content == null ? string.Empty : await request.Content.ReadAsStringAsync(); a2 = a2 + ":" + ComputeHash(content, algorithm); } string response = ComputeHash(ComputeHash(a1, algorithm) + ":" + nonce + ":" + digestResponse.NonceCount.ToString("x8") + ":" + cnonce + ":" + qop + ":" + ComputeHash(a2, algorithm), algorithm); // Add response sb.AppendKeyValue(Response, response); // Add algorithm sb.AppendKeyValue(Algorithm, algorithm, includeQuotes: false); // Add opaque sb.AppendKeyValue(Opaque, opaque); // Add qop sb.AppendKeyValue(Qop, qop, includeQuotes: false); // Add nc sb.AppendKeyValue(NC, digestResponse.NonceCount.ToString("x8"), includeQuotes: false); // Add cnonce sb.AppendKeyValue(CNonce, cnonce, includeComma: false); return(StringBuilderCache.GetStringAndRelease(sb)); }