提交 1f73cdd5 编写于 作者: W weijun

6765491: Krb5LoginModule a little too restrictive, and the doc is not clear.

Reviewed-by: valeriep
上级 fa1cfe5f
......@@ -86,6 +86,8 @@ import sun.misc.HexDumpEncoder;
* the principal name from the configuration is used. In the
* case where the principal property is not set and the principal
* entry also does not exist, the user is prompted for the name.
* When this property of entry is set, and <code>useTicketCache</code>
* is set to true, only TGT belonging to this principal is used.
*
* <p> The following is a list of configuration options supported
* for <code>Krb5LoginModule</code>:
......@@ -101,18 +103,19 @@ import sun.misc.HexDumpEncoder;
* to false if you do not want this module to use the ticket cache.
* (Default is False).
* This module will
* search for the tickect
* search for the ticket
* cache in the following locations:
* For Windows 2000, it will use Local Security Authority (LSA) API
* to get the TGT. On Solaris and Linux
* On Solaris and Linux
* it will look for the ticket cache in /tmp/krb5cc_<code>uid</code>
* where the uid is numeric user
* identifier. If the ticket cache is
* not available in either of the above locations, or if we are on a
* different Windows platform, it will look for the cache as
* not available in the above location, or if we are on a
* Windows platform, it will look for the cache as
* {user.home}{file.separator}krb5cc_{user.name}.
* You can override the ticket cache location by using
* <code>ticketCache</code>
* <code>ticketCache</code>.
* For Windows, if a ticket cannot be retrieved from the file ticket cache,
* it will use Local Security Authority (LSA) API to get the TGT.
* <P>
* <dt><b><code>ticketCache</code></b>:</dt>
* <dd>Set this to the name of the ticket
......@@ -129,20 +132,20 @@ import sun.misc.HexDumpEncoder;
* <dt><b><code>doNotPrompt</code></b>:</dt>
* <dd>Set this to true if you do not want to be
* prompted for the password
* if credentials can
* not be obtained from the cache or keytab.(Default is false)
* If set to true authentication will fail if credentials can
* not be obtained from the cache or keytab.</dd>
* if credentials can not be obtained from the cache, the keytab,
* or through shared state.(Default is false)
* If set to true, credential must be obtained through cache, keytab,
* or shared state. Otherwise, authentication will fail.</dd>
* <P>
* <dt><b><code>useKeyTab</code></b>:</dt>
* <dd>Set this to true if you
* want the module to get the principal's key from the
* the keytab.(default value is False)
* If <code>keyatb</code>
* If <code>keytab</code>
* is not set then
* the module will locate the keytab from the
* Kerberos configuration file.</dd>
* If it is not specifed in the Kerberos configuration file
* Kerberos configuration file.
* If it is not specified in the Kerberos configuration file
* then it will look for the file
* <code>{user.home}{file.separator}</code>krb5.keytab.</dd>
* <P>
......@@ -210,20 +213,34 @@ import sun.misc.HexDumpEncoder;
* exist for the username and password in the shared
* state, or if authentication fails.
*
* clearPass if, true, this <code>LoginModule</code> clears the
* username and password stored in the module's shared
* state after both phases of authentication
* (login and commit) have completed.
* clearPass if, true, this LoginModule clears the
* username and password stored in the module's shared
* state after both phases of authentication
* (login and commit) have completed.
* </pre>
* <p>If the principal system property or key is already provided, the value of
* "javax.security.auth.login.name" in the shared state is ignored.
* <p>When multiple mechanisms to retrieve a ticket or key is provided, the
* preference order looks like this:
* <ol>
* <li>ticket cache
* <li>keytab
* <li>shared state
* <li>user prompt
* </ol>
* <p>Note that if any step fails, it will fallback to the next step.
* There's only one exception, it the shared state step fails and
* <code>useFirstPass</code>=true, no user prompt is made.
* <p>Examples of some configuration values for Krb5LoginModule in
* JAAS config file and the results are:
* <ul>
* <p> <code>doNotPrompt</code>=true;
* </ul>
* <p> This is an illegal combination since <code>useTicketCache</code>
* is not set and the user can not be prompted for the password.
* <p> This is an illegal combination since none of <code>useTicketCache</code>,
* <code>useKeyTab</code>, <code>useFirstPass</code> and <code>tryFirstPass</code>
* is set and the user can not be prompted for the password.
*<ul>
* <p> <code>ticketCache</code> = < filename >;
* <p> <code>ticketCache</code> = &lt;filename&gt;;
*</ul>
* <p> This is an illegal combination since <code>useTicketCache</code>
* is not set to true and the ticketCache is set. A configuration error
......@@ -240,9 +257,9 @@ import sun.misc.HexDumpEncoder;
*</ul>
* <p> This is an illegal combination since <code>storeKey</code> is set to
* true but the key can not be obtained either by prompting the user or from
* the keytab.A configuration error will occur.
* the keytab, or from the shared state. A configuration error will occur.
* <ul>
* <p> <code>keyTab</code> = < filename > <code>doNotPrompt</code>=true ;
* <p> <code>keyTab</code> = &lt;filename&gt; <code>doNotPrompt</code>=true ;
* </ul>
* <p>This is an illegal combination since useKeyTab is not set to true and
* the keyTab is set. A configuration error will occur.
......@@ -260,7 +277,7 @@ import sun.misc.HexDumpEncoder;
* with the principal and TGT. If the TGT is not available,
* do not prompt the user, instead fail the authentication.
* <ul>
* <p><code>principal</code>=< name ><code>useTicketCache</code> = true
* <p><code>principal</code>=&lt;name&gt;<code>useTicketCache</code> = true
* <code>doNotPrompt</code>=true;
*</ul>
* <p> Get the TGT from the default cache for the principal and populate the
......@@ -269,9 +286,9 @@ import sun.misc.HexDumpEncoder;
* authentication will fail.
* <ul>
* <p> <code>useTicketCache</code> = true
* <code>ticketCache</code>=< file name ><code>useKeyTab</code> = true
* <code> keyTab</code>=< keytab filename >
* <code>principal</code> = < principal name >
* <code>ticketCache</code>=&lt;file name&gt;<code>useKeyTab</code> = true
* <code> keyTab</code>=&lt;keytab filename&gt;
* <code>principal</code> = &lt;principal name&gt;
* <code>doNotPrompt</code>=true;
*</ul>
* <p> Search the cache for the principal's TGT. If it is not available
......@@ -281,7 +298,7 @@ import sun.misc.HexDumpEncoder;
* If the key is not available or valid then authentication will fail.
* <ul>
* <p><code>useTicketCache</code> = true
* <code>ticketCache</code>=< file name >
* <code>ticketCache</code>=&lt;file name&gt;
*</ul>
* <p> The TGT will be obtained from the cache specified.
* The Kerberos principal name used will be the principal name in
......@@ -292,8 +309,8 @@ import sun.misc.HexDumpEncoder;
* The Subject will be populated with the TGT.
*<ul>
* <p> <code>useKeyTab</code> = true
* <code>keyTab</code>=< keytab filename >
* <code>principal</code>= < principal name >
* <code>keyTab</code>=&lt;keytab filename&gt;
* <code>principal</code>= &lt;principal name&gt;
* <code>storeKey</code>=true;
*</ul>
* <p> The key for the principal will be retrieved from the keytab.
......@@ -303,7 +320,7 @@ import sun.misc.HexDumpEncoder;
* password entered.
* <ul>
* <p> <code>useKeyTab</code> = true
* <code>keyTab</code>=< keytabname >
* <code>keyTab</code>=&lt;keytabname&gt;
* <code>storeKey</code>=true
* <code>doNotPrompt</code>=true;
*</ul>
......@@ -316,21 +333,23 @@ import sun.misc.HexDumpEncoder;
* Subject's private credentials set. Otherwise the authentication will
* fail.
*<ul>
* <p><code>useKeyTab</code> = true
* <code>keyTab</code>=< file name > <code>storeKey</code>=true
* <code>principal</code>= < principal name >
* <p>
* <code>useTicketCache</code>=true
* <code>ticketCache</code>=< file name >;
* <code>ticketCache</code>=&lt;file name&gt;;
* <code>useKeyTab</code> = true
* <code>keyTab</code>=&lt;file name&gt; <code>storeKey</code>=true
* <code>principal</code>= &lt;principal name&gt;
*</ul>
* <p>The principal's key will be retrieved from the keytab and added
* to the <code>Subject</code>'s private credentials. If the key
* is not available, the
* user will be prompted for the password; the key derived from the password
* will be added to the Subject's private credentials set. The
* client's TGT will be retrieved from the ticket cache and added to the
* <p>
* The client's TGT will be retrieved from the ticket cache and added to the
* <code>Subject</code>'s private credentials. If the TGT is not available
* in the ticket cache, it will be obtained using the authentication
* in the ticket cache, or the TGT's client name does not match the principal
* name, Java will use a secret key to obtain the TGT using the authentication
* exchange and added to the Subject's private credentials.
* This secret key will be first retrieved from the keytab. If the key
* is not available, the user will be prompted for the password. In either
* case, the key derived from the password will be added to the
* Subject's private credentials set.
* <ul>
* <p><code>isInitiator</code> = false
*</ul>
......@@ -856,11 +875,13 @@ public class Krb5LoginModule implements LoginModule {
}
private void validateConfiguration() throws LoginException {
if (doNotPrompt && !useTicketCache && !useKeyTab)
if (doNotPrompt && !useTicketCache && !useKeyTab
&& !tryFirstPass && !useFirstPass)
throw new LoginException
("Configuration Error"
+ " - either doNotPrompt should be "
+ " false or useTicketCache/useKeyTab "
+ " false or at least one of useTicketCache, "
+ " useKeyTab, tryFirstPass and useFirstPass"
+ " should be true");
if (ticketCacheName != null && !useTicketCache)
throw new LoginException
......@@ -872,11 +893,12 @@ public class Krb5LoginModule implements LoginModule {
throw new LoginException
("Configuration Error - useKeyTab should be set to true "
+ "to use the keytab" + keyTabName);
if (storeKey && doNotPrompt && !useKeyTab)
if (storeKey && doNotPrompt && !useKeyTab
&& !tryFirstPass && !useFirstPass)
throw new LoginException
("Configuration Error - either doNotPrompt "
+ "should be set to false or "
+ "useKeyTab must be set to true for storeKey option");
("Configuration Error - either doNotPrompt should be set to "
+ " false or at least one of tryFirstPass, useFirstPass "
+ "or useKeyTab must be set to true for storeKey option");
if (renewTGT && !useTicketCache)
throw new LoginException
("Configuration Error"
......
......@@ -109,13 +109,22 @@ public class Context {
out.s = new Subject();
Krb5LoginModule krb5 = new Krb5LoginModule();
Map<String, String> map = new HashMap<String, String>();
map.put("tryFirstPass", "true");
Map<String, Object> shared = new HashMap<String, Object>();
if (pass != null) {
map.put("useFirstPass", "true");
shared.put("javax.security.auth.login.name", user);
shared.put("javax.security.auth.login.password", pass);
} else {
map.put("doNotPrompt", "true");
map.put("useTicketCache", "true");
if (user != null) {
map.put("principal", user);
}
}
if (storeKey) {
map.put("storeKey", "true");
}
Map<String, Object> shared = new HashMap<String, Object>();
shared.put("javax.security.auth.login.name", user);
shared.put("javax.security.auth.login.password", pass);
krb5.initialize(out.s, null, shared, map);
krb5.login();
......
......@@ -32,6 +32,7 @@ import java.util.*;
import java.util.concurrent.*;
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.CredentialsCache;
import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.krb5.internal.ktab.KeyTab;
import sun.security.util.DerInputStream;
......@@ -765,7 +766,29 @@ public class KDC {
DerOutputStream out = new DerOutputStream();
out.write(DerValue.createTag(DerValue.TAG_APPLICATION,
true, (byte)Krb5.KRB_AS_REP), asRep.asn1Encode());
return out.toByteArray();
byte[] result = out.toByteArray();
// Added feature:
// Write the current issuing TGT into a ccache file specified
// by the system property below.
String ccache = System.getProperty("test.kdc.save.ccache");
if (ccache != null) {
asRep.encKDCRepPart = enc_part;
sun.security.krb5.internal.ccache.Credentials credentials =
new sun.security.krb5.internal.ccache.Credentials(asRep);
asReq.reqBody.cname.setRealm(getRealm());
CredentialsCache cache =
CredentialsCache.create(asReq.reqBody.cname, ccache);
if (cache == null) {
throw new IOException("Unable to create the cache file " +
ccache);
}
cache.update(credentials);
cache.save();
new File(ccache).deleteOnExit();
}
return result;
} catch (KrbException ke) {
ke.printStackTrace(System.out);
KRBError kerr = ke.getError();
......
/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/*
* @test
* @bug 6765491
* @summary Krb5LoginModule a little too restrictive, and the doc is not clear.
*/
import com.sun.security.auth.module.Krb5LoginModule;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
public class LoginModuleOptions {
private static final String NAME = "javax.security.auth.login.name";
private static final String PWD = "javax.security.auth.login.password";
public static void main(String[] args) throws Exception {
OneKDC kdc = new OneKDC(null);
kdc.addPrincipal("foo", "bar".toCharArray());
kdc.writeKtab(OneKDC.KTAB); // rewrite to add foo
// All 4 works: keytab, shared state, callback, cache
login(null, "useKeyTab", "true", "principal", "dummy");
login(null, "tryFirstPass", "true", NAME, OneKDC.USER,
PWD, OneKDC.PASS);
System.setProperty("test.kdc.save.ccache", "krbcc");
login(new MyCallback(OneKDC.USER, OneKDC.PASS)); // save the cache
System.clearProperty("test.kdc.save.ccache");
login(null, "useTicketCache", "true", "ticketCache", "krbcc");
// Fallbacks
// 1. ccache -> keytab
login(null, "useTicketCache", "true", "ticketCache", "krbcc_non_exists",
"useKeyTab", "true", "principal", "dummy");
// 2. keytab -> shared
login(null, "useKeyTab", "true", "principal", "dummy",
"keyTab", "ktab_non_exist",
"tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
// 3. shared -> callback
// 3.1. useFirstPass, no callback
boolean failed = false;
try {
login(new MyCallback(OneKDC.USER, OneKDC.PASS),
"useFirstPass", "true",
NAME, OneKDC.USER, PWD, "haha".toCharArray());
} catch (Exception e) {
failed = true;
}
if (!failed) {
throw new Exception("useFirstPass should not fallback to callback");
}
// 3.2. tryFirstPass, has callback
login(new MyCallback(OneKDC.USER, OneKDC.PASS),
"tryFirstPass", "true",
NAME, OneKDC.USER, PWD, "haha".toCharArray());
// Preferences of type
// 1. ccache preferred to keytab
login(new MyCallback("foo", null),
"useTicketCache", "true", "ticketCache", "krbcc",
"useKeyTab", "true");
// 2. keytab preferred to shared. This test case is not exactly correct,
// because principal=dummy would shadow the PWD setting in the shared
// state. So by only looking at the final authentication user name
// (which is how this program does), there's no way to tell if keyTab
// is picked first, or shared is tried first but fallback to keytab.
login(null, "useKeyTab", "true", "principal", "dummy",
"tryFirstPass", "true", NAME, "foo", PWD, "bar".toCharArray());
// 3. shared preferred to callback
login(new MyCallback("foo", "bar".toCharArray()),
"tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
// Preferences of username
// 1. principal preferred to NAME (NAME can be wrong or missing)
login(null, "principal", OneKDC.USER,
"tryFirstPass", "true", NAME, "someone_else", PWD, OneKDC.PASS);
login(null, "principal", OneKDC.USER,
"tryFirstPass", "true", PWD, OneKDC.PASS);
// 2. NAME preferred to callback
login(new MyCallback("someone_else", OneKDC.PASS),
"principal", OneKDC.USER);
// 3. With tryFirstPass, NAME preferred to callback
login(new MyCallback("someone_else", null),
"tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
// 3.1. you must provide a NAME (when there's no principal)
failed = false;
try {
login(new MyCallback(OneKDC.USER, null),
"tryFirstPass", "true", PWD, OneKDC.PASS);
} catch (Exception e) {
failed = true;
}
if (!failed) {
throw new Exception("useFirstPass must provide a NAME");
}
// 3.2 Hybrid, you can use NAME as "", and provide it using callback.
// I don't think this is designed.
login(new MyCallback(OneKDC.USER, null),
"tryFirstPass", "true", NAME, "", PWD, OneKDC.PASS);
// Test for the bug fix: doNotPrompt can be true if tryFirstPass=true
login(null, "doNotPrompt", "true", "storeKey", "true",
"tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
}
static void login(CallbackHandler callback, Object... options)
throws Exception {
Krb5LoginModule krb5 = new Krb5LoginModule();
Subject subject = new Subject();
Map<String, String> map = new HashMap<String, String>();
Map<String, Object> shared = new HashMap<String, Object>();
int count = options.length / 2;
for (int i = 0; i < count; i++) {
String key = (String) options[2 * i];
Object value = options[2 * i + 1];
if (key.startsWith("javax")) {
shared.put(key, value);
} else {
map.put(key, (String) value);
}
}
krb5.initialize(subject, callback, shared, map);
krb5.login();
krb5.commit();
if (!subject.getPrincipals().iterator().next()
.getName().startsWith(OneKDC.USER)) {
throw new Exception("The authenticated is not " + OneKDC.USER);
}
}
static class MyCallback implements CallbackHandler {
private String name;
private char[] password;
public MyCallback(String name, char[] password) {
this.name = name;
this.password = password;
}
public void handle(Callback[] callbacks) {
for (Callback callback : callbacks) {
System.err.println(callback);
if (callback instanceof NameCallback) {
System.err.println("name is " + name);
((NameCallback) callback).setName(name);
}
if (callback instanceof PasswordCallback) {
System.err.println("pass is " + new String(password));
((PasswordCallback) callback).setPassword(password);
}
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册