• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MapAcquireCredentialsStatusToError(SECURITY_STATUS status,const SEC_WCHAR * package)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 
AcquireExplicitCredentials(SSPILibrary * library,const SEC_WCHAR * package,const base::string16 & domain,const base::string16 & user,const base::string16 & password,CredHandle * cred)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 
AcquireDefaultCredentials(SSPILibrary * library,const SEC_WCHAR * package,CredHandle * cred)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 
MapInitializeSecurityContextStatusToError(SECURITY_STATUS status)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 
MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status)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 
MapFreeContextBufferStatusToError(SECURITY_STATUS status)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 
HttpAuthSSPI(SSPILibrary * library,const std::string & scheme,const SEC_WCHAR * security_package,ULONG max_token_length)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 
~HttpAuthSSPI()202 HttpAuthSSPI::~HttpAuthSSPI() {
203   ResetSecurityContext();
204   if (SecIsValidHandle(&cred_)) {
205     library_->FreeCredentialsHandle(&cred_);
206     SecInvalidateHandle(&cred_);
207   }
208 }
209 
NeedsIdentity() const210 bool HttpAuthSSPI::NeedsIdentity() const {
211   return decoded_server_auth_token_.empty();
212 }
213 
AllowsExplicitCredentials() const214 bool HttpAuthSSPI::AllowsExplicitCredentials() const {
215   return true;
216 }
217 
Delegate()218 void HttpAuthSSPI::Delegate() {
219   can_delegate_ = true;
220 }
221 
ResetSecurityContext()222 void HttpAuthSSPI::ResetSecurityContext() {
223   if (SecIsValidHandle(&ctxt_)) {
224     library_->DeleteSecurityContext(&ctxt_);
225     SecInvalidateHandle(&ctxt_);
226   }
227 }
228 
ParseChallenge(HttpAuthChallengeTokenizer * tok)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 
GenerateAuthToken(const AuthCredentials * credentials,const std::string & spn,std::string * auth_token)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 
OnFirstRound(const AuthCredentials * credentials)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 
GetNextSecurityToken(const std::string & spn,const void * in_token,int in_token_len,void ** out_token,int * out_token_len)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 
SplitDomainAndUser(const base::string16 & combined,base::string16 * domain,base::string16 * user)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 
DetermineMaxTokenLength(SSPILibrary * library,const std::wstring & package,ULONG * max_token_length)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