From 139c40de1bc595ccd4b8ca952da9e2a37bc8a18e Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Wed, 05 Nov 2008 13:22:43 +0000
Subject: [PATCH] These fixes add confidentiality/integrity to the SASL GSSAPI and DIGEST-MD5 mechanisms. The issue links:
---
opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java | 534 ++++++++++++++++++++++++++--------------------------------
1 files changed, 241 insertions(+), 293 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java b/opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java
index e45809b..6e3ff5f 100644
--- a/opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java
+++ b/opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java
@@ -32,11 +32,21 @@
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
+import java.io.IOException;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.meta.GSSAPISASLMechanismHandlerCfgDefn.*;
import org.opends.server.admin.std.server.GSSAPISASLMechanismHandlerCfg;
import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
import org.opends.server.api.ClientConnection;
@@ -45,19 +55,17 @@
import org.opends.server.config.ConfigException;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryServer;
-import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
-
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
+import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.ExtensionMessages.*;
-
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -69,28 +77,30 @@
*/
public class GSSAPISASLMechanismHandler
extends SASLMechanismHandler<GSSAPISASLMechanismHandlerCfg>
- implements ConfigurationChangeListener<
- GSSAPISASLMechanismHandlerCfg>
-{
- /**
- * The tracer object for the debug logger.
- */
+ implements ConfigurationChangeListener< GSSAPISASLMechanismHandlerCfg>,
+ CallbackHandler {
+
+ //The tracer object for the debug logger.
private static final DebugTracer TRACER = getTracer();
// The DN of the configuration entry for this SASL mechanism handler.
private DN configEntryDN;
// The current configuration for this SASL mechanism handler.
- private GSSAPISASLMechanismHandlerCfg currentConfig;
+ private GSSAPISASLMechanismHandlerCfg configuration;
- // The identity mapper that will be used to map the Kerberos principal to a
- // directory user.
+ // The identity mapper that will be used to map identities.
private IdentityMapper<?> identityMapper;
- // The fully-qualified domain name for the server system.
+ //The properties to use when creating a SASL server to process the GSSAPI
+ //authentication.
+ private HashMap<String,String> saslProps;
+
+ //The fully qualified domain name used when creating the SASL server.
private String serverFQDN;
-
+ //The login context used to perform server-side authentication.
+ private LoginContext loginContext;
/**
* Creates a new instance of this SASL mechanism handler. No initialization
@@ -103,221 +113,237 @@
}
-
/**
* {@inheritDoc}
*/
@Override()
- public void initializeSASLMechanismHandler(
- GSSAPISASLMechanismHandlerCfg configuration)
- throws ConfigException, InitializationException
- {
- configuration.addGSSAPIChangeListener(this);
-
- currentConfig = configuration;
- configEntryDN = configuration.dn();
-
-
- // Get the identity mapper that should be used to find users.
- DN identityMapperDN = configuration.getIdentityMapperDN();
- identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
-
-
- // Determine the fully-qualified hostname for this system. It may be
- // provided, but if not, then try to determine it programmatically.
- serverFQDN = configuration.getServerFqdn();
- if (serverFQDN == null)
- {
- try
- {
- serverFQDN = InetAddress.getLocalHost().getCanonicalHostName();
+ public void
+ initializeSASLMechanismHandler(GSSAPISASLMechanismHandlerCfg configuration)
+ throws ConfigException, InitializationException {
+ configuration.addGSSAPIChangeListener(this);
+ this.configuration = configuration;
+ configEntryDN = configuration.dn();
+ try {
+ DN identityMapperDN = configuration.getIdentityMapperDN();
+ identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
+ serverFQDN = getFQDN(configuration);
+ Message msg= INFO_GSSAPI_SERVER_FQDN.get(serverFQDN);
+ logError(msg);
+ saslProps = new HashMap<String,String>();
+ saslProps.put(Sasl.QOP, getQOP(configuration));
+ saslProps.put(Sasl.REUSE, "false");
+ String configFileName=configureLoginConfFile(configuration);
+ System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
+ System.setProperty(JAAS_PROPERTY_SUBJECT_CREDS_ONLY, "false");
+ getKdcRealm(configuration);
+ DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_GSSAPI,
+ this);
+ login();
+ } catch (UnknownHostException unhe) {
+ if (debugEnabled()) {
+ TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
+ }
+ Message message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
+ String.valueOf(configEntryDN), getExceptionMessage(unhe));
+ throw new InitializationException(message, unhe);
+ } catch(IOException ioe) {
+ if (debugEnabled()) {
+ TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
+ }
+ Message message = ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
+ getExceptionMessage(ioe));
+ throw new InitializationException(message, ioe);
+ } catch (LoginException le) {
+ if (debugEnabled()) {
+ TRACER.debugCaught(DebugLogLevel.ERROR, le);
+ }
+ Message message = ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT.get(
+ getExceptionMessage(le));
+ throw new InitializationException(message, le);
}
- catch (Exception e)
- {
- if (debugEnabled())
- {
- TRACER.debugCaught(DebugLogLevel.ERROR, e);
- }
+ }
- Message message = ERR_SASLGSSAPI_CANNOT_GET_SERVER_FQDN.get(
- String.valueOf(configEntryDN), getExceptionMessage(e));
- throw new InitializationException(message, e);
+
+ /**
+ * Checks to make sure that the ds-cfg-kdc-address and dc-cfg-realm are
+ * both defined in the configuration. If only one is set, then that is an
+ * error. If both are defined, or, both are null that is fine.
+ *
+ * @param configuration The configuration to use.
+ * @throws InitializationException If the properties violate the requirements.
+ */
+ private void getKdcRealm(GSSAPISASLMechanismHandlerCfg configuration)
+ throws InitializationException {
+ String kdcAddress = configuration.getKdcAddress();
+ String realm = configuration.getRealm();
+ if((kdcAddress != null && realm == null) ||
+ (kdcAddress == null && realm != null)) {
+ Message message = ERR_SASLGSSAPI_KDC_REALM_NOT_DEFINED.get();
+ throw new InitializationException(message);
+ } else if(kdcAddress != null && realm != null) {
+ System.setProperty(KRBV_PROPERTY_KDC, kdcAddress);
+ System.setProperty(KRBV_PROPERTY_REALM, realm);
+
}
- }
+ }
- // Since we're going to be using JAAS behind the scenes, we need to have a
- // JAAS configuration. Rather than always requiring the user to provide it,
- // we'll write one to a temporary file that will be deleted when the JVM
- // exits.
- String configFileName;
- try
- {
+ /**
+ * During login, callbacks are usually used to prompt for passwords. All of
+ * the GSSAPI login information is provided in the properties and login.conf
+ * file, so callbacks are ignored.
+ *
+ * @param callbacks An array of callbacks to process.
+ * @throws UnsupportedCallbackException if an error occurs.
+ */
+ public void handle(Callback[] callbacks)
+ throws UnsupportedCallbackException {
+ }
+
+
+ /**
+ * Returns the fully qualified name either defined in the configuration, or,
+ * determined by examining the system configuration.
+ *
+ * @param configuration The configuration to check.
+ * @return The fully qualified hostname of the server.
+ *
+ * @throws UnknownHostException If the name cannot be determined from the
+ * system configuration.
+ */
+ private String getFQDN(GSSAPISASLMechanismHandlerCfg configuration)
+ throws UnknownHostException {
+ String serverName = configuration.getServerFqdn();
+ if (serverName == null) {
+ serverName = InetAddress.getLocalHost().getCanonicalHostName();
+ }
+ return serverName;
+ }
+
+
+ /**
+ * Create a login context or login using the principal and keytab information
+ * specified in the configuration.
+ *
+ * @throws LoginException If a login context cannot be created.
+ */
+ private void login() throws LoginException {
+ loginContext =
+ new LoginContext(GSSAPISASLMechanismHandler.class.getName(), this);
+ loginContext.login();
+ }
+
+
+ /**
+ * Logout of the current login context.
+ *
+ */
+ private void logout() {
+ try {
+ loginContext.logout();
+ } catch (LoginException e) {
+ if (debugEnabled()) {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+ }
+
+
+ /**
+ * Creates an login.conf file from information in the specified configuration.
+ * This file is used during the login phase.
+ *
+ * @param configuration The new configuration to use.
+ * @return The filename of the new configuration file.
+ *
+ * @throws IOException If the configuration file cannot be created.
+ */
+ private String
+ configureLoginConfFile(GSSAPISASLMechanismHandlerCfg configuration)
+ throws IOException {
+ String configFileName;
File tempFile = File.createTempFile("login", "conf");
configFileName = tempFile.getAbsolutePath();
tempFile.deleteOnExit();
BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
-
w.write(getClass().getName() + " {");
w.newLine();
-
w.write(" com.sun.security.auth.module.Krb5LoginModule required " +
- "storeKey=true useKeyTab=true ");
-
+ "storeKey=true useKeyTab=true ");
String keyTabFile = configuration.getKeytab();
- if (keyTabFile != null)
- {
- w.write("keyTab=\"" + keyTabFile + "\" ");
+ if (keyTabFile != null) {
+ w.write("keyTab=\"" + keyTabFile + "\" ");
}
-
- // FIXME -- Should we add the ability to include "debug=true"?
-
- // FIXME -- Can we get away from hard-coding a protocol here?
- w.write("principal=\"ldap/" + serverFQDN);
-
+ StringBuilder principal= new StringBuilder();
+ String principalName = configuration.getPrincipalName();
String realm = configuration.getRealm();
- if (realm != null)
- {
- w.write("@" + realm);
+ if(principalName != null) {
+ principal.append("principal=\"" + principalName);
+ } else {
+ principal.append("principal=\"ldap/" + serverFQDN);
}
+ if (realm != null) {
+ principal.append("@" + realm);
+ }
+ w.write(principal.toString());
+ Message msg = INFO_GSSAPI_PRINCIPAL_NAME.get(principal.toString());
+ logError(msg);
w.write("\";");
-
w.newLine();
-
w.write("};");
w.newLine();
-
w.flush();
w.close();
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- TRACER.debugCaught(DebugLogLevel.ERROR, e);
- }
-
- Message message =
- ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(getExceptionMessage(e));
- throw new InitializationException(message, e);
- }
-
- System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
- System.setProperty(JAAS_PROPERTY_SUBJECT_CREDS_ONLY, "false");
-
-
- DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_GSSAPI, this);
+ return configFileName;
}
-
/**
* {@inheritDoc}
*/
@Override()
- public void finalizeSASLMechanismHandler()
- {
- currentConfig.removeGSSAPIChangeListener(this);
- DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_GSSAPI);
+ public void finalizeSASLMechanismHandler() {
+ logout();
+ configuration.removeGSSAPIChangeListener(this);
+ DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_GSSAPI);
}
-
-
/**
* {@inheritDoc}
*/
@Override()
- public void processSASLBind(BindOperation bindOperation)
- {
- // GSSAPI binds use multiple stages, so we need to determine whether this is
- // the first stage or a subsequent one. To do that, see if we have SASL
- // state information in the client connection.
- ClientConnection clientConnection = bindOperation.getClientConnection();
- if (clientConnection == null)
- {
- Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
-
- bindOperation.setAuthFailureReason(message);
- bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
- return;
- }
-
- GSSAPIStateInfo stateInfo = null;
- Object saslBindState = clientConnection.getSASLAuthStateInfo();
- if ((saslBindState != null) && (saslBindState instanceof GSSAPIStateInfo))
- {
- stateInfo = (GSSAPIStateInfo) saslBindState;
- }
- else
- {
- try
- {
- stateInfo = new GSSAPIStateInfo(this, bindOperation, serverFQDN);
+ public void processSASLBind(BindOperation bindOp) {
+ ClientConnection clientConnection = bindOp.getClientConnection();
+ if (clientConnection == null) {
+ Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
+ bindOp.setAuthFailureReason(message);
+ bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ return;
}
- catch (InitializationException ie)
- {
- if (debugEnabled())
- {
- TRACER.debugCaught(DebugLogLevel.ERROR, ie);
- }
-
- bindOperation.setAuthFailureReason(ie.getMessageObject());
- bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
- clientConnection.setSASLAuthStateInfo(null);
- return;
+ ClientConnection clientConn = bindOp.getClientConnection();
+ SASLContext saslContext = (SASLContext) clientConn.getSASLAuthStateInfo();
+ if(saslContext == null) {
+ try {
+ saslContext = SASLContext.createSASLContext(saslProps, serverFQDN,
+ SASL_MECHANISM_GSSAPI, identityMapper);
+ } catch (SaslException ex) {
+ if (debugEnabled()) {
+ TRACER.debugCaught(DebugLogLevel.ERROR, ex);
+ }
+ Message msg =
+ ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
+ getExceptionMessage(ex));
+ clientConn.setSASLAuthStateInfo(null);
+ bindOp.setAuthFailureReason(msg);
+ bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+ return;
+ }
}
- }
-
- stateInfo.setBindOperation(bindOperation);
- stateInfo.processAuthenticationStage();
-
-
- if (bindOperation.getResultCode() == ResultCode.SUCCESS)
- {
- // The authentication was successful, so set the proper state information
- // in the client connection and return success.
- Entry userEntry = stateInfo.getUserEntry();
- AuthenticationInfo authInfo =
- new AuthenticationInfo(userEntry, SASL_MECHANISM_GSSAPI,
- DirectoryServer.isRootDN(userEntry.getDN()));
- bindOperation.setAuthenticationInfo(authInfo);
- bindOperation.setResultCode(ResultCode.SUCCESS);
-
- // FIXME -- If we're using integrity or confidentiality, then we can't do
- // this.
- clientConnection.setSASLAuthStateInfo(null);
-
- try
- {
- stateInfo.dispose();
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- TRACER.debugCaught(DebugLogLevel.ERROR, e);
- }
- }
- }
- else if (bindOperation.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
- {
- // We need to store the SASL auth state with the client connection so we
- // can resume authentication the next time around.
- clientConnection.setSASLAuthStateInfo(stateInfo);
- }
- else
- {
- // The authentication failed. We don't want to keep the SASL state
- // around.
- // FIXME -- Are there other result codes that we need to check for and
- // preserve the auth state?
- clientConnection.setSASLAuthStateInfo(null);
- }
+ saslContext.performAuthentication(loginContext, bindOp);
}
-
/**
* Retrieves the user account for the user associated with the provided
* authorization ID.
@@ -328,7 +354,7 @@
* associated user.
*
* @return The user entry for the user with the specified authorization ID,
- * or <CODE>null</CODE> if none is identified.
+ * or {@code null} if none is identified.
*
* @throws DirectoryException If a problem occurs while searching the
* directory for the associated user, or if
@@ -392,119 +418,41 @@
}
-
/**
* {@inheritDoc}
*/
public ConfigChangeResult applyConfigurationChange(
- GSSAPISASLMechanismHandlerCfg configuration)
- {
- ResultCode resultCode = ResultCode.SUCCESS;
- boolean adminActionRequired = false;
- ArrayList<Message> messages = new ArrayList<Message>();
-
-
- // Get the identity mapper that should be used to find users.
- DN identityMapperDN = configuration.getIdentityMapperDN();
- IdentityMapper<?> newIdentityMapper =
- DirectoryServer.getIdentityMapper(identityMapperDN);
-
-
- // Determine the fully-qualified hostname for this system. It may be
- // provided, but if not, then try to determine it programmatically.
- String newFQDN = configuration.getServerFqdn();
- if (newFQDN == null)
- {
- try
- {
- newFQDN = InetAddress.getLocalHost().getCanonicalHostName();
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- TRACER.debugCaught(DebugLogLevel.ERROR, e);
- }
-
- if (resultCode == ResultCode.SUCCESS)
- {
- resultCode = DirectoryServer.getServerErrorResultCode();
- }
-
-
- messages.add(ERR_SASLGSSAPI_CANNOT_GET_SERVER_FQDN.get(
- String.valueOf(configEntryDN),
- getExceptionMessage(e)));
- }
- }
-
-
- if (resultCode == ResultCode.SUCCESS)
- {
- String configFileName;
- try
- {
- File tempFile = File.createTempFile("login", "conf");
- configFileName = tempFile.getAbsolutePath();
- tempFile.deleteOnExit();
- BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
-
- w.write(getClass().getName() + " {");
- w.newLine();
-
- w.write(" com.sun.security.auth.module.Krb5LoginModule required " +
- "storeKey=true useKeyTab=true ");
-
- String keyTabFile = configuration.getKeytab();
- if (keyTabFile != null)
- {
- w.write("keyTab=\"" + keyTabFile + "\" ");
- }
-
- // FIXME -- Should we add the ability to include "debug=true"?
-
- // FIXME -- Can we get away from hard-coding a protocol here?
- w.write("principal=\"ldap/" + serverFQDN);
-
- String realm = configuration.getRealm();
- if (realm != null)
- {
- w.write("@" + realm);
- }
- w.write("\";");
-
- w.newLine();
-
- w.write("};");
- w.newLine();
-
- w.flush();
- w.close();
- }
- catch (Exception e)
- {
- if (debugEnabled())
- {
- TRACER.debugCaught(DebugLogLevel.ERROR, e);
- }
-
- resultCode = DirectoryServer.getServerErrorResultCode();
-
- messages.add(ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
- getExceptionMessage(e)));
-
- return new ConfigChangeResult(resultCode, adminActionRequired, messages);
- }
-
- System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
-
+ GSSAPISASLMechanismHandlerCfg configuration) {
+ ResultCode resultCode = ResultCode.SUCCESS;
+ boolean adminActionRequired = false;
+ ArrayList<Message> messages = new ArrayList<Message>();
+ DN identityMapperDN = configuration.getIdentityMapperDN();
+ IdentityMapper<?> newIdentityMapper =
+ DirectoryServer.getIdentityMapper(identityMapperDN);
identityMapper = newIdentityMapper;
- serverFQDN = newFQDN;
- currentConfig = configuration;
- }
+ saslProps = new HashMap<String,String>();
+ saslProps.put(Sasl.QOP, getQOP(configuration));
+ saslProps.put(Sasl.REUSE, "false");
+ this.configuration = configuration;
+ return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+ }
- return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+ /**
+ * Retrieves the QOP (quality-of-protection) from the specified
+ * configuration.
+ *
+ * @param configuration The new configuration to use.
+ * @return A string representing the quality-of-protection.
+ */
+ private String
+ getQOP(GSSAPISASLMechanismHandlerCfg configuration) {
+ QualityOfProtection QOP = configuration.getQualityOfProtection();
+ if(QOP.equals(QualityOfProtection.CONFIDENTIALITY))
+ return "auth-conf";
+ else if(QOP.equals(QualityOfProtection.INTEGRITY))
+ return "auth-int";
+ else
+ return "auth";
}
}
-
--
Gitblit v1.10.0