OLD | NEW |
| (Empty) |
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/files/file_path.h" | |
12 #include "base/format_macros.h" | |
13 #include "base/logging.h" | |
14 #include "base/strings/string_util.h" | |
15 #include "base/strings/stringprintf.h" | |
16 #include "base/threading/thread_restrictions.h" | |
17 #include "net/base/net_errors.h" | |
18 #include "net/base/net_util.h" | |
19 #include "net/http/http_auth_challenge_tokenizer.h" | |
20 | |
21 // These are defined for the GSSAPI library: | |
22 // Paraphrasing the comments from gssapi.h: | |
23 // "The implementation must reserve static storage for a | |
24 // gss_OID_desc object for each constant. That constant | |
25 // should be initialized to point to that gss_OID_desc." | |
26 // These are encoded using ASN.1 BER encoding. | |
27 namespace { | |
28 | |
29 static gss_OID_desc GSS_C_NT_USER_NAME_VAL = { | |
30 10, | |
31 const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01") | |
32 }; | |
33 static gss_OID_desc GSS_C_NT_MACHINE_UID_NAME_VAL = { | |
34 10, | |
35 const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02") | |
36 }; | |
37 static gss_OID_desc GSS_C_NT_STRING_UID_NAME_VAL = { | |
38 10, | |
39 const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03") | |
40 }; | |
41 static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X_VAL = { | |
42 6, | |
43 const_cast<char*>("\x2b\x06\x01\x05\x06\x02") | |
44 }; | |
45 static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_VAL = { | |
46 10, | |
47 const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04") | |
48 }; | |
49 static gss_OID_desc GSS_C_NT_ANONYMOUS_VAL = { | |
50 6, | |
51 const_cast<char*>("\x2b\x06\01\x05\x06\x03") | |
52 }; | |
53 static gss_OID_desc GSS_C_NT_EXPORT_NAME_VAL = { | |
54 6, | |
55 const_cast<char*>("\x2b\x06\x01\x05\x06\x04") | |
56 }; | |
57 | |
58 } // namespace | |
59 | |
60 // Heimdal >= 1.4 will define the following as preprocessor macros. | |
61 // To avoid conflicting declarations, we have to undefine these. | |
62 #undef GSS_C_NT_USER_NAME | |
63 #undef GSS_C_NT_MACHINE_UID_NAME | |
64 #undef GSS_C_NT_STRING_UID_NAME | |
65 #undef GSS_C_NT_HOSTBASED_SERVICE_X | |
66 #undef GSS_C_NT_HOSTBASED_SERVICE | |
67 #undef GSS_C_NT_ANONYMOUS | |
68 #undef GSS_C_NT_EXPORT_NAME | |
69 | |
70 gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_VAL; | |
71 gss_OID GSS_C_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_VAL; | |
72 gss_OID GSS_C_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_VAL; | |
73 gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &GSS_C_NT_HOSTBASED_SERVICE_X_VAL; | |
74 gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_VAL; | |
75 gss_OID GSS_C_NT_ANONYMOUS = &GSS_C_NT_ANONYMOUS_VAL; | |
76 gss_OID GSS_C_NT_EXPORT_NAME = &GSS_C_NT_EXPORT_NAME_VAL; | |
77 | |
78 namespace net { | |
79 | |
80 // Exported mechanism for GSSAPI. We always use SPNEGO: | |
81 | |
82 // iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2) | |
83 gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = { | |
84 6, | |
85 const_cast<char*>("\x2b\x06\x01\x05\x05\x02") | |
86 }; | |
87 | |
88 gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC = | |
89 &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL; | |
90 | |
91 // Debugging helpers. | |
92 namespace { | |
93 | |
94 std::string DisplayStatus(OM_uint32 major_status, | |
95 OM_uint32 minor_status) { | |
96 if (major_status == GSS_S_COMPLETE) | |
97 return "OK"; | |
98 return base::StringPrintf("0x%08X 0x%08X", major_status, minor_status); | |
99 } | |
100 | |
101 std::string DisplayCode(GSSAPILibrary* gssapi_lib, | |
102 OM_uint32 status, | |
103 OM_uint32 status_code_type) { | |
104 const int kMaxDisplayIterations = 8; | |
105 const size_t kMaxMsgLength = 4096; | |
106 // msg_ctx needs to be outside the loop because it is invoked multiple times. | |
107 OM_uint32 msg_ctx = 0; | |
108 std::string rv = base::StringPrintf("(0x%08X)", status); | |
109 | |
110 // This loop should continue iterating until msg_ctx is 0 after the first | |
111 // iteration. To be cautious and prevent an infinite loop, it stops after | |
112 // a finite number of iterations as well. As an added sanity check, no | |
113 // individual message may exceed |kMaxMsgLength|, and the final result | |
114 // will not exceed |kMaxMsgLength|*2-1. | |
115 for (int i = 0; i < kMaxDisplayIterations && rv.size() < kMaxMsgLength; | |
116 ++i) { | |
117 OM_uint32 min_stat; | |
118 gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER; | |
119 OM_uint32 maj_stat = | |
120 gssapi_lib->display_status(&min_stat, status, status_code_type, | |
121 GSS_C_NULL_OID, &msg_ctx, &msg); | |
122 if (maj_stat == GSS_S_COMPLETE) { | |
123 int msg_len = (msg.length > kMaxMsgLength) ? | |
124 static_cast<int>(kMaxMsgLength) : | |
125 static_cast<int>(msg.length); | |
126 if (msg_len > 0 && msg.value != NULL) { | |
127 rv += base::StringPrintf(" %.*s", msg_len, | |
128 static_cast<char*>(msg.value)); | |
129 } | |
130 } | |
131 gssapi_lib->release_buffer(&min_stat, &msg); | |
132 if (!msg_ctx) | |
133 break; | |
134 } | |
135 return rv; | |
136 } | |
137 | |
138 std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib, | |
139 OM_uint32 major_status, | |
140 OM_uint32 minor_status) { | |
141 if (major_status == GSS_S_COMPLETE) | |
142 return "OK"; | |
143 std::string major = DisplayCode(gssapi_lib, major_status, GSS_C_GSS_CODE); | |
144 std::string minor = DisplayCode(gssapi_lib, minor_status, GSS_C_MECH_CODE); | |
145 return base::StringPrintf("Major: %s | Minor: %s", major.c_str(), | |
146 minor.c_str()); | |
147 } | |
148 | |
149 // ScopedName releases a gss_name_t when it goes out of scope. | |
150 class ScopedName { | |
151 public: | |
152 ScopedName(gss_name_t name, | |
153 GSSAPILibrary* gssapi_lib) | |
154 : name_(name), | |
155 gssapi_lib_(gssapi_lib) { | |
156 DCHECK(gssapi_lib_); | |
157 } | |
158 | |
159 ~ScopedName() { | |
160 if (name_ != GSS_C_NO_NAME) { | |
161 OM_uint32 minor_status = 0; | |
162 OM_uint32 major_status = | |
163 gssapi_lib_->release_name(&minor_status, &name_); | |
164 if (major_status != GSS_S_COMPLETE) { | |
165 LOG(WARNING) << "Problem releasing name. " | |
166 << DisplayStatus(major_status, minor_status); | |
167 } | |
168 name_ = GSS_C_NO_NAME; | |
169 } | |
170 } | |
171 | |
172 private: | |
173 gss_name_t name_; | |
174 GSSAPILibrary* gssapi_lib_; | |
175 | |
176 DISALLOW_COPY_AND_ASSIGN(ScopedName); | |
177 }; | |
178 | |
179 // ScopedBuffer releases a gss_buffer_t when it goes out of scope. | |
180 class ScopedBuffer { | |
181 public: | |
182 ScopedBuffer(gss_buffer_t buffer, | |
183 GSSAPILibrary* gssapi_lib) | |
184 : buffer_(buffer), | |
185 gssapi_lib_(gssapi_lib) { | |
186 DCHECK(gssapi_lib_); | |
187 } | |
188 | |
189 ~ScopedBuffer() { | |
190 if (buffer_ != GSS_C_NO_BUFFER) { | |
191 OM_uint32 minor_status = 0; | |
192 OM_uint32 major_status = | |
193 gssapi_lib_->release_buffer(&minor_status, buffer_); | |
194 if (major_status != GSS_S_COMPLETE) { | |
195 LOG(WARNING) << "Problem releasing buffer. " | |
196 << DisplayStatus(major_status, minor_status); | |
197 } | |
198 buffer_ = GSS_C_NO_BUFFER; | |
199 } | |
200 } | |
201 | |
202 private: | |
203 gss_buffer_t buffer_; | |
204 GSSAPILibrary* gssapi_lib_; | |
205 | |
206 DISALLOW_COPY_AND_ASSIGN(ScopedBuffer); | |
207 }; | |
208 | |
209 namespace { | |
210 | |
211 std::string AppendIfPredefinedValue(gss_OID oid, | |
212 gss_OID predefined_oid, | |
213 const char* predefined_oid_name) { | |
214 DCHECK(oid); | |
215 DCHECK(predefined_oid); | |
216 DCHECK(predefined_oid_name); | |
217 std::string output; | |
218 if (oid->length != predefined_oid->length) | |
219 return output; | |
220 if (0 != memcmp(oid->elements, | |
221 predefined_oid->elements, | |
222 predefined_oid->length)) | |
223 return output; | |
224 | |
225 output += " ("; | |
226 output += predefined_oid_name; | |
227 output += ")"; | |
228 return output; | |
229 } | |
230 | |
231 } // namespace | |
232 | |
233 std::string DescribeOid(GSSAPILibrary* gssapi_lib, const gss_OID oid) { | |
234 if (!oid) | |
235 return "<NULL>"; | |
236 std::string output; | |
237 const size_t kMaxCharsToPrint = 1024; | |
238 OM_uint32 byte_length = oid->length; | |
239 size_t char_length = byte_length / sizeof(char); | |
240 if (char_length > kMaxCharsToPrint) { | |
241 // This might be a plain ASCII string. | |
242 // Check if the first |kMaxCharsToPrint| characters | |
243 // contain only printable characters and are NULL terminated. | |
244 const char* str = reinterpret_cast<const char*>(oid); | |
245 size_t str_length = 0; | |
246 for ( ; str_length < kMaxCharsToPrint; ++str_length) { | |
247 if (!str[str_length] || !isprint(str[str_length])) | |
248 break; | |
249 } | |
250 if (!str[str_length]) { | |
251 output += base::StringPrintf("\"%s\"", str); | |
252 return output; | |
253 } | |
254 } | |
255 output = base::StringPrintf("(%u) \"", byte_length); | |
256 if (!oid->elements) { | |
257 output += "<NULL>"; | |
258 return output; | |
259 } | |
260 const unsigned char* elements = | |
261 reinterpret_cast<const unsigned char*>(oid->elements); | |
262 // Don't print more than |kMaxCharsToPrint| characters. | |
263 size_t i = 0; | |
264 for ( ; (i < byte_length) && (i < kMaxCharsToPrint); ++i) { | |
265 output += base::StringPrintf("\\x%02X", elements[i]); | |
266 } | |
267 if (i >= kMaxCharsToPrint) | |
268 output += "..."; | |
269 output += "\""; | |
270 | |
271 // Check if the OID is one of the predefined values. | |
272 output += AppendIfPredefinedValue(oid, | |
273 GSS_C_NT_USER_NAME, | |
274 "GSS_C_NT_USER_NAME"); | |
275 output += AppendIfPredefinedValue(oid, | |
276 GSS_C_NT_MACHINE_UID_NAME, | |
277 "GSS_C_NT_MACHINE_UID_NAME"); | |
278 output += AppendIfPredefinedValue(oid, | |
279 GSS_C_NT_STRING_UID_NAME, | |
280 "GSS_C_NT_STRING_UID_NAME"); | |
281 output += AppendIfPredefinedValue(oid, | |
282 GSS_C_NT_HOSTBASED_SERVICE_X, | |
283 "GSS_C_NT_HOSTBASED_SERVICE_X"); | |
284 output += AppendIfPredefinedValue(oid, | |
285 GSS_C_NT_HOSTBASED_SERVICE, | |
286 "GSS_C_NT_HOSTBASED_SERVICE"); | |
287 output += AppendIfPredefinedValue(oid, | |
288 GSS_C_NT_ANONYMOUS, | |
289 "GSS_C_NT_ANONYMOUS"); | |
290 output += AppendIfPredefinedValue(oid, | |
291 GSS_C_NT_EXPORT_NAME, | |
292 "GSS_C_NT_EXPORT_NAME"); | |
293 | |
294 return output; | |
295 } | |
296 | |
297 std::string DescribeName(GSSAPILibrary* gssapi_lib, const gss_name_t name) { | |
298 OM_uint32 major_status = 0; | |
299 OM_uint32 minor_status = 0; | |
300 gss_buffer_desc_struct output_name_buffer = GSS_C_EMPTY_BUFFER; | |
301 gss_OID_desc output_name_type_desc = GSS_C_EMPTY_BUFFER; | |
302 gss_OID output_name_type = &output_name_type_desc; | |
303 major_status = gssapi_lib->display_name(&minor_status, | |
304 name, | |
305 &output_name_buffer, | |
306 &output_name_type); | |
307 ScopedBuffer scoped_output_name(&output_name_buffer, gssapi_lib); | |
308 if (major_status != GSS_S_COMPLETE) { | |
309 std::string error = | |
310 base::StringPrintf("Unable to describe name 0x%p, %s", | |
311 name, | |
312 DisplayExtendedStatus(gssapi_lib, | |
313 major_status, | |
314 minor_status).c_str()); | |
315 return error; | |
316 } | |
317 int len = output_name_buffer.length; | |
318 std::string description = base::StringPrintf( | |
319 "%*s (Type %s)", | |
320 len, | |
321 reinterpret_cast<const char*>(output_name_buffer.value), | |
322 DescribeOid(gssapi_lib, output_name_type).c_str()); | |
323 return description; | |
324 } | |
325 | |
326 std::string DescribeContext(GSSAPILibrary* gssapi_lib, | |
327 const gss_ctx_id_t context_handle) { | |
328 OM_uint32 major_status = 0; | |
329 OM_uint32 minor_status = 0; | |
330 gss_name_t src_name = GSS_C_NO_NAME; | |
331 gss_name_t targ_name = GSS_C_NO_NAME; | |
332 OM_uint32 lifetime_rec = 0; | |
333 gss_OID mech_type = GSS_C_NO_OID; | |
334 OM_uint32 ctx_flags = 0; | |
335 int locally_initiated = 0; | |
336 int open = 0; | |
337 if (context_handle == GSS_C_NO_CONTEXT) | |
338 return std::string("Context: GSS_C_NO_CONTEXT"); | |
339 major_status = gssapi_lib->inquire_context(&minor_status, | |
340 context_handle, | |
341 &src_name, | |
342 &targ_name, | |
343 &lifetime_rec, | |
344 &mech_type, | |
345 &ctx_flags, | |
346 &locally_initiated, | |
347 &open); | |
348 ScopedName(src_name, gssapi_lib); | |
349 ScopedName(targ_name, gssapi_lib); | |
350 if (major_status != GSS_S_COMPLETE) { | |
351 std::string error = | |
352 base::StringPrintf("Unable to describe context 0x%p, %s", | |
353 context_handle, | |
354 DisplayExtendedStatus(gssapi_lib, | |
355 major_status, | |
356 minor_status).c_str()); | |
357 return error; | |
358 } | |
359 std::string source(DescribeName(gssapi_lib, src_name)); | |
360 std::string target(DescribeName(gssapi_lib, targ_name)); | |
361 std::string description = base::StringPrintf("Context 0x%p: " | |
362 "Source \"%s\", " | |
363 "Target \"%s\", " | |
364 "lifetime %d, " | |
365 "mechanism %s, " | |
366 "flags 0x%08X, " | |
367 "local %d, " | |
368 "open %d", | |
369 context_handle, | |
370 source.c_str(), | |
371 target.c_str(), | |
372 lifetime_rec, | |
373 DescribeOid(gssapi_lib, | |
374 mech_type).c_str(), | |
375 ctx_flags, | |
376 locally_initiated, | |
377 open); | |
378 return description; | |
379 } | |
380 | |
381 } // namespace | |
382 | |
383 GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name) | |
384 : initialized_(false), | |
385 gssapi_library_name_(gssapi_library_name), | |
386 gssapi_library_(NULL), | |
387 import_name_(NULL), | |
388 release_name_(NULL), | |
389 release_buffer_(NULL), | |
390 display_name_(NULL), | |
391 display_status_(NULL), | |
392 init_sec_context_(NULL), | |
393 wrap_size_limit_(NULL), | |
394 delete_sec_context_(NULL), | |
395 inquire_context_(NULL) { | |
396 } | |
397 | |
398 GSSAPISharedLibrary::~GSSAPISharedLibrary() { | |
399 if (gssapi_library_) { | |
400 base::UnloadNativeLibrary(gssapi_library_); | |
401 gssapi_library_ = NULL; | |
402 } | |
403 } | |
404 | |
405 bool GSSAPISharedLibrary::Init() { | |
406 if (!initialized_) | |
407 InitImpl(); | |
408 return initialized_; | |
409 } | |
410 | |
411 bool GSSAPISharedLibrary::InitImpl() { | |
412 DCHECK(!initialized_); | |
413 #if defined(DLOPEN_KERBEROS) | |
414 gssapi_library_ = LoadSharedLibrary(); | |
415 if (gssapi_library_ == NULL) | |
416 return false; | |
417 #endif // defined(DLOPEN_KERBEROS) | |
418 initialized_ = true; | |
419 return true; | |
420 } | |
421 | |
422 base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary() { | |
423 const char* const* library_names; | |
424 size_t num_lib_names; | |
425 const char* user_specified_library[1]; | |
426 if (!gssapi_library_name_.empty()) { | |
427 user_specified_library[0] = gssapi_library_name_.c_str(); | |
428 library_names = user_specified_library; | |
429 num_lib_names = 1; | |
430 } else { | |
431 static const char* const kDefaultLibraryNames[] = { | |
432 #if defined(OS_MACOSX) | |
433 "libgssapi_krb5.dylib" // MIT Kerberos | |
434 #elif defined(OS_OPENBSD) | |
435 "libgssapi.so" // Heimdal - OpenBSD | |
436 #else | |
437 "libgssapi_krb5.so.2", // MIT Kerberos - FC, Suse10, Debian | |
438 "libgssapi.so.4", // Heimdal - Suse10, MDK | |
439 "libgssapi.so.2", // Heimdal - Gentoo | |
440 "libgssapi.so.1" // Heimdal - Suse9, CITI - FC, MDK, Suse10 | |
441 #endif | |
442 }; | |
443 library_names = kDefaultLibraryNames; | |
444 num_lib_names = arraysize(kDefaultLibraryNames); | |
445 } | |
446 | |
447 for (size_t i = 0; i < num_lib_names; ++i) { | |
448 const char* library_name = library_names[i]; | |
449 base::FilePath file_path(library_name); | |
450 | |
451 // TODO(asanka): Move library loading to a separate thread. | |
452 // http://crbug.com/66702 | |
453 base::ThreadRestrictions::ScopedAllowIO allow_io_temporarily; | |
454 base::NativeLibrary lib = base::LoadNativeLibrary(file_path, NULL); | |
455 if (lib) { | |
456 // Only return this library if we can bind the functions we need. | |
457 if (BindMethods(lib)) | |
458 return lib; | |
459 base::UnloadNativeLibrary(lib); | |
460 } | |
461 } | |
462 LOG(WARNING) << "Unable to find a compatible GSSAPI library"; | |
463 return NULL; | |
464 } | |
465 | |
466 #if defined(DLOPEN_KERBEROS) | |
467 #define BIND(lib, x) \ | |
468 DCHECK(lib); \ | |
469 gss_##x##_type x = reinterpret_cast<gss_##x##_type>( \ | |
470 base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \ | |
471 if (x == NULL) { \ | |
472 LOG(WARNING) << "Unable to bind function \"" << "gss_" #x << "\""; \ | |
473 return false; \ | |
474 } | |
475 #else | |
476 #define BIND(lib, x) gss_##x##_type x = gss_##x | |
477 #endif | |
478 | |
479 bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) { | |
480 BIND(lib, import_name); | |
481 BIND(lib, release_name); | |
482 BIND(lib, release_buffer); | |
483 BIND(lib, display_name); | |
484 BIND(lib, display_status); | |
485 BIND(lib, init_sec_context); | |
486 BIND(lib, wrap_size_limit); | |
487 BIND(lib, delete_sec_context); | |
488 BIND(lib, inquire_context); | |
489 | |
490 import_name_ = import_name; | |
491 release_name_ = release_name; | |
492 release_buffer_ = release_buffer; | |
493 display_name_ = display_name; | |
494 display_status_ = display_status; | |
495 init_sec_context_ = init_sec_context; | |
496 wrap_size_limit_ = wrap_size_limit; | |
497 delete_sec_context_ = delete_sec_context; | |
498 inquire_context_ = inquire_context; | |
499 | |
500 return true; | |
501 } | |
502 | |
503 #undef BIND | |
504 | |
505 OM_uint32 GSSAPISharedLibrary::import_name( | |
506 OM_uint32* minor_status, | |
507 const gss_buffer_t input_name_buffer, | |
508 const gss_OID input_name_type, | |
509 gss_name_t* output_name) { | |
510 DCHECK(initialized_); | |
511 return import_name_(minor_status, input_name_buffer, input_name_type, | |
512 output_name); | |
513 } | |
514 | |
515 OM_uint32 GSSAPISharedLibrary::release_name( | |
516 OM_uint32* minor_status, | |
517 gss_name_t* input_name) { | |
518 DCHECK(initialized_); | |
519 return release_name_(minor_status, input_name); | |
520 } | |
521 | |
522 OM_uint32 GSSAPISharedLibrary::release_buffer( | |
523 OM_uint32* minor_status, | |
524 gss_buffer_t buffer) { | |
525 DCHECK(initialized_); | |
526 return release_buffer_(minor_status, buffer); | |
527 } | |
528 | |
529 OM_uint32 GSSAPISharedLibrary::display_name( | |
530 OM_uint32* minor_status, | |
531 const gss_name_t input_name, | |
532 gss_buffer_t output_name_buffer, | |
533 gss_OID* output_name_type) { | |
534 DCHECK(initialized_); | |
535 return display_name_(minor_status, | |
536 input_name, | |
537 output_name_buffer, | |
538 output_name_type); | |
539 } | |
540 | |
541 OM_uint32 GSSAPISharedLibrary::display_status( | |
542 OM_uint32* minor_status, | |
543 OM_uint32 status_value, | |
544 int status_type, | |
545 const gss_OID mech_type, | |
546 OM_uint32* message_context, | |
547 gss_buffer_t status_string) { | |
548 DCHECK(initialized_); | |
549 return display_status_(minor_status, status_value, status_type, mech_type, | |
550 message_context, status_string); | |
551 } | |
552 | |
553 OM_uint32 GSSAPISharedLibrary::init_sec_context( | |
554 OM_uint32* minor_status, | |
555 const gss_cred_id_t initiator_cred_handle, | |
556 gss_ctx_id_t* context_handle, | |
557 const gss_name_t target_name, | |
558 const gss_OID mech_type, | |
559 OM_uint32 req_flags, | |
560 OM_uint32 time_req, | |
561 const gss_channel_bindings_t input_chan_bindings, | |
562 const gss_buffer_t input_token, | |
563 gss_OID* actual_mech_type, | |
564 gss_buffer_t output_token, | |
565 OM_uint32* ret_flags, | |
566 OM_uint32* time_rec) { | |
567 DCHECK(initialized_); | |
568 return init_sec_context_(minor_status, | |
569 initiator_cred_handle, | |
570 context_handle, | |
571 target_name, | |
572 mech_type, | |
573 req_flags, | |
574 time_req, | |
575 input_chan_bindings, | |
576 input_token, | |
577 actual_mech_type, | |
578 output_token, | |
579 ret_flags, | |
580 time_rec); | |
581 } | |
582 | |
583 OM_uint32 GSSAPISharedLibrary::wrap_size_limit( | |
584 OM_uint32* minor_status, | |
585 const gss_ctx_id_t context_handle, | |
586 int conf_req_flag, | |
587 gss_qop_t qop_req, | |
588 OM_uint32 req_output_size, | |
589 OM_uint32* max_input_size) { | |
590 DCHECK(initialized_); | |
591 return wrap_size_limit_(minor_status, | |
592 context_handle, | |
593 conf_req_flag, | |
594 qop_req, | |
595 req_output_size, | |
596 max_input_size); | |
597 } | |
598 | |
599 OM_uint32 GSSAPISharedLibrary::delete_sec_context( | |
600 OM_uint32* minor_status, | |
601 gss_ctx_id_t* context_handle, | |
602 gss_buffer_t output_token) { | |
603 // This is called from the owner class' destructor, even if | |
604 // Init() is not called, so we can't assume |initialized_| | |
605 // is set. | |
606 if (!initialized_) | |
607 return 0; | |
608 return delete_sec_context_(minor_status, | |
609 context_handle, | |
610 output_token); | |
611 } | |
612 | |
613 OM_uint32 GSSAPISharedLibrary::inquire_context( | |
614 OM_uint32* minor_status, | |
615 const gss_ctx_id_t context_handle, | |
616 gss_name_t* src_name, | |
617 gss_name_t* targ_name, | |
618 OM_uint32* lifetime_rec, | |
619 gss_OID* mech_type, | |
620 OM_uint32* ctx_flags, | |
621 int* locally_initiated, | |
622 int* open) { | |
623 DCHECK(initialized_); | |
624 return inquire_context_(minor_status, | |
625 context_handle, | |
626 src_name, | |
627 targ_name, | |
628 lifetime_rec, | |
629 mech_type, | |
630 ctx_flags, | |
631 locally_initiated, | |
632 open); | |
633 } | |
634 | |
635 ScopedSecurityContext::ScopedSecurityContext(GSSAPILibrary* gssapi_lib) | |
636 : security_context_(GSS_C_NO_CONTEXT), | |
637 gssapi_lib_(gssapi_lib) { | |
638 DCHECK(gssapi_lib_); | |
639 } | |
640 | |
641 ScopedSecurityContext::~ScopedSecurityContext() { | |
642 if (security_context_ != GSS_C_NO_CONTEXT) { | |
643 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; | |
644 OM_uint32 minor_status = 0; | |
645 OM_uint32 major_status = gssapi_lib_->delete_sec_context( | |
646 &minor_status, &security_context_, &output_token); | |
647 if (major_status != GSS_S_COMPLETE) { | |
648 LOG(WARNING) << "Problem releasing security_context. " | |
649 << DisplayStatus(major_status, minor_status); | |
650 } | |
651 security_context_ = GSS_C_NO_CONTEXT; | |
652 } | |
653 } | |
654 | |
655 HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library, | |
656 const std::string& scheme, | |
657 gss_OID gss_oid) | |
658 : scheme_(scheme), | |
659 gss_oid_(gss_oid), | |
660 library_(library), | |
661 scoped_sec_context_(library), | |
662 can_delegate_(false) { | |
663 DCHECK(library_); | |
664 } | |
665 | |
666 HttpAuthGSSAPI::~HttpAuthGSSAPI() { | |
667 } | |
668 | |
669 bool HttpAuthGSSAPI::Init() { | |
670 if (!library_) | |
671 return false; | |
672 return library_->Init(); | |
673 } | |
674 | |
675 bool HttpAuthGSSAPI::NeedsIdentity() const { | |
676 return decoded_server_auth_token_.empty(); | |
677 } | |
678 | |
679 bool HttpAuthGSSAPI::AllowsExplicitCredentials() const { | |
680 return false; | |
681 } | |
682 | |
683 void HttpAuthGSSAPI::Delegate() { | |
684 can_delegate_ = true; | |
685 } | |
686 | |
687 HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge( | |
688 HttpAuthChallengeTokenizer* tok) { | |
689 // Verify the challenge's auth-scheme. | |
690 if (!LowerCaseEqualsASCII(tok->scheme(), | |
691 base::StringToLowerASCII(scheme_).c_str())) | |
692 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
693 | |
694 std::string encoded_auth_token = tok->base64_param(); | |
695 | |
696 if (encoded_auth_token.empty()) { | |
697 // If a context has already been established, an empty Negotiate challenge | |
698 // should be treated as a rejection of the current attempt. | |
699 if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT) | |
700 return HttpAuth::AUTHORIZATION_RESULT_REJECT; | |
701 DCHECK(decoded_server_auth_token_.empty()); | |
702 return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; | |
703 } else { | |
704 // If a context has not already been established, additional tokens should | |
705 // not be present in the auth challenge. | |
706 if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) | |
707 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
708 } | |
709 | |
710 // Make sure the additional token is base64 encoded. | |
711 std::string decoded_auth_token; | |
712 bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token); | |
713 if (!base64_rv) | |
714 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
715 decoded_server_auth_token_ = decoded_auth_token; | |
716 return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; | |
717 } | |
718 | |
719 int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials, | |
720 const std::string& spn, | |
721 std::string* auth_token) { | |
722 DCHECK(auth_token); | |
723 | |
724 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; | |
725 input_token.length = decoded_server_auth_token_.length(); | |
726 input_token.value = (input_token.length > 0) ? | |
727 const_cast<char*>(decoded_server_auth_token_.data()) : | |
728 NULL; | |
729 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; | |
730 ScopedBuffer scoped_output_token(&output_token, library_); | |
731 int rv = GetNextSecurityToken(spn, &input_token, &output_token); | |
732 if (rv != OK) | |
733 return rv; | |
734 | |
735 // Base64 encode data in output buffer and prepend the scheme. | |
736 std::string encode_input(static_cast<char*>(output_token.value), | |
737 output_token.length); | |
738 std::string encode_output; | |
739 base::Base64Encode(encode_input, &encode_output); | |
740 *auth_token = scheme_ + " " + encode_output; | |
741 return OK; | |
742 } | |
743 | |
744 | |
745 namespace { | |
746 | |
747 // GSSAPI status codes consist of a calling error (essentially, a programmer | |
748 // bug), a routine error (defined by the RFC), and supplementary information, | |
749 // all bitwise-or'ed together in different regions of the 32 bit return value. | |
750 // This means a simple switch on the return codes is not sufficient. | |
751 | |
752 int MapImportNameStatusToError(OM_uint32 major_status) { | |
753 VLOG(1) << "import_name returned 0x" << std::hex << major_status; | |
754 if (major_status == GSS_S_COMPLETE) | |
755 return OK; | |
756 if (GSS_CALLING_ERROR(major_status) != 0) | |
757 return ERR_UNEXPECTED; | |
758 OM_uint32 routine_error = GSS_ROUTINE_ERROR(major_status); | |
759 switch (routine_error) { | |
760 case GSS_S_FAILURE: | |
761 // Looking at the MIT Kerberos implementation, this typically is returned | |
762 // when memory allocation fails. However, the API does not guarantee | |
763 // that this is the case, so using ERR_UNEXPECTED rather than | |
764 // ERR_OUT_OF_MEMORY. | |
765 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
766 case GSS_S_BAD_NAME: | |
767 case GSS_S_BAD_NAMETYPE: | |
768 return ERR_MALFORMED_IDENTITY; | |
769 case GSS_S_DEFECTIVE_TOKEN: | |
770 // Not mentioned in the API, but part of code. | |
771 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
772 case GSS_S_BAD_MECH: | |
773 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
774 default: | |
775 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
776 } | |
777 } | |
778 | |
779 int MapInitSecContextStatusToError(OM_uint32 major_status) { | |
780 VLOG(1) << "init_sec_context returned 0x" << std::hex << major_status; | |
781 // Although GSS_S_CONTINUE_NEEDED is an additional bit, it seems like | |
782 // other code just checks if major_status is equivalent to it to indicate | |
783 // that there are no other errors included. | |
784 if (major_status == GSS_S_COMPLETE || major_status == GSS_S_CONTINUE_NEEDED) | |
785 return OK; | |
786 if (GSS_CALLING_ERROR(major_status) != 0) | |
787 return ERR_UNEXPECTED; | |
788 OM_uint32 routine_status = GSS_ROUTINE_ERROR(major_status); | |
789 switch (routine_status) { | |
790 case GSS_S_DEFECTIVE_TOKEN: | |
791 return ERR_INVALID_RESPONSE; | |
792 case GSS_S_DEFECTIVE_CREDENTIAL: | |
793 // Not expected since this implementation uses the default credential. | |
794 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
795 case GSS_S_BAD_SIG: | |
796 // Probably won't happen, but it's a bad response. | |
797 return ERR_INVALID_RESPONSE; | |
798 case GSS_S_NO_CRED: | |
799 return ERR_INVALID_AUTH_CREDENTIALS; | |
800 case GSS_S_CREDENTIALS_EXPIRED: | |
801 return ERR_INVALID_AUTH_CREDENTIALS; | |
802 case GSS_S_BAD_BINDINGS: | |
803 // This only happens with mutual authentication. | |
804 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
805 case GSS_S_NO_CONTEXT: | |
806 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
807 case GSS_S_BAD_NAMETYPE: | |
808 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
809 case GSS_S_BAD_NAME: | |
810 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
811 case GSS_S_BAD_MECH: | |
812 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
813 case GSS_S_FAILURE: | |
814 // This should be an "Unexpected Security Status" according to the | |
815 // GSSAPI documentation, but it's typically used to indicate that | |
816 // credentials are not correctly set up on a user machine, such | |
817 // as a missing credential cache or hitting this after calling | |
818 // kdestroy. | |
819 // TODO(cbentzel): Use minor code for even better mapping? | |
820 return ERR_MISSING_AUTH_CREDENTIALS; | |
821 default: | |
822 if (routine_status != 0) | |
823 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
824 break; | |
825 } | |
826 OM_uint32 supplemental_status = GSS_SUPPLEMENTARY_INFO(major_status); | |
827 // Replays could indicate an attack. | |
828 if (supplemental_status & (GSS_S_DUPLICATE_TOKEN | GSS_S_OLD_TOKEN | | |
829 GSS_S_UNSEQ_TOKEN | GSS_S_GAP_TOKEN)) | |
830 return ERR_INVALID_RESPONSE; | |
831 | |
832 // At this point, every documented status has been checked. | |
833 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
834 } | |
835 | |
836 } | |
837 | |
838 int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn, | |
839 gss_buffer_t in_token, | |
840 gss_buffer_t out_token) { | |
841 // Create a name for the principal | |
842 // TODO(cbentzel): Just do this on the first pass? | |
843 std::string spn_principal = spn; | |
844 gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER; | |
845 spn_buffer.value = const_cast<char*>(spn_principal.c_str()); | |
846 spn_buffer.length = spn_principal.size() + 1; | |
847 OM_uint32 minor_status = 0; | |
848 gss_name_t principal_name = GSS_C_NO_NAME; | |
849 OM_uint32 major_status = library_->import_name( | |
850 &minor_status, | |
851 &spn_buffer, | |
852 GSS_C_NT_HOSTBASED_SERVICE, | |
853 &principal_name); | |
854 int rv = MapImportNameStatusToError(major_status); | |
855 if (rv != OK) { | |
856 LOG(ERROR) << "Problem importing name from " | |
857 << "spn \"" << spn_principal << "\"\n" | |
858 << DisplayExtendedStatus(library_, major_status, minor_status); | |
859 return rv; | |
860 } | |
861 ScopedName scoped_name(principal_name, library_); | |
862 | |
863 // Continue creating a security context. | |
864 OM_uint32 req_flags = 0; | |
865 if (can_delegate_) | |
866 req_flags |= GSS_C_DELEG_FLAG; | |
867 major_status = library_->init_sec_context( | |
868 &minor_status, | |
869 GSS_C_NO_CREDENTIAL, | |
870 scoped_sec_context_.receive(), | |
871 principal_name, | |
872 gss_oid_, | |
873 req_flags, | |
874 GSS_C_INDEFINITE, | |
875 GSS_C_NO_CHANNEL_BINDINGS, | |
876 in_token, | |
877 NULL, // actual_mech_type | |
878 out_token, | |
879 NULL, // ret flags | |
880 NULL); | |
881 rv = MapInitSecContextStatusToError(major_status); | |
882 if (rv != OK) { | |
883 LOG(ERROR) << "Problem initializing context. \n" | |
884 << DisplayExtendedStatus(library_, major_status, minor_status) | |
885 << '\n' | |
886 << DescribeContext(library_, scoped_sec_context_.get()); | |
887 } | |
888 return rv; | |
889 } | |
890 | |
891 } // namespace net | |
OLD | NEW |