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 "nsMoonshotSessionState.h"
62 #include "nsHttpMoonshot.h"
64 /* #define HAVE_GSS_C_NT_HOSTBASED_SERVICE 1 */
66 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
68 #include <gssapi/gssapi_generic.h>
72 static gss_OID_desc gss_krb5_mech_oid_desc =
73 {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
75 static gss_OID_desc gss_spnego_mech_oid_desc =
76 {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
78 // in order to do logging, the following environment variables need to be set:
80 // set NSPR_LOG_MODULES=negotiate:4
81 // set NSPR_LOG_FILE=negotiate.log
83 #if defined(PR_LOGGING)
85 PRLogModuleInfo *gHttpLog = nsnull;
86 static PRLogModuleInfo* gNegotiateLog = nsnull;
90 #define LOG4(args) PR_LOG(gNegotiateLog, 4, args)
91 #define LOG(args) LOG4(args)
94 parse_oid(char *mechanism, gss_OID * oid)
98 OM_uint32 maj_stat, min_stat;
99 size_t i, mechlen = strlen(mechanism);
101 if (isdigit((int) mechanism[0])) {
102 mechstr = (char *)malloc(mechlen + 5);
104 fprintf(stderr, "Couldn't allocate mechanism scratch!\n");
109 for (i = 0; i < mechlen; i++)
110 mechstr[i + 2] = (mechanism[i] == '.') ? ' ' : mechanism[i];
111 mechstr[mechlen + 2] = ' ';
112 mechstr[mechlen + 3] = ' ';
113 mechstr[mechlen + 4] = '\0';
116 tok.value = mechanism;
117 tok.length = strlen((const char *)tok.value);
118 maj_stat = gss_str_to_oid(&min_stat, &tok, oid);
119 if (maj_stat != GSS_S_COMPLETE) {
120 //display_status("str_to_oid", maj_stat, min_stat);
132 parse_oid("{1 3 6 1 4 1 5322 22 1 18}", &mech_oid);
136 nsHttpMoonshot::nsHttpMoonshot()
140 #if defined(PR_LOGGING)
142 gNegotiateLog = PR_NewLogModule("moonshot");
143 #endif /* PR_LOGGING */
147 nsHttpMoonshot::~nsHttpMoonshot()
152 nsHttpMoonshot::GetAuthFlags(PRUint32 *flags)
154 *flags = REQUEST_BASED;
159 nsHttpMoonshot::ChallengeReceived(nsIHttpChannel *httpChannel,
160 const char *challenge,
162 nsISupports **sessionState,
163 nsISupports **continuationState,
164 PRBool *identityInvalid)
166 nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
169 // Use this opportunity to instantiate the session object
170 // that gets used later when we generate the credentials.
173 session = new nsMoonshotSessionState();
175 return(NS_ERROR_OUT_OF_MEMORY);
176 NS_ADDREF(*sessionState = session);
177 *identityInvalid = PR_TRUE;
178 LOG(("nsHttpMoonshot::A new session context established\n"));
180 LOG(("nsHttpMoonshot::Still using context from previous request\n"));
181 *identityInvalid = PR_FALSE;
188 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
189 nsIHttpAuthenticator_1_9_2)
191 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
195 // Generate proper GSSAPI error messages from the major and
196 // minor status codes.
199 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
202 OM_uint32 msg_ctx = 0;
203 gss_buffer_desc status1_string;
204 gss_buffer_desc status2_string;
206 nsCAutoString error(prefix);
210 ret = gss_display_status (&new_stat,
216 error += (char *)status1_string.value;
218 ret = gss_display_status (&new_stat,
224 error += (char *)status2_string.value;
227 } while (!GSS_ERROR(ret) && msg_ctx != 0);
229 // LOG(("%s", ToNewCString(error)));
230 LOG(("%s\n", error.get()));
234 // GenerateCredentials
236 // This routine is responsible for creating the correct authentication
237 // blob to pass to the server that requested "Negotiate" authentication.
240 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
241 const char *challenge,
243 const PRUnichar *domain,
244 const PRUnichar *user,
245 const PRUnichar *password,
246 nsISupports **sessionState,
247 nsISupports **continuationState,
250 LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
253 return GenerateCredentials_1_9_2(httpChannel,
266 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
267 const char *challenge,
269 const PRUnichar *domain,
270 const PRUnichar *username,
271 const PRUnichar *password,
272 nsISupports **sessionState,
273 nsISupports **continuationState,
277 OM_uint32 major_status, minor_status;
278 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
279 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
280 gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER;
282 nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
285 nsCOMPtr<nsIURI> uri;
287 nsCAutoString service;
289 LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
291 NS_ENSURE_ARG_POINTER(creds);
293 PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
294 strlen(NEGOTIATE_AUTH));
295 NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
297 rv = httpChannel->GetURI(getter_AddRefs(uri));
298 if (NS_FAILED(rv)) return rv;
300 rv = uri->GetAsciiHost(service);
301 if (NS_FAILED(rv)) return rv;
303 LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n",
307 // LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
310 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
311 // construct the proper service name for passing to "gss_import_name".
313 // TODO: Possibly make this a configurable service name for use
314 // with non-standard servers that use stuff like "khttp/f.q.d.n"
317 /* DK: service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
319 service.Insert("HTTP@", 0);
321 service.Insert("host@", 0);
324 input_token.value = (void *)service.get();
325 input_token.length = service.Length() + 1;
327 major_status = gss_import_name(&minor_status,
329 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
330 GSS_C_NT_HOSTBASED_SERVICE,
335 input_token.value = NULL;
336 input_token.length = 0;
337 if (GSS_ERROR(major_status)) {
338 LogGssError(major_status, minor_status, "gss_import_name() failed");
339 return NS_ERROR_FAILURE;
343 // If the "Negotiate:" header had some data associated with it,
344 // that data should be used as the input to this call. This may
345 // be a continuation of an earlier call because GSSAPI authentication
346 // often takes multiple round-trips to complete depending on the
347 // context flags given. We want to use MUTUAL_AUTHENTICATION which
348 // generally *does* require multiple round-trips. Don't assume
349 // auth can be completed in just 1 call.
351 unsigned int len = strlen(challenge);
353 if (len > strlen(NEGOTIATE_AUTH)) {
354 challenge += strlen(NEGOTIATE_AUTH);
355 while (*challenge == ' ') challenge++;
356 len = strlen(challenge);
359 if(len && (0 == (len & 3)) )
361 if( (char)'=' == challenge[len-1] )
363 if( (char)'=' == challenge[len-2] )
375 input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
376 // input_token.length = (len * 3)/4;
377 input_token.value = malloc(input_token.length + 1);
378 if (!input_token.value)
379 return (NS_ERROR_OUT_OF_MEMORY);
382 // Decode the response that followed the "Negotiate" token
384 if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
385 free(input_token.value);
386 return(NS_ERROR_UNEXPECTED);
388 in_token_ptr = &input_token;
389 LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
392 // Starting over, clear out any existing context and don't
393 // use an input token.
396 /* if (session->context_state == 2) {
397 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
399 return NS_ERROR_OUT_OF_MEMORY;
402 sprintf(*creds, "%s", NEGOTIATE_AUTH);
406 in_token_ptr = GSS_C_NO_BUFFER;
410 if (session->gss_cred == GSS_C_NO_CREDENTIAL)
412 OM_uint32 maj_stat, min_stat;
413 gss_buffer_desc tmp_token;
414 gss_name_t gss_username = GSS_C_NO_NAME;
415 gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
418 u = strdup(NS_LossyConvertUTF16toASCII(username).get());
419 p = strdup(NS_LossyConvertUTF16toASCII(password).get());
421 tmp_token.value = (void *) u;
422 tmp_token.length = strlen((const char *)tmp_token.value);
423 maj_stat = gss_import_name(&min_stat, &tmp_token,
427 if (GSS_ERROR(maj_stat)) {
428 LogGssError(maj_stat, min_stat, "gss_import_name() failed");
430 return NS_ERROR_FAILURE;
433 mechs.elements = GetOID();
437 tmp_token.value = (void *) p;
438 tmp_token.length = strlen(p);//strlen((const char*)tmp_token.value);
439 maj_stat = gss_acquire_cred_with_password(&min_stat,
440 gss_username, &tmp_token, 0,
441 mechsp, GSS_C_INITIATE,
442 &session->gss_cred, NULL, NULL);
443 if (GSS_ERROR(maj_stat)) {
444 LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
446 return NS_ERROR_FAILURE;
449 LOG(("Acquired credential for user '%s' using password '%s'\n",
453 major_status = gss_init_sec_context(&minor_status,
459 /* GSS_C_INDEFINITE */ 0,
460 GSS_C_NO_CHANNEL_BINDINGS,
467 if (GSS_ERROR(major_status)) {
468 LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
469 (void) gss_release_name(&minor_status, &server);
470 // gss_release_cred(&minor_status, &cred);
472 if (input_token.length > 0 && input_token.value != NULL)
473 (void) gss_release_buffer(&minor_status, &input_token);
474 return NS_ERROR_FAILURE;
477 if (major_status == GSS_S_COMPLETE) {
479 // We are done with this authentication, reset the context.
483 session->gss_state = GSS_CTX_ESTABLISHED;
484 LOG(("GSS Auth done"));
485 } else if (major_status == GSS_S_CONTINUE_NEEDED) {
487 // We could create a continuation state, but its not
490 // The important thing is that we do NOT reset the
491 // session context here because it will be needed on the
495 session->gss_state = GSS_CTX_IN_PROGRESS;
496 LOG(("GSS Auth continuing"));
499 // We don't need the input token data anymore.
500 if (input_token.length > 0 && input_token.value != NULL)
501 (void) gss_release_buffer(&minor_status, &input_token);
503 if (output_token.length == 0) {
504 LOG(("No GSS output token to send, exiting"));
505 (void) gss_release_name(&minor_status, &server);
506 // gss_release_cred(&minor_status, &cred);
507 return NS_ERROR_FAILURE;
511 // The token output from the gss_init_sec_context call is
512 // encoded and used as the Authentication response for the
515 char *encoded_token = PL_Base64Encode((char *)output_token.value,
518 if (!encoded_token) {
519 (void) gss_release_buffer(&minor_status, &output_token);
520 (void) gss_release_name(&minor_status, &server);
521 // gss_release_cred(&minor_status, &cred);
522 return NS_ERROR_OUT_OF_MEMORY;
525 LOG(("Sending a token of length %d\n", output_token.length));
527 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
528 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
530 PR_Free(encoded_token);
531 (void) gss_release_buffer(&minor_status, &output_token);
532 (void) gss_release_name(&minor_status, &server);
533 // gss_release_cred(&minor_status, &cred);
534 return NS_ERROR_OUT_OF_MEMORY;
537 sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
538 PR_Free(encoded_token);
540 (void) gss_release_buffer(&minor_status, &output_token);
541 (void) gss_release_name(&minor_status, &server);
542 // gss_release_cred(&minor_status, &cred);
544 LOG(("returning the call"));