#include "net/http/http_auth_handler_digest.h"
#include <array>
#include <string>
#include <string_view>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "crypto/random.h"
#include "net/base/features.h"
#include "net/base/net_errors.h"
#include "net/base/net_string_util.h"
#include "net/base/url_util.h"
#include "net/dns/host_resolver.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_challenge_tokenizer.h"
#include "net/http/http_auth_scheme.h"
#include "net/http/http_request_info.h"
#include "net/http/http_util.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "url/gurl.h"
namespace net {
HttpAuthHandlerDigest::NonceGenerator::NonceGenerator() = default;
HttpAuthHandlerDigest::NonceGenerator::~NonceGenerator() = default;
HttpAuthHandlerDigest::DynamicNonceGenerator::DynamicNonceGenerator() = default;
std::string HttpAuthHandlerDigest::DynamicNonceGenerator::GenerateNonce()
const {
std::array<uint8_t, 8> rand_bytes;
crypto::RandBytes(rand_bytes);
std::string cnonce;
cnonce.reserve(16);
for (const uint8_t byte : rand_bytes) {
base::AppendHexEncodedByte(byte, cnonce, false);
}
DCHECK_EQ(cnonce.size(), 16u);
return cnonce;
}
HttpAuthHandlerDigest::FixedNonceGenerator::FixedNonceGenerator(
const std::string& nonce)
: nonce_(nonce) {}
std::string HttpAuthHandlerDigest::FixedNonceGenerator::GenerateNonce() const {
return nonce_;
}
HttpAuthHandlerDigest::Factory::Factory()
: nonce_generator_(std::make_unique<DynamicNonceGenerator>()) {}
HttpAuthHandlerDigest::Factory::~Factory() = default;
void HttpAuthHandlerDigest::Factory::set_nonce_generator(
std::unique_ptr<const NonceGenerator> nonce_generator) {
nonce_generator_ = std::move(nonce_generator);
}
int HttpAuthHandlerDigest::Factory::CreateAuthHandler(
HttpAuthChallengeTokenizer* challenge,
HttpAuth::Target target,
const SSLInfo& ssl_info,
const NetworkAnonymizationKey& network_anonymization_key,
const url::SchemeHostPort& scheme_host_port,
CreateReason reason,
int digest_nonce_count,
const NetLogWithSource& net_log,
HostResolver* host_resolver,
std::unique_ptr<HttpAuthHandler>* handler) {
auto tmp_handler = base::WrapUnique(
new HttpAuthHandlerDigest(digest_nonce_count, nonce_generator_.get()));
if (!tmp_handler->InitFromChallenge(challenge, target, ssl_info,
network_anonymization_key,
scheme_host_port, net_log)) {
return ERR_INVALID_RESPONSE;
}
*handler = std::move(tmp_handler);
return OK;
}
bool HttpAuthHandlerDigest::Init(
HttpAuthChallengeTokenizer* challenge,
const SSLInfo& ssl_info,
const NetworkAnonymizationKey& network_anonymization_key) {
return ParseChallenge(challenge);
}
int HttpAuthHandlerDigest::GenerateAuthTokenImpl(
const AuthCredentials* credentials,
const HttpRequestInfo* request,
CompletionOnceCallback callback,
std::string* auth_token) {
std::string cnonce = nonce_generator_->GenerateNonce();
std::string method;
std::string path;
GetRequestMethodAndPath(request, &method, &path);
*auth_token =
AssembleCredentials(method, path, *credentials, cnonce, nonce_count_);
return OK;
}
HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallengeImpl(
HttpAuthChallengeTokenizer* challenge) {
if (challenge->auth_scheme() != kDigestAuthScheme) {
return HttpAuth::AUTHORIZATION_RESULT_INVALID;
}
HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
std::string original_realm;
while (parameters.GetNext()) {
if (base::EqualsCaseInsensitiveASCII(parameters.name(), "stale")) {
if (base::EqualsCaseInsensitiveASCII(parameters.value(), "true")) {
return HttpAuth::AUTHORIZATION_RESULT_STALE;
}
} else if (base::EqualsCaseInsensitiveASCII(parameters.name(), "realm")) {
original_realm = parameters.value();
}
}
return (original_realm_ != original_realm)
? HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM
: HttpAuth::AUTHORIZATION_RESULT_REJECT;
}
HttpAuthHandlerDigest::HttpAuthHandlerDigest(
int nonce_count,
const NonceGenerator* nonce_generator)
: nonce_count_(nonce_count), nonce_generator_(nonce_generator) {
DCHECK(nonce_generator_);
}
HttpAuthHandlerDigest::~HttpAuthHandlerDigest() = default;
bool HttpAuthHandlerDigest::ParseChallenge(
HttpAuthChallengeTokenizer* challenge) {
auth_scheme_ = HttpAuth::AUTH_SCHEME_DIGEST;
score_ = 2;
properties_ = ENCRYPTS_IDENTITY;
stale_ = false;
algorithm_ = Algorithm::UNSPECIFIED;
qop_ = QOP_UNSPECIFIED;
realm_ = original_realm_ = nonce_ = domain_ = opaque_ = std::string();
if (challenge->auth_scheme() != kDigestAuthScheme) {
return false;
}
HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
while (parameters.GetNext()) {
if (!ParseChallengeProperty(parameters.name(), parameters.value())) {
return false;
}
}
if (!parameters.valid()) {
return false;
}
if (nonce_.empty()) {
return false;
}
return true;
}
bool HttpAuthHandlerDigest::ParseChallengeProperty(std::string_view name,
std::string_view value) {
if (base::EqualsCaseInsensitiveASCII(name, "realm")) {
std::string realm;
if (!ConvertToUtf8AndNormalize(value, kCharsetLatin1, &realm)) {
return false;
}
realm_ = realm;
original_realm_ = std::string(value);
} else if (base::EqualsCaseInsensitiveASCII(name, "nonce")) {
nonce_ = std::string(value);
} else if (base::EqualsCaseInsensitiveASCII(name, "domain")) {
domain_ = std::string(value);
} else if (base::EqualsCaseInsensitiveASCII(name, "opaque")) {
opaque_ = std::string(value);
} else if (base::EqualsCaseInsensitiveASCII(name, "stale")) {
stale_ = base::EqualsCaseInsensitiveASCII(value, "true");
} else if (base::EqualsCaseInsensitiveASCII(name, "algorithm")) {
if (base::EqualsCaseInsensitiveASCII(value, "md5")) {
algorithm_ = Algorithm::MD5;
} else if (base::EqualsCaseInsensitiveASCII(value, "md5-sess")) {
algorithm_ = Algorithm::MD5_SESS;
} else if (base::EqualsCaseInsensitiveASCII(value, "sha-256")) {
algorithm_ = Algorithm::SHA256;
} else if (base::EqualsCaseInsensitiveASCII(value, "sha-256-sess")) {
algorithm_ = Algorithm::SHA256_SESS;
} else {
DVLOG(1) << "Unknown value of algorithm";
return false;
}
} else if (base::EqualsCaseInsensitiveASCII(name, "userhash")) {
userhash_ = base::EqualsCaseInsensitiveASCII(value, "true");
} else if (base::EqualsCaseInsensitiveASCII(name, "qop")) {
HttpUtil::ValuesIterator qop_values(value, ',');
qop_ = QOP_UNSPECIFIED;
while (qop_values.GetNext()) {
if (base::EqualsCaseInsensitiveASCII(qop_values.value(), "auth")) {
qop_ = QOP_AUTH;
break;
}
}
} else {
DVLOG(1) << "Skipping unrecognized digest property";
}
return true;
}
std::string HttpAuthHandlerDigest::QopToString(QualityOfProtection qop) {
switch (qop) {
case QOP_UNSPECIFIED:
return std::string();
case QOP_AUTH:
return "auth";
default:
NOTREACHED();
}
}
std::string HttpAuthHandlerDigest::AlgorithmToString(Algorithm algorithm) {
switch (algorithm) {
case Algorithm::UNSPECIFIED:
return std::string();
case Algorithm::MD5:
return "MD5";
case Algorithm::MD5_SESS:
return "MD5-sess";
case Algorithm::SHA256:
return "SHA-256";
case Algorithm::SHA256_SESS:
return "SHA-256-sess";
default:
NOTREACHED();
}
}
void HttpAuthHandlerDigest::GetRequestMethodAndPath(
const HttpRequestInfo* request,
std::string* method,
std::string* path) const {
DCHECK(request);
const GURL& url = request->url;
if (target_ == HttpAuth::AUTH_PROXY &&
(url.SchemeIs("https") || url.SchemeIsWSOrWSS())) {
*method = "CONNECT";
*path = GetHostAndPort(url);
} else {
*method = request->method;
*path = url.PathForRequest();
}
}
class HttpAuthHandlerDigest::DigestContext {
public:
explicit DigestContext(HttpAuthHandlerDigest::Algorithm algo) {
switch (algo) {
case HttpAuthHandlerDigest::Algorithm::MD5:
case HttpAuthHandlerDigest::Algorithm::MD5_SESS:
case HttpAuthHandlerDigest::Algorithm::UNSPECIFIED:
CHECK(EVP_DigestInit(md_ctx_.get(), EVP_md5()));
out_len_ = 16;
break;
case HttpAuthHandlerDigest::Algorithm::SHA256:
case HttpAuthHandlerDigest::Algorithm::SHA256_SESS:
CHECK(EVP_DigestInit(md_ctx_.get(), EVP_sha256()));
out_len_ = 32;
break;
}
}
void Update(std::string_view s) {
CHECK(EVP_DigestUpdate(md_ctx_.get(), s.data(), s.size()));
}
void Update(std::initializer_list<std::string_view> sps) {
for (const auto sp : sps) {
Update(sp);
}
}
std::string HexDigest() {
uint8_t md_value[EVP_MAX_MD_SIZE] = {};
unsigned int md_len = sizeof(md_value);
CHECK(EVP_DigestFinal_ex(md_ctx_.get(), md_value, &md_len));
return base::HexEncodeLower(base::span(md_value).first(out_len_));
}
private:
bssl::ScopedEVP_MD_CTX md_ctx_;
size_t out_len_;
};
std::string HttpAuthHandlerDigest::AssembleResponseDigest(
const std::string& method,
const std::string& path,
const AuthCredentials& credentials,
const std::string& cnonce,
const std::string& nc) const {
DigestContext ha1_ctx(algorithm_);
ha1_ctx.Update({base::UTF16ToUTF8(credentials.username()), ":",
original_realm_, ":",
base::UTF16ToUTF8(credentials.password())});
std::string ha1 = ha1_ctx.HexDigest();
if (algorithm_ == HttpAuthHandlerDigest::Algorithm::MD5_SESS ||
algorithm_ == HttpAuthHandlerDigest::Algorithm::SHA256_SESS) {
DigestContext sess_ctx(algorithm_);
sess_ctx.Update({ha1, ":", nonce_, ":", cnonce});
ha1 = sess_ctx.HexDigest();
}
DigestContext ha2_ctx(algorithm_);
ha2_ctx.Update({method, ":", path});
const std::string ha2 = ha2_ctx.HexDigest();
DigestContext resp_ctx(algorithm_);
resp_ctx.Update({ha1, ":", nonce_, ":"});
if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
resp_ctx.Update({nc, ":", cnonce, ":", QopToString(qop_), ":"});
}
resp_ctx.Update(ha2);
return resp_ctx.HexDigest();
}
std::string HttpAuthHandlerDigest::AssembleCredentials(
const std::string& method,
const std::string& path,
const AuthCredentials& credentials,
const std::string& cnonce,
int nonce_count) const {
std::string nc = base::StringPrintf("%08x", nonce_count);
std::string username = base::UTF16ToUTF8(credentials.username());
if (userhash_) {
DigestContext uh_ctx(algorithm_);
uh_ctx.Update({username, ":", realm_});
username = uh_ctx.HexDigest();
}
std::string authorization =
(std::string("Digest username=") + HttpUtil::Quote(username));
authorization += ", realm=" + HttpUtil::Quote(original_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, credentials, cnonce, nc);
authorization += ", response=\"" + response + "\"";
if (!opaque_.empty()) {
authorization += ", opaque=" + HttpUtil::Quote(opaque_);
}
if (qop_ != QOP_UNSPECIFIED) {
authorization += ", qop=" + QopToString(qop_);
authorization += ", nc=" + nc;
authorization += ", cnonce=" + HttpUtil::Quote(cnonce);
}
if (userhash_) {
authorization += ", userhash=true";
}
return authorization;
}
}