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(),
    233                             base::StringToLowerASCII(scheme_).c_str()))
    234     return HttpAuth::AUTHORIZATION_RESULT_INVALID;
    235 
    236   std::string encoded_auth_token = tok->base64_param();
    237   if (encoded_auth_token.empty()) {
    238     // If a context has already been established, an empty challenge
    239     // should be treated as a rejection of the current attempt.
    240     if (SecIsValidHandle(&ctxt_))
    241       return HttpAuth::AUTHORIZATION_RESULT_REJECT;
    242     DCHECK(decoded_server_auth_token_.empty());
    243     return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
    244   } else {
    245     // If a context has not already been established, additional tokens should
    246     // not be present in the auth challenge.
    247     if (!SecIsValidHandle(&ctxt_))
    248       return HttpAuth::AUTHORIZATION_RESULT_INVALID;
    249   }
    250 
    251   std::string decoded_auth_token;
    252   bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
    253   if (!base64_rv)
    254     return HttpAuth::AUTHORIZATION_RESULT_INVALID;
    255   decoded_server_auth_token_ = decoded_auth_token;
    256   return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
    257 }
    258 
    259 int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials,
    260                                     const std::string& spn,
    261                                     std::string* auth_token) {
    262   // Initial challenge.
    263   if (!SecIsValidHandle(&cred_)) {
    264     int rv = OnFirstRound(credentials);
    265     if (rv != OK)
    266       return rv;
    267   }
    268 
    269   DCHECK(SecIsValidHandle(&cred_));
    270   void* out_buf;
    271   int out_buf_len;
    272   int rv = GetNextSecurityToken(
    273       spn,
    274       static_cast<void *>(const_cast<char *>(
    275           decoded_server_auth_token_.c_str())),
    276       decoded_server_auth_token_.length(),
    277       &out_buf,
    278       &out_buf_len);
    279   if (rv != OK)
    280     return rv;
    281 
    282   // Base64 encode data in output buffer and prepend the scheme.
    283   std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
    284   std::string encode_output;
    285   base::Base64Encode(encode_input, &encode_output);
    286   // OK, we are done with |out_buf|
    287   free(out_buf);
    288   *auth_token = scheme_ + " " + encode_output;
    289   return OK;
    290 }
    291 
    292 int HttpAuthSSPI::OnFirstRound(const AuthCredentials* credentials) {
    293   DCHECK(!SecIsValidHandle(&cred_));
    294   int rv = OK;
    295   if (credentials) {
    296     base::string16 domain;
    297     base::string16 user;
    298     SplitDomainAndUser(credentials->username(), &domain, &user);
    299     rv = AcquireExplicitCredentials(library_, security_package_, domain,
    300                                     user, credentials->password(), &cred_);
    301     if (rv != OK)
    302       return rv;
    303   } else {
    304     rv = AcquireDefaultCredentials(library_, security_package_, &cred_);
    305     if (rv != OK)
    306       return rv;
    307   }
    308 
    309   return rv;
    310 }
    311 
    312 int HttpAuthSSPI::GetNextSecurityToken(
    313     const std::string& spn,
    314     const void* in_token,
    315     int in_token_len,
    316     void** out_token,
    317     int* out_token_len) {
    318   CtxtHandle* ctxt_ptr;
    319   SecBufferDesc in_buffer_desc, out_buffer_desc;
    320   SecBufferDesc* in_buffer_desc_ptr;
    321   SecBuffer in_buffer, out_buffer;
    322 
    323   if (in_token_len > 0) {
    324     // Prepare input buffer.
    325     in_buffer_desc.ulVersion = SECBUFFER_VERSION;
    326     in_buffer_desc.cBuffers = 1;
    327     in_buffer_desc.pBuffers = &in_buffer;
    328     in_buffer.BufferType = SECBUFFER_TOKEN;
    329     in_buffer.cbBuffer = in_token_len;
    330     in_buffer.pvBuffer = const_cast<void*>(in_token);
    331     ctxt_ptr = &ctxt_;
    332     in_buffer_desc_ptr = &in_buffer_desc;
    333   } else {
    334     // If there is no input token, then we are starting a new authentication
    335     // sequence.  If we have already initialized our security context, then
    336     // we're incorrectly reusing the auth handler for a new sequence.
    337     if (SecIsValidHandle(&ctxt_)) {
    338       NOTREACHED();
    339       return ERR_UNEXPECTED;
    340     }
    341     ctxt_ptr = NULL;
    342     in_buffer_desc_ptr = NULL;
    343   }
    344 
    345   // Prepare output buffer.
    346   out_buffer_desc.ulVersion = SECBUFFER_VERSION;
    347   out_buffer_desc.cBuffers = 1;
    348   out_buffer_desc.pBuffers = &out_buffer;
    349   out_buffer.BufferType = SECBUFFER_TOKEN;
    350   out_buffer.cbBuffer = max_token_length_;
    351   out_buffer.pvBuffer = malloc(out_buffer.cbBuffer);
    352   if (!out_buffer.pvBuffer)
    353     return ERR_OUT_OF_MEMORY;
    354 
    355   DWORD context_flags = 0;
    356   // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that
    357   // ISC_REQ_MUTUAL_AUTH must also be set.
    358   if (can_delegate_)
    359     context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH);
    360 
    361   // This returns a token that is passed to the remote server.
    362   DWORD context_attribute;
    363   std::wstring spn_wide = base::ASCIIToWide(spn);
    364   SECURITY_STATUS status = library_->InitializeSecurityContext(
    365       &cred_,  // phCredential
    366       ctxt_ptr,  // phContext
    367       const_cast<wchar_t *>(spn_wide.c_str()),  // pszTargetName
    368       context_flags,  // fContextReq
    369       0,  // Reserved1 (must be 0)
    370       SECURITY_NATIVE_DREP,  // TargetDataRep
    371       in_buffer_desc_ptr,  // pInput
    372       0,  // Reserved2 (must be 0)
    373       &ctxt_,  // phNewContext
    374       &out_buffer_desc,  // pOutput
    375       &context_attribute,  // pfContextAttr
    376       NULL);  // ptsExpiry
    377   int rv = MapInitializeSecurityContextStatusToError(status);
    378   if (rv != OK) {
    379     ResetSecurityContext();
    380     free(out_buffer.pvBuffer);
    381     return rv;
    382   }
    383   if (!out_buffer.cbBuffer) {
    384     free(out_buffer.pvBuffer);
    385     out_buffer.pvBuffer = NULL;
    386   }
    387   *out_token = out_buffer.pvBuffer;
    388   *out_token_len = out_buffer.cbBuffer;
    389   return OK;
    390 }
    391 
    392 void SplitDomainAndUser(const base::string16& combined,
    393                         base::string16* domain,
    394                         base::string16* user) {
    395   // |combined| may be in the form "user" or "DOMAIN\user".
    396   // Separate the two parts if they exist.
    397   // TODO(cbentzel): I believe user@domain is also a valid form.
    398   size_t backslash_idx = combined.find(L'\\');
    399   if (backslash_idx == base::string16::npos) {
    400     domain->clear();
    401     *user = combined;
    402   } else {
    403     *domain = combined.substr(0, backslash_idx);
    404     *user = combined.substr(backslash_idx + 1);
    405   }
    406 }
    407 
    408 int DetermineMaxTokenLength(SSPILibrary* library,
    409                             const std::wstring& package,
    410                             ULONG* max_token_length) {
    411   DCHECK(library);
    412   DCHECK(max_token_length);
    413   PSecPkgInfo pkg_info = NULL;
    414   SECURITY_STATUS status = library->QuerySecurityPackageInfo(
    415       const_cast<wchar_t *>(package.c_str()), &pkg_info);
    416   int rv = MapQuerySecurityPackageInfoStatusToError(status);
    417   if (rv != OK)
    418     return rv;
    419   int token_length = pkg_info->cbMaxToken;
    420   status = library->FreeContextBuffer(pkg_info);
    421   rv = MapFreeContextBufferStatusToError(status);
    422   if (rv != OK)
    423     return rv;
    424   *max_token_length = token_length;
    425   return OK;
    426 }
    427 
    428 }  // namespace net
    429