summaryrefslogtreecommitdiffstats
path: root/net/http/http_auth_handler_digest.cc
diff options
context:
space:
mode:
authorericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-27 03:19:42 +0000
committerericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-27 03:19:42 +0000
commitc3b35c2100dba30c517116bc9a5a4e4149c3a8e5 (patch)
treeff42c902c4ee9afd7864a2bda8e5e815a876bc76 /net/http/http_auth_handler_digest.cc
parente5be6612288df667ca6ae4a86060bc883a498eea (diff)
downloadchromium_src-c3b35c2100dba30c517116bc9a5a4e4149c3a8e5.zip
chromium_src-c3b35c2100dba30c517116bc9a5a4e4149c3a8e5.tar.gz
chromium_src-c3b35c2100dba30c517116bc9a5a4e4149c3a8e5.tar.bz2
Initial stab at http authentication (basic + digest) in new http stack.
General design: - class HttpAuth -- utility class for http-auth logic. - class HttpAuth::ChallengeTokenizer -- parsing of www-Authenticate headers. - class HttpAuthHandler -- base class for authentication schemes (inspired by nsIHttpAuthenticator) - class HttpAuthHandlerBasic : HttpAuthHandler -- logic for basic auth. - class HttpAuthHandlerDigest : HttpAuthHandler -- logic for digest auth. - The auth integration in HttpNetworkTransaction mimics that of HttpTransactionWinHttp: + HttpNetworkTransaction::ApplyAuth() -- set the authorization headers. + HttpNetworkTransaction::PopulateAuthChallenge() -- process the challenges. BUG=2346 Review URL: http://codereview.chromium.org/4063 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2658 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http/http_auth_handler_digest.cc')
-rw-r--r--net/http/http_auth_handler_digest.cc278
1 files changed, 278 insertions, 0 deletions
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
new file mode 100644
index 0000000..e665a0b
--- /dev/null
+++ b/net/http/http_auth_handler_digest.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http/http_auth_handler_digest.h"
+
+#include "base/md5.h"
+#include "base/string_util.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_util.h"
+
+// TODO(eroman): support qop=auth-int
+
+namespace net {
+
+// Digest authentication is specified in RFC 2617.
+// The expanded derivations are listed in the tables below.
+
+//==========+==========+==========================================+
+// qop |algorithm | response |
+//==========+==========+==========================================+
+// ? | ?, md5, | MD5(MD5(A1):nonce:MD5(A2)) |
+// | md5-sess | |
+//--------- +----------+------------------------------------------+
+// auth, | ?, md5, | MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2)) |
+// auth-int | md5-sess | |
+//==========+==========+==========================================+
+// qop |algorithm | A1 |
+//==========+==========+==========================================+
+// | ?, md5 | user:realm:password |
+//----------+----------+------------------------------------------+
+// | md5-sess | MD5(user:realm:password):nonce:cnonce |
+//==========+==========+==========================================+
+// qop |algorithm | A2 |
+//==========+==========+==========================================+
+// ?, auth | | req-method:req-uri |
+//----------+----------+------------------------------------------+
+// auth-int | | req-method:req-uri:MD5(req-entity-body) |
+//=====================+==========================================+
+
+
+// static
+std::string HttpAuthHandlerDigest::GenerateNonce() {
+ // This is how mozilla generates their cnonce -- a 16 digit hex string.
+ static const char domain[] = "0123456789abcdef";
+ std::string cnonce;
+ cnonce.reserve(16);
+ // TODO(eroman): use rand_util::RandIntSecure()
+ for (int i = 0; i < 16; ++i)
+ cnonce.push_back(domain[rand() % 16]);
+ return cnonce;
+}
+
+// static
+std::string HttpAuthHandlerDigest::QopToString(int qop) {
+ switch (qop) {
+ case QOP_AUTH:
+ return "auth";
+ case QOP_AUTH_INT:
+ return "auth-int";
+ default:
+ return "";
+ }
+}
+
+// static
+std::string HttpAuthHandlerDigest::AlgorithmToString(int algorithm) {
+ switch (algorithm) {
+ case ALGORITHM_MD5:
+ return "MD5";
+ case ALGORITHM_MD5_SESS:
+ return "MD5-sess";
+ default:
+ return "";
+ }
+}
+
+std::string HttpAuthHandlerDigest::GenerateCredentials(
+ const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy) {
+ // Generate a random client nonce.
+ std::string cnonce = GenerateNonce();
+
+ // The nonce-count should be incremented after re-use per the spec.
+ // This may not be possible when there are multiple connections to the
+ // server though:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=114451
+ // TODO(eroman): leave as 1 for now, and possibly permanently.
+ int nonce_count = 1;
+
+ // Extract the request method and path -- the meaning of 'path' is overloaded
+ // in certain cases, to be a hostname.
+ std::string method;
+ std::string path;
+ GetRequestMethodAndPath(request, proxy, &method, &path);
+
+ return AssembleCredentials(method, path,
+ // TODO(eroman): is this the right encoding?
+ WideToUTF8(username),
+ WideToUTF8(password),
+ cnonce, nonce_count);
+}
+
+void HttpAuthHandlerDigest::GetRequestMethodAndPath(
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy,
+ std::string* method,
+ std::string* path) const {
+ DCHECK(request);
+ DCHECK(proxy);
+
+ const GURL& url = request->url;
+
+ if (target_ == HttpAuth::AUTH_PROXY && url.SchemeIs("https")) {
+ *method = "CONNECT";
+ *path = url.host() + ":" + GetImplicitPort(url);
+ } else {
+ *method = request->method;
+ *path = HttpUtil::PathForRequest(url);
+ }
+}
+
+std::string HttpAuthHandlerDigest::AssembleResponseDigest(
+ const std::string& method,
+ const std::string& path,
+ const std::string& username,
+ const std::string& password,
+ const std::string& cnonce,
+ const std::string& nc) const {
+ // ha1 = MD5(A1)
+ std::string ha1 = MD5String(username + ":" + realm_ + ":" + password);
+ if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS)
+ ha1 = MD5String(ha1 + ":" + nonce_ + ":" + cnonce);
+
+ // ha2 = MD5(A2)
+ // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int.
+ std::string ha2 = MD5String(method + ":" + path);
+
+ std::string nc_part;
+ if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
+ nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":";
+ }
+
+ return MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
+}
+
+std::string HttpAuthHandlerDigest::AssembleCredentials(
+ const std::string& method,
+ const std::string& path,
+ const std::string& username,
+ const std::string& password,
+ const std::string& cnonce,
+ int nonce_count) const {
+ // the nonce-count is an 8 digit hex string.
+ std::string nc = StringPrintf("%08x", nonce_count);
+
+ std::string authorization = std::string("Digest username=") +
+ HttpUtil::Quote(username);
+ authorization += ", realm=" + HttpUtil::Quote(realm_);
+ authorization += ", nonce=" + HttpUtil::Quote(nonce_);
+ authorization += ", uri=" + HttpUtil::Quote(path);
+
+ if (algorithm_ != ALGORITHM_UNSPECIFIED) {
+ authorization += ", algorithm=" + AlgorithmToString(algorithm_);
+ }
+ std::string response = AssembleResponseDigest(method, path, username,
+ password, cnonce, nc);
+ // No need to call HttpUtil::Quote() as the response digest cannot contain
+ // any characters needing to be escaped.
+ authorization += ", response=\"" + response + "\"";
+
+ if (!opaque_.empty()) {
+ authorization += ", opaque=" + HttpUtil::Quote(opaque_);
+ }
+ if (qop_ != QOP_UNSPECIFIED) {
+ // TODO(eroman): Supposedly IIS server requires quotes surrounding qop.
+ authorization += ", qop=" + QopToString(qop_);
+ authorization += ", nc=" + nc;
+ authorization += ", cnonce=" + HttpUtil::Quote(cnonce);
+ }
+
+ return authorization;
+}
+
+// The digest challenge header looks like:
+// WWW-Authenticate: Digest
+// realm="<realm-value>"
+// nonce="<nonce-value>"
+// [domain="<list-of-URIs>"]
+// [opaque="<opaque-token-value>"]
+// [stale="<true-or-false>"]
+// [algorithm="<digest-algorithm>"]
+// [qop="<list-of-qop-values>"]
+// [<extension-directive>]
+bool HttpAuthHandlerDigest::ParseChallenge(
+ std::string::const_iterator challenge_begin,
+ std::string::const_iterator challenge_end) {
+ scheme_ = "digest";
+ score_ = 2;
+
+ // Initialize to defaults.
+ stale_ = false;
+ algorithm_ = ALGORITHM_UNSPECIFIED;
+ qop_ = QOP_UNSPECIFIED;
+ realm_ = nonce_ = domain_ = opaque_ = std::string();
+
+ HttpAuth::ChallengeTokenizer props(challenge_begin, challenge_end);
+
+ if (!props.valid() || !LowerCaseEqualsASCII(props.scheme(), "digest"))
+ return false; // FAIL -- Couldn't match auth-scheme.
+
+ // Loop through all the properties.
+ while (props.GetNext()) {
+ if (props.value().empty()) {
+ DLOG(INFO) << "Invalid digest property";
+ return false;
+ }
+
+ if (!ParseChallengeProperty(props.name(), props.unquoted_value()))
+ return false; // FAIL -- couldn't parse a property.
+ }
+
+ // Check if tokenizer failed.
+ if (!props.valid())
+ return false; // FAIL
+
+ // Check that a minimum set of properties were provided.
+ if (realm_.empty() || nonce_.empty())
+ return false; // FAIL
+
+ return true;
+}
+
+bool HttpAuthHandlerDigest::ParseChallengeProperty(const std::string& name,
+ const std::string& value) {
+ if (LowerCaseEqualsASCII(name, "realm")) {
+ realm_ = value;
+ } else if (LowerCaseEqualsASCII(name, "nonce")) {
+ nonce_ = value;
+ } else if (LowerCaseEqualsASCII(name, "domain")) {
+ domain_ = value;
+ } else if (LowerCaseEqualsASCII(name, "opaque")) {
+ opaque_ = value;
+ } else if (LowerCaseEqualsASCII(name, "stale")) {
+ // Parse the stale boolean.
+ stale_ = LowerCaseEqualsASCII(value, "true");
+ } else if (LowerCaseEqualsASCII(name, "algorithm")) {
+ // Parse the algorithm.
+ if (LowerCaseEqualsASCII(value, "md5")) {
+ algorithm_ = ALGORITHM_MD5;
+ } else if (LowerCaseEqualsASCII(value, "md5-sess")) {
+ algorithm_ = ALGORITHM_MD5_SESS;
+ } else {
+ DLOG(INFO) << "Unknown value of algorithm";
+ return false; // FAIL -- unsupported value of algorithm.
+ }
+ } else if (LowerCaseEqualsASCII(name, "qop")) {
+ // Parse the comma separated list of qops.
+ HttpUtil::ValuesIterator qop_values(value.begin(), value.end(), ',');
+ while (qop_values.GetNext()) {
+ if (LowerCaseEqualsASCII(qop_values.value(), "auth")) {
+ qop_ |= QOP_AUTH;
+ } else if (LowerCaseEqualsASCII(qop_values.value(), "auth-int")) {
+ qop_ |= QOP_AUTH_INT;
+ }
+ }
+ } else {
+ DLOG(INFO) << "Skipping unrecognized digest property";
+ // TODO(eroman): perhaps we should fail instead of silently skipping?
+ }
+ return true;
+}
+
+} // namespace net