OLD | NEW |
| (Empty) |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
202 HttpAuthSSPI::~HttpAuthSSPI() { | |
203 ResetSecurityContext(); | |
204 if (SecIsValidHandle(&cred_)) { | |
205 library_->FreeCredentialsHandle(&cred_); | |
206 SecInvalidateHandle(&cred_); | |
207 } | |
208 } | |
209 | |
210 bool HttpAuthSSPI::NeedsIdentity() const { | |
211 return decoded_server_auth_token_.empty(); | |
212 } | |
213 | |
214 bool HttpAuthSSPI::AllowsExplicitCredentials() const { | |
215 return true; | |
216 } | |
217 | |
218 void HttpAuthSSPI::Delegate() { | |
219 can_delegate_ = true; | |
220 } | |
221 | |
222 void HttpAuthSSPI::ResetSecurityContext() { | |
223 if (SecIsValidHandle(&ctxt_)) { | |
224 library_->DeleteSecurityContext(&ctxt_); | |
225 SecInvalidateHandle(&ctxt_); | |
226 } | |
227 } | |
228 | |
229 HttpAuth::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 | |
259 int 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 | |
292 int 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 | |
312 int 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 base::string16 spn16 = base::ASCIIToUTF16(spn); | |
364 SECURITY_STATUS status = library_->InitializeSecurityContext( | |
365 &cred_, // phCredential | |
366 ctxt_ptr, // phContext | |
367 const_cast<base::char16*>(spn16.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 | |
392 void 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 | |
408 int 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 | |
OLD | NEW |