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 LOG(("nsHttpMoonshot::A new session context established\n"));
179 LOG(("nsHttpMoonshot::Still using context from previous request\n"));
182 LOG(("nsHttpMoonshot:: gss_state = %d\n", session->gss_state));
185 (session->gss_state == GSS_CTX_EMPTY) ? PR_TRUE : PR_FALSE;
191 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
192 nsIHttpAuthenticator_1_9_2)
194 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
198 // Generate proper GSSAPI error messages from the major and
199 // minor status codes.
202 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
205 OM_uint32 msg_ctx = 0;
206 gss_buffer_desc status1_string;
207 gss_buffer_desc status2_string;
209 nsCAutoString error(prefix);
213 ret = gss_display_status (&new_stat,
219 error += (char *)status1_string.value;
221 ret = gss_display_status (&new_stat,
227 error += (char *)status2_string.value;
230 } while (!GSS_ERROR(ret) && msg_ctx != 0);
232 // LOG(("%s", ToNewCString(error)));
233 LOG(("%s\n", error.get()));
237 // GenerateCredentials
239 // This routine is responsible for creating the correct authentication
240 // blob to pass to the server that requested "Negotiate" authentication.
243 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
244 const char *challenge,
246 const PRUnichar *domain,
247 const PRUnichar *user,
248 const PRUnichar *password,
249 nsISupports **sessionState,
250 nsISupports **continuationState,
253 LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
256 return GenerateCredentials_1_9_2(httpChannel,
269 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
270 const char *challenge,
272 const PRUnichar *domain,
273 const PRUnichar *username,
274 const PRUnichar *password,
275 nsISupports **sessionState,
276 nsISupports **continuationState,
280 OM_uint32 major_status, minor_status;
281 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
282 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
283 gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER;
285 nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
288 nsCOMPtr<nsIURI> uri;
290 nsCAutoString service;
292 LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
294 NS_ENSURE_ARG_POINTER(creds);
296 PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
297 strlen(NEGOTIATE_AUTH));
298 NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
300 rv = httpChannel->GetURI(getter_AddRefs(uri));
301 if (NS_FAILED(rv)) return rv;
303 rv = uri->GetAsciiHost(service);
304 if (NS_FAILED(rv)) return rv;
306 LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n",
310 // LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
313 // The correct service name for IIS servers is "HTTP/f.q.d.n", so
314 // construct the proper service name for passing to "gss_import_name".
316 // TODO: Possibly make this a configurable service name for use
317 // with non-standard servers that use stuff like "khttp/f.q.d.n"
320 /* DK: service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
322 service.Insert("HTTP@", 0);
324 service.Insert("host@", 0);
327 input_token.value = (void *)service.get();
328 input_token.length = service.Length() + 1;
330 major_status = gss_import_name(&minor_status,
332 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
333 GSS_C_NT_HOSTBASED_SERVICE,
338 input_token.value = NULL;
339 input_token.length = 0;
340 if (GSS_ERROR(major_status)) {
341 LogGssError(major_status, minor_status, "gss_import_name() failed");
342 return NS_ERROR_FAILURE;
346 // If the "Negotiate:" header had some data associated with it,
347 // that data should be used as the input to this call. This may
348 // be a continuation of an earlier call because GSSAPI authentication
349 // often takes multiple round-trips to complete depending on the
350 // context flags given. We want to use MUTUAL_AUTHENTICATION which
351 // generally *does* require multiple round-trips. Don't assume
352 // auth can be completed in just 1 call.
354 unsigned int len = strlen(challenge);
356 if (len > strlen(NEGOTIATE_AUTH)) {
357 challenge += strlen(NEGOTIATE_AUTH);
358 while (*challenge == ' ') challenge++;
359 len = strlen(challenge);
362 if(len && (0 == (len & 3)) )
364 if( (char)'=' == challenge[len-1] )
366 if( (char)'=' == challenge[len-2] )
378 input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
379 // input_token.length = (len * 3)/4;
380 input_token.value = malloc(input_token.length + 1);
381 if (!input_token.value)
382 return (NS_ERROR_OUT_OF_MEMORY);
385 // Decode the response that followed the "Negotiate" token
387 if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
388 free(input_token.value);
389 return(NS_ERROR_UNEXPECTED);
391 in_token_ptr = &input_token;
392 LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
395 // Starting over, clear out any existing context and don't
396 // use an input token.
399 /* if (session->context_state == 2) {
400 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
402 return NS_ERROR_OUT_OF_MEMORY;
405 sprintf(*creds, "%s", NEGOTIATE_AUTH);
409 in_token_ptr = GSS_C_NO_BUFFER;
413 if (session->gss_cred == GSS_C_NO_CREDENTIAL)
415 OM_uint32 maj_stat, min_stat;
416 gss_buffer_desc tmp_token;
417 gss_name_t gss_username = GSS_C_NO_NAME;
418 gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
421 u = strdup(NS_LossyConvertUTF16toASCII(username).get());
422 p = strdup(NS_LossyConvertUTF16toASCII(password).get());
424 LOG(("Acquiring credentials for user '%s' using password '%s'\n",
427 tmp_token.value = (void *) u;
428 tmp_token.length = strlen((const char *)tmp_token.value);
429 maj_stat = gss_import_name(&min_stat, &tmp_token,
433 if (GSS_ERROR(maj_stat)) {
434 LogGssError(maj_stat, min_stat, "gss_import_name() failed");
436 return NS_ERROR_FAILURE;
439 mechs.elements = GetOID();
443 tmp_token.value = (void *) p;
444 tmp_token.length = strlen(p);//strlen((const char*)tmp_token.value);
445 maj_stat = gss_acquire_cred_with_password(&min_stat,
446 gss_username, &tmp_token, 0,
447 mechsp, GSS_C_INITIATE,
448 &session->gss_cred, NULL, NULL);
449 if (GSS_ERROR(maj_stat)) {
450 LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
452 return NS_ERROR_FAILURE;
455 LOG(("Acquired credential for user '%s' using password '%s'\n",
459 major_status = gss_init_sec_context(&minor_status,
465 /* GSS_C_INDEFINITE */ 0,
466 GSS_C_NO_CHANNEL_BINDINGS,
473 if (GSS_ERROR(major_status)) {
474 LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
475 (void) gss_release_name(&minor_status, &server);
476 // gss_release_cred(&minor_status, &cred);
478 if (input_token.length > 0 && input_token.value != NULL)
479 (void) gss_release_buffer(&minor_status, &input_token);
480 return NS_ERROR_FAILURE;
483 if (major_status == GSS_S_COMPLETE) {
485 // We are done with this authentication, reset the context.
489 session->gss_state = GSS_CTX_ESTABLISHED;
490 LOG(("GSS Auth done"));
491 } else if (major_status == GSS_S_CONTINUE_NEEDED) {
493 // We could create a continuation state, but its not
496 // The important thing is that we do NOT reset the
497 // session context here because it will be needed on the
501 session->gss_state = GSS_CTX_IN_PROGRESS;
502 LOG(("GSS Auth continuing"));
505 // We don't need the input token data anymore.
506 if (input_token.length > 0 && input_token.value != NULL)
507 (void) gss_release_buffer(&minor_status, &input_token);
509 if (output_token.length == 0) {
510 LOG(("No GSS output token to send, exiting"));
511 (void) gss_release_name(&minor_status, &server);
512 // gss_release_cred(&minor_status, &cred);
513 return NS_ERROR_FAILURE;
517 // The token output from the gss_init_sec_context call is
518 // encoded and used as the Authentication response for the
521 char *encoded_token = PL_Base64Encode((char *)output_token.value,
524 if (!encoded_token) {
525 (void) gss_release_buffer(&minor_status, &output_token);
526 (void) gss_release_name(&minor_status, &server);
527 // gss_release_cred(&minor_status, &cred);
528 return NS_ERROR_OUT_OF_MEMORY;
531 LOG(("Sending a token of length %d\n", output_token.length));
533 // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
534 *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
536 PR_Free(encoded_token);
537 (void) gss_release_buffer(&minor_status, &output_token);
538 (void) gss_release_name(&minor_status, &server);
539 // gss_release_cred(&minor_status, &cred);
540 return NS_ERROR_OUT_OF_MEMORY;
543 sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
544 PR_Free(encoded_token);
546 (void) gss_release_buffer(&minor_status, &output_token);
547 (void) gss_release_name(&minor_status, &server);
548 // gss_release_cred(&minor_status, &cred);
550 LOG(("returning the call"));