This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/defer-registration-until-provider-is-available in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-crypto.git
commit fa1121f10604a01e98c1ca49809093c9d1de6213 Author: Konrad Windszus <[email protected]> AuthorDate: Sun Sep 13 17:17:09 2026 +0200 SLING-13280 Allow deferring registration of crypto service until a given security provider is registered with OSGi Register both custom (provided by bundle) and default (provided by JRE) security providers with OSGi. Allow depending on it from both JcaPbeCryptoService and deprecated JasyptStandardPbeStringCryptoService --- .../OsgiAwareSecurityProviderInstaller.java | 67 ++++++++++++++++++---- .../JasyptStandardPbeStringCryptoService.java | 6 ++ .../crypto/jca/internal/JcaPbeCryptoService.java | 5 +- .../internal/JcaPbeCryptoServiceConfiguration.java | 7 +++ 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/apache/sling/commons/crypto/internal/OsgiAwareSecurityProviderInstaller.java b/src/main/java/org/apache/sling/commons/crypto/internal/OsgiAwareSecurityProviderInstaller.java index 6c495a5..7b37507 100644 --- a/src/main/java/org/apache/sling/commons/crypto/internal/OsgiAwareSecurityProviderInstaller.java +++ b/src/main/java/org/apache/sling/commons/crypto/internal/OsgiAwareSecurityProviderInstaller.java @@ -29,10 +29,13 @@ import java.security.Security; import java.util.ArrayList; import java.util.Collection; import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; +import org.osgi.framework.ServiceRegistration; import org.osgi.framework.SynchronousBundleListener; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -42,22 +45,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This DS component listens for bundle events and automatically installs or uninstalls security providers + * This DS component listens for bundle events and automatically adds or removes security providers with the Java Security API * based on the presence of a service registration file {@value #SECURITY_PROVIDER_CONFIGURATION_FILE} in the - * started/stopping bundle. + * started/stopping bundle via {@link Security#addProvider(Provider)} and {@link Security#removeProvider(String)}. + * In addition it also registers each provider (even the ones shipping with the JVM) as an OSGi service so that other services can defer loading + * until a certain provider is available. + * The OSGi service registration includes a property {@value #PROVIDER_NAME_PROPERTY} with the name of the provider. */ @Component(immediate = true, service= {}, name = "org.apache.sling.commons.crypto.internal.AutoRegisterSecurityProvider") @ServiceDescription("Apache Sling Commons Crypto – Auto Register Security Provider") public final class OsgiAwareSecurityProviderInstaller implements SynchronousBundleListener { private static final String SECURITY_PROVIDER_CONFIGURATION_FILE = "META-INF/services/java.security.Provider"; private static final Logger LOGGER = LoggerFactory.getLogger(OsgiAwareSecurityProviderInstaller.class); + private static final String PROVIDER_NAME_PROPERTY = "provider.name"; + private final Map<String, ServiceRegistration<Provider>> registeredProviders; + private final BundleContext bundleContext; @Activate public OsgiAwareSecurityProviderInstaller(BundleContext bundleContext) { + registeredProviders = new ConcurrentHashMap<>(); + this.bundleContext = bundleContext; bundleContext.addBundleListener(this); + registerOrUnregisterDefaultProviders(true); for (Bundle bundle : bundleContext.getBundles()) { if (bundle.getState() == Bundle.ACTIVE) { - addOrRemoveProviders(true, bundle); + addOrRemoveCustomProviders(true, bundle); } } } @@ -67,9 +79,10 @@ public final class OsgiAwareSecurityProviderInstaller implements SynchronousBund bundleContext.removeBundleListener(this); for (Bundle bundle : bundleContext.getBundles()) { if (bundle.getState() == Bundle.ACTIVE) { - addOrRemoveProviders(false, bundle); + addOrRemoveCustomProviders(false, bundle); } } + registerOrUnregisterDefaultProviders(false); } @Override @@ -84,15 +97,25 @@ public final class OsgiAwareSecurityProviderInstaller implements SynchronousBund LOGGER.debug("Ignoring bundle event {} for bundle {}", event.getType(), bundle.getSymbolicName()); return; } - addOrRemoveProviders(isAdd, bundle); + addOrRemoveCustomProviders(isAdd, bundle); + } + + protected void registerOrUnregisterDefaultProviders(boolean isRegister) { + for (Provider provider : Security.getProviders()) { + if (isRegister) { + registerProviderWithOsgi(this.bundleContext, provider); + } else { + unregisterProviderWithOsgi(provider.getName(), registeredProviders.remove(provider.getName())); + } + } } - protected void addOrRemoveProviders(boolean isAdd, Bundle bundle) { + protected void addOrRemoveCustomProviders(boolean isAdd, Bundle bundle) { try { Collection<String> classNames = collectClassNamesFromProviderConfigurationFile(bundle); for (String className : classNames) { try { - addOrRemoveProvider(isAdd, bundle, className); + addOrRemoveCustomProvider(isAdd, bundle, className); } catch (ClassNotFoundException e) { LOGGER.error("Class {} not found in bundle {}: {}", className, bundle.getSymbolicName(), e.getMessage(), e); } catch (Exception e) { @@ -124,7 +147,7 @@ public final class OsgiAwareSecurityProviderInstaller implements SynchronousBund return classNames; } - protected void addOrRemoveProvider(boolean isAdd, Bundle bundle, String providerClassName) + protected void addOrRemoveCustomProvider(boolean isAdd, Bundle bundle, String providerClassName) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Class<?> clazz = bundle.loadClass(providerClassName); @@ -138,11 +161,8 @@ public final class OsgiAwareSecurityProviderInstaller implements SynchronousBund if (position == -1) { LOGGER.warn("Failed to add security provider {} (name {}) from bundle {} to the security providers list. Provider with that name already registered.", providerClassName, provider.getName(), bundle); } - // also add service registration for the provider so that other services can defer loading until the provider is available - Hashtable<String, String> props = new Hashtable<>(); - props.put("provider.name", provider.getName()); - bundle.getBundleContext().registerService(Provider.class, provider, props); LOGGER.info("Added security provider {} (name {}) from bundle {} to last position {}", providerClassName, provider.getName(), bundle, position); + registerProviderWithOsgi(bundle.getBundleContext(), provider); } else { if (Security.getProvider(provider.getName()) != null) { Security.removeProvider(provider.getName()); @@ -150,6 +170,29 @@ public final class OsgiAwareSecurityProviderInstaller implements SynchronousBund } else { LOGGER.warn("Security provider {} (name {}) not found for removal", providerClassName, provider.getName()); } + unregisterProviderWithOsgi(provider.getName(), registeredProviders.remove(provider.getName())); + } + } + + private void registerProviderWithOsgi(BundleContext context, Provider provider) { + // also add service registration for the provider so that other services can defer loading until the provider is available + Hashtable<String, String> props = new Hashtable<>(); + props.put(PROVIDER_NAME_PROPERTY, provider.getName()); + ServiceRegistration<Provider> registration = context.registerService(Provider.class, provider, props); + registeredProviders.put(provider.getName(), registration); + LOGGER.info("Registered security provider with name {} as OSGi service", provider.getName()); + } + + private void unregisterProviderWithOsgi(String name, ServiceRegistration<Provider> registration) { + if (registration == null) { + LOGGER.warn("No service registration found for security provider with name '{}' to unregister", name); + return; + } + try { + registration.unregister(); + LOGGER.info("Unregistered security provider with name '{}' as OSGi service", name); + } catch (IllegalStateException e) { + LOGGER.warn("Service for provider with name {} is already unregistered: {}", name, e.getMessage(), e); } } } diff --git a/src/main/java/org/apache/sling/commons/crypto/jasypt/internal/JasyptStandardPbeStringCryptoService.java b/src/main/java/org/apache/sling/commons/crypto/jasypt/internal/JasyptStandardPbeStringCryptoService.java index 6776f8c..6164c53 100644 --- a/src/main/java/org/apache/sling/commons/crypto/jasypt/internal/JasyptStandardPbeStringCryptoService.java +++ b/src/main/java/org/apache/sling/commons/crypto/jasypt/internal/JasyptStandardPbeStringCryptoService.java @@ -18,6 +18,7 @@ */ package org.apache.sling.commons.crypto.jasypt.internal; +import java.security.Provider; import java.util.Arrays; import java.util.Objects; import java.util.Set; @@ -70,6 +71,11 @@ public final class JasyptStandardPbeStringCryptoService implements CryptoService ) private volatile SaltGenerator saltGenerator; + // only used to potentially defer loading of this service until the security provider is available, but not used directly + @SuppressWarnings("unused") + @Reference + private volatile Provider securityProvider; + private StandardPBEStringEncryptor encryptor; private String algorithm; diff --git a/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java b/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java index b394aa2..8946ff0 100644 --- a/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java +++ b/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoService.java @@ -70,7 +70,6 @@ import org.slf4j.LoggerFactory; @Component(service = CryptoService.class) @Designate(ocd = JcaPbeCryptoServiceConfiguration.class, factory = true) @ServiceDescription("Apache Sling Commons Crypto – JCA PBE String Crypto Service") -@SuppressWarnings({ "java:S1117", "java:S3077", "java:S6212" }) public final class JcaPbeCryptoService implements CryptoService { private static final Logger LOGGER = LoggerFactory.getLogger(JcaPbeCryptoService.class); @@ -90,8 +89,8 @@ public final class JcaPbeCryptoService implements CryptoService { @Activate public JcaPbeCryptoService(final JcaPbeCryptoServiceConfiguration configuration, BundleContext bundleContext, - @Reference(name="passwordProvider") PasswordProvider passwordProvider, @Reference(name="saltProvider") SaltProvider saltProvider) - throws NoSuchAlgorithmException { // + @Reference(name="passwordProvider") PasswordProvider passwordProvider, @Reference(name="saltProvider") SaltProvider saltProvider, @Reference(name="securityProvider") Provider securityProvider) + throws NoSuchAlgorithmException { this(configuration, saltProvider.getSalt(), passwordProvider); } diff --git a/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceConfiguration.java b/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceConfiguration.java index 8deaf03..848956c 100644 --- a/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceConfiguration.java +++ b/src/main/java/org/apache/sling/commons/crypto/jca/internal/JcaPbeCryptoServiceConfiguration.java @@ -73,6 +73,13 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition; ) String securityProviderName() default "SunJCE"; + @AttributeDefinition( + name = "Security Provider Target", + description = "Filter expression to target a Security Provider (usually by name with a pattern like \"(provider.name=<securityProviderName>)\"). Useful to defer loading of the service until the custom Security Provider bundle is available and properly registered.", + required = false + ) + String securityProvider_target(); + // automatically evaluated (https://docs.osgi.org/specification/osgi.cmpn/8.0.0/service.component.html#service.component-target.property) @AttributeDefinition( name = "Password Provider Target",
