xref: /aosp_15_r20/external/cronet/net/http/http_auth_handler_negotiate.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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