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