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