History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/com/sun/security/auth/module/Krb5LoginModule.java (1 of 18352 files). Show all

Unified · Split

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