1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/http_auth_handler_negotiate.h"
6
7 #include <utility>
8
9 #include "base/check_op.h"
10 #include "base/functional/bind.h"
11 #include "base/functional/callback_helpers.h"
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/values.h"
17 #include "build/build_config.h"
18 #include "build/chromeos_buildflags.h"
19 #include "net/base/address_family.h"
20 #include "net/base/address_list.h"
21 #include "net/base/host_port_pair.h"
22 #include "net/base/net_errors.h"
23 #include "net/cert/x509_util.h"
24 #include "net/dns/host_resolver.h"
25 #include "net/http/http_auth.h"
26 #include "net/http/http_auth_filter.h"
27 #include "net/http/http_auth_preferences.h"
28 #include "net/log/net_log_capture_mode.h"
29 #include "net/log/net_log_event_type.h"
30 #include "net/log/net_log_with_source.h"
31 #include "net/ssl/ssl_info.h"
32 #include "url/scheme_host_port.h"
33
34 namespace net {
35
36 using DelegationType = HttpAuth::DelegationType;
37
38 namespace {
39
NetLogParameterChannelBindings(const std::string & channel_binding_token,NetLogCaptureMode capture_mode)40 base::Value::Dict NetLogParameterChannelBindings(
41 const std::string& channel_binding_token,
42 NetLogCaptureMode capture_mode) {
43 base::Value::Dict dict;
44 if (!NetLogCaptureIncludesSocketBytes(capture_mode))
45 return dict;
46
47 dict.Set("token", base::HexEncode(channel_binding_token));
48 return dict;
49 }
50
51 // Uses |negotiate_auth_system_factory| to create the auth system, otherwise
52 // creates the default auth system for each platform.
CreateAuthSystem(HttpAuthHandlerNegotiate::AuthLibrary * auth_library,const HttpAuthPreferences * prefs,HttpAuthMechanismFactory negotiate_auth_system_factory)53 std::unique_ptr<HttpAuthMechanism> CreateAuthSystem(
54 #if !BUILDFLAG(IS_ANDROID)
55 HttpAuthHandlerNegotiate::AuthLibrary* auth_library,
56 #endif
57 const HttpAuthPreferences* prefs,
58 HttpAuthMechanismFactory negotiate_auth_system_factory) {
59 if (negotiate_auth_system_factory)
60 return negotiate_auth_system_factory.Run(prefs);
61 #if BUILDFLAG(IS_ANDROID)
62 return std::make_unique<net::android::HttpAuthNegotiateAndroid>(prefs);
63 #elif BUILDFLAG(IS_WIN)
64 return std::make_unique<HttpAuthSSPI>(auth_library,
65 HttpAuth::AUTH_SCHEME_NEGOTIATE);
66 #elif BUILDFLAG(IS_POSIX)
67 return std::make_unique<HttpAuthGSSAPI>(auth_library,
68 CHROME_GSS_SPNEGO_MECH_OID_DESC);
69 #endif
70 }
71
72 } // namespace
73
Factory(HttpAuthMechanismFactory negotiate_auth_system_factory)74 HttpAuthHandlerNegotiate::Factory::Factory(
75 HttpAuthMechanismFactory negotiate_auth_system_factory)
76 : negotiate_auth_system_factory_(negotiate_auth_system_factory) {}
77
78 HttpAuthHandlerNegotiate::Factory::~Factory() = default;
79
80 #if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(IS_POSIX)
GetLibraryNameForTesting() const81 const std::string& HttpAuthHandlerNegotiate::Factory::GetLibraryNameForTesting()
82 const {
83 return auth_library_->GetLibraryNameForTesting();
84 }
85 #endif // !BUILDFLAG(IS_ANDROID) && BUILDFLAG(IS_POSIX)
86
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)87 int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
88 HttpAuthChallengeTokenizer* challenge,
89 HttpAuth::Target target,
90 const SSLInfo& ssl_info,
91 const NetworkAnonymizationKey& network_anonymization_key,
92 const url::SchemeHostPort& scheme_host_port,
93 CreateReason reason,
94 int digest_nonce_count,
95 const NetLogWithSource& net_log,
96 HostResolver* host_resolver,
97 std::unique_ptr<HttpAuthHandler>* handler) {
98 #if BUILDFLAG(IS_WIN)
99 if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
100 return ERR_UNSUPPORTED_AUTH_SCHEME;
101 // TODO(cbentzel): Move towards model of parsing in the factory
102 // method and only constructing when valid.
103 std::unique_ptr<HttpAuthHandler> tmp_handler(
104 std::make_unique<HttpAuthHandlerNegotiate>(
105 CreateAuthSystem(auth_library_.get(), http_auth_preferences(),
106 negotiate_auth_system_factory_),
107 http_auth_preferences(), host_resolver));
108 #elif BUILDFLAG(IS_ANDROID)
109 if (is_unsupported_ || !http_auth_preferences() ||
110 http_auth_preferences()->AuthAndroidNegotiateAccountType().empty() ||
111 reason == CREATE_PREEMPTIVE)
112 return ERR_UNSUPPORTED_AUTH_SCHEME;
113 // TODO(cbentzel): Move towards model of parsing in the factory
114 // method and only constructing when valid.
115 std::unique_ptr<HttpAuthHandler> tmp_handler(
116 std::make_unique<HttpAuthHandlerNegotiate>(
117 CreateAuthSystem(http_auth_preferences(),
118 negotiate_auth_system_factory_),
119 http_auth_preferences(), host_resolver));
120 #elif BUILDFLAG(IS_POSIX)
121 if (is_unsupported_)
122 return ERR_UNSUPPORTED_AUTH_SCHEME;
123 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
124 // Note: Don't set is_unsupported_ = true here. AllowGssapiLibraryLoad()
125 // might change to true during a session.
126 if (!http_auth_preferences() ||
127 !http_auth_preferences()->AllowGssapiLibraryLoad()) {
128 return ERR_UNSUPPORTED_AUTH_SCHEME;
129 }
130 #endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
131 if (!auth_library_->Init(net_log)) {
132 is_unsupported_ = true;
133 return ERR_UNSUPPORTED_AUTH_SCHEME;
134 }
135 // TODO(ahendrickson): Move towards model of parsing in the factory
136 // method and only constructing when valid.
137 std::unique_ptr<HttpAuthHandler> tmp_handler(
138 std::make_unique<HttpAuthHandlerNegotiate>(
139 CreateAuthSystem(auth_library_.get(), http_auth_preferences(),
140 negotiate_auth_system_factory_),
141 http_auth_preferences(), host_resolver));
142 #endif
143 if (!tmp_handler->InitFromChallenge(challenge, target, ssl_info,
144 network_anonymization_key,
145 scheme_host_port, net_log)) {
146 return ERR_INVALID_RESPONSE;
147 }
148 handler->swap(tmp_handler);
149 return OK;
150 }
151
HttpAuthHandlerNegotiate(std::unique_ptr<HttpAuthMechanism> auth_system,const HttpAuthPreferences * prefs,HostResolver * resolver)152 HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
153 std::unique_ptr<HttpAuthMechanism> auth_system,
154 const HttpAuthPreferences* prefs,
155 HostResolver* resolver)
156 : auth_system_(std::move(auth_system)),
157 resolver_(resolver),
158 http_auth_preferences_(prefs) {}
159
160 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() = default;
161
162 // Require identity on first pass instead of second.
NeedsIdentity()163 bool HttpAuthHandlerNegotiate::NeedsIdentity() {
164 return auth_system_->NeedsIdentity();
165 }
166
AllowsDefaultCredentials()167 bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
168 if (target_ == HttpAuth::AUTH_PROXY)
169 return true;
170 if (!http_auth_preferences_)
171 return false;
172 return http_auth_preferences_->CanUseDefaultCredentials(scheme_host_port_);
173 }
174
AllowsExplicitCredentials()175 bool HttpAuthHandlerNegotiate::AllowsExplicitCredentials() {
176 return auth_system_->AllowsExplicitCredentials();
177 }
178
179 // The Negotiate challenge header looks like:
180 // WWW-Authenticate: NEGOTIATE auth-data
Init(HttpAuthChallengeTokenizer * challenge,const SSLInfo & ssl_info,const NetworkAnonymizationKey & network_anonymization_key)181 bool HttpAuthHandlerNegotiate::Init(
182 HttpAuthChallengeTokenizer* challenge,
183 const SSLInfo& ssl_info,
184 const NetworkAnonymizationKey& network_anonymization_key) {
185 network_anonymization_key_ = network_anonymization_key;
186 #if BUILDFLAG(IS_POSIX)
187 if (!auth_system_->Init(net_log())) {
188 VLOG(1) << "can't initialize GSSAPI library";
189 return false;
190 }
191 // GSSAPI does not provide a way to enter username/password to obtain a TGT,
192 // however ChromesOS provides the user an opportunity to enter their
193 // credentials and generate a new TGT on OS level (see b/260522530). If the
194 // default credentials are not allowed for a particular site
195 // (based on allowlist), fall back to a different scheme.
196 if (!AllowsDefaultCredentials()) {
197 return false;
198 }
199 #endif
200 auth_system_->SetDelegation(GetDelegationType());
201 auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE;
202 score_ = 4;
203 properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
204
205 HttpAuth::AuthorizationResult auth_result =
206 auth_system_->ParseChallenge(challenge);
207 if (auth_result != HttpAuth::AUTHORIZATION_RESULT_ACCEPT)
208 return false;
209
210 // Try to extract channel bindings.
211 if (ssl_info.is_valid())
212 x509_util::GetTLSServerEndPointChannelBinding(*ssl_info.cert,
213 &channel_bindings_);
214 if (!channel_bindings_.empty())
215 net_log().AddEvent(NetLogEventType::AUTH_CHANNEL_BINDINGS,
216 [&](NetLogCaptureMode capture_mode) {
217 return NetLogParameterChannelBindings(
218 channel_bindings_, capture_mode);
219 });
220 return true;
221 }
222
GenerateAuthTokenImpl(const AuthCredentials * credentials,const HttpRequestInfo * request,CompletionOnceCallback callback,std::string * auth_token)223 int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
224 const AuthCredentials* credentials,
225 const HttpRequestInfo* request,
226 CompletionOnceCallback callback,
227 std::string* auth_token) {
228 DCHECK(callback_.is_null());
229 DCHECK(auth_token_ == nullptr);
230 auth_token_ = auth_token;
231 if (already_called_) {
232 DCHECK((!has_credentials_ && credentials == nullptr) ||
233 (has_credentials_ && credentials->Equals(credentials_)));
234 next_state_ = STATE_GENERATE_AUTH_TOKEN;
235 } else {
236 already_called_ = true;
237 if (credentials) {
238 has_credentials_ = true;
239 credentials_ = *credentials;
240 }
241 next_state_ = STATE_RESOLVE_CANONICAL_NAME;
242 }
243 int rv = DoLoop(OK);
244 if (rv == ERR_IO_PENDING)
245 callback_ = std::move(callback);
246 return rv;
247 }
248
249 HttpAuth::AuthorizationResult
HandleAnotherChallengeImpl(HttpAuthChallengeTokenizer * challenge)250 HttpAuthHandlerNegotiate::HandleAnotherChallengeImpl(
251 HttpAuthChallengeTokenizer* challenge) {
252 return auth_system_->ParseChallenge(challenge);
253 }
254
CreateSPN(const std::string & server,const url::SchemeHostPort & scheme_host_port)255 std::string HttpAuthHandlerNegotiate::CreateSPN(
256 const std::string& server,
257 const url::SchemeHostPort& scheme_host_port) {
258 // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
259 // and in the form HTTP@<host>:<port> through GSSAPI
260 // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
261 //
262 // However, reality differs from the specification. A good description of
263 // the problems can be found here:
264 // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
265 //
266 // Typically the <host> portion should be the canonical FQDN for the service.
267 // If this could not be resolved, the original hostname in the URL will be
268 // attempted instead. However, some intranets register SPNs using aliases
269 // for the same canonical DNS name to allow multiple web services to reside
270 // on the same host machine without requiring different ports. IE6 and IE7
271 // have hotpatches that allow the default behavior to be overridden.
272 // http://support.microsoft.com/kb/911149
273 // http://support.microsoft.com/kb/938305
274 //
275 // According to the spec, the <port> option should be included if it is a
276 // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
277 // historically browsers have not included the port, even on non-standard
278 // ports. IE6 required a hotpatch and a registry setting to enable
279 // including non-standard ports, and IE7 and IE8 also require the same
280 // registry setting, but no hotpatch. Firefox does not appear to have an
281 // option to include non-standard ports as of 3.6.
282 // http://support.microsoft.com/kb/908209
283 //
284 // Without any command-line flags, Chrome matches the behavior of Firefox
285 // and IE. Users can override the behavior so aliases are allowed and
286 // non-standard ports are included.
287 int port = scheme_host_port.port();
288 #if BUILDFLAG(IS_WIN)
289 static const char kSpnSeparator = '/';
290 #elif BUILDFLAG(IS_POSIX)
291 static const char kSpnSeparator = '@';
292 #endif
293 if (port != 80 && port != 443 &&
294 (http_auth_preferences_ &&
295 http_auth_preferences_->NegotiateEnablePort())) {
296 return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, server.c_str(),
297 port);
298 } else {
299 return base::StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str());
300 }
301 }
302
OnIOComplete(int result)303 void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
304 int rv = DoLoop(result);
305 if (rv != ERR_IO_PENDING)
306 DoCallback(rv);
307 }
308
DoCallback(int rv)309 void HttpAuthHandlerNegotiate::DoCallback(int rv) {
310 DCHECK(rv != ERR_IO_PENDING);
311 DCHECK(!callback_.is_null());
312 std::move(callback_).Run(rv);
313 }
314
DoLoop(int result)315 int HttpAuthHandlerNegotiate::DoLoop(int result) {
316 DCHECK(next_state_ != STATE_NONE);
317
318 int rv = result;
319 do {
320 State state = next_state_;
321 next_state_ = STATE_NONE;
322 switch (state) {
323 case STATE_RESOLVE_CANONICAL_NAME:
324 DCHECK_EQ(OK, rv);
325 rv = DoResolveCanonicalName();
326 break;
327 case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
328 rv = DoResolveCanonicalNameComplete(rv);
329 break;
330 case STATE_GENERATE_AUTH_TOKEN:
331 DCHECK_EQ(OK, rv);
332 rv = DoGenerateAuthToken();
333 break;
334 case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
335 rv = DoGenerateAuthTokenComplete(rv);
336 break;
337 default:
338 NOTREACHED() << "bad state";
339 rv = ERR_FAILED;
340 break;
341 }
342 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
343
344 return rv;
345 }
346
DoResolveCanonicalName()347 int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
348 next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
349 if ((http_auth_preferences_ &&
350 http_auth_preferences_->NegotiateDisableCnameLookup()) ||
351 !resolver_)
352 return OK;
353
354 // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
355 HostResolver::ResolveHostParameters parameters;
356 parameters.include_canonical_name = true;
357 resolve_host_request_ = resolver_->CreateRequest(
358 scheme_host_port_, network_anonymization_key_, net_log(), parameters);
359 return resolve_host_request_->Start(base::BindOnce(
360 &HttpAuthHandlerNegotiate::OnIOComplete, base::Unretained(this)));
361 }
362
DoResolveCanonicalNameComplete(int rv)363 int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
364 DCHECK_NE(ERR_IO_PENDING, rv);
365 std::string server = scheme_host_port_.host();
366 if (resolve_host_request_) {
367 if (rv == OK) {
368 // Expect at most a single DNS alias representing the canonical name
369 // because the `HostResolver` request was made with
370 // `include_canonical_name`.
371 DCHECK(resolve_host_request_->GetDnsAliasResults());
372 DCHECK_LE(resolve_host_request_->GetDnsAliasResults()->size(), 1u);
373 if (!resolve_host_request_->GetDnsAliasResults()->empty()) {
374 server = *resolve_host_request_->GetDnsAliasResults()->begin();
375 DCHECK(!server.empty());
376 }
377 } else {
378 // Even in the error case, try to use origin_.host instead of
379 // passing the failure on to the caller.
380 VLOG(1) << "Problem finding canonical name for SPN for host "
381 << scheme_host_port_.host() << ": " << ErrorToString(rv);
382 rv = OK;
383 }
384 }
385
386 next_state_ = STATE_GENERATE_AUTH_TOKEN;
387 spn_ = CreateSPN(server, scheme_host_port_);
388 resolve_host_request_ = nullptr;
389 return rv;
390 }
391
DoGenerateAuthToken()392 int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
393 next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
394 AuthCredentials* credentials = has_credentials_ ? &credentials_ : nullptr;
395 return auth_system_->GenerateAuthToken(
396 credentials, spn_, channel_bindings_, auth_token_, net_log(),
397 base::BindOnce(&HttpAuthHandlerNegotiate::OnIOComplete,
398 base::Unretained(this)));
399 }
400
DoGenerateAuthTokenComplete(int rv)401 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
402 DCHECK_NE(ERR_IO_PENDING, rv);
403 auth_token_ = nullptr;
404 return rv;
405 }
406
GetDelegationType() const407 DelegationType HttpAuthHandlerNegotiate::GetDelegationType() const {
408 if (!http_auth_preferences_)
409 return DelegationType::kNone;
410
411 // TODO(cbentzel): Should delegation be allowed on proxies?
412 if (target_ == HttpAuth::AUTH_PROXY)
413 return DelegationType::kNone;
414
415 return http_auth_preferences_->GetDelegationType(scheme_host_port_);
416 }
417
418 } // namespace net
419