All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.ldap.sdk.GSSAPIBindRequest Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

There is a newer version: 7.0.1
Show newest version
/*
 * Copyright 2009-2018 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2009-2018 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 = System.getProperty(PROPERTY_CONFIG_FILE); /** * The default KDC address that will be used if none is explicitly configured. */ private static final String DEFAULT_KDC_ADDRESS = System.getProperty(PROPERTY_KDC_ADDRESS); /** * The default realm that will be used if none is explicitly configured. */ private static final String DEFAULT_REALM = System.getProperty(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)) { System.clearProperty(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)) { System.setProperty(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 + '}'); } } }