Home | History | Annotate | Download | only in http
      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 // See "SSPI Sample Application" at
      6 // http://msdn.microsoft.com/en-us/library/aa918273.aspx
      7 
      8 #include "net/http/http_auth_sspi_win.h"
      9 
     10 #include "base/base64.h"
     11 #include "base/logging.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "net/base/net_errors.h"
     15 #include "net/http/http_auth.h"
     16 #include "net/http/http_auth_challenge_tokenizer.h"
     17 
     18 namespace net {
     19 
     20 namespace {
     21 
     22 int MapAcquireCredentialsStatusToError(SECURITY_STATUS status,
     23                                        const SEC_WCHAR* package) {
     24   VLOG(1) << "AcquireCredentialsHandle returned 0x" << std::hex << status;
     25   switch (status) {
     26     case SEC_E_OK:
     27       return OK;
     28     case SEC_E_INSUFFICIENT_MEMORY:
     29       return ERR_OUT_OF_MEMORY;
     30     case SEC_E_INTERNAL_ERROR:
     31       LOG(WARNING)
     32           << "AcquireCredentialsHandle returned unexpected status 0x"
     33           << std::hex << status;
     34       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
     35     case SEC_E_NO_CREDENTIALS:
     36     case SEC_E_NOT_OWNER:
     37     case SEC_E_UNKNOWN_CREDENTIALS:
     38       return ERR_INVALID_AUTH_CREDENTIALS;
     39     case SEC_E_SECPKG_NOT_FOUND:
     40       // This indicates that the SSPI configuration does not match expectations
     41       return ERR_UNSUPPORTED_AUTH_SCHEME;
     42     default:
     43       LOG(WARNING)
     44           << "AcquireCredentialsHandle returned undocumented status 0x"
     45           << std::hex << status;
     46       return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
     47   }
     48 }
     49 
     50 int AcquireExplicitCredentials(SSPILibrary* library,
     51                                const SEC_WCHAR* package,
     52                                const base::string16& domain,
     53                                const base::string16& user,
     54                                const base::string16& password,
     55                                CredHandle* cred) {
     56   SEC_WINNT_AUTH_IDENTITY identity;
     57   identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
     58   identity.User =
     59       reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user.c_str()));
     60   identity.UserLength = user.size();
     61   identity.Domain =
     62       reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain.c_str()));
     63   identity.DomainLength = domain.size();
     64   identity.Password =
     65       reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password.c_str()));
     66   identity.PasswordLength = password.size();
     67 
     68   TimeStamp expiry;
     69 
     70   // Pass the username/password to get the credentials handle.
     71   SECURITY_STATUS status = library->AcquireCredentialsHandle(
     72       NULL,  // pszPrincipal
     73       const_cast<SEC_WCHAR*>(package),  // pszPackage
     74       SECPKG_CRED_OUTBOUND,  // fCredentialUse
     75       NULL,  // pvLogonID
     76       &identity,  // pAuthData
     77       NULL,  // pGetKeyFn (not used)
     78       NULL,  // pvGetKeyArgument (not used)
     79       cred,  // phCredential
     80       &expiry);  // ptsExpiry
     81 
     82   return MapAcquireCredentialsStatusToError(status, package);
     83 }
     84 
     85 int AcquireDefaultCredentials(SSPILibrary* library, const SEC_WCHAR* package,
     86                               CredHandle* cred) {
     87   TimeStamp expiry;
     88 
     89   // Pass the username/password to get the credentials handle.
     90   // Note: Since the 5th argument is NULL, it uses the default
     91   // cached credentials for the logged in user, which can be used
     92   // for a single sign-on.
     93   SECURITY_STATUS status = library->AcquireCredentialsHandle(
     94       NULL,  // pszPrincipal
     95       const_cast<SEC_WCHAR*>(package),  // pszPackage
     96       SECPKG_CRED_OUTBOUND,  // fCredentialUse
     97       NULL,  // pvLogonID
     98       NULL,  // pAuthData
     99       NULL,  // pGetKeyFn (not used)
    100       NULL,  // pvGetKeyArgument (not used)
    101       cred,  // phCredential
    102       &expiry);  // ptsExpiry
    103 
    104   return MapAcquireCredentialsStatusToError(status, package);
    105 }
    106 
    107 int MapInitializeSecurityContextStatusToError(SECURITY_STATUS status) {
    108   VLOG(1) << "InitializeSecurityContext returned 0x" << std::hex << status;
    109   switch (status) {
    110     case SEC_E_OK:
    111     case SEC_I_CONTINUE_NEEDED:
    112       return OK;
    113     case SEC_I_COMPLETE_AND_CONTINUE:
    114     case SEC_I_COMPLETE_NEEDED:
    115     case SEC_I_INCOMPLETE_CREDENTIALS:
    116     case SEC_E_INCOMPLETE_MESSAGE:
    117     case SEC_E_INTERNAL_ERROR:
    118       // These are return codes reported by InitializeSecurityContext
    119       // but not expected by Chrome (for example, INCOMPLETE_CREDENTIALS
    120       // and INCOMPLETE_MESSAGE are intended for schannel).
    121       LOG(WARNING)
    122           << "InitializeSecurityContext returned unexpected status 0x"
    123           << std::hex << status;
    124       return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
    125     case SEC_E_INSUFFICIENT_MEMORY:
    126       return ERR_OUT_OF_MEMORY;
    127     case SEC_E_UNSUPPORTED_FUNCTION:
    128       NOTREACHED();
    129       return ERR_UNEXPECTED;
    130     case SEC_E_INVALID_HANDLE:
    131       NOTREACHED();
    132       return ERR_INVALID_HANDLE;
    133     case SEC_E_INVALID_TOKEN:
    134       return ERR_INVALID_RESPONSE;
    135     case SEC_E_LOGON_DENIED:
    136       return ERR_ACCESS_DENIED;
    137     case SEC_E_NO_CREDENTIALS:
    138     case SEC_E_WRONG_PRINCIPAL:
    139       return ERR_INVALID_AUTH_CREDENTIALS;
    140     case SEC_E_NO_AUTHENTICATING_AUTHORITY:
    141     case SEC_E_TARGET_UNKNOWN:
    142       return ERR_MISCONFIGURED_AUTH_ENVIRONMENT;
    143     default:
    144       LOG(WARNING)
    145           << "InitializeSecurityContext returned undocumented status 0x"
    146           << std::hex << status;
    147       return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
    148   }
    149 }
    150 
    151 int MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status) {
    152   VLOG(1) << "QuerySecurityPackageInfo returned 0x" << std::hex << status;
    153   switch (status) {
    154     case SEC_E_OK:
    155       return OK;
    156     case SEC_E_SECPKG_NOT_FOUND:
    157       // This isn't a documented return code, but has been encountered
    158       // during testing.
    159       return ERR_UNSUPPORTED_AUTH_SCHEME;
    160     default:
    161       LOG(WARNING)
    162           << "QuerySecurityPackageInfo returned undocumented status 0x"
    163           << std::hex << status;
    164       return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
    165   }
    166 }
    167 
    168 int MapFreeContextBufferStatusToError(SECURITY_STATUS status) {
    169   VLOG(1) << "FreeContextBuffer returned 0x" << std::hex << status;
    170   switch (status) {
    171     case SEC_E_OK:
    172       return OK;
    173     default:
    174       // The documentation at
    175       // http://msdn.microsoft.com/en-us/library/aa375416(VS.85).aspx
    176       // only mentions that a non-zero (or non-SEC_E_OK) value is returned
    177       // if the function fails, and does not indicate what the failure
    178       // conditions are.
    179       LOG(WARNING)
    180           << "FreeContextBuffer returned undocumented status 0x"
    181           << std::hex << status;
    182       return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
    183   }
    184 }
    185 
    186 }  // anonymous namespace
    187 
    188 HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library,
    189                            const std::string& scheme,
    190                            const SEC_WCHAR* security_package,
    191                            ULONG max_token_length)
    192     : library_(library),
    193       scheme_(scheme),
    194       security_package_(security_package),
    195       max_token_length_(max_token_length),
    196       can_delegate_(false) {
    197   DCHECK(library_);
    198   SecInvalidateHandle(&cred_);
    199   SecInvalidateHandle(&ctxt_);
    200 }
    201 
    202 HttpAuthSSPI::~HttpAuthSSPI() {
    203   ResetSecurityContext();
    204   if (SecIsValidHandle(&cred_)) {
    205     library_->FreeCredentialsHandle(&cred_);
    206     SecInvalidateHandle(&cred_);
    207   }
    208 }
    209 
    210 bool HttpAuthSSPI::NeedsIdentity() const {
    211   return decoded_server_auth_token_.empty();
    212 }
    213 
    214 bool HttpAuthSSPI::AllowsExplicitCredentials() const {
    215   return true;
    216 }
    217 
    218 void HttpAuthSSPI::Delegate() {
    219   can_delegate_ = true;
    220 }
    221 
    222 void HttpAuthSSPI::ResetSecurityContext() {
    223   if (SecIsValidHandle(&ctxt_)) {
    224     library_->DeleteSecurityContext(&ctxt_);
    225     SecInvalidateHandle(&ctxt_);
    226   }
    227 }
    228 
    229 HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
    230     HttpAuthChallengeTokenizer* tok) {
    231   // Verify the challenge's auth-scheme.
    232   if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
    233     return HttpAuth::AUTHORIZATION_RESULT_INVALID;
    234 
    235   std::string encoded_auth_token = tok->base64_param();
    236   if (encoded_auth_token.empty()) {
    237     // If a context has already been established, an empty challenge
    238     // should be treated as a rejection of the current attempt.
    239     if (SecIsValidHandle(&ctxt_))
    240       return HttpAuth::AUTHORIZATION_RESULT_REJECT;
    241     DCHECK(decoded_server_auth_token_.empty());
    242     return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
    243   } else {
    244     // If a context has not already been established, additional tokens should
    245     // not be present in the auth challenge.
    246     if (!SecIsValidHandle(&ctxt_))
    247       return HttpAuth::AUTHORIZATION_RESULT_INVALID;
    248   }
    249 
    250   std::string decoded_auth_token;
    251   bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
    252   if (!base64_rv)
    253     return HttpAuth::AUTHORIZATION_RESULT_INVALID;
    254   decoded_server_auth_token_ = decoded_auth_token;
    255   return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
    256 }
    257 
    258 int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials,
    259                                     const std::string& spn,
    260                                     std::string* auth_token) {
    261   // Initial challenge.
    262   if (!SecIsValidHandle(&cred_)) {
    263     int rv = OnFirstRound(credentials);
    264     if (rv != OK)
    265       return rv;
    266   }
    267 
    268   DCHECK(SecIsValidHandle(&cred_));
    269   void* out_buf;
    270   int out_buf_len;
    271   int rv = GetNextSecurityToken(
    272       spn,
    273       static_cast<void *>(const_cast<char *>(
    274           decoded_server_auth_token_.c_str())),
    275       decoded_server_auth_token_.length(),
    276       &out_buf,
    277       &out_buf_len);
    278   if (rv != OK)
    279     return rv;
    280 
    281   // Base64 encode data in output buffer and prepend the scheme.
    282   std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
    283   std::string encode_output;
    284   base::Base64Encode(encode_input, &encode_output);
    285   // OK, we are done with |out_buf|
    286   free(out_buf);
    287   *auth_token = scheme_ + " " + encode_output;
    288   return OK;
    289 }
    290 
    291 int HttpAuthSSPI::OnFirstRound(const AuthCredentials* credentials) {
    292   DCHECK(!SecIsValidHandle(&cred_));
    293   int rv = OK;
    294   if (credentials) {
    295     base::string16 domain;
    296     base::string16 user;
    297     SplitDomainAndUser(credentials->username(), &domain, &user);
    298     rv = AcquireExplicitCredentials(library_, security_package_, domain,
    299                                     user, credentials->password(), &cred_);
    300     if (rv != OK)
    301       return rv;
    302   } else {
    303     rv = AcquireDefaultCredentials(library_, security_package_, &cred_);
    304     if (rv != OK)
    305       return rv;
    306   }
    307 
    308   return rv;
    309 }
    310 
    311 int HttpAuthSSPI::GetNextSecurityToken(
    312     const std::string& spn,
    313     const void* in_token,
    314     int in_token_len,
    315     void** out_token,
    316     int* out_token_len) {
    317   CtxtHandle* ctxt_ptr;
    318   SecBufferDesc in_buffer_desc, out_buffer_desc;
    319   SecBufferDesc* in_buffer_desc_ptr;
    320   SecBuffer in_buffer, out_buffer;
    321 
    322   if (in_token_len > 0) {
    323     // Prepare input buffer.
    324     in_buffer_desc.ulVersion = SECBUFFER_VERSION;
    325     in_buffer_desc.cBuffers = 1;
    326     in_buffer_desc.pBuffers = &in_buffer;
    327     in_buffer.BufferType = SECBUFFER_TOKEN;
    328     in_buffer.cbBuffer = in_token_len;
    329     in_buffer.pvBuffer = const_cast<void*>(in_token);
    330     ctxt_ptr = &ctxt_;
    331     in_buffer_desc_ptr = &in_buffer_desc;
    332   } else {
    333     // If there is no input token, then we are starting a new authentication
    334     // sequence.  If we have already initialized our security context, then
    335     // we're incorrectly reusing the auth handler for a new sequence.
    336     if (SecIsValidHandle(&ctxt_)) {
    337       NOTREACHED();
    338       return ERR_UNEXPECTED;
    339     }
    340     ctxt_ptr = NULL;
    341     in_buffer_desc_ptr = NULL;
    342   }
    343 
    344   // Prepare output buffer.
    345   out_buffer_desc.ulVersion = SECBUFFER_VERSION;
    346   out_buffer_desc.cBuffers = 1;
    347   out_buffer_desc.pBuffers = &out_buffer;
    348   out_buffer.BufferType = SECBUFFER_TOKEN;
    349   out_buffer.cbBuffer = max_token_length_;
    350   out_buffer.pvBuffer = malloc(out_buffer.cbBuffer);
    351   if (!out_buffer.pvBuffer)
    352     return ERR_OUT_OF_MEMORY;
    353 
    354   DWORD context_flags = 0;
    355   // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that
    356   // ISC_REQ_MUTUAL_AUTH must also be set.
    357   if (can_delegate_)
    358     context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH);
    359 
    360   // This returns a token that is passed to the remote server.
    361   DWORD context_attribute;
    362   std::wstring spn_wide = base::ASCIIToWide(spn);
    363   SECURITY_STATUS status = library_->InitializeSecurityContext(
    364       &cred_,  // phCredential
    365       ctxt_ptr,  // phContext
    366       const_cast<wchar_t *>(spn_wide.c_str()),  // pszTargetName
    367       context_flags,  // fContextReq
    368       0,  // Reserved1 (must be 0)
    369       SECURITY_NATIVE_DREP,  // TargetDataRep
    370       in_buffer_desc_ptr,  // pInput
    371       0,  // Reserved2 (must be 0)
    372       &ctxt_,  // phNewContext
    373       &out_buffer_desc,  // pOutput
    374       &context_attribute,  // pfContextAttr
    375       NULL);  // ptsExpiry
    376   int rv = MapInitializeSecurityContextStatusToError(status);
    377   if (rv != OK) {
    378     ResetSecurityContext();
    379     free(out_buffer.pvBuffer);
    380     return rv;
    381   }
    382   if (!out_buffer.cbBuffer) {
    383     free(out_buffer.pvBuffer);
    384     out_buffer.pvBuffer = NULL;
    385   }
    386   *out_token = out_buffer.pvBuffer;
    387   *out_token_len = out_buffer.cbBuffer;
    388   return OK;
    389 }
    390 
    391 void SplitDomainAndUser(const base::string16& combined,
    392                         base::string16* domain,
    393                         base::string16* user) {
    394   // |combined| may be in the form "user" or "DOMAIN\user".
    395   // Separate the two parts if they exist.
    396   // TODO(cbentzel): I believe user@domain is also a valid form.
    397   size_t backslash_idx = combined.find(L'\\');
    398   if (backslash_idx == base::string16::npos) {
    399     domain->clear();
    400     *user = combined;
    401   } else {
    402     *domain = combined.substr(0, backslash_idx);
    403     *user = combined.substr(backslash_idx + 1);
    404   }
    405 }
    406 
    407 int DetermineMaxTokenLength(SSPILibrary* library,
    408                             const std::wstring& package,
    409                             ULONG* max_token_length) {
    410   DCHECK(library);
    411   DCHECK(max_token_length);
    412   PSecPkgInfo pkg_info = NULL;
    413   SECURITY_STATUS status = library->QuerySecurityPackageInfo(
    414       const_cast<wchar_t *>(package.c_str()), &pkg_info);
    415   int rv = MapQuerySecurityPackageInfoStatusToError(status);
    416   if (rv != OK)
    417     return rv;
    418   int token_length = pkg_info->cbMaxToken;
    419   status = library->FreeContextBuffer(pkg_info);
    420   rv = MapFreeContextBufferStatusToError(status);
    421   if (rv != OK)
    422     return rv;
    423   *max_token_length = token_length;
    424   return OK;
    425 }
    426 
    427 }  // namespace net
    428