blob: e883373b117ff0d58288266f0cebe7435ed154d3 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26
27package com.sun.security.auth.module;
28
29import java.io.*;
30import java.net.*;
31import java.text.MessageFormat;
32import java.util.*;
33
34import javax.security.auth.*;
35import javax.security.auth.kerberos.*;
36import javax.security.auth.callback.*;
37import javax.security.auth.login.*;
38import javax.security.auth.spi.*;
39
40import sun.security.krb5.*;
41import sun.security.krb5.Config;
42import sun.security.krb5.RealmException;
43import sun.security.util.AuthResources;
44import sun.security.jgss.krb5.Krb5Util;
45import sun.security.krb5.Credentials;
46import sun.misc.HexDumpEncoder;
47
48/**
49 * <p> This <code>LoginModule</code> authenticates users using
50 * Kerberos protocols.
51 *
52 * <p> The configuration entry for <code>Krb5LoginModule</code> has
53 * several options that control the authentication process and
54 * additions to the <code>Subject</code>'s private credential
55 * set. Irrespective of these options, the <code>Subject</code>'s
56 * principal set and private credentials set are updated only when
57 * <code>commit</code> is called.
58 * When <code>commit</code> is called, the <code>KerberosPrincipal</code>
59 * is added to the <code>Subject</code>'s
60 * principal set and <code>KerberosTicket</code> is
61 * added to the <code>Subject</code>'s private credentials.
62 *
63 * <p> If the configuration entry for <code>KerberosLoginModule</code>
64 * has the option <code>storeKey</code> set to true, then
65 * <code>KerberosKey</code> will also be added to the
66 * subject's private credentials. <code>KerberosKey</code>, the principal's
67 * key will be either obtained from the keytab or
68 * derived from user's password.
69 *
70 * <p> This <code>LoginModule</code> recognizes the <code>doNotPrompt</code>
71 * option. If set to true the user will not be prompted for the password.
72 *
73 * <p> The user can specify the location of the ticket cache by using
74 * the option <code>ticketCache</code> in the configuration entry.
75 *
76 * <p>The user can specify the keytab location by using
77 * the option <code>keyTab</code>
78 * in the configuration entry.
79 *
80 * <p> The principal name can be specified in the configuration entry
81 * by using the option <code>principal</code>. The principal name
82 * can either be a simple user name or a service name such as
83 * <code>host/mission.eng.sun.com</code>. The principal can also
84 * be set using the system property <code>sun.security.krb5.principal</code>.
85 * This property is checked during login. If this property is not set, then
86 * the principal name from the configuration is used. In the
87 * case where the principal property is not set and the principal
88 * entry also does not exist, the user is prompted for the name.
89 *
90 * <p> The following is a list of configuration options supported
91 * for <code>Krb5LoginModule</code>:
92 * <dl>
93 * <blockquote><dt><b><code>refreshKrb5Config</code></b>:</dt>
94 * <dd> Set this to true, if you want the configuration
95 * to be refreshed before the <code>login</code> method is called.</dd>
96 * <P>
97 * <dt><b><code>useTicketCache</code></b>:</dt>
98 * <dd>Set this to true, if you want the
99 * TGT to be obtained
100 * from the ticket cache. Set this option
101 * to false if you do not want this module to use the ticket cache.
102 * (Default is False).
103 * This module will
104 * search for the tickect
105 * cache in the following locations:
106 * For Windows 2000, it will use Local Security Authority (LSA) API
107 * to get the TGT. On Solaris and Linux
108 * it will look for the ticket cache in /tmp/krb5cc_<code>uid</code>
109 * where the uid is numeric user
110 * identifier. If the ticket cache is
111 * not available in either of the above locations, or if we are on a
112 * different Windows platform, it will look for the cache as
113 * {user.home}{file.separator}krb5cc_{user.name}.
114 * You can override the ticket cache location by using
115 * <code>ticketCache</code>
116 * <P>
117 * <dt><b><code>ticketCache</code></b>:</dt>
118 * <dd>Set this to the name of the ticket
119 * cache that contains user's TGT.
120 * If this is set, <code>useTicketCache</code>
121 * must also be set to true; Otherwise a configuration error will
122 * be returned.</dd>
123 * <P>
124 * <dt><b><code>renewTGT</code></b>:</dt>
125 * <dd>Set this to true, if you want to renew
126 * the TGT. If this is set, <code>useTicketCache</code> must also be
127 * set to true; otherwise a configuration error will be returned.</dd>
128 * <p>
129 * <dt><b><code>doNotPrompt</code></b>:</dt>
130 * <dd>Set this to true if you do not want to be
131 * prompted for the password
132 * if credentials can
133 * not be obtained from the cache or keytab.(Default is false)
134 * If set to true authentication will fail if credentials can
135 * not be obtained from the cache or keytab.</dd>
136 * <P>
137 * <dt><b><code>useKeyTab</code></b>:</dt>
138 * <dd>Set this to true if you
139 * want the module to get the principal's key from the
140 * the keytab.(default value is False)
141 * If <code>keyatb</code>
142 * is not set then
143 * the module will locate the keytab from the
144 * Kerberos configuration file.</dd>
145 * If it is not specifed in the Kerberos configuration file
146 * then it will look for the file
147 * <code>{user.home}{file.separator}</code>krb5.keytab.</dd>
148 * <P>
149 * <dt><b><code>keyTab</code></b>:</dt>
150 * <dd>Set this to the file name of the
151 * keytab to get principal's secret key.</dd>
152 * <P>
153 * <dt><b><code>storeKey</code></b>:</dt>
154 * <dd>Set this to true to if you want the
155 * principal's key to be stored in the Subject's private credentials. </dd>
156 * <p>
157 * <dt><b><code>principal</code></b>:</dt>
158 * <dd>The name of the principal that should
159 * be used. The principal can be a simple username such as
160 * "<code>testuser</code>" or a service name such as
161 * "<code>host/testhost.eng.sun.com</code>". You can use the
162 * <code>principal</code> option to set the principal when there are
163 * credentials for multiple principals in the
164 * <code>keyTab</code> or when you want a specific ticket cache only.
165 * The principal can also be set using the system property
166 * <code>sun.security.krb5.principal</code>. In addition, if this
167 * system property is defined, then it will be used. If this property
168 * is not set, then the principal name from the configuration will be
169 * used.</dd>
170 * <P>
171 * <dt><b><code>isInitiator</code></b>:</dt>
172 * <dd>Set this to true, if initiator. Set this to false, if acceptor only.
173 * (Default is true).
174 * Note: Do not set this value to false for initiators.</dd>
175 * </dl></blockquote>
176 *
177 * <p> This <code>LoginModule</code> also recognizes the following additional
178 * <code>Configuration</code>
179 * options that enable you to share username and passwords across different
180 * authentication modules:
181 * <pre>
182 *
183 * useFirstPass if, true, this LoginModule retrieves the
184 * username and password from the module's shared state,
185 * using "javax.security.auth.login.name" and
186 * "javax.security.auth.login.password" as the respective
187 * keys. The retrieved values are used for authentication.
188 * If authentication fails, no attempt for a retry
189 * is made, and the failure is reported back to the
190 * calling application.
191 *
192 * tryFirstPass if, true, this LoginModule retrieves the
193 * the username and password from the module's shared
194 * state using "javax.security.auth.login.name" and
195 * "javax.security.auth.login.password" as the respective
196 * keys. The retrieved values are used for
197 * authentication.
198 * If authentication fails, the module uses the
199 * CallbackHandler to retrieve a new username
200 * and password, and another attempt to authenticate
201 * is made. If the authentication fails,
202 * the failure is reported back to the calling application
203 *
204 * storePass if, true, this LoginModule stores the username and
205 * password obtained from the CallbackHandler in the
206 * modules shared state, using
207 * "javax.security.auth.login.name" and
208 * "javax.security.auth.login.password" as the respective
209 * keys. This is not performed if existing values already
210 * exist for the username and password in the shared
211 * state, or if authentication fails.
212 *
213 * clearPass if, true, this <code>LoginModule</code> clears the
214 * username and password stored in the module's shared
215 * state after both phases of authentication
216 * (login and commit) have completed.
217 * </pre>
218 * <p>Examples of some configuration values for Krb5LoginModule in
219 * JAAS config file and the results are:
220 * <ul>
221 * <p> <code>doNotPrompt</code>=true;
222 * </ul>
223 * <p> This is an illegal combination since <code>useTicketCache</code>
224 * is not set and the user can not be prompted for the password.
225 *<ul>
226 * <p> <code>ticketCache</code> = < filename >;
227 *</ul>
228 * <p> This is an illegal combination since <code>useTicketCache</code>
229 * is not set to true and the ticketCache is set. A configuration error
230 * will occur.
231 * <ul>
232 * <p> <code>renewTGT</code>=true;
233 *</ul>
234 * <p> This is an illegal combination since <code>useTicketCache</code> is
235 * not set to true and renewTGT is set. A configuration error will occur.
236 * <ul>
237 * <p> <code>storeKey</code>=true
238 * <code>useTicketCache</code> = true
239 * <code>doNotPrompt</code>=true;;
240 *</ul>
241 * <p> This is an illegal combination since <code>storeKey</code> is set to
242 * true but the key can not be obtained either by prompting the user or from
243 * the keytab.A configuration error will occur.
244 * <ul>
245 * <p> <code>keyTab</code> = < filename > <code>doNotPrompt</code>=true ;
246 * </ul>
247 * <p>This is an illegal combination since useKeyTab is not set to true and
248 * the keyTab is set. A configuration error will occur.
249 * <ul>
250 * <p> <code>debug=true </code>
251 *</ul>
252 * <p> Prompt the user for the principal name and the password.
253 * Use the authentication exchange to get TGT from the KDC and
254 * populate the <code>Subject</code> with the principal and TGT.
255 * Output debug messages.
256 * <ul>
257 * <p> <code>useTicketCache</code> = true <code>doNotPrompt</code>=true;
258 *</ul>
259 * <p>Check the default cache for TGT and populate the <code>Subject</code>
260 * with the principal and TGT. If the TGT is not available,
261 * do not prompt the user, instead fail the authentication.
262 * <ul>
263 * <p><code>principal</code>=< name ><code>useTicketCache</code> = true
264 * <code>doNotPrompt</code>=true;
265 *</ul>
266 * <p> Get the TGT from the default cache for the principal and populate the
267 * Subject's principal and private creds set. If ticket cache is
268 * not available or does not contain the principal's TGT
269 * authentication will fail.
270 * <ul>
271 * <p> <code>useTicketCache</code> = true
272 * <code>ticketCache</code>=< file name ><code>useKeyTab</code> = true
273 * <code> keyTab</code>=< keytab filename >
274 * <code>principal</code> = < principal name >
275 * <code>doNotPrompt</code>=true;
276 *</ul>
277 * <p> Search the cache for the principal's TGT. If it is not available
278 * use the key in the keytab to perform authentication exchange with the
279 * KDC and acquire the TGT.
280 * The Subject will be populated with the principal and the TGT.
281 * If the key is not available or valid then authentication will fail.
282 * <ul>
283 * <p><code>useTicketCache</code> = true
284 * <code>ticketCache</code>=< file name >
285 *</ul>
286 * <p> The TGT will be obtained from the cache specified.
287 * The Kerberos principal name used will be the principal name in
288 * the Ticket cache. If the TGT is not available in the
289 * ticket cache the user will be prompted for the principal name
290 * and the password. The TGT will be obtained using the authentication
291 * exchange with the KDC.
292 * The Subject will be populated with the TGT.
293 *<ul>
294 * <p> <code>useKeyTab</code> = true
295 * <code>keyTab</code>=< keytab filename >
296 * <code>principal</code>= < principal name >
297 * <code>storeKey</code>=true;
298 *</ul>
299 * <p> The key for the principal will be retrieved from the keytab.
300 * If the key is not available in the keytab the user will be prompted
301 * for the principal's password. The Subject will be populated
302 * with the principal's key either from the keytab or derived from the
303 * password entered.
304 * <ul>
305 * <p> <code>useKeyTab</code> = true
306 * <code>keyTab</code>=< keytabname >
307 * <code>storeKey</code>=true
308 * <code>doNotPrompt</code>=true;
309 *</ul>
310 * <p>The user will be prompted for the service principal name.
311 * If the principal's
312 * longterm key is available in the keytab , it will be added to the
313 * Subject's private credentials. An authentication exchange will be
314 * attempted with the principal name and the key from the Keytab.
315 * If successful the TGT will be added to the
316 * Subject's private credentials set. Otherwise the authentication will
317 * fail.
318 *<ul>
319 * <p><code>useKeyTab</code> = true
320 * <code>keyTab</code>=< file name > <code>storeKey</code>=true
321 * <code>principal</code>= < principal name >
322 * <code>useTicketCache</code>=true
323 * <code>ticketCache</code>=< file name >;
324 *</ul>
325 * <p>The principal's key will be retrieved from the keytab and added
326 * to the <code>Subject</code>'s private credentials. If the key
327 * is not available, the
328 * user will be prompted for the password; the key derived from the password
329 * will be added to the Subject's private credentials set. The
330 * client's TGT will be retrieved from the ticket cache and added to the
331 * <code>Subject</code>'s private credentials. If the TGT is not available
332 * in the ticket cache, it will be obtained using the authentication
333 * exchange and added to the Subject's private credentials.
334 * <ul>
335 * <p><code>isInitiator</code> = false
336 *</ul>
337 * <p>Configured to act as acceptor only, credentials are not acquired
338 * via AS exchange. For acceptors only, set this value to false.
339 * For initiators, do not set this value to false.
340 * <ul>
341 * <p><code>isInitiator</code> = true
342 *</ul>
343 * <p>Configured to act as initiator, credentials are acquired
344 * via AS exchange. For initiators, set this value to true, or leave this
345 * option unset, in which case default value (true) will be used.
346 *
347 * @author Ram Marti
348 */
349
350public class Krb5LoginModule implements LoginModule {
351
352 // initial state
353 private Subject subject;
354 private CallbackHandler callbackHandler;
355 private Map sharedState;
356 private Map<String, ?> options;
357
358 // configurable option
359 private boolean debug = false;
360 private boolean storeKey = false;
361 private boolean doNotPrompt = false;
362 private boolean useTicketCache = false;
363 private boolean useKeyTab = false;
364 private String ticketCacheName = null;
365 private String keyTabName = null;
366 private String princName = null;
367
368 private boolean useFirstPass = false;
369 private boolean tryFirstPass = false;
370 private boolean storePass = false;
371 private boolean clearPass = false;
372 private boolean refreshKrb5Config = false;
373 private boolean renewTGT = false;
374
375 // specify if initiator.
376 // perform authentication exchange if initiator
377 private boolean isInitiator = true;
378
379 // the authentication status
380 private boolean succeeded = false;
381 private boolean commitSucceeded = false;
382 private String username;
383 private EncryptionKey[] encKeys = null;
384 private Credentials cred = null;
385
386 private PrincipalName principal = null;
387 private KerberosPrincipal kerbClientPrinc = null;
388 private KerberosTicket kerbTicket = null;
389 private KerberosKey[] kerbKeys = null;
390 private StringBuffer krb5PrincName = null;
391 private char[] password = null;
392
393 private static final String NAME = "javax.security.auth.login.name";
394 private static final String PWD = "javax.security.auth.login.password";
395 static final java.util.ResourceBundle rb =
396 java.util.ResourceBundle.getBundle("sun.security.util.AuthResources");
397
398 /**
399 * Initialize this <code>LoginModule</code>.
400 *
401 * <p>
402 * @param subject the <code>Subject</code> to be authenticated. <p>
403 *
404 * @param callbackHandler a <code>CallbackHandler</code> for
405 * communication with the end user (prompting for
406 * usernames and passwords, for example). <p>
407 *
408 * @param sharedState shared <code>LoginModule</code> state. <p>
409 *
410 * @param options options specified in the login
411 * <code>Configuration</code> for this particular
412 * <code>LoginModule</code>.
413 */
414
415 public void initialize(Subject subject,
416 CallbackHandler callbackHandler,
417 Map<String, ?> sharedState,
418 Map<String, ?> options) {
419
420 this.subject = subject;
421 this.callbackHandler = callbackHandler;
422 this.sharedState = sharedState;
423 this.options = options;
424
425 // initialize any configured options
426
427 debug = "true".equalsIgnoreCase((String)options.get("debug"));
428 storeKey = "true".equalsIgnoreCase((String)options.get("storeKey"));
429 doNotPrompt = "true".equalsIgnoreCase((String)options.get
430 ("doNotPrompt"));
431 useTicketCache = "true".equalsIgnoreCase((String)options.get
432 ("useTicketCache"));
433 useKeyTab = "true".equalsIgnoreCase((String)options.get("useKeyTab"));
434 ticketCacheName = (String)options.get("ticketCache");
435 keyTabName = (String)options.get("keyTab");
436 princName = (String)options.get("principal");
437 refreshKrb5Config =
438 "true".equalsIgnoreCase((String)options.get("refreshKrb5Config"));
439 renewTGT =
440 "true".equalsIgnoreCase((String)options.get("renewTGT"));
441
442 // check isInitiator value
443 String isInitiatorValue = ((String)options.get("isInitiator"));
444 if (isInitiatorValue == null) {
445 // use default, if value not set
446 } else {
447 isInitiator = "true".equalsIgnoreCase(isInitiatorValue);
448 }
449
450 tryFirstPass =
451 "true".equalsIgnoreCase
452 ((String)options.get("tryFirstPass"));
453 useFirstPass =
454 "true".equalsIgnoreCase
455 ((String)options.get("useFirstPass"));
456 storePass =
457 "true".equalsIgnoreCase((String)options.get("storePass"));
458 clearPass =
459 "true".equalsIgnoreCase((String)options.get("clearPass"));
460 if (debug) {
461 System.out.print("Debug is " + debug
462 + " storeKey " + storeKey
463 + " useTicketCache " + useTicketCache
464 + " useKeyTab " + useKeyTab
465 + " doNotPrompt " + doNotPrompt
466 + " ticketCache is " + ticketCacheName
467 + " isInitiator " + isInitiator
468 + " KeyTab is " + keyTabName
469 + " refreshKrb5Config is " + refreshKrb5Config
470 + " principal is " + princName
471 + " tryFirstPass is " + tryFirstPass
472 + " useFirstPass is " + useFirstPass
473 + " storePass is " + storePass
474 + " clearPass is " + clearPass + "\n");
475 }
476 }
477
478
479 /**
480 * Authenticate the user
481 *
482 * <p>
483 *
484 * @return true in all cases since this <code>LoginModule</code>
485 * should not be ignored.
486 *
487 * @exception FailedLoginException if the authentication fails. <p>
488 *
489 * @exception LoginException if this <code>LoginModule</code>
490 * is unable to perform the authentication.
491 */
492 public boolean login() throws LoginException {
493
494 int len;
495 validateConfiguration();
496 if (refreshKrb5Config) {
497 try {
498 if (debug) {
499 System.out.println("Refreshing Kerberos configuration");
500 }
501 sun.security.krb5.Config.refresh();
502 } catch (KrbException ke) {
503 LoginException le = new LoginException(ke.getMessage());
504 le.initCause(ke);
505 throw le;
506 }
507 }
508 String principalProperty = System.getProperty
509 ("sun.security.krb5.principal");
510 if (principalProperty != null) {
511 krb5PrincName = new StringBuffer(principalProperty);
512 } else {
513 if (princName != null) {
514 krb5PrincName = new StringBuffer(princName);
515 }
516 }
517
518 if (tryFirstPass) {
519 try {
520 attemptAuthentication(true);
521 if (debug)
522 System.out.println("\t\t[Krb5LoginModule] " +
523 "authentication succeeded");
524 succeeded = true;
525 cleanState();
526 return true;
527 } catch (LoginException le) {
528 // authentication failed -- try again below by prompting
529 cleanState();
530 if (debug) {
531 System.out.println("\t\t[Krb5LoginModule] " +
532 "tryFirstPass failed with:" +
533 le.getMessage());
534 }
535 }
536 } else if (useFirstPass) {
537 try {
538 attemptAuthentication(true);
539 succeeded = true;
540 cleanState();
541 return true;
542 } catch (LoginException e) {
543 // authentication failed -- clean out state
544 if (debug) {
545 System.out.println("\t\t[Krb5LoginModule] " +
546 "authentication failed \n" +
547 e.getMessage());
548 }
549 succeeded = false;
550 cleanState();
551 throw e;
552 }
553 }
554
555 // attempt the authentication by getting the username and pwd
556 // by prompting or configuration i.e. not from shared state
557
558 try {
559 attemptAuthentication(false);
560 succeeded = true;
561 cleanState();
562 return true;
563 } catch (LoginException e) {
564 // authentication failed -- clean out state
565 if (debug) {
566 System.out.println("\t\t[Krb5LoginModule] " +
567 "authentication failed \n" +
568 e.getMessage());
569 }
570 succeeded = false;
571 cleanState();
572 throw e;
573 }
574 }
575 /**
576 * process the configuration options
577 * Get the TGT either out of
578 * cache or from the KDC using the password entered
579 * Check the permission before getting the TGT
580 */
581
582 private void attemptAuthentication(boolean getPasswdFromSharedState)
583 throws LoginException {
584
585 /*
586 * Check the creds cache to see whether
587 * we have TGT for this client principal
588 */
589 if (krb5PrincName != null) {
590 try {
591 principal = new PrincipalName
592 (krb5PrincName.toString(),
593 PrincipalName.KRB_NT_PRINCIPAL);
594 } catch (KrbException e) {
595 LoginException le = new LoginException(e.getMessage());
596 le.initCause(e);
597 throw le;
598 }
599 }
600
601 try {
602 if (useTicketCache) {
603 // ticketCacheName == null implies the default cache
604 if (debug)
605 System.out.println("Acquire TGT from Cache");
606 cred = Credentials.acquireTGTFromCache
607 (principal, ticketCacheName);
608
609 if (cred != null) {
610 // check to renew credentials
611 if (!isCurrent(cred)) {
612 if (renewTGT) {
613 cred = renewCredentials(cred);
614 } else {
615 // credentials have expired
616 cred = null;
617 if (debug)
618 System.out.println("Credentials are" +
619 " no longer valid");
620 }
621 }
622 }
623
624 if (cred != null) {
625 // get the principal name from the ticket cache
626 if (principal == null) {
627 principal = cred.getClient();
628 }
629 }
630 if (debug) {
631 System.out.println("Principal is " + principal);
632 if (cred == null) {
633 System.out.println
634 ("null credentials from Ticket Cache");
635 }
636 }
637 }
638
639 // cred = null indicates that we didn't get the creds
640 // from the cache or useTicketCache was false
641
642 if (cred == null) {
643 // We need the principal name whether we use keytab
644 // or AS Exchange
645 if (principal == null) {
646 promptForName(getPasswdFromSharedState);
647 principal = new PrincipalName
648 (krb5PrincName.toString(),
649 PrincipalName.KRB_NT_PRINCIPAL);
650 }
651 if (useKeyTab) {
652 encKeys =
653 EncryptionKey.acquireSecretKeys(principal, keyTabName);
654
655 if (debug) {
656 if (encKeys != null)
657 System.out.println
658 ("principal's key obtained from the keytab");
659 else
660 System.out.println
661 ("Key for the principal " +
662 principal +
663 " not available in " +
664 ((keyTabName == null) ?
665 "default key tab" : keyTabName));
666 }
667
668 }
669 // We can't get the key from the keytab so prompt
670 if (encKeys == null) {
671 promptForPass(getPasswdFromSharedState);
672
673 encKeys = EncryptionKey.acquireSecretKeys(
674 password, principal.getSalt());
675
676 if (isInitiator) {
677 if (debug)
678 System.out.println("Acquire TGT using AS Exchange");
679 cred = Credentials.acquireTGT(principal,
680 encKeys, password);
681 // update keys after pre-auth
682 encKeys = EncryptionKey.acquireSecretKeys(password,
683 principal.getSalt());
684 }
685 } else {
686 if (isInitiator) {
687 if (debug)
688 System.out.println("Acquire TGT using AS Exchange");
689 cred = Credentials.acquireTGT(principal,
690 encKeys, password);
691 }
692 }
693
694 // Get the TGT using AS Exchange
695 if (debug) {
696 System.out.println("principal is " + principal);
697 HexDumpEncoder hd = new HexDumpEncoder();
698 for (int i = 0; i < encKeys.length; i++) {
699 System.out.println("EncryptionKey: keyType=" +
700 encKeys[i].getEType() + " keyBytes (hex dump)=" +
701 hd.encode(encKeys[i].getBytes()));
702 }
703 }
704
705 // we should hava a non-null cred
706 if (isInitiator && (cred == null)) {
707 throw new LoginException
708 ("TGT Can not be obtained from the KDC ");
709 }
710
711 }
712 } catch (KrbException e) {
713 LoginException le = new LoginException(e.getMessage());
714 le.initCause(e);
715 throw le;
716 } catch (IOException ioe) {
717 LoginException ie = new LoginException(ioe.getMessage());
718 ie.initCause(ioe);
719 throw ie;
720 }
721 }
722
723 private void promptForName(boolean getPasswdFromSharedState)
724 throws LoginException {
725 krb5PrincName = new StringBuffer("");
726 if (getPasswdFromSharedState) {
727 // use the name saved by the first module in the stack
728 username = (String)sharedState.get(NAME);
729 if (debug) {
730 System.out.println
731 ("username from shared state is " + username + "\n");
732 }
733 if (username == null) {
734 System.out.println
735 ("username from shared state is null\n");
736 throw new LoginException
737 ("Username can not be obtained from sharedstate ");
738 }
739 if (debug) {
740 System.out.println
741 ("username from shared state is " + username + "\n");
742 }
743 if (username != null && username.length() > 0) {
744 krb5PrincName.insert(0, username);
745 return;
746 }
747 }
748
749 if (doNotPrompt) {
750 throw new LoginException
751 ("Unable to obtain Princpal Name for authentication ");
752 } else {
753 if (callbackHandler == null)
754 throw new LoginException("No CallbackHandler "
755 + "available "
756 + "to garner authentication "
757 + "information from the user");
758 try {
759 String defUsername = System.getProperty("user.name");
760
761 Callback[] callbacks = new Callback[1];
762 MessageFormat form = new MessageFormat(
763 rb.getString(
764 "Kerberos username [[defUsername]]: "));
765 Object[] source = {defUsername};
766 callbacks[0] = new NameCallback(form.format(source));
767 callbackHandler.handle(callbacks);
768 username = ((NameCallback)callbacks[0]).getName();
769 if (username == null || username.length() == 0)
770 username = defUsername;
771 krb5PrincName.insert(0, username);
772
773 } catch (java.io.IOException ioe) {
774 throw new LoginException(ioe.getMessage());
775 } catch (UnsupportedCallbackException uce) {
776 throw new LoginException
777 (uce.getMessage()
778 +" not available to garner "
779 +" authentication information "
780 +" from the user");
781 }
782 }
783 }
784
785 private void promptForPass(boolean getPasswdFromSharedState)
786 throws LoginException {
787
788 if (getPasswdFromSharedState) {
789 // use the password saved by the first module in the stack
790 password = (char[])sharedState.get(PWD);
791 if (password == null) {
792 if (debug) {
793 System.out.println
794 ("Password from shared state is null");
795 }
796 throw new LoginException
797 ("Password can not be obtained from sharedstate ");
798 }
799 if (debug) {
800 System.out.println
801 ("password is " + new String(password));
802 }
803 return;
804 }
805 if (doNotPrompt) {
806 throw new LoginException
807 ("Unable to obtain password from user\n");
808 } else {
809 if (callbackHandler == null)
810 throw new LoginException("No CallbackHandler "
811 + "available "
812 + "to garner authentication "
813 + "information from the user");
814 try {
815 Callback[] callbacks = new Callback[1];
816 String userName = krb5PrincName.toString();
817 MessageFormat form = new MessageFormat(
818 rb.getString(
819 "Kerberos password for [username]: "));
820 Object[] source = {userName};
821 callbacks[0] = new PasswordCallback(
822 form.format(source),
823 false);
824 callbackHandler.handle(callbacks);
825 char[] tmpPassword = ((PasswordCallback)
826 callbacks[0]).getPassword();
827 if (tmpPassword == null) {
828 // treat a NULL password as an empty password
829 tmpPassword = new char[0];
830 }
831 password = new char[tmpPassword.length];
832 System.arraycopy(tmpPassword, 0,
833 password, 0, tmpPassword.length);
834 ((PasswordCallback)callbacks[0]).clearPassword();
835
836
837 // clear tmpPassword
838 for (int i = 0; i < tmpPassword.length; i++)
839 tmpPassword[i] = ' ';
840 tmpPassword = null;
841 if (debug) {
842 System.out.println("\t\t[Krb5LoginModule] " +
843 "user entered username: " +
844 krb5PrincName);
845 System.out.println();
846 }
847 } catch (java.io.IOException ioe) {
848 throw new LoginException(ioe.getMessage());
849 } catch (UnsupportedCallbackException uce) {
850 throw new LoginException(uce.getMessage()
851 +" not available to garner "
852 +" authentication information "
853 + "from the user");
854 }
855 }
856 }
857
858 private void validateConfiguration() throws LoginException {
859 if (doNotPrompt && !useTicketCache && !useKeyTab)
860 throw new LoginException
861 ("Configuration Error"
862 + " - either doNotPrompt should be "
863 + " false or useTicketCache/useKeyTab "
864 + " should be true");
865 if (ticketCacheName != null && !useTicketCache)
866 throw new LoginException
867 ("Configuration Error "
868 + " - useTicketCache should be set "
869 + "to true to use the ticket cache"
870 + ticketCacheName);
871 if (keyTabName != null & !useKeyTab)
872 throw new LoginException
873 ("Configuration Error - useKeyTab should be set to true "
874 + "to use the keytab" + keyTabName);
875 if (storeKey && doNotPrompt && !useKeyTab)
876 throw new LoginException
877 ("Configuration Error - either doNotPrompt "
878 + "should be set to false or "
879 + "useKeyTab must be set to true for storeKey option");
880 if (renewTGT && !useTicketCache)
881 throw new LoginException
882 ("Configuration Error"
883 + " - either useTicketCache should be "
884 + " true or renewTGT should be false");
885 }
886
887 private boolean isCurrent(Credentials creds)
888 {
889 Date endTime = creds.getEndTime();
890 if (endTime != null) {
891 return (System.currentTimeMillis() <= endTime.getTime());
892 }
893 return true;
894 }
895
896 private Credentials renewCredentials(Credentials creds)
897 {
898 Credentials lcreds;
899 try {
900 if (!creds.isRenewable())
901 throw new RefreshFailedException("This ticket" +
902 " is not renewable");
903 if (System.currentTimeMillis() > cred.getRenewTill().getTime())
904 throw new RefreshFailedException("This ticket is past "
905 + "its last renewal time.");
906 lcreds = creds.renew();
907 if (debug)
908 System.out.println("Renewed Kerberos Ticket");
909 } catch (Exception e) {
910 lcreds = null;
911 if (debug)
912 System.out.println("Ticket could not be renewed : "
913 + e.getMessage());
914 }
915 return lcreds;
916 }
917
918 /**
919 * <p> This method is called if the LoginContext's
920 * overall authentication succeeded
921 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
922 * LoginModules succeeded).
923 *
924 * <p> If this LoginModule's own authentication attempt
925 * succeeded (checked by retrieving the private state saved by the
926 * <code>login</code> method), then this method associates a
927 * <code>Krb5Principal</code>
928 * with the <code>Subject</code> located in the
929 * <code>LoginModule</code>. It adds Kerberos Credentials to the
930 * the Subject's private credentials set. If this LoginModule's own
931 * authentication attempted failed, then this method removes
932 * any state that was originally saved.
933 *
934 * <p>
935 *
936 * @exception LoginException if the commit fails.
937 *
938 * @return true if this LoginModule's own login and commit
939 * attempts succeeded, or false otherwise.
940 */
941
942 public boolean commit() throws LoginException {
943
944 /*
945 * Let us add the Krb5 Creds to the Subject's
946 * private credentials. The credentials are of type
947 * KerberosKey or KerberosTicket
948 */
949 if (succeeded == false) {
950 return false;
951 } else {
952
953 if (isInitiator && (cred == null)) {
954 succeeded = false;
955 throw new LoginException("Null Client Credential");
956 }
957
958 if (subject.isReadOnly()) {
959 cleanKerberosCred();
960 throw new LoginException("Subject is Readonly");
961 }
962
963 /*
964 * Add the Principal (authenticated identity)
965 * to the Subject's principal set and
966 * add the credentials (TGT or Service key) to the
967 * Subject's private credentials
968 */
969
970 Set<Object> privCredSet = subject.getPrivateCredentials();
971 Set<java.security.Principal> princSet = subject.getPrincipals();
972 kerbClientPrinc = new KerberosPrincipal(principal.getName());
973
974 // create Kerberos Ticket
975 if (isInitiator) {
976 kerbTicket = Krb5Util.credsToTicket(cred);
977 }
978
979 if (storeKey) {
980 if (encKeys == null || encKeys.length <= 0) {
981 succeeded = false;
982 throw new LoginException("Null Server Key ");
983 }
984
985 kerbKeys = new KerberosKey[encKeys.length];
986 for (int i = 0; i < encKeys.length; i ++) {
987 Integer temp = encKeys[i].getKeyVersionNumber();
988 kerbKeys[i] = new KerberosKey(kerbClientPrinc,
989 encKeys[i].getBytes(),
990 encKeys[i].getEType(),
991 (temp == null?
992 0: temp.intValue()));
993 }
994
995 }
996 // Let us add the kerbClientPrinc,kerbTicket and kerbKey (if
997 // storeKey is true)
998 if (!princSet.contains(kerbClientPrinc))
999 princSet.add(kerbClientPrinc);
1000
1001 // add the TGT
1002 if (kerbTicket != null) {
1003 if (!privCredSet.contains(kerbTicket))
1004 privCredSet.add(kerbTicket);
1005 }
1006
1007 if (storeKey) {
1008 for (int i = 0; i < kerbKeys.length; i++) {
1009 if (!privCredSet.contains(kerbKeys[i])) {
1010 privCredSet.add(kerbKeys[i]);
1011 }
1012 encKeys[i].destroy();
1013 encKeys[i] = null;
1014 if (debug) {
1015 System.out.println("Added server's key"
1016 + kerbKeys[i]);
1017 System.out.println("\t\t[Krb5LoginModule] " +
1018 "added Krb5Principal " +
1019 kerbClientPrinc.toString()
1020 + " to Subject");
1021 }
1022 }
1023 }
1024 }
1025 commitSucceeded = true;
1026 if (debug)
1027 System.out.println("Commit Succeeded \n");
1028 return true;
1029 }
1030
1031 /**
1032 * <p> This method is called if the LoginContext's
1033 * overall authentication failed.
1034 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
1035 * LoginModules did not succeed).
1036 *
1037 * <p> If this LoginModule's own authentication attempt
1038 * succeeded (checked by retrieving the private state saved by the
1039 * <code>login</code> and <code>commit</code> methods),
1040 * then this method cleans up any state that was originally saved.
1041 *
1042 * <p>
1043 *
1044 * @exception LoginException if the abort fails.
1045 *
1046 * @return false if this LoginModule's own login and/or commit attempts
1047 * failed, and true otherwise.
1048 */
1049
1050 public boolean abort() throws LoginException {
1051 if (succeeded == false) {
1052 return false;
1053 } else if (succeeded == true && commitSucceeded == false) {
1054 // login succeeded but overall authentication failed
1055 succeeded = false;
1056 cleanKerberosCred();
1057 } else {
1058 // overall authentication succeeded and commit succeeded,
1059 // but someone else's commit failed
1060 logout();
1061 }
1062 return true;
1063 }
1064
1065 /**
1066 * Logout the user.
1067 *
1068 * <p> This method removes the <code>Krb5Principal</code>
1069 * that was added by the <code>commit</code> method.
1070 *
1071 * <p>
1072 *
1073 * @exception LoginException if the logout fails.
1074 *
1075 * @return true in all cases since this <code>LoginModule</code>
1076 * should not be ignored.
1077 */
1078 public boolean logout() throws LoginException {
1079
1080 if (debug) {
1081 System.out.println("\t\t[Krb5LoginModule]: " +
1082 "Entering logout");
1083 }
1084
1085 if (subject.isReadOnly()) {
1086 cleanKerberosCred();
1087 throw new LoginException("Subject is Readonly");
1088 }
1089
1090 subject.getPrincipals().remove(kerbClientPrinc);
1091 // Let us remove all Kerberos credentials stored in the Subject
1092 Iterator<Object> it = subject.getPrivateCredentials().iterator();
1093 while (it.hasNext()) {
1094 Object o = it.next();
1095 if (o instanceof KerberosTicket ||
1096 o instanceof KerberosKey) {
1097 it.remove();
1098 }
1099 }
1100 // clean the kerberos ticket and keys
1101 cleanKerberosCred();
1102
1103 succeeded = false;
1104 commitSucceeded = false;
1105 if (debug) {
1106 System.out.println("\t\t[Krb5LoginModule]: " +
1107 "logged out Subject");
1108 }
1109 return true;
1110 }
1111
1112 /**
1113 * Clean Kerberos credentials
1114 */
1115 private void cleanKerberosCred() throws LoginException {
1116 // Clean the ticket and server key
1117 try {
1118 if (kerbTicket != null)
1119 kerbTicket.destroy();
1120 if (kerbKeys != null) {
1121 for (int i = 0; i < kerbKeys.length; i++) {
1122 kerbKeys[i].destroy();
1123 }
1124 }
1125 } catch (DestroyFailedException e) {
1126 throw new LoginException
1127 ("Destroy Failed on Kerberos Private Credentials");
1128 }
1129 kerbTicket = null;
1130 kerbKeys = null;
1131 kerbClientPrinc = null;
1132 }
1133
1134 /**
1135 * Clean out the state
1136 */
1137 private void cleanState() {
1138
1139 // save input as shared state only if
1140 // authentication succeeded
1141 if (succeeded) {
1142 if (storePass &&
1143 !sharedState.containsKey(NAME) &&
1144 !sharedState.containsKey(PWD)) {
1145 sharedState.put(NAME, username);
1146 sharedState.put(PWD, password);
1147 }
1148 }
1149 username = null;
1150 password = null;
1151 if (krb5PrincName != null && krb5PrincName.length() != 0)
1152 krb5PrincName.delete(0, krb5PrincName.length());
1153 krb5PrincName = null;
1154 if (clearPass) {
1155 sharedState.remove(NAME);
1156 sharedState.remove(PWD);
1157 }
1158 }
1159}