xref: /aosp_15_r20/external/cronet/net/http/http_auth_gssapi_posix.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_gssapi_posix.h"
6 
7 #include <limits>
8 #include <string>
9 #include <string_view>
10 
11 #include "base/base64.h"
12 #include "base/compiler_specific.h"
13 #include "base/feature_list.h"
14 #include "base/files/file_path.h"
15 #include "base/format_macros.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/threading/thread_restrictions.h"
22 #include "base/values.h"
23 #include "build/build_config.h"
24 #include "net/base/features.h"
25 #include "net/base/net_errors.h"
26 #include "net/http/http_auth.h"
27 #include "net/http/http_auth_gssapi_posix.h"
28 #include "net/http/http_auth_multi_round_parse.h"
29 #include "net/log/net_log_event_type.h"
30 #include "net/log/net_log_values.h"
31 #include "net/log/net_log_with_source.h"
32 #include "net/net_buildflags.h"
33 
34 namespace net {
35 
36 using DelegationType = HttpAuth::DelegationType;
37 
38 // Exported mechanism for GSSAPI. We always use SPNEGO:
39 
40 // iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
41 gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = {
42   6,
43   const_cast<char*>("\x2b\x06\x01\x05\x05\x02")
44 };
45 
46 gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC =
47     &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;
48 
DelegationTypeToFlag(DelegationType delegation_type)49 OM_uint32 DelegationTypeToFlag(DelegationType delegation_type) {
50   switch (delegation_type) {
51     case DelegationType::kNone:
52       return 0;
53     case DelegationType::kByKdcPolicy:
54       return GSS_C_DELEG_POLICY_FLAG;
55     case DelegationType::kUnconstrained:
56       return GSS_C_DELEG_FLAG;
57   }
58 }
59 
60 // ScopedBuffer releases a gss_buffer_t when it goes out of scope.
61 class ScopedBuffer {
62  public:
ScopedBuffer(gss_buffer_t buffer,GSSAPILibrary * gssapi_lib)63   ScopedBuffer(gss_buffer_t buffer, GSSAPILibrary* gssapi_lib)
64       : buffer_(buffer), gssapi_lib_(gssapi_lib) {
65     DCHECK(gssapi_lib_);
66   }
67 
68   ScopedBuffer(const ScopedBuffer&) = delete;
69   ScopedBuffer& operator=(const ScopedBuffer&) = delete;
70 
~ScopedBuffer()71   ~ScopedBuffer() {
72     if (buffer_ != GSS_C_NO_BUFFER) {
73       OM_uint32 minor_status = 0;
74       OM_uint32 major_status =
75           gssapi_lib_->release_buffer(&minor_status, buffer_);
76       DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
77           << "Problem releasing buffer. major=" << major_status
78           << ", minor=" << minor_status;
79       buffer_ = GSS_C_NO_BUFFER;
80     }
81   }
82 
83  private:
84   gss_buffer_t buffer_;
85   raw_ptr<GSSAPILibrary> gssapi_lib_;
86 };
87 
88 // ScopedName releases a gss_name_t when it goes out of scope.
89 class ScopedName {
90  public:
ScopedName(gss_name_t name,GSSAPILibrary * gssapi_lib)91   ScopedName(gss_name_t name, GSSAPILibrary* gssapi_lib)
92       : name_(name), gssapi_lib_(gssapi_lib) {
93     DCHECK(gssapi_lib_);
94   }
95 
96   ScopedName(const ScopedName&) = delete;
97   ScopedName& operator=(const ScopedName&) = delete;
98 
~ScopedName()99   ~ScopedName() {
100     if (name_ != GSS_C_NO_NAME) {
101       OM_uint32 minor_status = 0;
102       OM_uint32 major_status = gssapi_lib_->release_name(&minor_status, &name_);
103       if (major_status != GSS_S_COMPLETE) {
104         DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
105             << "Problem releasing name. "
106             << GetGssStatusValue(nullptr, "gss_release_name", major_status,
107                                  minor_status);
108       }
109       name_ = GSS_C_NO_NAME;
110     }
111   }
112 
113  private:
114   gss_name_t name_;
115   raw_ptr<GSSAPILibrary> gssapi_lib_;
116 };
117 
OidEquals(const gss_OID left,const gss_OID right)118 bool OidEquals(const gss_OID left, const gss_OID right) {
119   if (left->length != right->length)
120     return false;
121   return 0 == memcmp(left->elements, right->elements, right->length);
122 }
123 
GetGssStatusCodeValue(GSSAPILibrary * gssapi_lib,OM_uint32 status,OM_uint32 status_code_type)124 base::Value::Dict GetGssStatusCodeValue(GSSAPILibrary* gssapi_lib,
125                                         OM_uint32 status,
126                                         OM_uint32 status_code_type) {
127   base::Value::Dict rv;
128 
129   rv.Set("status", static_cast<int>(status));
130 
131   // Message lookups aren't performed if there's no library or if the status
132   // indicates success.
133   if (!gssapi_lib || status == GSS_S_COMPLETE)
134     return rv;
135 
136   // gss_display_status() can potentially return multiple strings by sending
137   // each string on successive invocations. State is maintained across these
138   // invocations in a caller supplied OM_uint32.  After each successful call,
139   // the context is set to a non-zero value that should be passed as a message
140   // context to each successive gss_display_status() call.  The initial and
141   // terminal values of this context storage is 0.
142   OM_uint32 message_context = 0;
143 
144   // To account for the off chance that gss_display_status() misbehaves and gets
145   // into an infinite loop, we'll artificially limit the number of iterations to
146   // |kMaxDisplayIterations|. This limit is arbitrary.
147   constexpr size_t kMaxDisplayIterations = 8;
148   size_t iterations = 0;
149 
150   // In addition, each message string is again arbitrarily limited to
151   // |kMaxMsgLength|. There's no real documented limit to work with here.
152   constexpr size_t kMaxMsgLength = 4096;
153 
154   base::Value::List messages;
155   do {
156     gss_buffer_desc_struct message_buffer = GSS_C_EMPTY_BUFFER;
157     ScopedBuffer message_buffer_releaser(&message_buffer, gssapi_lib);
158 
159     OM_uint32 minor_status = 0;
160     OM_uint32 major_status = gssapi_lib->display_status(
161         &minor_status, status, status_code_type, GSS_C_NO_OID, &message_context,
162         &message_buffer);
163 
164     if (major_status != GSS_S_COMPLETE || message_buffer.length == 0 ||
165         !message_buffer.value) {
166       continue;
167     }
168 
169     std::string_view message_string{
170         static_cast<const char*>(message_buffer.value),
171         std::min(kMaxMsgLength, message_buffer.length)};
172 
173     // The returned string is almost assuredly ASCII, but be defensive.
174     if (!base::IsStringUTF8(message_string))
175       continue;
176 
177     messages.Append(message_string);
178   } while (message_context != 0 && ++iterations < kMaxDisplayIterations);
179 
180   if (!messages.empty())
181     rv.Set("message", std::move(messages));
182   return rv;
183 }
184 
GetGssStatusValue(GSSAPILibrary * gssapi_lib,std::string_view method,OM_uint32 major_status,OM_uint32 minor_status)185 base::Value::Dict GetGssStatusValue(GSSAPILibrary* gssapi_lib,
186                                     std::string_view method,
187                                     OM_uint32 major_status,
188                                     OM_uint32 minor_status) {
189   base::Value::Dict params;
190   params.Set("function", method);
191   params.Set("major_status",
192              GetGssStatusCodeValue(gssapi_lib, major_status, GSS_C_GSS_CODE));
193   params.Set("minor_status",
194              GetGssStatusCodeValue(gssapi_lib, minor_status, GSS_C_MECH_CODE));
195   return params;
196 }
197 
OidToValue(gss_OID oid)198 base::Value::Dict OidToValue(gss_OID oid) {
199   base::Value::Dict params;
200 
201   if (!oid || oid->length == 0) {
202     params.Set("oid", "<Empty OID>");
203     return params;
204   }
205 
206   params.Set("length", static_cast<int>(oid->length));
207   if (!oid->elements)
208     return params;
209 
210   // Cap OID content at arbitrary limit 1k.
211   constexpr OM_uint32 kMaxOidDataSize = 1024;
212   params.Set("bytes", NetLogBinaryValue(oid->elements, std::min(kMaxOidDataSize,
213                                                                 oid->length)));
214 
215   // Based on RFC 2744 Appendix A. Hardcoding the OIDs in the list below to
216   // avoid having a static dependency on the library.
217   static const struct {
218     const char* symbolic_name;
219     const gss_OID_desc oid_desc;
220   } kWellKnownOIDs[] = {
221       {"GSS_C_NT_USER_NAME",
222        {10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01")}},
223       {"GSS_C_NT_MACHINE_UID_NAME",
224        {10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02")}},
225       {"GSS_C_NT_STRING_UID_NAME",
226        {10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03")}},
227       {"GSS_C_NT_HOSTBASED_SERVICE_X",
228        {6, const_cast<char*>("\x2b\x06\x01\x05\x06\x02")}},
229       {"GSS_C_NT_HOSTBASED_SERVICE",
230        {10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")}},
231       {"GSS_C_NT_ANONYMOUS", {6, const_cast<char*>("\x2b\x06\01\x05\x06\x03")}},
232       {"GSS_C_NT_EXPORT_NAME",
233        {6, const_cast<char*>("\x2b\x06\x01\x05\x06\x04")}}};
234 
235   for (auto& well_known_oid : kWellKnownOIDs) {
236     if (OidEquals(oid, const_cast<const gss_OID>(&well_known_oid.oid_desc)))
237       params.Set("oid", well_known_oid.symbolic_name);
238   }
239 
240   return params;
241 }
242 
GetDisplayNameValue(GSSAPILibrary * gssapi_lib,const gss_name_t gss_name)243 base::Value::Dict GetDisplayNameValue(GSSAPILibrary* gssapi_lib,
244                                       const gss_name_t gss_name) {
245   OM_uint32 major_status = 0;
246   OM_uint32 minor_status = 0;
247   gss_buffer_desc_struct name = GSS_C_EMPTY_BUFFER;
248   gss_OID name_type = GSS_C_NO_OID;
249 
250   base::Value::Dict rv;
251   major_status =
252       gssapi_lib->display_name(&minor_status, gss_name, &name, &name_type);
253   ScopedBuffer scoped_output_name(&name, gssapi_lib);
254   if (major_status != GSS_S_COMPLETE) {
255     rv.Set("error", GetGssStatusValue(gssapi_lib, "gss_display_name",
256                                       major_status, minor_status));
257     return rv;
258   }
259   auto name_string =
260       std::string_view(reinterpret_cast<const char*>(name.value), name.length);
261   rv.Set("name", base::IsStringUTF8(name_string)
262                      ? NetLogStringValue(name_string)
263                      : NetLogBinaryValue(name.value, name.length));
264   rv.Set("type", OidToValue(name_type));
265   return rv;
266 }
267 
ContextFlagsToValue(OM_uint32 flags)268 base::Value::Dict ContextFlagsToValue(OM_uint32 flags) {
269   base::Value::Dict rv;
270   rv.Set("value", base::StringPrintf("0x%08x", flags));
271   rv.Set("delegated", (flags & GSS_C_DELEG_FLAG) == GSS_C_DELEG_FLAG);
272   rv.Set("mutual", (flags & GSS_C_MUTUAL_FLAG) == GSS_C_MUTUAL_FLAG);
273   return rv;
274 }
275 
GetContextStateAsValue(GSSAPILibrary * gssapi_lib,const gss_ctx_id_t context_handle)276 base::Value::Dict GetContextStateAsValue(GSSAPILibrary* gssapi_lib,
277                                          const gss_ctx_id_t context_handle) {
278   base::Value::Dict rv;
279   if (context_handle == GSS_C_NO_CONTEXT) {
280     rv.Set("error", GetGssStatusValue(nullptr, "<none>", GSS_S_NO_CONTEXT, 0));
281     return rv;
282   }
283 
284   OM_uint32 major_status = 0;
285   OM_uint32 minor_status = 0;
286   gss_name_t src_name = GSS_C_NO_NAME;
287   gss_name_t targ_name = GSS_C_NO_NAME;
288   OM_uint32 lifetime_rec = 0;
289   gss_OID mech_type = GSS_C_NO_OID;
290   OM_uint32 ctx_flags = 0;
291   int locally_initiated = 0;
292   int open = 0;
293   major_status = gssapi_lib->inquire_context(&minor_status,
294                                              context_handle,
295                                              &src_name,
296                                              &targ_name,
297                                              &lifetime_rec,
298                                              &mech_type,
299                                              &ctx_flags,
300                                              &locally_initiated,
301                                              &open);
302   if (major_status != GSS_S_COMPLETE) {
303     rv.Set("error", GetGssStatusValue(gssapi_lib, "gss_inquire_context",
304                                       major_status, minor_status));
305     return rv;
306   }
307   ScopedName scoped_src_name(src_name, gssapi_lib);
308   ScopedName scoped_targ_name(targ_name, gssapi_lib);
309 
310   rv.Set("source", GetDisplayNameValue(gssapi_lib, src_name));
311   rv.Set("target", GetDisplayNameValue(gssapi_lib, targ_name));
312   // lifetime_rec is a uint32, while base::Value only takes ints. On 32 bit
313   // platforms uint32 doesn't fit on an int.
314   rv.Set("lifetime", base::NumberToString(lifetime_rec));
315   rv.Set("mechanism", OidToValue(mech_type));
316   rv.Set("flags", ContextFlagsToValue(ctx_flags));
317   rv.Set("open", !!open);
318   return rv;
319 }
320 
321 namespace {
322 
323 // Return a NetLog value for the result of loading a library.
LibraryLoadResultParams(std::string_view library_name,std::string_view load_result)324 base::Value::Dict LibraryLoadResultParams(std::string_view library_name,
325                                           std::string_view load_result) {
326   base::Value::Dict params;
327   params.Set("library_name", library_name);
328   if (!load_result.empty())
329     params.Set("load_result", load_result);
330   return params;
331 }
332 
333 }  // namespace
334 
GSSAPISharedLibrary(const std::string & gssapi_library_name)335 GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name)
336     : gssapi_library_name_(gssapi_library_name) {}
337 
~GSSAPISharedLibrary()338 GSSAPISharedLibrary::~GSSAPISharedLibrary() {
339   if (gssapi_library_) {
340     base::UnloadNativeLibrary(gssapi_library_);
341     gssapi_library_ = nullptr;
342   }
343 }
344 
Init(const NetLogWithSource & net_log)345 bool GSSAPISharedLibrary::Init(const NetLogWithSource& net_log) {
346   if (!initialized_)
347     InitImpl(net_log);
348   return initialized_;
349 }
350 
InitImpl(const NetLogWithSource & net_log)351 bool GSSAPISharedLibrary::InitImpl(const NetLogWithSource& net_log) {
352   DCHECK(!initialized_);
353   gssapi_library_ = LoadSharedLibrary(net_log);
354   if (gssapi_library_ == nullptr)
355     return false;
356   initialized_ = true;
357   return true;
358 }
359 
LoadSharedLibrary(const NetLogWithSource & net_log)360 base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary(
361     const NetLogWithSource& net_log) {
362   const char* const* library_names;
363   size_t num_lib_names;
364   const char* user_specified_library[1];
365   if (!gssapi_library_name_.empty()) {
366     user_specified_library[0] = gssapi_library_name_.c_str();
367     library_names = user_specified_library;
368     num_lib_names = 1;
369   } else {
370     static const char* const kDefaultLibraryNames[] = {
371 #if BUILDFLAG(IS_APPLE)
372       "/System/Library/Frameworks/GSS.framework/GSS"
373 #elif BUILDFLAG(IS_OPENBSD)
374       "libgssapi.so"  // Heimdal - OpenBSD
375 #else
376       "libgssapi_krb5.so.2",  // MIT Kerberos - FC, Suse10, Debian
377       "libgssapi.so.4",       // Heimdal - Suse10, MDK
378       "libgssapi.so.2",       // Heimdal - Gentoo
379       "libgssapi.so.1"        // Heimdal - Suse9, CITI - FC, MDK, Suse10
380 #endif
381     };
382     library_names = kDefaultLibraryNames;
383     num_lib_names = std::size(kDefaultLibraryNames);
384   }
385 
386   net_log.BeginEvent(NetLogEventType::AUTH_LIBRARY_LOAD);
387 
388   // There has to be at least one candidate.
389   DCHECK_NE(0u, num_lib_names);
390 
391   const char* library_name = nullptr;
392   base::NativeLibraryLoadError load_error;
393 
394   for (size_t i = 0; i < num_lib_names; ++i) {
395     load_error = base::NativeLibraryLoadError();
396     library_name = library_names[i];
397     base::FilePath file_path(library_name);
398 
399     // TODO(asanka): Move library loading to a separate thread.
400     //               http://crbug.com/66702
401     base::ScopedAllowBlocking scoped_allow_blocking_temporarily;
402     base::NativeLibrary lib = base::LoadNativeLibrary(file_path, &load_error);
403     if (lib) {
404       if (BindMethods(lib, library_name, net_log)) {
405         net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_LOAD, [&] {
406           return LibraryLoadResultParams(library_name, "");
407         });
408         return lib;
409       }
410       base::UnloadNativeLibrary(lib);
411     }
412   }
413 
414   // If loading failed, then log the result of the final attempt. Doing so
415   // is specially important on platforms where there's only one possible
416   // library. Doing so also always logs the failure when the GSSAPI library
417   // name is explicitly specified.
418   net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_LOAD, [&] {
419     return LibraryLoadResultParams(library_name, load_error.ToString());
420   });
421   return nullptr;
422 }
423 
424 namespace {
425 
BindFailureParams(std::string_view library_name,std::string_view method)426 base::Value::Dict BindFailureParams(std::string_view library_name,
427                                     std::string_view method) {
428   base::Value::Dict params;
429   params.Set("library_name", library_name);
430   params.Set("method", method);
431   return params;
432 }
433 
BindUntypedMethod(base::NativeLibrary lib,std::string_view library_name,const char * method,const NetLogWithSource & net_log)434 void* BindUntypedMethod(base::NativeLibrary lib,
435                         std::string_view library_name,
436                         const char* method,
437                         const NetLogWithSource& net_log) {
438   void* ptr = base::GetFunctionPointerFromNativeLibrary(lib, method);
439   if (ptr == nullptr) {
440     net_log.AddEvent(NetLogEventType::AUTH_LIBRARY_BIND_FAILED,
441                      [&] { return BindFailureParams(library_name, method); });
442   }
443   return ptr;
444 }
445 
446 template <typename T>
BindMethod(base::NativeLibrary lib,std::string_view library_name,const char * method,T * receiver,const NetLogWithSource & net_log)447 bool BindMethod(base::NativeLibrary lib,
448                 std::string_view library_name,
449                 const char* method,
450                 T* receiver,
451                 const NetLogWithSource& net_log) {
452   *receiver = reinterpret_cast<T>(
453       BindUntypedMethod(lib, library_name, method, net_log));
454   return *receiver != nullptr;
455 }
456 
457 }  // namespace
458 
BindMethods(base::NativeLibrary lib,std::string_view name,const NetLogWithSource & net_log)459 bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib,
460                                       std::string_view name,
461                                       const NetLogWithSource& net_log) {
462   bool ok = true;
463   // It's unlikely for BindMethods() to fail if LoadNativeLibrary() succeeded. A
464   // failure in this function indicates an interoperability issue whose
465   // diagnosis requires knowing all the methods that are missing. Hence |ok| is
466   // updated in a manner that prevents short-circuiting the BindGssMethod()
467   // invocations.
468   ok &= BindMethod(lib, name, "gss_delete_sec_context", &delete_sec_context_,
469                    net_log);
470   ok &= BindMethod(lib, name, "gss_display_name", &display_name_, net_log);
471   ok &= BindMethod(lib, name, "gss_display_status", &display_status_, net_log);
472   ok &= BindMethod(lib, name, "gss_import_name", &import_name_, net_log);
473   ok &= BindMethod(lib, name, "gss_init_sec_context", &init_sec_context_,
474                    net_log);
475   ok &=
476       BindMethod(lib, name, "gss_inquire_context", &inquire_context_, net_log);
477   ok &= BindMethod(lib, name, "gss_release_buffer", &release_buffer_, net_log);
478   ok &= BindMethod(lib, name, "gss_release_name", &release_name_, net_log);
479   ok &=
480       BindMethod(lib, name, "gss_wrap_size_limit", &wrap_size_limit_, net_log);
481 
482   if (LIKELY(ok))
483     return true;
484 
485   delete_sec_context_ = nullptr;
486   display_name_ = nullptr;
487   display_status_ = nullptr;
488   import_name_ = nullptr;
489   init_sec_context_ = nullptr;
490   inquire_context_ = nullptr;
491   release_buffer_ = nullptr;
492   release_name_ = nullptr;
493   wrap_size_limit_ = nullptr;
494   return false;
495 }
496 
import_name(OM_uint32 * minor_status,const gss_buffer_t input_name_buffer,const gss_OID input_name_type,gss_name_t * output_name)497 OM_uint32 GSSAPISharedLibrary::import_name(
498     OM_uint32* minor_status,
499     const gss_buffer_t input_name_buffer,
500     const gss_OID input_name_type,
501     gss_name_t* output_name) {
502   DCHECK(initialized_);
503   return import_name_(minor_status, input_name_buffer, input_name_type,
504                       output_name);
505 }
506 
release_name(OM_uint32 * minor_status,gss_name_t * input_name)507 OM_uint32 GSSAPISharedLibrary::release_name(
508     OM_uint32* minor_status,
509     gss_name_t* input_name) {
510   DCHECK(initialized_);
511   return release_name_(minor_status, input_name);
512 }
513 
release_buffer(OM_uint32 * minor_status,gss_buffer_t buffer)514 OM_uint32 GSSAPISharedLibrary::release_buffer(
515     OM_uint32* minor_status,
516     gss_buffer_t buffer) {
517   DCHECK(initialized_);
518   return release_buffer_(minor_status, buffer);
519 }
520 
display_name(OM_uint32 * minor_status,const gss_name_t input_name,gss_buffer_t output_name_buffer,gss_OID * output_name_type)521 OM_uint32 GSSAPISharedLibrary::display_name(
522     OM_uint32* minor_status,
523     const gss_name_t input_name,
524     gss_buffer_t output_name_buffer,
525     gss_OID* output_name_type) {
526   DCHECK(initialized_);
527   return display_name_(minor_status,
528                        input_name,
529                        output_name_buffer,
530                        output_name_type);
531 }
532 
display_status(OM_uint32 * minor_status,OM_uint32 status_value,int status_type,const gss_OID mech_type,OM_uint32 * message_context,gss_buffer_t status_string)533 OM_uint32 GSSAPISharedLibrary::display_status(
534     OM_uint32* minor_status,
535     OM_uint32 status_value,
536     int status_type,
537     const gss_OID mech_type,
538     OM_uint32* message_context,
539     gss_buffer_t status_string) {
540   DCHECK(initialized_);
541   return display_status_(minor_status, status_value, status_type, mech_type,
542                          message_context, status_string);
543 }
544 
init_sec_context(OM_uint32 * minor_status,const gss_cred_id_t initiator_cred_handle,gss_ctx_id_t * context_handle,const gss_name_t target_name,const gss_OID mech_type,OM_uint32 req_flags,OM_uint32 time_req,const gss_channel_bindings_t input_chan_bindings,const gss_buffer_t input_token,gss_OID * actual_mech_type,gss_buffer_t output_token,OM_uint32 * ret_flags,OM_uint32 * time_rec)545 OM_uint32 GSSAPISharedLibrary::init_sec_context(
546     OM_uint32* minor_status,
547     const gss_cred_id_t initiator_cred_handle,
548     gss_ctx_id_t* context_handle,
549     const gss_name_t target_name,
550     const gss_OID mech_type,
551     OM_uint32 req_flags,
552     OM_uint32 time_req,
553     const gss_channel_bindings_t input_chan_bindings,
554     const gss_buffer_t input_token,
555     gss_OID* actual_mech_type,
556     gss_buffer_t output_token,
557     OM_uint32* ret_flags,
558     OM_uint32* time_rec) {
559   DCHECK(initialized_);
560   return init_sec_context_(minor_status,
561                            initiator_cred_handle,
562                            context_handle,
563                            target_name,
564                            mech_type,
565                            req_flags,
566                            time_req,
567                            input_chan_bindings,
568                            input_token,
569                            actual_mech_type,
570                            output_token,
571                            ret_flags,
572                            time_rec);
573 }
574 
wrap_size_limit(OM_uint32 * minor_status,const gss_ctx_id_t context_handle,int conf_req_flag,gss_qop_t qop_req,OM_uint32 req_output_size,OM_uint32 * max_input_size)575 OM_uint32 GSSAPISharedLibrary::wrap_size_limit(
576     OM_uint32* minor_status,
577     const gss_ctx_id_t context_handle,
578     int conf_req_flag,
579     gss_qop_t qop_req,
580     OM_uint32 req_output_size,
581     OM_uint32* max_input_size) {
582   DCHECK(initialized_);
583   return wrap_size_limit_(minor_status,
584                           context_handle,
585                           conf_req_flag,
586                           qop_req,
587                           req_output_size,
588                           max_input_size);
589 }
590 
delete_sec_context(OM_uint32 * minor_status,gss_ctx_id_t * context_handle,gss_buffer_t output_token)591 OM_uint32 GSSAPISharedLibrary::delete_sec_context(
592     OM_uint32* minor_status,
593     gss_ctx_id_t* context_handle,
594     gss_buffer_t output_token) {
595   // This is called from the owner class' destructor, even if
596   // Init() is not called, so we can't assume |initialized_|
597   // is set.
598   if (!initialized_)
599     return 0;
600   return delete_sec_context_(minor_status,
601                              context_handle,
602                              output_token);
603 }
604 
inquire_context(OM_uint32 * minor_status,const gss_ctx_id_t context_handle,gss_name_t * src_name,gss_name_t * targ_name,OM_uint32 * lifetime_rec,gss_OID * mech_type,OM_uint32 * ctx_flags,int * locally_initiated,int * open)605 OM_uint32 GSSAPISharedLibrary::inquire_context(
606     OM_uint32* minor_status,
607     const gss_ctx_id_t context_handle,
608     gss_name_t* src_name,
609     gss_name_t* targ_name,
610     OM_uint32* lifetime_rec,
611     gss_OID* mech_type,
612     OM_uint32* ctx_flags,
613     int* locally_initiated,
614     int* open) {
615   DCHECK(initialized_);
616   return inquire_context_(minor_status,
617                           context_handle,
618                           src_name,
619                           targ_name,
620                           lifetime_rec,
621                           mech_type,
622                           ctx_flags,
623                           locally_initiated,
624                           open);
625 }
626 
GetLibraryNameForTesting()627 const std::string& GSSAPISharedLibrary::GetLibraryNameForTesting() {
628   return gssapi_library_name_;
629 }
630 
ScopedSecurityContext(GSSAPILibrary * gssapi_lib)631 ScopedSecurityContext::ScopedSecurityContext(GSSAPILibrary* gssapi_lib)
632     : security_context_(GSS_C_NO_CONTEXT),
633       gssapi_lib_(gssapi_lib) {
634   DCHECK(gssapi_lib_);
635 }
636 
~ScopedSecurityContext()637 ScopedSecurityContext::~ScopedSecurityContext() {
638   if (security_context_ != GSS_C_NO_CONTEXT) {
639     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
640     OM_uint32 minor_status = 0;
641     OM_uint32 major_status = gssapi_lib_->delete_sec_context(
642         &minor_status, &security_context_, &output_token);
643     DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
644         << "Problem releasing security_context. "
645         << GetGssStatusValue(gssapi_lib_, "delete_sec_context", major_status,
646                              minor_status);
647     security_context_ = GSS_C_NO_CONTEXT;
648   }
649 }
650 
HttpAuthGSSAPI(GSSAPILibrary * library,gss_OID gss_oid)651 HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library, gss_OID gss_oid)
652     : gss_oid_(gss_oid), library_(library), scoped_sec_context_(library) {
653   DCHECK(library_);
654 }
655 
656 HttpAuthGSSAPI::~HttpAuthGSSAPI() = default;
657 
Init(const NetLogWithSource & net_log)658 bool HttpAuthGSSAPI::Init(const NetLogWithSource& net_log) {
659   if (!library_)
660     return false;
661   return library_->Init(net_log);
662 }
663 
NeedsIdentity() const664 bool HttpAuthGSSAPI::NeedsIdentity() const {
665   return decoded_server_auth_token_.empty();
666 }
667 
AllowsExplicitCredentials() const668 bool HttpAuthGSSAPI::AllowsExplicitCredentials() const {
669 #if BUILDFLAG(IS_CHROMEOS)
670   if (base::FeatureList::IsEnabled(features::kKerberosInBrowserRedirect)) {
671     return true;
672   } else {
673     return false;
674   }
675 #else
676   return false;
677 #endif
678 }
679 
SetDelegation(DelegationType delegation_type)680 void HttpAuthGSSAPI::SetDelegation(DelegationType delegation_type) {
681   delegation_type_ = delegation_type;
682 }
683 
ParseChallenge(HttpAuthChallengeTokenizer * tok)684 HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
685     HttpAuthChallengeTokenizer* tok) {
686   if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) {
687     return net::ParseFirstRoundChallenge(HttpAuth::AUTH_SCHEME_NEGOTIATE, tok);
688   }
689   std::string encoded_auth_token;
690   return net::ParseLaterRoundChallenge(HttpAuth::AUTH_SCHEME_NEGOTIATE, tok,
691                                        &encoded_auth_token,
692                                        &decoded_server_auth_token_);
693 }
694 
GenerateAuthToken(const AuthCredentials * credentials,const std::string & spn,const std::string & channel_bindings,std::string * auth_token,const NetLogWithSource & net_log,CompletionOnceCallback)695 int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials,
696                                       const std::string& spn,
697                                       const std::string& channel_bindings,
698                                       std::string* auth_token,
699                                       const NetLogWithSource& net_log,
700                                       CompletionOnceCallback /*callback*/) {
701   DCHECK(auth_token);
702 
703   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
704   input_token.length = decoded_server_auth_token_.length();
705   input_token.value = (input_token.length > 0)
706                           ? const_cast<char*>(decoded_server_auth_token_.data())
707                           : nullptr;
708   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
709   ScopedBuffer scoped_output_token(&output_token, library_);
710   int rv = GetNextSecurityToken(spn, channel_bindings, &input_token,
711                                 &output_token, net_log);
712   if (rv != OK)
713     return rv;
714 
715   // Base64 encode data in output buffer and prepend the scheme.
716   std::string encode_input(static_cast<char*>(output_token.value),
717                            output_token.length);
718   std::string encode_output = base::Base64Encode(encode_input);
719   *auth_token = "Negotiate " + encode_output;
720   return OK;
721 }
722 
723 namespace {
724 
725 // GSSAPI status codes consist of a calling error (essentially, a programmer
726 // bug), a routine error (defined by the RFC), and supplementary information,
727 // all bitwise-or'ed together in different regions of the 32 bit return value.
728 // This means a simple switch on the return codes is not sufficient.
729 
MapImportNameStatusToError(OM_uint32 major_status)730 int MapImportNameStatusToError(OM_uint32 major_status) {
731   if (major_status == GSS_S_COMPLETE)
732     return OK;
733   if (GSS_CALLING_ERROR(major_status) != 0)
734     return ERR_UNEXPECTED;
735   OM_uint32 routine_error = GSS_ROUTINE_ERROR(major_status);
736   switch (routine_error) {
737     case GSS_S_FAILURE:
738       // Looking at the MIT Kerberos implementation, this typically is returned
739       // when memory allocation fails. However, the API does not guarantee
740       // that this is the case, so using ERR_UNEXPECTED rather than
741       // ERR_OUT_OF_MEMORY.
742       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
743     case GSS_S_BAD_NAME:
744     case GSS_S_BAD_NAMETYPE:
745       return ERR_MALFORMED_IDENTITY;
746     case GSS_S_DEFECTIVE_TOKEN:
747       // Not mentioned in the API, but part of code.
748       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
749     case GSS_S_BAD_MECH:
750       return ERR_UNSUPPORTED_AUTH_SCHEME;
751     default:
752       return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
753   }
754 }
755 
MapInitSecContextStatusToError(OM_uint32 major_status)756 int MapInitSecContextStatusToError(OM_uint32 major_status) {
757   // Although GSS_S_CONTINUE_NEEDED is an additional bit, it seems like
758   // other code just checks if major_status is equivalent to it to indicate
759   // that there are no other errors included.
760   if (major_status == GSS_S_COMPLETE || major_status == GSS_S_CONTINUE_NEEDED)
761     return OK;
762   if (GSS_CALLING_ERROR(major_status) != 0)
763     return ERR_UNEXPECTED;
764   OM_uint32 routine_status = GSS_ROUTINE_ERROR(major_status);
765   switch (routine_status) {
766     case GSS_S_DEFECTIVE_TOKEN:
767       return ERR_INVALID_RESPONSE;
768     case GSS_S_DEFECTIVE_CREDENTIAL:
769       // Not expected since this implementation uses the default credential.
770       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
771     case GSS_S_BAD_SIG:
772       // Probably won't happen, but it's a bad response.
773       return ERR_INVALID_RESPONSE;
774     case GSS_S_NO_CRED:
775       return ERR_INVALID_AUTH_CREDENTIALS;
776     case GSS_S_CREDENTIALS_EXPIRED:
777       return ERR_INVALID_AUTH_CREDENTIALS;
778     case GSS_S_BAD_BINDINGS:
779       // This only happens with mutual authentication.
780       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
781     case GSS_S_NO_CONTEXT:
782       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
783     case GSS_S_BAD_NAMETYPE:
784       return ERR_UNSUPPORTED_AUTH_SCHEME;
785     case GSS_S_BAD_NAME:
786       return ERR_UNSUPPORTED_AUTH_SCHEME;
787     case GSS_S_BAD_MECH:
788       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
789     case GSS_S_FAILURE:
790       // This should be an "Unexpected Security Status" according to the
791       // GSSAPI documentation, but it's typically used to indicate that
792       // credentials are not correctly set up on a user machine, such
793       // as a missing credential cache or hitting this after calling
794       // kdestroy.
795       // TODO(cbentzel): Use minor code for even better mapping?
796       return ERR_MISSING_AUTH_CREDENTIALS;
797     default:
798       if (routine_status != 0)
799         return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
800       break;
801   }
802   OM_uint32 supplemental_status = GSS_SUPPLEMENTARY_INFO(major_status);
803   // Replays could indicate an attack.
804   if (supplemental_status & (GSS_S_DUPLICATE_TOKEN | GSS_S_OLD_TOKEN |
805                              GSS_S_UNSEQ_TOKEN | GSS_S_GAP_TOKEN))
806     return ERR_INVALID_RESPONSE;
807 
808   // At this point, every documented status has been checked.
809   return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
810 }
811 
ImportNameErrorParams(GSSAPILibrary * library,std::string_view spn,OM_uint32 major_status,OM_uint32 minor_status)812 base::Value::Dict ImportNameErrorParams(GSSAPILibrary* library,
813                                         std::string_view spn,
814                                         OM_uint32 major_status,
815                                         OM_uint32 minor_status) {
816   base::Value::Dict params;
817   params.Set("spn", spn);
818   if (major_status != GSS_S_COMPLETE)
819     params.Set("status", GetGssStatusValue(library, "import_name", major_status,
820                                            minor_status));
821   return params;
822 }
823 
InitSecContextErrorParams(GSSAPILibrary * library,gss_ctx_id_t context,OM_uint32 major_status,OM_uint32 minor_status)824 base::Value::Dict InitSecContextErrorParams(GSSAPILibrary* library,
825                                             gss_ctx_id_t context,
826                                             OM_uint32 major_status,
827                                             OM_uint32 minor_status) {
828   base::Value::Dict params;
829   if (major_status != GSS_S_COMPLETE)
830     params.Set("status", GetGssStatusValue(library, "gss_init_sec_context",
831                                            major_status, minor_status));
832   if (context != GSS_C_NO_CONTEXT)
833     params.Set("context", GetContextStateAsValue(library, context));
834   return params;
835 }
836 
837 }  // anonymous namespace
838 
GetNextSecurityToken(const std::string & spn,const std::string & channel_bindings,gss_buffer_t in_token,gss_buffer_t out_token,const NetLogWithSource & net_log)839 int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn,
840                                          const std::string& channel_bindings,
841                                          gss_buffer_t in_token,
842                                          gss_buffer_t out_token,
843                                          const NetLogWithSource& net_log) {
844   // GSSAPI header files, to this day, require OIDs passed in as non-const
845   // pointers. Rather than const casting, let's just leave this as non-const.
846   // Even if the OID pointer is const, the inner |elements| pointer is still
847   // non-const.
848   static gss_OID_desc kGSS_C_NT_HOSTBASED_SERVICE = {
849       10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")};
850 
851   // Create a name for the principal
852   // TODO(cbentzel): Just do this on the first pass?
853   std::string spn_principal = spn;
854   gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER;
855   spn_buffer.value = const_cast<char*>(spn_principal.c_str());
856   spn_buffer.length = spn_principal.size() + 1;
857   OM_uint32 minor_status = 0;
858   gss_name_t principal_name = GSS_C_NO_NAME;
859 
860   OM_uint32 major_status =
861       library_->import_name(&minor_status, &spn_buffer,
862                             &kGSS_C_NT_HOSTBASED_SERVICE, &principal_name);
863   net_log.AddEvent(NetLogEventType::AUTH_LIBRARY_IMPORT_NAME, [&] {
864     return ImportNameErrorParams(library_, spn, major_status, minor_status);
865   });
866   int rv = MapImportNameStatusToError(major_status);
867   if (rv != OK)
868     return rv;
869   ScopedName scoped_name(principal_name, library_);
870 
871   // Continue creating a security context.
872   net_log.BeginEvent(NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX);
873   major_status = library_->init_sec_context(
874       &minor_status, GSS_C_NO_CREDENTIAL, scoped_sec_context_.receive(),
875       principal_name, gss_oid_, DelegationTypeToFlag(delegation_type_),
876       GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, in_token,
877       nullptr,  // actual_mech_type
878       out_token,
879       nullptr,  // ret flags
880       nullptr);
881   net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX, [&] {
882     return InitSecContextErrorParams(library_, scoped_sec_context_.get(),
883                                      major_status, minor_status);
884   });
885   return MapInitSecContextStatusToError(major_status);
886 }
887 
888 }  // namespace net
889