Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.unboundid.ldap.sdk.GSSAPIBindRequest Maven / Gradle / Ivy
/*
* Copyright 2009-2019 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2009-2019 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
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;
import javax.security.auth.login.LoginContext;
import javax.security.sasl.RealmCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.util.Debug;
import com.unboundid.util.DebugType;
import com.unboundid.util.InternalUseOnly;
import com.unboundid.util.NotMutable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import static com.unboundid.ldap.sdk.LDAPMessages.*;
/**
* This class provides a SASL GSSAPI bind request implementation as described in
* RFC 4752 . It provides the
* ability to authenticate to a directory server using Kerberos V, which can
* serve as a kind of single sign-on mechanism that may be shared across
* client applications that support Kerberos.
*
* This class uses the Java Authentication and Authorization Service (JAAS)
* behind the scenes to perform all Kerberos processing. This framework
* requires a configuration file to indicate the underlying mechanism to be
* used. It is possible for clients to explicitly specify the path to the
* configuration file that should be used, but if none is given then a default
* file will be created and used. This default file should be sufficient for
* Sun-provided JVMs, but a custom file may be required for JVMs provided by
* other vendors.
*
* Elements included in a GSSAPI bind request include:
*
* Authentication ID -- A string which identifies the user that is
* attempting to authenticate. It should be the user's Kerberos
* principal.
* Authorization ID -- An optional string which specifies an alternate
* authorization identity that should be used for subsequent operations
* requested on the connection. Like the authentication ID, the
* authorization ID should be a Kerberos principal.
* KDC Address -- An optional string which specifies the IP address or
* resolvable name for the Kerberos key distribution center. If this is
* not provided, an attempt will be made to determine the appropriate
* value from the system configuration.
* Realm -- An optional string which specifies the realm into which the
* user should authenticate. If this is not provided, an attempt will be
* made to determine the appropriate value from the system
* configuration
* Password -- The clear-text password for the target user in the Kerberos
* realm.
*
* Example
* The following example demonstrates the process for performing a GSSAPI bind
* against a directory server with a username of "john.doe" and a password
* of "password":
*
* GSSAPIBindRequestProperties gssapiProperties =
* new GSSAPIBindRequestProperties("[email protected] ", "password");
* gssapiProperties.setKDCAddress("kdc.example.com");
* gssapiProperties.setRealm("EXAMPLE.COM");
*
* GSSAPIBindRequest bindRequest =
* new GSSAPIBindRequest(gssapiProperties);
* BindResult bindResult;
* try
* {
* bindResult = connection.bind(bindRequest);
* // If we get here, then the bind was successful.
* }
* catch (LDAPException le)
* {
* // The bind failed for some reason.
* bindResult = new BindResult(le.toLDAPResult());
* ResultCode resultCode = le.getResultCode();
* String errorMessageFromServer = le.getDiagnosticMessage();
* }
*
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class GSSAPIBindRequest
extends SASLBindRequest
implements CallbackHandler, PrivilegedExceptionAction
{
/**
* The name for the GSSAPI SASL mechanism.
*/
public static final String GSSAPI_MECHANISM_NAME = "GSSAPI";
/**
* The name of the configuration property used to specify the address of the
* Kerberos key distribution center.
*/
private static final String PROPERTY_KDC_ADDRESS = "java.security.krb5.kdc";
/**
* The name of the configuration property used to specify the Kerberos realm.
*/
private static final String PROPERTY_REALM = "java.security.krb5.realm";
/**
* The name of the configuration property used to specify the path to the JAAS
* configuration file.
*/
private static final String PROPERTY_CONFIG_FILE =
"java.security.auth.login.config";
/**
* The name of the configuration property used to indicate whether credentials
* can come from somewhere other than the location specified in the JAAS
* configuration file.
*/
private static final String PROPERTY_SUBJECT_CREDS_ONLY =
"javax.security.auth.useSubjectCredsOnly";
/**
* The value for the java.security.auth.login.config property at the time that
* this class was loaded. If this is set, then it will be used in place of
* an automatically-generated config file.
*/
private static final String DEFAULT_CONFIG_FILE =
StaticUtils.getSystemProperty(PROPERTY_CONFIG_FILE);
/**
* The default KDC address that will be used if none is explicitly configured.
*/
private static final String DEFAULT_KDC_ADDRESS =
StaticUtils.getSystemProperty(PROPERTY_KDC_ADDRESS);
/**
* The default realm that will be used if none is explicitly configured.
*/
private static final String DEFAULT_REALM =
StaticUtils.getSystemProperty(PROPERTY_REALM);
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 2511890818146955112L;
// The password for the GSSAPI bind request.
private final ASN1OctetString password;
// A reference to the connection to use for bind processing.
private final AtomicReference conn;
// Indicates whether to enable JVM-level debugging for GSSAPI processing.
private final boolean enableGSSAPIDebugging;
// Indicates whether the client should act as the GSSAPI initiator or the
// acceptor.
private final Boolean isInitiator;
// Indicates whether to attempt to refresh the configuration before the JAAS
// login method is called.
private final boolean refreshKrb5Config;
// Indicates whether to attempt to renew the client's existing ticket-granting
// ticket if authentication uses an existing Kerberos session.
private final boolean renewTGT;
// Indicates whether to require that the credentials be obtained from the
// ticket cache such that authentication will fail if the client does not have
// an existing Kerberos session.
private final boolean requireCachedCredentials;
// Indicates whether to allow the to obtain the credentials to be obtained
// from a keytab.
private final boolean useKeyTab;
// Indicates whether to allow the client to use credentials that are outside
// of the current subject.
private final boolean useSubjectCredentialsOnly;
// Indicates whether to enable the use pf a ticket cache.
private final boolean useTicketCache;
// The message ID from the last LDAP message sent from this request.
private int messageID;
// The SASL quality of protection value(s) allowed for the DIGEST-MD5 bind
// request.
private final List allowedQoP;
// A list that will be updated with messages about any unhandled callbacks
// encountered during processing.
private final List unhandledCallbackMessages;
// The names of any system properties that should not be altered by GSSAPI
// processing.
private Set suppressedSystemProperties;
// The authentication ID string for the GSSAPI bind request.
private final String authenticationID;
// The authorization ID string for the GSSAPI bind request, if available.
private final String authorizationID;
// The path to the JAAS configuration file to use for bind processing.
private final String configFilePath;
// The name that will be used to identify this client in the JAAS framework.
private final String jaasClientName;
// The KDC address for the GSSAPI bind request, if available.
private final String kdcAddress;
// The path to the keytab file to use if useKeyTab is true.
private final String keyTabPath;
// The realm for the GSSAPI bind request, if available.
private final String realm;
// The server name that should be used when creating the Java SaslClient, if
// defined.
private final String saslClientServerName;
// The protocol that should be used in the Kerberos service principal for
// the server system.
private final String servicePrincipalProtocol;
// The path to the Kerberos ticket cache to use.
private final String ticketCachePath;
/**
* Creates a new SASL GSSAPI bind request with the provided authentication ID
* and password.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param password The password for this bind request. It must not
* be {@code null}.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID, final String password)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, password));
}
/**
* Creates a new SASL GSSAPI bind request with the provided authentication ID
* and password.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param password The password for this bind request. It must not
* be {@code null}.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID, final byte[] password)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, password));
}
/**
* Creates a new SASL GSSAPI bind request with the provided authentication ID
* and password.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param password The password for this bind request. It must not
* be {@code null}.
* @param controls The set of controls to include in the request.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID, final String password,
final Control[] controls)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, password), controls);
}
/**
* Creates a new SASL GSSAPI bind request with the provided authentication ID
* and password.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param password The password for this bind request. It must not
* be {@code null}.
* @param controls The set of controls to include in the request.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID, final byte[] password,
final Control[] controls)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, password), controls);
}
/**
* Creates a new SASL GSSAPI bind request with the provided information.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param authorizationID The authorization ID for this bind request. It
* may be {@code null} if no alternate authorization
* ID should be used.
* @param password The password for this bind request. It must not
* be {@code null}.
* @param realm The realm to use for the authentication. It may
* be {@code null} to attempt to use the default
* realm from the system configuration.
* @param kdcAddress The address of the Kerberos key distribution
* center. It may be {@code null} to attempt to use
* the default KDC from the system configuration.
* @param configFilePath The path to the JAAS configuration file to use
* for the authentication processing. It may be
* {@code null} to use the default JAAS
* configuration.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID,
final String authorizationID, final String password,
final String realm, final String kdcAddress,
final String configFilePath)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
new ASN1OctetString(password), realm, kdcAddress, configFilePath));
}
/**
* Creates a new SASL GSSAPI bind request with the provided information.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param authorizationID The authorization ID for this bind request. It
* may be {@code null} if no alternate authorization
* ID should be used.
* @param password The password for this bind request. It must not
* be {@code null}.
* @param realm The realm to use for the authentication. It may
* be {@code null} to attempt to use the default
* realm from the system configuration.
* @param kdcAddress The address of the Kerberos key distribution
* center. It may be {@code null} to attempt to use
* the default KDC from the system configuration.
* @param configFilePath The path to the JAAS configuration file to use
* for the authentication processing. It may be
* {@code null} to use the default JAAS
* configuration.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID,
final String authorizationID, final byte[] password,
final String realm, final String kdcAddress,
final String configFilePath)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
new ASN1OctetString(password), realm, kdcAddress, configFilePath));
}
/**
* Creates a new SASL GSSAPI bind request with the provided information.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param authorizationID The authorization ID for this bind request. It
* may be {@code null} if no alternate authorization
* ID should be used.
* @param password The password for this bind request. It must not
* be {@code null}.
* @param realm The realm to use for the authentication. It may
* be {@code null} to attempt to use the default
* realm from the system configuration.
* @param kdcAddress The address of the Kerberos key distribution
* center. It may be {@code null} to attempt to use
* the default KDC from the system configuration.
* @param configFilePath The path to the JAAS configuration file to use
* for the authentication processing. It may be
* {@code null} to use the default JAAS
* configuration.
* @param controls The set of controls to include in the request.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID,
final String authorizationID, final String password,
final String realm, final String kdcAddress,
final String configFilePath,
final Control[] controls)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
new ASN1OctetString(password), realm, kdcAddress, configFilePath),
controls);
}
/**
* Creates a new SASL GSSAPI bind request with the provided information.
*
* @param authenticationID The authentication ID for this bind request. It
* must not be {@code null}.
* @param authorizationID The authorization ID for this bind request. It
* may be {@code null} if no alternate authorization
* ID should be used.
* @param password The password for this bind request. It must not
* be {@code null}.
* @param realm The realm to use for the authentication. It may
* be {@code null} to attempt to use the default
* realm from the system configuration.
* @param kdcAddress The address of the Kerberos key distribution
* center. It may be {@code null} to attempt to use
* the default KDC from the system configuration.
* @param configFilePath The path to the JAAS configuration file to use
* for the authentication processing. It may be
* {@code null} to use the default JAAS
* configuration.
* @param controls The set of controls to include in the request.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final String authenticationID,
final String authorizationID, final byte[] password,
final String realm, final String kdcAddress,
final String configFilePath,
final Control[] controls)
throws LDAPException
{
this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
new ASN1OctetString(password), realm, kdcAddress, configFilePath),
controls);
}
/**
* Creates a new SASL GSSAPI bind request with the provided set of properties.
*
* @param gssapiProperties The set of properties that should be used for
* the GSSAPI bind request. It must not be
* {@code null}.
* @param controls The set of controls to include in the request.
*
* @throws LDAPException If a problem occurs while creating the JAAS
* configuration file to use during authentication
* processing.
*/
public GSSAPIBindRequest(final GSSAPIBindRequestProperties gssapiProperties,
final Control... controls)
throws LDAPException
{
super(controls);
Validator.ensureNotNull(gssapiProperties);
authenticationID = gssapiProperties.getAuthenticationID();
password = gssapiProperties.getPassword();
realm = gssapiProperties.getRealm();
allowedQoP = gssapiProperties.getAllowedQoP();
kdcAddress = gssapiProperties.getKDCAddress();
jaasClientName = gssapiProperties.getJAASClientName();
saslClientServerName = gssapiProperties.getSASLClientServerName();
servicePrincipalProtocol = gssapiProperties.getServicePrincipalProtocol();
enableGSSAPIDebugging = gssapiProperties.enableGSSAPIDebugging();
useKeyTab = gssapiProperties.useKeyTab();
useSubjectCredentialsOnly = gssapiProperties.useSubjectCredentialsOnly();
useTicketCache = gssapiProperties.useTicketCache();
requireCachedCredentials = gssapiProperties.requireCachedCredentials();
refreshKrb5Config = gssapiProperties.refreshKrb5Config();
renewTGT = gssapiProperties.renewTGT();
keyTabPath = gssapiProperties.getKeyTabPath();
ticketCachePath = gssapiProperties.getTicketCachePath();
isInitiator = gssapiProperties.getIsInitiator();
suppressedSystemProperties =
gssapiProperties.getSuppressedSystemProperties();
unhandledCallbackMessages = new ArrayList<>(5);
conn = new AtomicReference<>();
messageID = -1;
final String authzID = gssapiProperties.getAuthorizationID();
if (authzID == null)
{
authorizationID = null;
}
else
{
authorizationID = authzID;
}
final String cfgPath = gssapiProperties.getConfigFilePath();
if (cfgPath == null)
{
if (DEFAULT_CONFIG_FILE == null)
{
configFilePath = getConfigFilePath(gssapiProperties);
}
else
{
configFilePath = DEFAULT_CONFIG_FILE;
}
}
else
{
configFilePath = cfgPath;
}
}
/**
* {@inheritDoc}
*/
@Override()
public String getSASLMechanismName()
{
return GSSAPI_MECHANISM_NAME;
}
/**
* Retrieves the authentication ID for the GSSAPI bind request, if defined.
*
* @return The authentication ID for the GSSAPI bind request, or {@code null}
* if an existing Kerberos session should be used.
*/
public String getAuthenticationID()
{
return authenticationID;
}
/**
* Retrieves the authorization ID for this bind request, if any.
*
* @return The authorization ID for this bind request, or {@code null} if
* there should not be a separate authorization identity.
*/
public String getAuthorizationID()
{
return authorizationID;
}
/**
* Retrieves the string representation of the password for this bind request,
* if defined.
*
* @return The string representation of the password for this bind request,
* or {@code null} if an existing Kerberos session should be used.
*/
public String getPasswordString()
{
if (password == null)
{
return null;
}
else
{
return password.stringValue();
}
}
/**
* Retrieves the bytes that comprise the the password for this bind request,
* if defined.
*
* @return The bytes that comprise the password for this bind request, or
* {@code null} if an existing Kerberos session should be used.
*/
public byte[] getPasswordBytes()
{
if (password == null)
{
return null;
}
else
{
return password.getValue();
}
}
/**
* Retrieves the realm for this bind request, if any.
*
* @return The realm for this bind request, or {@code null} if none was
* defined and the client should attempt to determine the realm from
* the system configuration.
*/
public String getRealm()
{
return realm;
}
/**
* Retrieves the list of allowed qualities of protection that may be used for
* communication that occurs on the connection after the authentication has
* completed, in order from most preferred to least preferred.
*
* @return The list of allowed qualities of protection that may be used for
* communication that occurs on the connection after the
* authentication has completed, in order from most preferred to
* least preferred.
*/
public List getAllowedQoP()
{
return allowedQoP;
}
/**
* Retrieves the address of the Kerberos key distribution center.
*
* @return The address of the Kerberos key distribution center, or
* {@code null} if none was defined and the client should attempt to
* determine the KDC address from the system configuration.
*/
public String getKDCAddress()
{
return kdcAddress;
}
/**
* Retrieves the path to the JAAS configuration file that will be used during
* authentication processing.
*
* @return The path to the JAAS configuration file that will be used during
* authentication processing.
*/
public String getConfigFilePath()
{
return configFilePath;
}
/**
* Retrieves the protocol specified in the service principal that the
* directory server uses for its communication with the KDC.
*
* @return The protocol specified in the service principal that the directory
* server uses for its communication with the KDC.
*/
public String getServicePrincipalProtocol()
{
return servicePrincipalProtocol;
}
/**
* Indicates whether to refresh the configuration before the JAAS
* {@code login} method is called.
*
* @return {@code true} if the GSSAPI implementation should refresh the
* configuration before the JAAS {@code login} method is called, or
* {@code false} if not.
*/
public boolean refreshKrb5Config()
{
return refreshKrb5Config;
}
/**
* Indicates whether to use a keytab to obtain the user credentials.
*
* @return {@code true} if the GSSAPI login attempt should use a keytab to
* obtain the user credentials, or {@code false} if not.
*/
public boolean useKeyTab()
{
return useKeyTab;
}
/**
* Retrieves the path to the keytab file from which to obtain the user
* credentials. This will only be used if {@link #useKeyTab} returns
* {@code true}.
*
* @return The path to the keytab file from which to obtain the user
* credentials, or {@code null} if the default keytab location should
* be used.
*/
public String getKeyTabPath()
{
return keyTabPath;
}
/**
* Indicates whether to enable the use of a ticket cache to to avoid the need
* to supply credentials if the client already has an existing Kerberos
* session.
*
* @return {@code true} if a ticket cache may be used to take advantage of an
* existing Kerberos session, or {@code false} if Kerberos
* credentials should always be provided.
*/
public boolean useTicketCache()
{
return useTicketCache;
}
/**
* Indicates whether GSSAPI authentication should only occur using an existing
* Kerberos session.
*
* @return {@code true} if GSSAPI authentication should only use an existing
* Kerberos session and should fail if the client does not have an
* existing session, or {@code false} if the client will be allowed
* to create a new session if one does not already exist.
*/
public boolean requireCachedCredentials()
{
return requireCachedCredentials;
}
/**
* Retrieves the path to the Kerberos ticket cache file that should be used
* during authentication, if defined.
*
* @return The path to the Kerberos ticket cache file that should be used
* during authentication, or {@code null} if the default ticket cache
* file should be used.
*/
public String getTicketCachePath()
{
return ticketCachePath;
}
/**
* Indicates whether to attempt to renew the client's ticket-granting ticket
* (TGT) if an existing Kerberos session is used to authenticate.
*
* @return {@code true} if the client should attempt to renew its
* ticket-granting ticket if the authentication is processed using an
* existing Kerberos session, or {@code false} if not.
*/
public boolean renewTGT()
{
return renewTGT;
}
/**
* Indicates whether to allow the client to use credentials that are outside
* of the current subject, obtained via some system-specific mechanism.
*
* @return {@code true} if the client will only be allowed to use credentials
* that are within the current subject, or {@code false} if the
* client will be allowed to use credentials outside the current
* subject.
*/
public boolean useSubjectCredentialsOnly()
{
return useSubjectCredentialsOnly;
}
/**
* Indicates whether the client should be configured so that it explicitly
* indicates whether it is the initiator or the acceptor.
*
* @return {@code Boolean.TRUE} if the client should explicitly indicate that
* it is the GSSAPI initiator, {@code Boolean.FALSE} if the client
* should explicitly indicate that it is the GSSAPI acceptor, or
* {@code null} if the client should not explicitly indicate either
* state (which is the default behavior unless the
* {@link GSSAPIBindRequestProperties#setIsInitiator} method has
* been used to explicitly specify a value).
*/
public Boolean getIsInitiator()
{
return isInitiator;
}
/**
* Retrieves a set of system properties that will not be altered by GSSAPI
* processing.
*
* @return A set of system properties that will not be altered by GSSAPI
* processing.
*/
public Set getSuppressedSystemProperties()
{
return suppressedSystemProperties;
}
/**
* Indicates whether JVM-level debugging should be enabled for GSSAPI bind
* processing.
*
* @return {@code true} if JVM-level debugging should be enabled for GSSAPI
* bind processing, or {@code false} if not.
*/
public boolean enableGSSAPIDebugging()
{
return enableGSSAPIDebugging;
}
/**
* Retrieves the path to the default JAAS configuration file that will be used
* if no file was explicitly provided. A new file may be created if
* necessary.
*
* @param properties The GSSAPI properties that should be used for
* authentication.
*
* @return The path to the default JAAS configuration file that will be used
* if no file was explicitly provided.
*
* @throws LDAPException If an error occurs while attempting to create the
* configuration file.
*/
private static String getConfigFilePath(
final GSSAPIBindRequestProperties properties)
throws LDAPException
{
try
{
final File f =
File.createTempFile("GSSAPIBindRequest-JAAS-Config-", ".conf");
f.deleteOnExit();
final PrintWriter w = new PrintWriter(new FileWriter(f));
try
{
// The JAAS configuration file may vary based on the JVM that we're
// using. For Sun-based JVMs, the module will be
// "com.sun.security.auth.module.Krb5LoginModule".
try
{
final Class> sunModuleClass =
Class.forName("com.sun.security.auth.module.Krb5LoginModule");
if (sunModuleClass != null)
{
writeSunJAASConfig(w, properties);
return f.getAbsolutePath();
}
}
catch (final ClassNotFoundException cnfe)
{
// This is fine.
Debug.debugException(cnfe);
}
// For the IBM JVMs, the module will be
// "com.ibm.security.auth.module.Krb5LoginModule".
try
{
final Class> ibmModuleClass =
Class.forName("com.ibm.security.auth.module.Krb5LoginModule");
if (ibmModuleClass != null)
{
writeIBMJAASConfig(w, properties);
return f.getAbsolutePath();
}
}
catch (final ClassNotFoundException cnfe)
{
// This is fine.
Debug.debugException(cnfe);
}
// If we've gotten here, then we can't generate an appropriate
// configuration.
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
ERR_GSSAPI_NO_SUPPORTED_JAAS_MODULE.get()));
}
finally
{
w.close();
}
}
catch (final LDAPException le)
{
Debug.debugException(le);
throw le;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Writes a JAAS configuration file in a form appropriate for Sun VMs.
*
* @param w The writer to use to create the config file.
* @param p The properties to use for GSSAPI authentication.
*/
private static void writeSunJAASConfig(final PrintWriter w,
final GSSAPIBindRequestProperties p)
{
w.println(p.getJAASClientName() + " {");
w.println(" com.sun.security.auth.module.Krb5LoginModule required");
w.println(" client=true");
if (p.getIsInitiator() != null)
{
w.println(" isInitiator=" + p.getIsInitiator());
}
if (p.refreshKrb5Config())
{
w.println(" refreshKrb5Config=true");
}
if (p.useKeyTab())
{
w.println(" useKeyTab=true");
if (p.getKeyTabPath() != null)
{
w.println(" keyTab=\"" + p.getKeyTabPath() + '"');
}
}
if (p.useTicketCache())
{
w.println(" useTicketCache=true");
w.println(" renewTGT=" + p.renewTGT());
w.println(" doNotPrompt=" + p.requireCachedCredentials());
final String ticketCachePath = p.getTicketCachePath();
if (ticketCachePath != null)
{
w.println(" ticketCache=\"" + ticketCachePath + '"');
}
}
else
{
w.println(" useTicketCache=false");
}
if (p.enableGSSAPIDebugging())
{
w.println(" debug=true");
}
w.println(" ;");
w.println("};");
}
/**
* Writes a JAAS configuration file in a form appropriate for IBM VMs.
*
* @param w The writer to use to create the config file.
* @param p The properties to use for GSSAPI authentication.
*/
private static void writeIBMJAASConfig(final PrintWriter w,
final GSSAPIBindRequestProperties p)
{
// NOTE: It does not appear that the IBM GSSAPI implementation has any
// analog for the renewTGT property, so it will be ignored.
w.println(p.getJAASClientName() + " {");
w.println(" com.ibm.security.auth.module.Krb5LoginModule required");
if ((p.getIsInitiator() == null) || p.getIsInitiator().booleanValue())
{
w.println(" credsType=initiator");
}
else
{
w.println(" credsType=acceptor");
}
if (p.refreshKrb5Config())
{
w.println(" refreshKrb5Config=true");
}
if (p.useKeyTab())
{
w.println(" useKeyTab=true");
if (p.getKeyTabPath() != null)
{
w.println(" keyTab=\"" + p.getKeyTabPath() + '"');
}
}
if (p.useTicketCache())
{
final String ticketCachePath = p.getTicketCachePath();
if (ticketCachePath == null)
{
if (p.requireCachedCredentials())
{
w.println(" useDefaultCcache=true");
}
}
else
{
final File f = new File(ticketCachePath);
final String path = f.getAbsolutePath().replace('\\', '/');
w.println(" useCcache=\"file://" + path + '"');
}
}
else
{
w.println(" useDefaultCcache=false");
}
if (p.enableGSSAPIDebugging())
{
w.println(" debug=true");
}
w.println(" ;");
w.println("};");
}
/**
* Sends this bind request to the target server over the provided connection
* and returns the corresponding response.
*
* @param connection The connection to use to send this bind request to the
* server and read the associated response.
* @param depth The current referral depth for this request. It should
* always be one for the initial request, and should only
* be incremented when following referrals.
*
* @return The bind response read from the server.
*
* @throws LDAPException If a problem occurs while sending the request or
* reading the response.
*/
@Override()
protected BindResult process(final LDAPConnection connection, final int depth)
throws LDAPException
{
if (! conn.compareAndSet(null, connection))
{
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_GSSAPI_MULTIPLE_CONCURRENT_REQUESTS.get());
}
setProperty(PROPERTY_CONFIG_FILE, configFilePath);
setProperty(PROPERTY_SUBJECT_CREDS_ONLY,
String.valueOf(useSubjectCredentialsOnly));
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Using config file property " + PROPERTY_CONFIG_FILE + " = '" +
configFilePath + "'.");
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Using subject creds only property " + PROPERTY_SUBJECT_CREDS_ONLY +
" = '" + useSubjectCredentialsOnly + "'.");
}
if (kdcAddress == null)
{
if (DEFAULT_KDC_ADDRESS == null)
{
clearProperty(PROPERTY_KDC_ADDRESS);
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Clearing kdcAddress property '" + PROPERTY_KDC_ADDRESS + "'.");
}
}
else
{
setProperty(PROPERTY_KDC_ADDRESS, DEFAULT_KDC_ADDRESS);
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Using default kdcAddress property " + PROPERTY_KDC_ADDRESS +
" = '" + DEFAULT_KDC_ADDRESS + "'.");
}
}
}
else
{
setProperty(PROPERTY_KDC_ADDRESS, kdcAddress);
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Using kdcAddress property " + PROPERTY_KDC_ADDRESS + " = '" +
kdcAddress + "'.");
}
}
if (realm == null)
{
if (DEFAULT_REALM == null)
{
clearProperty(PROPERTY_REALM);
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Clearing realm property '" + PROPERTY_REALM + "'.");
}
}
else
{
setProperty(PROPERTY_REALM, DEFAULT_REALM);
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Using default realm property " + PROPERTY_REALM + " = '" +
DEFAULT_REALM + "'.");
}
}
}
else
{
setProperty(PROPERTY_REALM, realm);
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.CONFIG, DebugType.LDAP,
"Using realm property " + PROPERTY_REALM + " = '" + realm + "'.");
}
}
try
{
final LoginContext context;
try
{
context = new LoginContext(jaasClientName, this);
context.login();
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_GSSAPI_CANNOT_INITIALIZE_JAAS_CONTEXT.get(
StaticUtils.getExceptionMessage(e)),
e);
}
try
{
return (BindResult) Subject.doAs(context.getSubject(), this);
}
catch (final Exception e)
{
Debug.debugException(e);
if (e instanceof LDAPException)
{
throw (LDAPException) e;
}
else
{
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_GSSAPI_AUTHENTICATION_FAILED.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
}
finally
{
conn.set(null);
}
}
/**
* Perform the privileged portion of the authentication processing.
*
* @return {@code null}, since no return value is actually needed.
*
* @throws LDAPException If a problem occurs during processing.
*/
@InternalUseOnly()
@Override()
public Object run()
throws LDAPException
{
unhandledCallbackMessages.clear();
final LDAPConnection connection = conn.get();
final HashMap saslProperties =
new HashMap<>(StaticUtils.computeMapCapacity(2));
saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP));
saslProperties.put(Sasl.SERVER_AUTH, "true");
final SaslClient saslClient;
try
{
String serverName = saslClientServerName;
if (serverName == null)
{
serverName = connection.getConnectedAddress();
}
final String[] mechanisms = { GSSAPI_MECHANISM_NAME };
saslClient = Sasl.createSaslClient(mechanisms, authorizationID,
servicePrincipalProtocol, serverName, saslProperties, this);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.LOCAL_ERROR,
ERR_GSSAPI_CANNOT_CREATE_SASL_CLIENT.get(
StaticUtils.getExceptionMessage(e)),
e);
}
final SASLHelper helper = new SASLHelper(this, connection,
GSSAPI_MECHANISM_NAME, saslClient, getControls(),
getResponseTimeoutMillis(connection), unhandledCallbackMessages);
try
{
return helper.processSASLBind();
}
finally
{
messageID = helper.getMessageID();
}
}
/**
* {@inheritDoc}
*/
@Override()
public GSSAPIBindRequest getRebindRequest(final String host, final int port)
{
try
{
final GSSAPIBindRequestProperties gssapiProperties =
new GSSAPIBindRequestProperties(authenticationID, authorizationID,
password, realm, kdcAddress, configFilePath);
gssapiProperties.setAllowedQoP(allowedQoP);
gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol);
gssapiProperties.setUseTicketCache(useTicketCache);
gssapiProperties.setRequireCachedCredentials(requireCachedCredentials);
gssapiProperties.setRenewTGT(renewTGT);
gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly);
gssapiProperties.setTicketCachePath(ticketCachePath);
gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging);
gssapiProperties.setJAASClientName(jaasClientName);
gssapiProperties.setSASLClientServerName(saslClientServerName);
gssapiProperties.setSuppressedSystemProperties(
suppressedSystemProperties);
return new GSSAPIBindRequest(gssapiProperties, getControls());
}
catch (final Exception e)
{
// This should never happen.
Debug.debugException(e);
return null;
}
}
/**
* Handles any necessary callbacks required for SASL authentication.
*
* @param callbacks The set of callbacks to be handled.
*
* @throws UnsupportedCallbackException If an unsupported type of callback
* was received.
*/
@InternalUseOnly()
@Override()
public void handle(final Callback[] callbacks)
throws UnsupportedCallbackException
{
for (final Callback callback : callbacks)
{
if (callback instanceof NameCallback)
{
((NameCallback) callback).setName(authenticationID);
}
else if (callback instanceof PasswordCallback)
{
if (password == null)
{
throw new UnsupportedCallbackException(callback,
ERR_GSSAPI_NO_PASSWORD_AVAILABLE.get());
}
else
{
((PasswordCallback) callback).setPassword(
password.stringValue().toCharArray());
}
}
else if (callback instanceof RealmCallback)
{
final RealmCallback rc = (RealmCallback) callback;
if (realm == null)
{
unhandledCallbackMessages.add(
ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED.get(rc.getPrompt()));
}
else
{
rc.setText(realm);
}
}
else
{
// This is an unexpected callback.
if (Debug.debugEnabled(DebugType.LDAP))
{
Debug.debug(Level.WARNING, DebugType.LDAP,
"Unexpected GSSAPI SASL callback of type " +
callback.getClass().getName());
}
unhandledCallbackMessages.add(ERR_GSSAPI_UNEXPECTED_CALLBACK.get(
callback.getClass().getName()));
}
}
}
/**
* {@inheritDoc}
*/
@Override()
public int getLastMessageID()
{
return messageID;
}
/**
* {@inheritDoc}
*/
@Override()
public GSSAPIBindRequest duplicate()
{
return duplicate(getControls());
}
/**
* {@inheritDoc}
*/
@Override()
public GSSAPIBindRequest duplicate(final Control[] controls)
{
try
{
final GSSAPIBindRequestProperties gssapiProperties =
new GSSAPIBindRequestProperties(authenticationID, authorizationID,
password, realm, kdcAddress, configFilePath);
gssapiProperties.setAllowedQoP(allowedQoP);
gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol);
gssapiProperties.setUseTicketCache(useTicketCache);
gssapiProperties.setRequireCachedCredentials(requireCachedCredentials);
gssapiProperties.setRenewTGT(renewTGT);
gssapiProperties.setRefreshKrb5Config(refreshKrb5Config);
gssapiProperties.setUseKeyTab(useKeyTab);
gssapiProperties.setKeyTabPath(keyTabPath);
gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly);
gssapiProperties.setTicketCachePath(ticketCachePath);
gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging);
gssapiProperties.setJAASClientName(jaasClientName);
gssapiProperties.setSASLClientServerName(saslClientServerName);
gssapiProperties.setIsInitiator(isInitiator);
gssapiProperties.setSuppressedSystemProperties(
suppressedSystemProperties);
final GSSAPIBindRequest bindRequest =
new GSSAPIBindRequest(gssapiProperties, controls);
bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
return bindRequest;
}
catch (final Exception e)
{
// This should never happen.
Debug.debugException(e);
return null;
}
}
/**
* Clears the specified system property, unless it is one that is configured
* to be suppressed.
*
* @param name The name of the property to be suppressed.
*/
private void clearProperty(final String name)
{
if (! suppressedSystemProperties.contains(name))
{
StaticUtils.clearSystemProperty(name);
}
}
/**
* Sets the specified system property, unless it is one that is configured to
* be suppressed.
*
* @param name The name of the property to be suppressed.
* @param value The value of the property to be suppressed.
*/
private void setProperty(final String name, final String value)
{
if (! suppressedSystemProperties.contains(name))
{
StaticUtils.setSystemProperty(name, value);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("GSSAPIBindRequest(authenticationID='");
buffer.append(authenticationID);
buffer.append('\'');
if (authorizationID != null)
{
buffer.append(", authorizationID='");
buffer.append(authorizationID);
buffer.append('\'');
}
if (realm != null)
{
buffer.append(", realm='");
buffer.append(realm);
buffer.append('\'');
}
buffer.append(", qop='");
buffer.append(SASLQualityOfProtection.toString(allowedQoP));
buffer.append('\'');
if (kdcAddress != null)
{
buffer.append(", kdcAddress='");
buffer.append(kdcAddress);
buffer.append('\'');
}
if (isInitiator != null)
{
buffer.append(", isInitiator=");
buffer.append(isInitiator);
}
buffer.append(", jaasClientName='");
buffer.append(jaasClientName);
buffer.append("', configFilePath='");
buffer.append(configFilePath);
buffer.append("', servicePrincipalProtocol='");
buffer.append(servicePrincipalProtocol);
buffer.append("', enableGSSAPIDebugging=");
buffer.append(enableGSSAPIDebugging);
final Control[] controls = getControls();
if (controls.length > 0)
{
buffer.append(", controls={");
for (int i=0; i < controls.length; i++)
{
if (i > 0)
{
buffer.append(", ");
}
buffer.append(controls[i]);
}
buffer.append('}');
}
buffer.append(')');
}
/**
* {@inheritDoc}
*/
@Override()
public void toCode(final List lineList, final String requestID,
final int indentSpaces, final boolean includeProcessing)
{
// Create and update the bind request properties object.
ToCodeHelper.generateMethodCall(lineList, indentSpaces,
"GSSAPIBindRequestProperties", requestID + "RequestProperties",
"new GSSAPIBindRequestProperties",
ToCodeArgHelper.createString(authenticationID, "Authentication ID"),
ToCodeArgHelper.createString("---redacted-password---", "Password"));
if (authorizationID != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setAuthorizationID",
ToCodeArgHelper.createString(authorizationID, null));
}
if (realm != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setRealm",
ToCodeArgHelper.createString(realm, null));
}
final ArrayList qopValues = new ArrayList<>(3);
for (final SASLQualityOfProtection qop : allowedQoP)
{
qopValues.add("SASLQualityOfProtection." + qop.name());
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setAllowedQoP",
ToCodeArgHelper.createRaw(qopValues, null));
if (kdcAddress != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setKDCAddress",
ToCodeArgHelper.createString(kdcAddress, null));
}
if (jaasClientName != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setJAASClientName",
ToCodeArgHelper.createString(jaasClientName, null));
}
if (configFilePath != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setConfigFilePath",
ToCodeArgHelper.createString(configFilePath, null));
}
if (saslClientServerName != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setSASLClientServerName",
ToCodeArgHelper.createString(saslClientServerName, null));
}
if (servicePrincipalProtocol != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setServicePrincipalProtocol",
ToCodeArgHelper.createString(servicePrincipalProtocol, null));
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setRefreshKrb5Config",
ToCodeArgHelper.createBoolean(refreshKrb5Config, null));
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setUseKeyTab",
ToCodeArgHelper.createBoolean(useKeyTab, null));
if (keyTabPath != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setKeyTabPath",
ToCodeArgHelper.createString(keyTabPath, null));
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setUseSubjectCredentialsOnly",
ToCodeArgHelper.createBoolean(useSubjectCredentialsOnly, null));
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setUseTicketCache",
ToCodeArgHelper.createBoolean(useTicketCache, null));
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setRequireCachedCredentials",
ToCodeArgHelper.createBoolean(requireCachedCredentials, null));
if (ticketCachePath != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setTicketCachePath",
ToCodeArgHelper.createString(ticketCachePath, null));
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setRenewTGT",
ToCodeArgHelper.createBoolean(renewTGT, null));
if (isInitiator != null)
{
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setIsInitiator",
ToCodeArgHelper.createBoolean(isInitiator, null));
}
if ((suppressedSystemProperties != null) &&
(! suppressedSystemProperties.isEmpty()))
{
final ArrayList suppressedArgs =
new ArrayList<>(suppressedSystemProperties.size());
for (final String s : suppressedSystemProperties)
{
suppressedArgs.add(ToCodeArgHelper.createString(s, null));
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, "List",
requestID + "SuppressedProperties", "Arrays.asList", suppressedArgs);
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setSuppressedSystemProperties",
ToCodeArgHelper.createRaw(requestID + "SuppressedProperties", null));
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
requestID + "RequestProperties.setEnableGSSAPIDebugging",
ToCodeArgHelper.createBoolean(enableGSSAPIDebugging, null));
// Create the request variable.
final ArrayList constructorArgs = new ArrayList<>(2);
constructorArgs.add(
ToCodeArgHelper.createRaw(requestID + "RequestProperties", null));
final Control[] controls = getControls();
if (controls.length > 0)
{
constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
"Bind Controls"));
}
ToCodeHelper.generateMethodCall(lineList, indentSpaces, "GSSAPIBindRequest",
requestID + "Request", "new GSSAPIBindRequest", constructorArgs);
// Add lines for processing the request and obtaining the result.
if (includeProcessing)
{
// Generate a string with the appropriate indent.
final StringBuilder buffer = new StringBuilder();
for (int i=0; i < indentSpaces; i++)
{
buffer.append(' ');
}
final String indent = buffer.toString();
lineList.add("");
lineList.add(indent + "try");
lineList.add(indent + '{');
lineList.add(indent + " BindResult " + requestID +
"Result = connection.bind(" + requestID + "Request);");
lineList.add(indent + " // The bind was processed successfully.");
lineList.add(indent + '}');
lineList.add(indent + "catch (LDAPException e)");
lineList.add(indent + '{');
lineList.add(indent + " // The bind failed. Maybe the following will " +
"help explain why.");
lineList.add(indent + " // Note that the connection is now likely in " +
"an unauthenticated state.");
lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
lineList.add(indent + " String message = e.getMessage();");
lineList.add(indent + " String matchedDN = e.getMatchedDN();");
lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
lineList.add(indent + " Control[] responseControls = " +
"e.getResponseControls();");
lineList.add(indent + '}');
}
}
}