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