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