From 5d55d244f41e152120cb3ba3c6a475dec090c4bf Mon Sep 17 00:00:00 2001 From: Neil Wilson Date: Fri, 5 Jan 2024 18:36:16 -0600 Subject: [PATCH] Reuse GSSAPI JAAS config files when appropriate Added the ability to reuse automatically generated JAAS configuration files if possible when attempting a SASL GSSAPI bind. Previously, if the caller didn't explicitly provide a JAAS config file, the LDAP SDK would generate one based on the provided bind request properties. It would do this for every GSSAPI bind attempt, even if they all used the same properties, which means that it could generate many copies of the same JAAS config file, which could unnecessarily waste disk space and memory. It will now detect when a bind request would use a JAAS configuration that matches one that has already been generated, and will reuse the previously generated file rather than creating a new one. --- docs/release-notes.html | 13 +++ messages/unboundid-ldapsdk-ldap.properties | 3 + .../unboundid/ldap/sdk/GSSAPIBindRequest.java | 53 ++++++++++- .../ldap/sdk/GSSAPIBindRequestProperties.java | 92 +++++++++++++++++++ 4 files changed, 159 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.html b/docs/release-notes.html index 6ba1c5f61..823a97c77 100644 --- a/docs/release-notes.html +++ b/docs/release-notes.html @@ -19,6 +19,19 @@

Version 7.0.0



+
  • + Added the ability to reuse automatically generated JAAS configuration files if + possible when attempting a SASL GSSAPI bind. Previously, if the caller didn't + explicitly provide a JAAS config file, the LDAP SDK would generate one based on + the provided bind request properties. It would do this for every GSSAPI bind + attempt, even if they all used the same properties, which means that it could + generate many copies of the same JAAS config file, which could unnecessarily + waste disk space and memory. It will now detect when a bind request would use a + JAAS configuration that matches one that has already been generated, and will + reuse the previously generated file rather than creating a new one. +

    +
  • +
  • Added client-side support for the LDAP relax rules request control as defined in draft-zeilenga-ldap-relax-03. Note that this support is currently classified as diff --git a/messages/unboundid-ldapsdk-ldap.properties b/messages/unboundid-ldapsdk-ldap.properties index b65ddf3f2..69fc8ea3c 100644 --- a/messages/unboundid-ldapsdk-ldap.properties +++ b/messages/unboundid-ldapsdk-ldap.properties @@ -618,6 +618,9 @@ ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED=The GSSAPI bind processing \ made available to the bind request. ERR_GSSAPI_UNEXPECTED_CALLBACK=The GSSAPI bind request received an unexpected \ and unsupported callback type of {0}. +ERR_GSSAPI_PROPERTIES_CANNOT_COMPUTE_DIGEST=An error occurred while \ + attempting to compute a SHA-256 digest of the GSSAPI bind request \ + properties used to generate the JAAS configuration file: {0} ERR_SASL_CANNOT_CREATE_INITIAL_REQUEST=Unable to create the initial {0} SASL \ request: {1}. ERR_SASL_CANNOT_CREATE_INITIAL_REQUEST_UNHANDLED_CALLBACKS=Unable to create \ diff --git a/src/com/unboundid/ldap/sdk/GSSAPIBindRequest.java b/src/com/unboundid/ldap/sdk/GSSAPIBindRequest.java index d376f8e65..87909c487 100644 --- a/src/com/unboundid/ldap/sdk/GSSAPIBindRequest.java +++ b/src/com/unboundid/ldap/sdk/GSSAPIBindRequest.java @@ -47,7 +47,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import javax.net.ssl.SSLSession; @@ -229,6 +231,16 @@ public final class GSSAPIBindRequest + /** + * A map of generated JAAS configuration files, each of which is indexed by a + * digest of the relevant {@link GSSAPIBindRequestProperties} object used to + * generate the file. + */ + @NotNull private static final Map + JAAS_CONFIG_FILE_PATHS_BY_PROPERTIES_DIGEST = new ConcurrentHashMap<>(); + + + /** * The serial version UID for this serializable class. */ @@ -1027,6 +1039,27 @@ private static String getConfigFilePath( @NotNull final GSSAPIBindRequestProperties properties) throws LDAPException { + // If we already have an appropriate configuration file for the given + // properties, then reuse that file. + ASN1OctetString propertiesConfigDigest = null; + try + { + final byte[] digestBytes = properties.getConfigFileDigest(); + propertiesConfigDigest = new ASN1OctetString(digestBytes); + + final String path = JAAS_CONFIG_FILE_PATHS_BY_PROPERTIES_DIGEST.get( + propertiesConfigDigest); + if (path != null) + { + return path; + } + } + catch (final Exception e) + { + Debug.debugException(e); + } + + try { final File f = @@ -1046,7 +1079,15 @@ private static String getConfigFilePath( if (sunModuleClass != null) { writeSunJAASConfig(w, properties); - return f.getAbsolutePath(); + + final String path = f.getAbsolutePath(); + if (propertiesConfigDigest != null) + { + JAAS_CONFIG_FILE_PATHS_BY_PROPERTIES_DIGEST.put( + propertiesConfigDigest, path); + } + + return path; } } catch (final ClassNotFoundException cnfe) @@ -1065,7 +1106,15 @@ private static String getConfigFilePath( if (ibmModuleClass != null) { writeIBMJAASConfig(w, properties); - return f.getAbsolutePath(); + + final String path = f.getAbsolutePath(); + if (propertiesConfigDigest != null) + { + JAAS_CONFIG_FILE_PATHS_BY_PROPERTIES_DIGEST.put( + propertiesConfigDigest, path); + } + + return path; } } catch (final ClassNotFoundException cnfe) diff --git a/src/com/unboundid/ldap/sdk/GSSAPIBindRequestProperties.java b/src/com/unboundid/ldap/sdk/GSSAPIBindRequestProperties.java index 3f3be7a5f..562eb86a6 100644 --- a/src/com/unboundid/ldap/sdk/GSSAPIBindRequestProperties.java +++ b/src/com/unboundid/ldap/sdk/GSSAPIBindRequestProperties.java @@ -47,6 +47,8 @@ import java.util.Set; import com.unboundid.asn1.ASN1OctetString; +import com.unboundid.util.CryptoHelper; +import com.unboundid.util.Debug; import com.unboundid.util.Mutable; import com.unboundid.util.NotNull; import com.unboundid.util.Nullable; @@ -54,6 +56,9 @@ import com.unboundid.util.ThreadSafety; import com.unboundid.util.ThreadSafetyLevel; import com.unboundid.util.Validator; +import com.unboundid.util.json.JSONBuffer; + +import static com.unboundid.ldap.sdk.LDAPMessages.*; @@ -108,6 +113,9 @@ public final class GSSAPIBindRequestProperties // Indicates whether to enable the use of a ticket cache. private boolean useTicketCache; + // A digest of the settings that are relevant to a JAAS configuration file. + @Nullable private byte[] configFileDigest; + // The type of channel binding to use for the bind request. @NotNull private GSSAPIChannelBindingType channelBindingType; @@ -537,6 +545,7 @@ public void setJAASClientName(@NotNull final String jaasClientName) Validator.ensureNotNull(jaasClientName); this.jaasClientName = jaasClientName; + configFileDigest = null; } @@ -674,6 +683,7 @@ public boolean refreshKrb5Config() public void setRefreshKrb5Config(final boolean refreshKrb5Config) { this.refreshKrb5Config = refreshKrb5Config; + configFileDigest = null; } @@ -734,6 +744,7 @@ public boolean useKeyTab() public void setUseKeyTab(final boolean useKeyTab) { this.useKeyTab = useKeyTab; + configFileDigest = null; } @@ -766,6 +777,7 @@ public String getKeyTabPath() public void setKeyTabPath(@Nullable final String keyTabPath) { this.keyTabPath = keyTabPath; + configFileDigest = null; } @@ -799,6 +811,7 @@ public boolean useTicketCache() public void setUseTicketCache(final boolean useTicketCache) { this.useTicketCache = useTicketCache; + configFileDigest = null; } @@ -835,6 +848,7 @@ public void setRequireCachedCredentials( final boolean requireCachedCredentials) { this.requireCachedCredentials = requireCachedCredentials; + configFileDigest = null; } @@ -867,6 +881,7 @@ public String getTicketCachePath() public void setTicketCachePath(@Nullable final String ticketCachePath) { this.ticketCachePath = ticketCachePath; + configFileDigest = null; } @@ -897,6 +912,7 @@ public boolean renewTGT() public void setRenewTGT(final boolean renewTGT) { this.renewTGT = renewTGT; + configFileDigest = null; } @@ -940,6 +956,7 @@ public Boolean getIsInitiator() public void setIsInitiator(@Nullable final Boolean isInitiator) { this.isInitiator = isInitiator; + configFileDigest = null; } @@ -1055,6 +1072,80 @@ public boolean enableGSSAPIDebugging() public void setEnableGSSAPIDebugging(final boolean enableGSSAPIDebugging) { this.enableGSSAPIDebugging = enableGSSAPIDebugging; + configFileDigest = null; + } + + + + /** + * Retrieves a digest of the settings that are relevant to a JAAS + * configuration file generated from these properties. + * + * @return A digest of the settings that are relevant to a JAAS configuration + * file generated from these properties. + * + * @throws LDAPException If a problem occurs while attempting to generate + * the digest. + */ + @NotNull() + byte[] getConfigFileDigest() + throws LDAPException + { + // If we have previously generated a digest from the current settings, then + // just return that. + if (configFileDigest != null) + { + return configFileDigest; + } + + + // Generate a JSON object from the relevant settings. + final JSONBuffer buffer = new JSONBuffer(); + buffer.beginObject(); + buffer.appendString("jaasClientName", jaasClientName); + + if (isInitiator != null) + { + buffer.appendBoolean("isInitiator", isInitiator); + } + + buffer.appendBoolean("refreshKrb5Config", refreshKrb5Config); + buffer.appendBoolean("useKeyTab", useKeyTab); + + if (keyTabPath != null) + { + buffer.appendString("keyTabPath", keyTabPath); + } + + buffer.appendBoolean("(useTicketCache", useTicketCache); + buffer.appendBoolean("renewTGT", renewTGT); + buffer.appendBoolean("requireCachedCredentials", requireCachedCredentials); + + if (ticketCachePath != null) + { + buffer.appendString("ticketCachePath", ticketCachePath); + } + + buffer.appendBoolean("enableGSSAPIDebugging", enableGSSAPIDebugging); + buffer.endObject(); + + + // Generate, cache, and return a 256-bit SHA digest of the JSON object. + try + { + final byte[] bufferBytes = buffer.getBuffer().toByteArray(); + configFileDigest = CryptoHelper.sha256(bufferBytes); + return configFileDigest; + } + catch (final Exception e) + { + Debug.debugException(e); + throw new LDAPException(ResultCode.LOCAL_ERROR, + ERR_GSSAPI_PROPERTIES_CANNOT_COMPUTE_DIGEST.get( + StaticUtils.getExceptionMessage(e)), + e); + + } } @@ -1200,3 +1291,4 @@ public void toString(@NotNull final StringBuilder buffer) buffer.append(')'); } } +