• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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