1 /* The contents of this file are subject to the Mozilla Public License Version
2 * 1.1 (the "License"); you may not use this file except in compliance with
3 * the License. You may obtain a copy of the License at
4 * http://www.mozilla.org/MPL/
6 * Software distributed under the License is distributed on an "AS IS" basis,
7 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
8 * for the specific language governing rights and limitations under the
11 * The Original Code is the Negotiateauth
13 * The Initial Developer of the Original Code is Daniel Kouril.
14 * Portions created by the Initial Developer are Copyright (C) 2003
15 * the Initial Developer. All Rights Reserved.
18 * Daniel Kouril <kouril@ics.muni.cz> (original author)
19 * Wyllys Ingersoll <wyllys.ingersoll@sun.com>
20 * Christopher Nebergall <cneberg@sandia.gov>
24 // GSSAPI Authentication Support Module
26 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
27 // (formerly draft-brezak-spnego-http-04.txt)
29 // Also described here:
30 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
34 /* this #define must run before prlog.h is included */
35 #define FORCE_PR_LOG 1
38 #include "nsAutoRef.h"
40 #include "nsIHttpChannel.h"
41 #include "nsIServiceManager.h"
42 #include "nsISupportsPrimitives.h"
49 #include "nsISupportsUtils.h"
51 /* XXX, just for debugging */
52 #ifdef MOZILLA_INTERNAL_API
55 #include "nsStringAPI.h"
62 #include "nsMoonshotSessionState.h"
63 #include "nsHttpMoonshot.h"
65 /* #define HAVE_GSS_C_NT_HOSTBASED_SERVICE 1 */
67 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
69 #include <gssapi/gssapi_generic.h>
73 NS_SPECIALIZE_TEMPLATE
74 class nsAutoRefTraits<nsMoonshotSessionState> : public nsPointerRefTraits<nsMoonshotSessionState>
77 static void Release(nsMoonshotSessionState *ptr) { ptr->Release(); }
78 static void AddRef(nsMoonshotSessionState *ptr) { ptr->AddRef(); }
81 static gss_OID_desc gss_krb5_mech_oid_desc =
82 {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
84 static gss_OID_desc gss_spnego_mech_oid_desc =
85 {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
87 // in order to do logging, the following environment variables need to be set:
89 // set NSPR_LOG_MODULES=negotiate:4
90 // set NSPR_LOG_FILE=negotiate.log
92 #if defined(PR_LOGGING)
94 PRLogModuleInfo *gHttpLog = nsnull;
95 static PRLogModuleInfo* gNegotiateLog = nsnull;
99 #define LOG4(args) PR_LOG(gNegotiateLog, 4, args)
100 #define LOG(args) LOG4(args)
103 parse_oid(char *mechanism, gss_OID * oid)
107 OM_uint32 maj_stat, min_stat;
108 size_t i, mechlen = strlen(mechanism);
110 if (isdigit((int) mechanism[0])) {
111 mechstr = (char *)malloc(mechlen + 5);
113 fprintf(stderr, "Couldn't allocate mechanism scratch!\n");
118 for (i = 0; i < mechlen; i++)
119 mechstr[i + 2] = (mechanism[i] == '.') ? ' ' : mechanism[i];
120 mechstr[mechlen + 2] = ' ';
121 mechstr[mechlen + 3] = ' ';
122 mechstr[mechlen + 4] = '\0';
125 tok.value = mechanism;
126 tok.length = strlen((const char *)tok.value);
127 maj_stat = gss_str_to_oid(&min_stat, &tok, oid);
128 if (maj_stat != GSS_S_COMPLETE) {
129 //display_status("str_to_oid", maj_stat, min_stat);
141 parse_oid("{1 3 6 1 4 1 5322 22 1 18}", &mech_oid);
145 nsHttpMoonshot::nsHttpMoonshot()
149 #if defined(PR_LOGGING)
151 gNegotiateLog = PR_NewLogModule("moonshot");
152 #endif /* PR_LOGGING */
156 nsHttpMoonshot::~nsHttpMoonshot()
161 nsHttpMoonshot::GetAuthFlags(PRUint32 *flags)
163 *flags = REQUEST_BASED;
168 nsHttpMoonshot::ChallengeReceived(nsIHttpChannel *httpChannel,
169 const char *challenge,
171 nsISupports **sessionState,
172 nsISupports **continuationState,
173 PRBool *identityInvalid)
175 nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
177 session = (nsMoonshotSessionState *) *continuationState;
179 ((session==NULL) || (session->gss_state == GSS_CTX_EMPTY)) ? PR_TRUE : PR_FALSE;
185 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
186 nsIHttpAuthenticator_1_9_2)
188 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
192 // Generate proper GSSAPI error messages from the major and
193 // minor status codes.
196 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
199 OM_uint32 msg_ctx = 0;
200 gss_buffer_desc status1_string;
201 gss_buffer_desc status2_string;
203 nsCAutoString error(prefix);
207 ret = gss_display_status (&new_stat,
213 error += (char *)status1_string.value;
215 ret = gss_display_status (&new_stat,
221 error += (char *)status2_string.value;
224 } while (!GSS_ERROR(ret) && msg_ctx != 0);
226 // LOG(("%s", ToNewCString(error)));
227 LOG(("%s\n", error.get()));
231 // GenerateCredentials
233 // This routine is responsible for creating the correct authentication
234 // blob to pass to the server that requested "Negotiate" authentication.
237 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
238 const char *challenge,
240 const PRUnichar *domain,
241 const PRUnichar *user,
242 const PRUnichar *password,
243 nsISupports **sessionState,
244 nsISupports **continuationState,
247 LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
250 return GenerateCredentials_1_9_2(httpChannel,
263 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
264 const char *challenge,
266 const PRUnichar *domain,
267 const PRUnichar *username,
268 const PRUnichar *password,
269 nsISupports **sessionState,
270 nsISupports **continuationState,
274 OM_uint32 major_status, minor_status;
275 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
276 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
277 gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER;
279 nsCountedRef<nsMoonshotSessionState> session(static_cast<nsMoonshotSessionState *>(*sessionState));
281 session = static_cast<nsMoonshotSessionState *>(*continuationState);
283 nsCOMPtr<nsIURI> uri;
285 nsCAutoString service;
287 LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
289 NS_ENSURE_ARG_POINTER(creds);
291 PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
292 strlen(NEGOTIATE_AUTH));
293 NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
295 rv = httpChannel->GetURI(getter_AddRefs(uri));
296 if (NS_FAILED(rv)) return rv;
298 rv = uri->GetAsciiHost(service);
299 if (NS_FAILED(rv)) return rv;
301 LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n",
305 // LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
308 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
309 // construct the proper service name for passing to "gss_import_name".
311 // TODO: Possibly make this a configurable service name for use
312 // with non-standard servers that use stuff like "khttp/f.q.d.n"
315 /* DK: service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
317 service.Insert("HTTP@", 0);
319 service.Insert("host@", 0);
322 input_token.value = (void *)service.get();
323 input_token.length = service.Length() + 1;
325 major_status = gss_import_name(&minor_status,
327 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
328 GSS_C_NT_HOSTBASED_SERVICE,
333 input_token.value = NULL;
334 input_token.length = 0;
335 if (GSS_ERROR(major_status)) {
336 LogGssError(major_status, minor_status, "gss_import_name() failed");
337 return NS_ERROR_FAILURE;
340 // Create session state if none added yet.
342 session = new nsMoonshotSessionState();
344 return(NS_ERROR_OUT_OF_MEMORY);
345 LOG(("nsHttpMoonshot::A new session context established\n"));
347 LOG(("nsHttpMoonshot::Still using context from previous request\n"));
351 // If the "Negotiate:" header had some data associated with it,
352 // that data should be used as the input to this call. This may
353 // be a continuation of an earlier call because GSSAPI authentication
354 // often takes multiple round-trips to complete depending on the
355 // context flags given. We want to use MUTUAL_AUTHENTICATION which
356 // generally *does* require multiple round-trips. Don't assume
357 // auth can be completed in just 1 call.
359 unsigned int len = strlen(challenge);
361 if (len > strlen(NEGOTIATE_AUTH)) {
362 challenge += strlen(NEGOTIATE_AUTH);
363 while (*challenge == ' ') challenge++;
364 len = strlen(challenge);
367 if(len && (0 == (len & 3)) )
369 if( (char)'=' == challenge[len-1] )
371 if( (char)'=' == challenge[len-2] )
383 input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
384 // input_token.length = (len * 3)/4;
385 input_token.value = malloc(input_token.length + 1);
386 if (!input_token.value)
387 return (NS_ERROR_OUT_OF_MEMORY);
390 // Decode the response that followed the "Negotiate" token
392 if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
393 free(input_token.value);
394 return(NS_ERROR_UNEXPECTED);
396 in_token_ptr = &input_token;
397 LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
400 // Starting over, clear out any existing context and don't
401 // use an input token.
404 /* if (session->context_state == 2) {
405 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
407 return NS_ERROR_OUT_OF_MEMORY;
410 sprintf(*creds, "%s", NEGOTIATE_AUTH);
414 in_token_ptr = GSS_C_NO_BUFFER;
418 if (session->gss_cred == GSS_C_NO_CREDENTIAL)
420 OM_uint32 maj_stat, min_stat;
421 gss_buffer_desc tmp_token;
422 gss_name_t gss_username = GSS_C_NO_NAME;
423 gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
426 u = strdup(NS_LossyConvertUTF16toASCII(username).get());
427 p = strdup(NS_LossyConvertUTF16toASCII(password).get());
429 LOG(("Acquiring credentials for user '%s' using password '%s'\n",
432 tmp_token.value = (void *) u;
433 tmp_token.length = strlen((const char *)tmp_token.value);
434 maj_stat = gss_import_name(&min_stat, &tmp_token,
438 if (GSS_ERROR(maj_stat)) {
439 LogGssError(maj_stat, min_stat, "gss_import_name() failed");
441 return NS_ERROR_FAILURE;
444 mechs.elements = GetOID();
448 tmp_token.value = (void *) p;
449 tmp_token.length = strlen(p);//strlen((const char*)tmp_token.value);
450 maj_stat = gss_acquire_cred_with_password(&min_stat,
451 gss_username, &tmp_token, 0,
452 mechsp, GSS_C_INITIATE,
453 &session->gss_cred, NULL, NULL);
454 if (GSS_ERROR(maj_stat)) {
455 LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
457 return NS_ERROR_FAILURE;
460 LOG(("Acquired credential for user '%s' using password '%s'\n",
464 major_status = gss_init_sec_context(&minor_status,
470 /* GSS_C_INDEFINITE */ 0,
471 GSS_C_NO_CHANNEL_BINDINGS,
478 if (GSS_ERROR(major_status)) {
479 LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
480 (void) gss_release_name(&minor_status, &server);
481 // gss_release_cred(&minor_status, &cred);
483 if (input_token.length > 0 && input_token.value != NULL)
484 (void) gss_release_buffer(&minor_status, &input_token);
485 return NS_ERROR_FAILURE;
488 if (major_status == GSS_S_COMPLETE) {
490 // We are done with this authentication, reset the context.
494 session->gss_state = GSS_CTX_ESTABLISHED;
495 if (*sessionState != session)
497 NS_ADDREF(*sessionState = session);
498 // clean up continuation state
499 if (*continuationState)
500 NS_RELEASE(*continuationState);
502 LOG(("GSS Auth done"));
503 } else if (major_status == GSS_S_CONTINUE_NEEDED) {
505 // We could create a continuation state, but its not
508 // The important thing is that we do NOT reset the
509 // session context here because it will be needed on the
513 session->gss_state = GSS_CTX_IN_PROGRESS;
514 if (*continuationState != session)
516 // Assert continuationState==NULL
517 NS_ADDREF(*continuationState = session);
519 LOG(("GSS Auth continuing"));
522 // We don't need the input token data anymore.
523 if (input_token.length > 0 && input_token.value != NULL)
524 (void) gss_release_buffer(&minor_status, &input_token);
526 if (output_token.length == 0) {
527 LOG(("No GSS output token to send, exiting"));
528 (void) gss_release_name(&minor_status, &server);
529 // gss_release_cred(&minor_status, &cred);
530 return NS_ERROR_FAILURE;
534 // The token output from the gss_init_sec_context call is
535 // encoded and used as the Authentication response for the
538 char *encoded_token = PL_Base64Encode((char *)output_token.value,
541 if (!encoded_token) {
542 (void) gss_release_buffer(&minor_status, &output_token);
543 (void) gss_release_name(&minor_status, &server);
544 // gss_release_cred(&minor_status, &cred);
545 return NS_ERROR_OUT_OF_MEMORY;
548 LOG(("Sending a token of length %d\n", output_token.length));
550 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
551 *creds = (char *) PR_Malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
553 PR_Free(encoded_token);
554 (void) gss_release_buffer(&minor_status, &output_token);
555 (void) gss_release_name(&minor_status, &server);
556 // gss_release_cred(&minor_status, &cred);
557 return NS_ERROR_OUT_OF_MEMORY;
560 sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
561 PR_Free(encoded_token);
563 (void) gss_release_buffer(&minor_status, &output_token);
564 (void) gss_release_name(&minor_status, &server);
565 // gss_release_cred(&minor_status, &cred);
567 LOG(("returning the call"));