OLD | NEW |
---|---|
(Empty) | |
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 #include "net/http/http_auth_gssapi_posix.h" | |
6 | |
7 #include "base/base64.h" | |
8 #include "base/file_path.h" | |
9 #include "base/logging.h" | |
10 #include "base/singleton.h" | |
11 #include "base/string_util.h" | |
12 #include "net/base/net_errors.h" | |
13 #include "net/base/net_util.h" | |
14 | |
15 namespace { | |
16 | |
17 gssapi::gss_OID_desc LOCAL_GSS_C_NT_HOSTBASED_SERVICE_VAL = { | |
18 10, | |
19 const_cast<char *>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04") | |
20 }; | |
21 | |
22 gssapi::gss_OID LOCAL_GSS_C_NT_HOSTBASED_SERVICE = | |
23 &LOCAL_GSS_C_NT_HOSTBASED_SERVICE_VAL; | |
24 | |
25 } // namespace | |
26 | |
27 namespace net { | |
28 | |
29 GSSAPISharedLibrary::GSSAPISharedLibrary() | |
30 : initialized_(false), | |
31 gssapi_library_(NULL), | |
32 import_name_(NULL), | |
33 release_name_(NULL), | |
34 release_buffer_(NULL), | |
35 display_status_(NULL), | |
36 init_sec_context_(NULL), | |
37 wrap_size_limit_(NULL) { | |
38 } | |
39 | |
40 GSSAPISharedLibrary::~GSSAPISharedLibrary() { | |
41 if (gssapi_library_) { | |
42 base::UnloadNativeLibrary(gssapi_library_); | |
43 gssapi_library_ = NULL; | |
44 } | |
45 } | |
46 | |
47 bool GSSAPISharedLibrary::Init() { | |
48 if (!initialized_) | |
49 InitImpl(); | |
50 return initialized_; | |
51 } | |
52 | |
53 bool GSSAPISharedLibrary::InitImpl() { | |
54 DCHECK(!initialized_); | |
55 gssapi_library_ = LoadSharedObject(); | |
56 if (gssapi_library_ == NULL) | |
57 return false; | |
58 if (!BindMethods()) | |
59 return false; | |
60 initialized_ = true; | |
61 return true; | |
62 } | |
63 | |
64 base::NativeLibrary GSSAPISharedLibrary::LoadSharedObject() { | |
65 static const char* kLibraryNames[] = { | |
66 #if defined(OS_MACOSX) | |
67 "libgssapi_krb5.dylib" // MIT Kerberos | |
68 #else | |
69 "libgssapi_krb5.so.2", // MIT Kerberos | |
70 "libgssapi.so.4", // Heimdal | |
71 "libgssapi.so.1" // Heimdal | |
72 #endif | |
73 }; | |
74 static size_t num_lib_names = arraysize(kLibraryNames); | |
75 | |
76 for (size_t i = 0; i < num_lib_names; ++i) { | |
77 const char* library_name = kLibraryNames[i]; | |
78 FilePath file_path(library_name); | |
79 base::NativeLibrary lib = base::LoadNativeLibrary(file_path); | |
80 if (lib) | |
81 return lib; | |
82 } | |
83 return NULL; | |
84 } | |
85 | |
86 template <typename T> | |
87 bool FindAndBind(base::NativeLibrary library, const char* name, T* t) { | |
88 void* func = base::GetFunctionPointerFromNativeLibrary(library, name); | |
89 if (func == NULL) | |
90 return false; | |
91 *t = reinterpret_cast<T>(func); | |
92 return true; | |
93 } | |
94 | |
95 bool GSSAPISharedLibrary::BindMethods() { | |
96 DCHECK(gssapi_library_ != NULL); | |
97 if (!FindAndBind(gssapi_library_, "gss_import_name", &import_name_)) | |
98 return false; | |
99 if (!FindAndBind(gssapi_library_, "gss_release_name", &release_name_)) | |
100 return false; | |
101 if (!FindAndBind(gssapi_library_, "gss_release_buffer", &release_buffer_)) | |
102 return false; | |
103 if (!FindAndBind(gssapi_library_, "gss_display_status", &display_status_)) | |
104 return false; | |
105 if (!FindAndBind(gssapi_library_, "gss_init_sec_context", &init_sec_context_)) | |
106 return false; | |
107 if (!FindAndBind(gssapi_library_, "gss_wrap_size_limit", &wrap_size_limit_)) | |
108 return false; | |
109 return true; | |
110 } | |
111 | |
112 gssapi::OM_uint32 GSSAPISharedLibrary::import_name( | |
113 gssapi::OM_uint32* minor_status, | |
114 const gssapi::gss_buffer_t input_name_buffer, | |
115 const gssapi::gss_OID input_name_type, | |
116 gssapi::gss_name_t* output_name) { | |
117 DCHECK(initialized_); | |
118 return import_name_(minor_status, input_name_buffer, input_name_type, | |
119 output_name); | |
120 } | |
121 | |
122 gssapi::OM_uint32 GSSAPISharedLibrary::release_name( | |
123 gssapi::OM_uint32* minor_status, | |
124 gssapi::gss_name_t* input_name) { | |
125 DCHECK(initialized_); | |
126 return release_name_(minor_status, input_name); | |
127 } | |
128 | |
129 gssapi::OM_uint32 GSSAPISharedLibrary::release_buffer( | |
130 gssapi::OM_uint32* minor_status, | |
131 gssapi::gss_buffer_t buffer) { | |
132 DCHECK(initialized_); | |
133 return release_buffer_(minor_status, buffer); | |
134 } | |
135 | |
136 gssapi::OM_uint32 GSSAPISharedLibrary::display_status( | |
137 gssapi::OM_uint32* minor_status, | |
138 gssapi::OM_uint32 status_value, | |
139 int status_type, | |
140 const gssapi::gss_OID mech_type, | |
141 gssapi::OM_uint32* message_context, | |
142 gssapi::gss_buffer_t status_string) { | |
143 DCHECK(initialized_); | |
144 return display_status_(minor_status, status_value, status_type, mech_type, | |
145 message_context, status_string); | |
146 } | |
147 | |
148 gssapi::OM_uint32 GSSAPISharedLibrary::init_sec_context( | |
149 gssapi::OM_uint32* minor_status, | |
150 const gssapi::gss_cred_id_t initiator_cred_handle, | |
151 gssapi::gss_ctx_id_t* context_handle, | |
152 const gssapi::gss_name_t target_name, | |
153 const gssapi::gss_OID mech_type, | |
154 gssapi::OM_uint32 req_flags, | |
155 gssapi::OM_uint32 time_req, | |
156 const gssapi::gss_channel_bindings_t input_chan_bindings, | |
157 const gssapi::gss_buffer_t input_token, | |
158 gssapi::gss_OID* actual_mech_type, | |
159 gssapi::gss_buffer_t output_token, | |
160 gssapi::OM_uint32* ret_flags, | |
161 gssapi::OM_uint32* time_rec) { | |
162 DCHECK(initialized_); | |
163 return init_sec_context_(minor_status, | |
164 initiator_cred_handle, | |
165 context_handle, | |
166 target_name, | |
167 mech_type, | |
168 req_flags, | |
169 time_req, | |
170 input_chan_bindings, | |
171 input_token, | |
172 actual_mech_type, | |
173 output_token, | |
174 ret_flags, | |
175 time_rec); | |
176 } | |
177 | |
178 gssapi::OM_uint32 GSSAPISharedLibrary::wrap_size_limit( | |
179 gssapi::OM_uint32* minor_status, | |
180 const gssapi::gss_ctx_id_t context_handle, | |
181 int conf_req_flag, | |
182 gssapi::gss_qop_t qop_req, | |
183 gssapi::OM_uint32 req_output_size, | |
184 gssapi::OM_uint32* max_input_size) { | |
185 DCHECK(initialized_); | |
186 return wrap_size_limit_(minor_status, | |
187 context_handle, | |
188 conf_req_flag, | |
189 qop_req, | |
190 req_output_size, | |
191 max_input_size); | |
192 } | |
193 | |
194 GSSAPILibrary* GSSAPILibrary::GetDefault() { | |
195 return Singleton<GSSAPISharedLibrary>::get(); | |
196 } | |
197 | |
198 namespace { | |
199 | |
200 std::string DisplayStatus(gssapi::OM_uint32 major_status, | |
201 gssapi::OM_uint32 minor_status) { | |
202 if (major_status == GSS_S_COMPLETE) | |
203 return "OK"; | |
204 return StringPrintf("0x%08x 0x%08x", major_status, minor_status); | |
wtc
2010/06/04 21:10:34
Nit: be consistent with using either %x or %X in t
| |
205 } | |
206 | |
207 std::string DisplayCode(GSSAPILibrary* gssapi_lib, | |
208 gssapi::OM_uint32 status, | |
209 gssapi::OM_uint32 status_code_type) { | |
210 const int kMaxDisplayIterations = 8; | |
211 // msg_ctx needs to be outside the loop because it is invoked multiple times. | |
212 gssapi::OM_uint32 msg_ctx = 0; | |
213 std::string rv = StringPrintf("(0x%08X)", status); | |
214 | |
215 // This loop should continue iterating until msg_ctx is 0 after the first | |
216 // iteration. To be cautious and prevent an infinite loop, it stops after | |
217 // a finite number of iterations as well. | |
218 for (int i = 0; i < kMaxDisplayIterations; ++i) { | |
219 gssapi::OM_uint32 min_stat; | |
220 gssapi::gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER; | |
221 gssapi_lib->display_status(&min_stat, status, status_code_type, | |
222 GSS_C_NULL_OID, | |
223 &msg_ctx, &msg); | |
224 rv += StringPrintf(" %s", static_cast<char *>(msg.value)); | |
225 gssapi_lib->release_buffer(&min_stat, &msg); | |
226 if (!msg_ctx) | |
227 break; | |
228 } | |
229 return rv; | |
230 } | |
231 | |
232 std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib, | |
233 gssapi::OM_uint32 major_status, | |
234 gssapi::OM_uint32 minor_status) { | |
235 if (major_status == GSS_S_COMPLETE) | |
236 return "OK"; | |
237 std::string major = DisplayCode(gssapi_lib, major_status, GSS_C_GSS_CODE); | |
238 std::string minor = DisplayCode(gssapi_lib, minor_status, GSS_C_MECH_CODE); | |
239 return StringPrintf("Major: %s | Minor: %s", major.c_str(), minor.c_str()); | |
240 } | |
241 | |
242 // ScopedName releases a gssapi::gss_name_t when it goes out of scope. | |
243 class ScopedName { | |
244 public: | |
245 ScopedName(gssapi::gss_name_t name, | |
246 GSSAPILibrary* gssapi_lib) | |
247 : name_(name), | |
248 gssapi_lib_(gssapi_lib) { | |
249 DCHECK(gssapi_lib_); | |
250 } | |
251 | |
252 ~ScopedName() { | |
253 if (name_ != GSS_C_NO_NAME) { | |
254 gssapi::OM_uint32 minor_status = 0; | |
255 gssapi::OM_uint32 major_status = | |
256 gssapi_lib_->release_name(&minor_status, &name_); | |
257 if (major_status != GSS_S_COMPLETE) { | |
258 LOG(WARNING) << "Problem releasing name. " | |
259 << DisplayStatus(major_status, minor_status); | |
260 } | |
261 name_ = GSS_C_NO_NAME; | |
262 } | |
263 } | |
264 | |
265 private: | |
266 gssapi::gss_name_t name_; | |
267 GSSAPILibrary* gssapi_lib_; | |
268 | |
269 DISALLOW_COPY_AND_ASSIGN(ScopedName); | |
270 }; | |
271 | |
272 // ScopedBuffer releases a gssapi::gss_buffer_t when it goes out of scope. | |
273 class ScopedBuffer { | |
wtc
2010/06/04 21:10:34
ScopedBuffer is not being used. Are you planning
| |
274 public: | |
275 ScopedBuffer(gssapi::gss_buffer_t buffer, | |
276 GSSAPILibrary* gssapi_lib) | |
277 : buffer_(buffer), | |
278 gssapi_lib_(gssapi_lib) { | |
279 DCHECK(gssapi_lib_); | |
280 } | |
281 | |
282 ~ScopedBuffer() { | |
283 if (buffer_ != GSS_C_NO_BUFFER) { | |
284 gssapi::OM_uint32 minor_status = 0; | |
285 gssapi::OM_uint32 major_status = | |
286 gssapi_lib_->release_buffer(&minor_status, buffer_); | |
287 if (major_status != GSS_S_COMPLETE) { | |
288 LOG(WARNING) << "Problem releasing buffer. " | |
289 << DisplayStatus(major_status, minor_status); | |
290 } | |
291 buffer_ = GSS_C_NO_BUFFER; | |
292 } | |
293 } | |
294 | |
295 private: | |
296 gssapi::gss_buffer_t buffer_; | |
297 GSSAPILibrary* gssapi_lib_; | |
298 | |
299 DISALLOW_COPY_AND_ASSIGN(ScopedBuffer); | |
300 }; | |
301 | |
302 } // namespace | |
303 | |
304 HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library, | |
305 const std::string& scheme, | |
306 gssapi::gss_OID gss_oid) | |
307 : scheme_(scheme), | |
308 gss_oid_(gss_oid), | |
309 library_(library), | |
310 sec_context_(NULL) { | |
311 } | |
312 | |
313 HttpAuthGSSAPI::~HttpAuthGSSAPI() { | |
314 } | |
315 | |
316 bool HttpAuthGSSAPI::NeedsIdentity() const { | |
317 return decoded_server_auth_token_.empty(); | |
318 } | |
319 | |
320 bool HttpAuthGSSAPI::IsFinalRound() const { | |
321 return !NeedsIdentity(); | |
322 } | |
323 | |
324 bool HttpAuthGSSAPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) { | |
325 // Verify the challenge's auth-scheme. | |
326 if (!tok->valid() || | |
327 !LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str())) | |
328 return false; | |
329 | |
330 tok->set_expect_base64_token(true); | |
331 if (!tok->GetNext()) { | |
332 decoded_server_auth_token_.clear(); | |
333 return true; | |
334 } | |
335 | |
336 std::string encoded_auth_token = tok->value(); | |
337 std::string decoded_auth_token; | |
338 bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token); | |
339 if (!base64_rv) { | |
340 LOG(ERROR) << "Base64 decoding of auth token failed."; | |
341 return false; | |
342 } | |
343 decoded_server_auth_token_ = decoded_auth_token; | |
344 return true; | |
345 } | |
346 | |
347 int HttpAuthGSSAPI::GenerateAuthToken(const std::wstring* username, | |
348 const std::wstring* password, | |
349 const std::wstring& spn, | |
350 const HttpRequestInfo* request, | |
351 const ProxyInfo* proxy, | |
352 std::string* out_credentials) { | |
353 DCHECK(library_); | |
354 DCHECK((username == NULL) == (password == NULL)); | |
355 | |
356 library_->Init(); | |
357 | |
358 if (!IsFinalRound()) { | |
359 int rv = OnFirstRound(username, password); | |
360 if (rv != OK) | |
361 return rv; | |
362 } | |
363 | |
364 gssapi::gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; | |
365 input_token.length = decoded_server_auth_token_.length(); | |
wtc
2010/06/04 21:10:34
We may need to pass GSS_NO_BUFFER instead of &inpu
| |
366 input_token.value = const_cast<char *>(decoded_server_auth_token_.data()); | |
367 gssapi::gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; | |
368 int rv = GetNextSecurityToken(spn, &input_token, &output_token); | |
369 if (rv != OK) | |
370 return rv; | |
371 | |
372 // Base64 encode data in output buffer and prepend the scheme. | |
373 std::string encode_input(static_cast<char*>(output_token.value), | |
374 output_token.length); | |
375 std::string encode_output; | |
376 bool ok = base::Base64Encode(encode_input, &encode_output); | |
377 gssapi::OM_uint32 minor_status = 0; | |
378 library_->release_buffer(&minor_status, &output_token); | |
379 if (!ok) | |
380 return ERR_UNEXPECTED; | |
381 *out_credentials = scheme_ + " " + encode_output; | |
382 return OK; | |
383 } | |
384 | |
385 int HttpAuthGSSAPI::OnFirstRound(const std::wstring* username, | |
386 const std::wstring* password) { | |
387 // TODO(cbentzel): Acquire credentials? | |
388 DCHECK((username == NULL) == (password == NULL)); | |
389 username_.clear(); | |
390 password_.clear(); | |
391 if (username) { | |
392 username_ = *username; | |
393 password_ = *password; | |
394 } | |
395 return OK; | |
396 } | |
397 | |
398 int HttpAuthGSSAPI::GetNextSecurityToken(const std::wstring& spn, | |
399 gssapi::gss_buffer_t in_token, | |
400 gssapi::gss_buffer_t out_token) { | |
401 // Create a name for the principal | |
402 // TODO(cbentzel): Should this be username@spn? What about domain? | |
403 // TODO(cbentzel): Just do this on the first pass? | |
404 const GURL spn_url(WideToASCII(spn)); | |
405 std::string spn_principal = GetHostAndPort(spn_url); | |
406 gssapi::gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER; | |
407 spn_buffer.value = const_cast<char *>(spn_principal.data()); | |
wtc
2010/06/04 21:10:34
Use c_str() instead of data(), since spn_buffer.le
| |
408 spn_buffer.length = spn_principal.size() + 1; | |
409 gssapi::OM_uint32 minor_status = 0; | |
410 gssapi::gss_name_t principal_name; | |
411 gssapi::OM_uint32 major_status = library_->import_name( | |
412 &minor_status, | |
413 &spn_buffer, | |
414 LOCAL_GSS_C_NT_HOSTBASED_SERVICE, | |
415 &principal_name); | |
416 if (major_status != GSS_S_COMPLETE) { | |
417 LOG(WARNING) << "Problem importing name. " | |
wtc
2010/06/04 21:10:34
This log message should be at the ERROR level if w
| |
418 << DisplayExtendedStatus(library_, | |
419 major_status, | |
420 minor_status); | |
421 return ERR_UNEXPECTED; | |
422 } | |
423 ScopedName scoped_name(principal_name, library_); | |
424 | |
425 // Create a security context. | |
wtc
2010/06/04 21:10:34
This comment may not be accurate... I think it's
| |
426 gssapi::OM_uint32 req_flags = 0; | |
427 major_status = library_->init_sec_context( | |
428 &minor_status, | |
429 GSS_C_NO_CREDENTIAL, | |
430 &sec_context_, | |
wtc
2010/06/04 21:10:34
The returned sec_context_ value should be deleted
| |
431 principal_name, | |
432 gss_oid_, | |
433 req_flags, | |
434 GSS_C_INDEFINITE, | |
435 GSS_C_NO_CHANNEL_BINDINGS, | |
436 in_token, | |
437 NULL, // actual_mech_type | |
438 out_token, | |
439 NULL, // ret flags | |
440 NULL); | |
441 if (major_status != GSS_S_COMPLETE && | |
442 major_status != GSS_S_CONTINUE_NEEDED) { | |
443 LOG(WARNING) << "Problem initializing context. " | |
444 << DisplayExtendedStatus(library_, | |
445 major_status, | |
446 minor_status); | |
447 return ERR_UNEXPECTED; | |
448 } | |
449 | |
450 return (major_status != GSS_S_COMPLETE) ? ERR_IO_PENDING : OK; | |
wtc
2010/06/04 21:10:34
Are you sure it's right to return ERR_IO_PENDING i
| |
451 } | |
452 | |
453 } // namespace net | |
454 | |
OLD | NEW |