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
18namespace net {
19
20namespace {
21
22int 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
50int 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
85int 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
107int 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
151int 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
168int 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
188HttpAuthSSPI::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
202HttpAuthSSPI::~HttpAuthSSPI() {
203  ResetSecurityContext();
204  if (SecIsValidHandle(&cred_)) {
205    library_->FreeCredentialsHandle(&cred_);
206    SecInvalidateHandle(&cred_);
207  }
208}
209
210bool HttpAuthSSPI::NeedsIdentity() const {
211  return decoded_server_auth_token_.empty();
212}
213
214bool HttpAuthSSPI::AllowsExplicitCredentials() const {
215  return true;
216}
217
218void HttpAuthSSPI::Delegate() {
219  can_delegate_ = true;
220}
221
222void HttpAuthSSPI::ResetSecurityContext() {
223  if (SecIsValidHandle(&ctxt_)) {
224    library_->DeleteSecurityContext(&ctxt_);
225    SecInvalidateHandle(&ctxt_);
226  }
227}
228
229HttpAuth::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
259int 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
292int 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
312int 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
392void 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
408int 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