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