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
39 #include "nsIHttpChannel.h"
40 #include "nsIServiceManager.h"
41 #include "nsISupportsPrimitives.h"
48 #include "nsISupportsUtils.h"
50 /* XXX, just for debugging */
51 #ifdef MOZILLA_INTERNAL_API
54 #include "nsStringAPI.h"
61 #include "nsHttpMoonshot.h"
63 /* #define HAVE_GSS_C_NT_HOSTBASED_SERVICE 1 */
65 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
67 #include <gssapi/gssapi_generic.h>
71 static gss_OID_desc gss_krb5_mech_oid_desc =
72 {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
74 static gss_OID_desc gss_spnego_mech_oid_desc =
75 {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
77 // in order to do logging, the following environment variables need to be set:
79 // set NSPR_LOG_MODULES=negotiate:4
80 // set NSPR_LOG_FILE=negotiate.log
82 #if defined(PR_LOGGING)
84 PRLogModuleInfo *gHttpLog = nsnull;
85 static PRLogModuleInfo* gNegotiateLog = nsnull;
89 #define LOG4(args) PR_LOG(gNegotiateLog, 4, args)
90 #define LOG(args) LOG4(args)
93 parse_oid(char *mechanism, gss_OID * oid)
97 OM_uint32 maj_stat, min_stat;
98 size_t i, mechlen = strlen(mechanism);
100 if (isdigit((int) mechanism[0])) {
101 mechstr = (char *)malloc(mechlen + 5);
103 fprintf(stderr, "Couldn't allocate mechanism scratch!\n");
108 for (i = 0; i < mechlen; i++)
109 mechstr[i + 2] = (mechanism[i] == '.') ? ' ' : mechanism[i];
110 mechstr[mechlen + 2] = ' ';
111 mechstr[mechlen + 3] = ' ';
112 mechstr[mechlen + 4] = '\0';
115 tok.value = mechanism;
116 tok.length = strlen((const char *)tok.value);
117 maj_stat = gss_str_to_oid(&min_stat, &tok, oid);
118 if (maj_stat != GSS_S_COMPLETE) {
119 //display_status("str_to_oid", maj_stat, min_stat);
126 class nsMoonshotSessionState : public nsISupports
131 nsMoonshotSessionState();
133 virtual ~nsMoonshotSessionState() {
134 OM_uint32 minor_status;
135 if (mCtx != GSS_C_NO_CONTEXT)
136 (void)gss_delete_sec_context(&minor_status, &mCtx, GSS_C_NO_BUFFER);
137 mCtx = GSS_C_NO_CONTEXT;
138 mech_oid = GSS_C_NO_OID;
142 OM_uint32 minor_status;
143 if (mCtx != GSS_C_NO_CONTEXT)
144 (void)gss_delete_sec_context(&minor_status, &mCtx, GSS_C_NO_BUFFER);
145 mCtx = GSS_C_NO_CONTEXT;
149 gss_OID GetOID() { return (mech_oid); }
152 int GetCount() { return ++count; }
161 nsMoonshotSessionState::nsMoonshotSessionState()
163 OM_uint32 minstat, majstat;
164 //gss_buffer_desc buffer;
165 gss_OID_set mech_set;
166 //int mech_found = 0;
171 mCtx = GSS_C_NO_CONTEXT;
172 mech_oid = &gss_krb5_mech_oid_desc;
176 // Now, look at the list of supported mechanisms,
177 // if SPNEGO is found, then use it.
178 // Otherwise, set the desired mechanism to krb5
180 // Using Kerberos directly (instead of negotiating
181 // with SPNEGO) may work in some cases depending
182 // on how smart the server side is.
187 LOG(("nsMoonshotSessionState::nsMoonshotSessionState [count=%d]\n", count));
189 majstat = gss_indicate_mechs(&minstat, &mech_set);
190 if (GSS_ERROR(majstat))
193 for (i=0; i<mech_set->count; i++) {
194 item = &mech_set->elements[i];
195 if (item->length == gss_spnego_mech_oid_desc.length &&
196 !memcmp(item->elements, gss_spnego_mech_oid_desc.elements,
198 mech_oid = &gss_spnego_mech_oid_desc;
202 (void) gss_release_oid_set(&minstat, &mech_set);
204 parse_oid("{1 3 6 1 4 1 5322 22 1 18}", &mech_oid);
207 NS_IMPL_ISUPPORTS0(nsMoonshotSessionState)
210 nsHttpMoonshot::nsHttpMoonshot()
214 #if defined(PR_LOGGING)
216 gNegotiateLog = PR_NewLogModule("moonshot");
217 #endif /* PR_LOGGING */
223 nsHttpMoonshot::~nsHttpMoonshot()
229 nsHttpMoonshot::GetAuthFlags(PRUint32 *flags)
232 // GSSAPI creds should not be reused across multiple requests.
233 // Only perform the negotiation when it is explicitly requested
234 // by the server. Thus, do *NOT* use the "REUSABLE_CREDENTIALS"
237 *flags = REQUEST_BASED;
242 // Always set *identityInvalid == FALSE here. This
243 // will prevent the browser from popping up the authentication
244 // prompt window. Because GSSAPI does not have an API
245 // for fetching initial credentials (ex: A Kerberos TGT),
246 // there is no correct way to get the users credentials.
249 nsHttpMoonshot::ChallengeReceived(nsIHttpChannel *httpChannel,
250 const char *challenge,
252 nsISupports **sessionState,
253 nsISupports **continuationState,
254 PRBool *identityInvalid)
256 nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
258 *identityInvalid = PR_FALSE;
260 // Use this opportunity to instantiate the session object
261 // that gets used later when we generate the credentials.
264 session = new nsMoonshotSessionState();
266 return(NS_ERROR_OUT_OF_MEMORY);
267 NS_ADDREF(*sessionState = session);
268 LOG(("nsHttpMoonshot::A new session context established\n"));
270 LOG(("nsHttpMoonshot::Still using context from previous request [ctx=%p]\n", session->mCtx));
277 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
278 nsIHttpAuthenticator_1_9_2)
280 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
284 // Generate proper GSSAPI error messages from the major and
285 // minor status codes.
288 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
291 OM_uint32 msg_ctx = 0;
292 gss_buffer_desc status1_string;
293 gss_buffer_desc status2_string;
295 nsCAutoString error(prefix);
299 ret = gss_display_status (&new_stat,
305 error += (char *)status1_string.value;
307 ret = gss_display_status (&new_stat,
313 error += (char *)status2_string.value;
316 } while (!GSS_ERROR(ret) && msg_ctx != 0);
318 // LOG(("%s", ToNewCString(error)));
319 LOG(("%s\n", error.get()));
323 // GenerateCredentials
325 // This routine is responsible for creating the correct authentication
326 // blob to pass to the server that requested "Negotiate" authentication.
329 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
330 const char *challenge,
332 const PRUnichar *domain,
333 const PRUnichar *user,
334 const PRUnichar *password,
335 nsISupports **sessionState,
336 nsISupports **continuationState,
339 LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
342 return GenerateCredentials_1_9_2(httpChannel,
355 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
356 const char *challenge,
358 const PRUnichar *domain,
359 const PRUnichar *username,
360 const PRUnichar *password,
361 nsISupports **sessionState,
362 nsISupports **continuationState,
366 OM_uint32 major_status, minor_status;
367 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
368 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
369 gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER;
371 nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
372 gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
375 nsCOMPtr<nsIURI> uri;
377 nsCAutoString service;
379 LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
381 NS_ENSURE_ARG_POINTER(creds);
383 PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
384 strlen(NEGOTIATE_AUTH));
385 NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
387 rv = httpChannel->GetURI(getter_AddRefs(uri));
388 if (NS_FAILED(rv)) return rv;
390 rv = uri->GetAsciiHost(service);
391 if (NS_FAILED(rv)) return rv;
393 LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n",
397 LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
400 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
401 // construct the proper service name for passing to "gss_import_name".
403 // TODO: Possibly make this a configurable service name for use
404 // with non-standard servers that use stuff like "khttp/f.q.d.n"
407 /* DK: service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
409 service.Insert("HTTP@", 0);
411 service.Insert("host@", 0);
414 input_token.value = (void *)service.get();
415 input_token.length = service.Length() + 1;
417 major_status = gss_import_name(&minor_status,
419 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
420 GSS_C_NT_HOSTBASED_SERVICE,
425 input_token.value = NULL;
426 input_token.length = 0;
427 if (GSS_ERROR(major_status)) {
428 LogGssError(major_status, minor_status, "gss_import_name() failed");
429 return NS_ERROR_FAILURE;
433 // If the "Negotiate:" header had some data associated with it,
434 // that data should be used as the input to this call. This may
435 // be a continuation of an earlier call because GSSAPI authentication
436 // often takes multiple round-trips to complete depending on the
437 // context flags given. We want to use MUTUAL_AUTHENTICATION which
438 // generally *does* require multiple round-trips. Don't assume
439 // auth can be completed in just 1 call.
441 unsigned int len = strlen(challenge);
443 if (len > strlen(NEGOTIATE_AUTH)) {
444 challenge += strlen(NEGOTIATE_AUTH);
445 while (*challenge == ' ') challenge++;
446 len = strlen(challenge);
449 if(len && (0 == (len & 3)) )
451 if( (char)'=' == challenge[len-1] )
453 if( (char)'=' == challenge[len-2] )
465 input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
466 // input_token.length = (len * 3)/4;
467 input_token.value = malloc(input_token.length + 1);
468 if (!input_token.value)
469 return (NS_ERROR_OUT_OF_MEMORY);
472 // Decode the response that followed the "Negotiate" token
474 if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
475 free(input_token.value);
476 return(NS_ERROR_UNEXPECTED);
478 in_token_ptr = &input_token;
479 LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
482 // Starting over, clear out any existing context and don't
483 // use an input token.
486 /* if (session->context_state == 2) {
487 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
489 return NS_ERROR_OUT_OF_MEMORY;
492 sprintf(*creds, "%s", NEGOTIATE_AUTH);
496 in_token_ptr = GSS_C_NO_BUFFER;
502 OM_uint32 maj_stat, min_stat;
503 gss_buffer_desc tmp_token;
504 gss_name_t gss_username = GSS_C_NO_NAME;
505 gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
507 tmp_token.value = (void *) "steve@local";
508 tmp_token.length = strlen((const char *)tmp_token.value);
509 maj_stat = gss_import_name(&min_stat, &tmp_token,
513 if (GSS_ERROR(maj_stat)) {
514 LogGssError(maj_stat, min_stat, "gss_import_name() failed");
516 return NS_ERROR_FAILURE;
519 mechs.elements = session->GetOID();
523 tmp_token.value = (void *)"testing";
524 tmp_token.length = strlen((const char*)tmp_token.value);
525 maj_stat = gss_acquire_cred_with_password(&min_stat,
526 gss_username, &tmp_token, 0,
527 mechsp, GSS_C_INITIATE,
529 if (GSS_ERROR(maj_stat)) {
530 LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
532 return NS_ERROR_FAILURE;
536 major_status = gss_init_sec_context(&minor_status,
542 /* GSS_C_INDEFINITE */ 0,
543 GSS_C_NO_CHANNEL_BINDINGS,
550 if (GSS_ERROR(major_status)) {
551 LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
552 (void) gss_release_name(&minor_status, &server);
553 gss_release_cred(&minor_status, &cred);
555 if (input_token.length > 0 && input_token.value != NULL)
556 (void) gss_release_buffer(&minor_status, &input_token);
557 return NS_ERROR_FAILURE;
560 if (major_status == GSS_S_COMPLETE) {
562 // We are done with this authentication, reset the context.
566 session->context_state = 2;
567 LOG(("GSS Auth done"));
568 } else if (major_status == GSS_S_CONTINUE_NEEDED) {
570 // We could create a continuation state, but its not
573 // The important thing is that we do NOT reset the
574 // session context here because it will be needed on the
578 session->context_state = 1;
579 LOG(("GSS Auth continuing"));
582 // We don't need the input token data anymore.
583 if (input_token.length > 0 && input_token.value != NULL)
584 (void) gss_release_buffer(&minor_status, &input_token);
586 if (output_token.length == 0) {
587 LOG(("No GSS output token to send, exiting"));
588 (void) gss_release_name(&minor_status, &server);
589 gss_release_cred(&minor_status, &cred);
590 return NS_ERROR_FAILURE;
594 // The token output from the gss_init_sec_context call is
595 // encoded and used as the Authentication response for the
598 char *encoded_token = PL_Base64Encode((char *)output_token.value,
601 if (!encoded_token) {
602 (void) gss_release_buffer(&minor_status, &output_token);
603 (void) gss_release_name(&minor_status, &server);
604 gss_release_cred(&minor_status, &cred);
605 return NS_ERROR_OUT_OF_MEMORY;
608 LOG(("Sending a token of length %d\n", output_token.length));
610 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
611 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
613 PR_Free(encoded_token);
614 (void) gss_release_buffer(&minor_status, &output_token);
615 (void) gss_release_name(&minor_status, &server);
616 gss_release_cred(&minor_status, &cred);
617 return NS_ERROR_OUT_OF_MEMORY;
620 sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
621 PR_Free(encoded_token);
623 (void) gss_release_buffer(&minor_status, &output_token);
624 (void) gss_release_name(&minor_status, &server);
625 gss_release_cred(&minor_status, &cred);
627 LOG(("returning the call"));
634 nsMoonshotConstructor(nsISupports *outer, REFNSIID iid, void **result)
637 return NS_ERROR_NO_AGGREGATION;