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
MapAcquireCredentialsStatusToError(SECURITY_STATUS status,const SEC_WCHAR * package)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
AcquireExplicitCredentials(SSPILibrary * library,const SEC_WCHAR * package,const string16 & domain,const string16 & user,const string16 & password,CredHandle * cred)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
AcquireDefaultCredentials(SSPILibrary * library,const SEC_WCHAR * package,CredHandle * cred)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
MapInitializeSecurityContextStatusToError(SECURITY_STATUS status)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
MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status)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
MapFreeContextBufferStatusToError(SECURITY_STATUS status)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
HttpAuthSSPI(SSPILibrary * library,const std::string & scheme,SEC_WCHAR * security_package,ULONG max_token_length)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
~HttpAuthSSPI()201 HttpAuthSSPI::~HttpAuthSSPI() {
202 ResetSecurityContext();
203 if (SecIsValidHandle(&cred_)) {
204 library_->FreeCredentialsHandle(&cred_);
205 SecInvalidateHandle(&cred_);
206 }
207 }
208
NeedsIdentity() const209 bool HttpAuthSSPI::NeedsIdentity() const {
210 return decoded_server_auth_token_.empty();
211 }
212
Delegate()213 void HttpAuthSSPI::Delegate() {
214 can_delegate_ = true;
215 }
216
ResetSecurityContext()217 void HttpAuthSSPI::ResetSecurityContext() {
218 if (SecIsValidHandle(&ctxt_)) {
219 library_->DeleteSecurityContext(&ctxt_);
220 SecInvalidateHandle(&ctxt_);
221 }
222 }
223
ParseChallenge(HttpAuth::ChallengeTokenizer * tok)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
GenerateAuthToken(const string16 * username,const string16 * password,const std::wstring & spn,std::string * auth_token)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
OnFirstRound(const string16 * username,const string16 * password)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
GetNextSecurityToken(const std::wstring & spn,const void * in_token,int in_token_len,void ** out_token,int * out_token_len)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
SplitDomainAndUser(const string16 & combined,string16 * domain,string16 * user)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
DetermineMaxTokenLength(SSPILibrary * library,const std::wstring & package,ULONG * max_token_length)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