Kerberos: synchronize access to clientLoginContext

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@2275 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2014-03-19 09:29:50 +00:00
parent d4efba3599
commit 31377bfb02
2 changed files with 91 additions and 73 deletions

View File

@ -29,7 +29,7 @@ import javax.security.auth.callback.*;
import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import java.awt.GraphicsEnvironment; import java.awt.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -44,7 +44,7 @@ public class KerberosHelper {
protected static final Logger LOGGER = Logger.getLogger(KerberosHelper.class); protected static final Logger LOGGER = Logger.getLogger(KerberosHelper.class);
protected static final Object LOCK = new Object(); protected static final Object LOCK = new Object();
protected static final KerberosCallbackHandler KERBEROS_CALLBACK_HANDLER; protected static final KerberosCallbackHandler KERBEROS_CALLBACK_HANDLER;
protected static LoginContext clientLoginContext; private static LoginContext clientLoginContext;
static { static {
// Load Jaas configuration from class // Load Jaas configuration from class
@ -53,25 +53,26 @@ public class KerberosHelper {
KERBEROS_CALLBACK_HANDLER = new KerberosCallbackHandler(); KERBEROS_CALLBACK_HANDLER = new KerberosCallbackHandler();
} }
private KerberosHelper() {
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
protected static class KerberosCallbackHandler implements CallbackHandler { protected static class KerberosCallbackHandler implements CallbackHandler {
String principal; String principal;
String password; String password;
protected KerberosCallbackHandler() {
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) { for (Callback callback : callbacks) {
if (callbacks[i] instanceof NameCallback) { if (callback instanceof NameCallback) {
if (principal == null) { if (principal == null) {
// if we get there kerberos token is missing or invalid // if we get there kerberos token is missing or invalid
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) { if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
// headless or server mode // headless or server mode
System.out.print(((NameCallback) callbacks[i]).getPrompt()); System.out.print(((NameCallback) callback).getPrompt());
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in)); BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
principal = inReader.readLine(); principal = inReader.readLine();
} else { } else {
CredentialPromptDialog credentialPromptDialog = new CredentialPromptDialog(((NameCallback) callbacks[i]).getPrompt()); CredentialPromptDialog credentialPromptDialog = new CredentialPromptDialog(((NameCallback) callback).getPrompt());
principal = credentialPromptDialog.getPrincipal(); principal = credentialPromptDialog.getPrincipal();
password = String.valueOf(credentialPromptDialog.getPassword()); password = String.valueOf(credentialPromptDialog.getPassword());
} }
@ -79,14 +80,14 @@ public class KerberosHelper {
if (principal == null) { if (principal == null) {
throw new IOException("KerberosCallbackHandler: failed to retrieve principal"); throw new IOException("KerberosCallbackHandler: failed to retrieve principal");
} }
((NameCallback) callbacks[i]).setName(principal); ((NameCallback) callback).setName(principal);
} else if (callbacks[i] instanceof PasswordCallback) { } else if (callback instanceof PasswordCallback) {
if (password == null) { if (password == null) {
// if we get there kerberos token is missing or invalid // if we get there kerberos token is missing or invalid
if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) { if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
// headless or server mode // headless or server mode
System.out.print(((PasswordCallback) callbacks[i]).getPrompt()); System.out.print(((PasswordCallback) callback).getPrompt());
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in)); BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
password = inReader.readLine(); password = inReader.readLine();
} }
@ -94,20 +95,30 @@ public class KerberosHelper {
if (password == null) { if (password == null) {
throw new IOException("KerberosCallbackHandler: failed to retrieve password"); throw new IOException("KerberosCallbackHandler: failed to retrieve password");
} }
((PasswordCallback) callbacks[i]).setPassword(password.toCharArray()); ((PasswordCallback) callback).setPassword(password.toCharArray());
} else { } else {
throw new UnsupportedCallbackException(callbacks[i]); throw new UnsupportedCallbackException(callback);
} }
} }
} }
} }
public static void setPrincipal(String principal) { /**
* Force client principal in callback handler
*
* @param principal client principal
*/
public static void setClientPrincipal(String principal) {
KERBEROS_CALLBACK_HANDLER.principal = principal; KERBEROS_CALLBACK_HANDLER.principal = principal;
} }
public static void setPassword(String password) { /**
* Force client password in callback handler
*
* @param password client password
*/
public static void setClientPassword(String password) {
KERBEROS_CALLBACK_HANDLER.password = password; KERBEROS_CALLBACK_HANDLER.password = password;
} }
@ -141,17 +152,19 @@ public class KerberosHelper {
public static byte[] initSecurityContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) throws GSSException, LoginException { public static byte[] initSecurityContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) throws GSSException, LoginException {
LOGGER.debug("KerberosHelper.initSecurityContext " + protocol + "/" + host + " " + token.length + " bytes token"); LOGGER.debug("KerberosHelper.initSecurityContext " + protocol + "/" + host + " " + token.length + " bytes token");
// create client login context synchronized (LOCK) {
clientLogin(); // create client login context
clientLogin();
Object result = internalInitSecContext(protocol, host, delegatedCredentials, token); Object result = internalInitSecContext(protocol, host, delegatedCredentials, token);
if (result instanceof GSSException) { if (result instanceof GSSException) {
LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((GSSException) result).getMessage()); LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
throw (GSSException) result; throw (GSSException) result;
}
LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
return (byte[]) result;
} }
LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
return (byte[]) result;
} }
protected static Object internalInitSecContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) { protected static Object internalInitSecContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) {
@ -180,7 +193,7 @@ public class KerberosHelper {
try { try {
context.dispose(); context.dispose();
} catch (GSSException e) { } catch (GSSException e) {
LOGGER.debug("KerberosHelper.internalInitSecContext " + e + " " + e.getMessage()); LOGGER.debug("KerberosHelper.internalInitSecContext " + e + ' ' + e.getMessage());
} }
} }
} }
@ -194,37 +207,35 @@ public class KerberosHelper {
* *
* @throws LoginException on error * @throws LoginException on error
*/ */
public static void clientLogin() throws LoginException { protected static void clientLogin() throws LoginException {
synchronized (LOCK) { if (clientLoginContext != null) {
if (clientLoginContext != null) { // check cached TGT
// check cached TGT
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
KerberosTicket kerberosTicket = (KerberosTicket) ticket;
if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
clientLoginContext = null;
}
}
}
if (clientLoginContext == null) {
final LoginContext localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
localLoginContext.login();
clientLoginContext = localLoginContext;
}
// try to renew almost expired tickets
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) { for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
KerberosTicket kerberosTicket = (KerberosTicket) ticket; KerberosTicket kerberosTicket = (KerberosTicket) ticket;
LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime()); if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) { LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
if (kerberosTicket.isRenewable()) { clientLoginContext = null;
try { }
kerberosTicket.refresh(); }
} catch (RefreshFailedException e) { }
LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket.toString()); if (clientLoginContext == null) {
} final LoginContext localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
} else { localLoginContext.login();
LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable"); clientLoginContext = localLoginContext;
}
// try to renew almost expired tickets
for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
KerberosTicket kerberosTicket = (KerberosTicket) ticket;
LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime());
if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) {
if (kerberosTicket.isRenewable()) {
try {
kerberosTicket.refresh();
} catch (RefreshFailedException e) {
LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket.toString());
} }
} else {
LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable");
} }
} }
} }
@ -233,24 +244,24 @@ public class KerberosHelper {
/** /**
* Create server side Kerberos login context for provided credentials. * Create server side Kerberos login context for provided credentials.
* *
* @param principal server principal * @param serverPrincipal server principal
* @param password server passsword * @param serverPassword server passsword
* @return LoginContext server login context * @return LoginContext server login context
* @throws LoginException on error * @throws LoginException on error
*/ */
public static LoginContext serverLogin(final String principal, final String password) throws LoginException { public static LoginContext serverLogin(final String serverPrincipal, final String serverPassword) throws LoginException {
LoginContext serverLoginContext = new LoginContext("spnego-server", new CallbackHandler() { LoginContext serverLoginContext = new LoginContext("spnego-server", new CallbackHandler() {
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) { for (Callback callback : callbacks) {
if (callbacks[i] instanceof NameCallback) { if (callback instanceof NameCallback) {
final NameCallback nameCallback = (NameCallback) callbacks[i]; final NameCallback nameCallback = (NameCallback) callback;
nameCallback.setName(principal); nameCallback.setName(serverPrincipal);
} else if (callbacks[i] instanceof PasswordCallback) { } else if (callback instanceof PasswordCallback) {
final PasswordCallback passCallback = (PasswordCallback) callbacks[i]; final PasswordCallback passCallback = (PasswordCallback) callback;
passCallback.setPassword(password.toCharArray()); passCallback.setPassword(serverPassword.toCharArray());
} else { } else {
throw new UnsupportedCallbackException(callbacks[i]); throw new UnsupportedCallbackException(callback);
} }
} }
@ -290,7 +301,7 @@ public class KerberosHelper {
Object result = Subject.doAs(serverLoginContext.getSubject(), new PrivilegedAction() { Object result = Subject.doAs(serverLoginContext.getSubject(), new PrivilegedAction() {
public Object run() { public Object run() {
Object result; Object innerResult;
SecurityContext securityContext = new SecurityContext(); SecurityContext securityContext = new SecurityContext();
GSSContext context = null; GSSContext context = null;
try { try {
@ -315,23 +326,23 @@ public class KerberosHelper {
securityContext.clientCredential = context.getDelegCred(); securityContext.clientCredential = context.getDelegCred();
} }
} }
result = securityContext; innerResult = securityContext;
} catch (GSSException e) { } catch (GSSException e) {
result = e; innerResult = e;
} finally { } finally {
if (context != null) { if (context != null) {
try { try {
context.dispose(); context.dispose();
} catch (GSSException e) { } catch (GSSException e) {
LOGGER.debug("KerberosHelper.acceptSecurityContext " + e + " " + e.getMessage()); LOGGER.debug("KerberosHelper.acceptSecurityContext " + e + ' ' + e.getMessage());
} }
} }
} }
return result; return innerResult;
} }
}); });
if (result instanceof GSSException) { if (result instanceof GSSException) {
LOGGER.info("KerberosHelper.acceptSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((GSSException) result).getMessage()); LOGGER.info("KerberosHelper.acceptSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
throw (GSSException) result; throw (GSSException) result;
} }
return (SecurityContext) result; return (SecurityContext) result;

View File

@ -56,7 +56,10 @@ public class KerberosLoginConfiguration extends Configuration {
//clientLoginModuleOptions.put("ticketCache", FileCredentialsCache.getDefaultCacheName()); //clientLoginModuleOptions.put("ticketCache", FileCredentialsCache.getDefaultCacheName());
//clientLoginModuleOptions.put("refreshKrb5Config", "true"); //clientLoginModuleOptions.put("refreshKrb5Config", "true");
//clientLoginModuleOptions.put("storeKey", "true"); //clientLoginModuleOptions.put("storeKey", "true");
CLIENT_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, clientLoginModuleOptions)}; CLIENT_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
clientLoginModuleOptions)};
HashMap<String, String> serverLoginModuleOptions = new HashMap<String, String>(); HashMap<String, String> serverLoginModuleOptions = new HashMap<String, String>();
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
@ -66,7 +69,10 @@ public class KerberosLoginConfiguration extends Configuration {
serverLoginModuleOptions.put("isInitiator", "false"); // acceptor (server) mode serverLoginModuleOptions.put("isInitiator", "false"); // acceptor (server) mode
serverLoginModuleOptions.put("useKeyTab", "false"); // do not use credentials stored in keytab file serverLoginModuleOptions.put("useKeyTab", "false"); // do not use credentials stored in keytab file
serverLoginModuleOptions.put("storeKey", "true"); // store credentials in subject serverLoginModuleOptions.put("storeKey", "true"); // store credentials in subject
SERVER_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, serverLoginModuleOptions)}; SERVER_LOGIN_MODULE = new AppConfigurationEntry[]{new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
serverLoginModuleOptions)};
} }
@Override @Override
@ -80,6 +86,7 @@ public class KerberosLoginConfiguration extends Configuration {
} }
} }
@Override
public void refresh() { public void refresh() {
// nothing to do // nothing to do
} }