From db2b51617f63d0aa10b535da7192e0fd0dbc0f07 Mon Sep 17 00:00:00 2001 From: Sabrina Lee Date: Fri, 3 Oct 2025 14:32:56 -0400 Subject: [PATCH] Implement cleaner to replace deprecated finalizers The cleaner is the replacement for the deprecated finalize() method for cleaning objects before garbage collection. Each class that was previously using finalize() will have an anonymous function that cleans up the necessary resources. The number of cleaner threads can be set by a property with the default value being 2. The object and its anonymous function are registered to one of the cleaner threads in round robin order. Signed-off-by: Sabrina Lee --- README.md | 9 +- .../crypto/plus/provider/DSASignature.java | 4 +- .../crypto/plus/provider/ECDSASignature.java | 4 +- .../crypto/plus/provider/MessageDigest.java | 2 +- .../ibm/crypto/plus/provider/OpenJCEPlus.java | 4 - .../crypto/plus/provider/OpenJCEPlusFIPS.java | 4 - .../plus/provider/OpenJCEPlusProvider.java | 33 +++++ .../crypto/plus/provider/RSASignature.java | 2 +- .../ibm/crypto/plus/provider/ock/Digest.java | 125 ++++++++++-------- .../crypto/plus/provider/ock/Signature.java | 11 +- 10 files changed, 125 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 5266908ac..fce220cad 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [Run All Tests](#run-all-tests) - [Run Single Test](#run-single-test) - [OpenJCEPlus and OpenJCEPlusFIPS Provider SDK Installation](#openjceplus-and-openjceplusfips-provider-sdk-installation) +- [Configuration Options](#configuration-options) - [Features and Algorithms](#features-and-algorithms) - [Contributions](#contributions) @@ -263,6 +264,13 @@ take effect. ```console -Djgskit.library.path=$ANYDIRECTORY ``` +## Configuration Options + +The following properties can be used to configure application behavior at runtime. + +| Property | Use Case | +|----------|----------| +| `-Dopenjceplus.cleaners.num=` | The cleaner is used for cleaning up native memory no longer in use by OpenJCEPlus and OpenJCEPlusFIPS providers. This option sets the number of cleaner threads to improve cleaning efficiency, particularly useful when encountering `Out Of Memory` (OOM) errors. Default value is `2`. | # Features And Algorithms @@ -466,7 +474,6 @@ AES Key Wrap based on NIST SP800-38F. Code does not allow the specification of an IV. However, it will return the default ICV as defined in the NIST SP800-38F. - # Contributions The following contribution guidelines should be followed: diff --git a/src/main/java/com/ibm/crypto/plus/provider/DSASignature.java b/src/main/java/com/ibm/crypto/plus/provider/DSASignature.java index 63073383b..c7595b141 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/DSASignature.java +++ b/src/main/java/com/ibm/crypto/plus/provider/DSASignature.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023 + * Copyright IBM Corp. 2023, 2025 * * This code is free software; you can redistribute it and/or modify it * under the terms provided by IBM in the LICENSE file that accompanied @@ -28,7 +28,7 @@ abstract class DSASignature extends SignatureSpi { DSASignature(OpenJCEPlusProvider provider, String ockDigestAlgo) { try { this.provider = provider; - this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo); + this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo, provider); } catch (Exception e) { throw provider.providerException("Failed to initialize DSA signature", e); } diff --git a/src/main/java/com/ibm/crypto/plus/provider/ECDSASignature.java b/src/main/java/com/ibm/crypto/plus/provider/ECDSASignature.java index 9e818888e..4eb8ec1f5 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/ECDSASignature.java +++ b/src/main/java/com/ibm/crypto/plus/provider/ECDSASignature.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * This code is free software; you can redistribute it and/or modify it * under the terms provided by IBM in the LICENSE file that accompanied @@ -33,7 +33,7 @@ abstract class ECDSASignature extends SignatureSpi { ECDSASignature(OpenJCEPlusProvider provider, String ockDigestAlgo) { try { this.provider = provider; - this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo); + this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo, provider); } catch (Exception e) { throw provider.providerException("Failed to initialize ECDSA signature", e); } diff --git a/src/main/java/com/ibm/crypto/plus/provider/MessageDigest.java b/src/main/java/com/ibm/crypto/plus/provider/MessageDigest.java index a18c4bb8e..900996236 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/MessageDigest.java +++ b/src/main/java/com/ibm/crypto/plus/provider/MessageDigest.java @@ -19,7 +19,7 @@ abstract class MessageDigest extends MessageDigestSpi implements Cloneable { MessageDigest(OpenJCEPlusProvider provider, String ockDigestAlgo) { try { this.provider = provider; - this.digest = Digest.getInstance(provider.getOCKContext(), ockDigestAlgo); + this.digest = Digest.getInstance(provider.getOCKContext(), ockDigestAlgo, provider); } catch (Exception e) { throw provider.providerException("Failure in MessageDigest", e); } diff --git a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java index 150c569e6..3b3018784 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java +++ b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import javax.crypto.SecretKey; -import sun.security.util.Debug; public final class OpenJCEPlus extends OpenJCEPlusProvider { @@ -67,9 +66,6 @@ public final class OpenJCEPlus extends OpenJCEPlusProvider { // to find ourselves or run the risk of not being in the list. private static volatile OpenJCEPlus instance; - // User enabled debugging - private static Debug debug = Debug.getInstance(DEBUG_VALUE); - private static boolean ockInitialized = false; private static OCKContext ockContext; private static Map attrs; diff --git a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java index acb630e07..e6aaab4d2 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java +++ b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import javax.crypto.SecretKey; -import sun.security.util.Debug; public final class OpenJCEPlusFIPS extends OpenJCEPlusProvider { @@ -62,9 +61,6 @@ public final class OpenJCEPlusFIPS extends OpenJCEPlusProvider { // to find ourselves or run the risk of not being in the list. private static volatile OpenJCEPlusFIPS instance; - // User enabled debugging - private static Debug debug = Debug.getInstance(DEBUG_VALUE); - private static boolean ockInitialized = false; private static OCKContext ockContext; diff --git a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusProvider.java b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusProvider.java index 0805f984c..f32c91ee0 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusProvider.java +++ b/src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusProvider.java @@ -9,7 +9,10 @@ package com.ibm.crypto.plus.provider; import com.ibm.crypto.plus.provider.ock.OCKContext; +import java.lang.ref.Cleaner; import java.security.ProviderException; +import java.util.concurrent.atomic.AtomicInteger; +import sun.security.util.Debug; // Internal interface for OpenJCEPlus and OpenJCEPlus implementation classes. // Implemented as an abstract class rather than an interface so that @@ -31,8 +34,29 @@ public abstract class OpenJCEPlusProvider extends java.security.Provider { // private static boolean verifiedSelfIntegrity = false; private static final boolean verifiedSelfIntegrity = true; + private final Cleaner[] cleaners; + + private final int DEFAULT_NUM_CLEANERS = 2; + + private final int numCleaners; + + private AtomicInteger count = new AtomicInteger(0); + + protected static final Debug debug = Debug.getInstance(DEBUG_VALUE); + OpenJCEPlusProvider(String name, String info) { super(name, PROVIDER_VER, info); + + numCleaners = Integer.getInteger("openjceplus.cleaners.num", DEFAULT_NUM_CLEANERS); + if (numCleaners < 1){ + throw new IllegalArgumentException(numCleaners + " is an invalid number of cleaner threads, must be at least 1."); + } + + cleaners = new Cleaner[numCleaners]; + for (int i = 0; i < numCleaners; i++) { + final Cleaner cleaner = Cleaner.create(); + cleaners[i] = cleaner; + } } static final boolean verifySelfIntegrity(Object c) { @@ -47,6 +71,15 @@ private static final synchronized boolean doSelfVerification(Object c) { return true; } + public void registerCleanable(Object owner, Runnable cleanAction) { + Cleaner cleaner = cleaners[Math.abs(count.getAndIncrement() % numCleaners)]; + cleaner.register(owner, cleanAction); + } + + public static Debug getDebug() { + return debug; + } + // Get OCK context for crypto operations // abstract OCKContext getOCKContext(); diff --git a/src/main/java/com/ibm/crypto/plus/provider/RSASignature.java b/src/main/java/com/ibm/crypto/plus/provider/RSASignature.java index 5fbf28dc7..500985f53 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/RSASignature.java +++ b/src/main/java/com/ibm/crypto/plus/provider/RSASignature.java @@ -35,7 +35,7 @@ abstract class RSASignature extends SignatureSpi { try { this.provider = provider; this.ockDigestAlgo = ockDigestAlgo; - this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo); + this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo, provider); } catch (Exception e) { throw provider.providerException("Failed to initialize RSA signature", e); } diff --git a/src/main/java/com/ibm/crypto/plus/provider/ock/Digest.java b/src/main/java/com/ibm/crypto/plus/provider/ock/Digest.java index 938b99ccf..4424adfba 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/ock/Digest.java +++ b/src/main/java/com/ibm/crypto/plus/provider/ock/Digest.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023, 2024 + * Copyright IBM Corp. 2023, 2025 * * This code is free software; you can redistribute it and/or modify it * under the terms provided by IBM in the LICENSE file that accompanied @@ -8,6 +8,7 @@ package com.ibm.crypto.plus.provider.ock; +import com.ibm.crypto.plus.provider.OpenJCEPlusProvider; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -26,7 +27,7 @@ public final class Digest implements Cloneable { // -2 : Not a SHA* digest algorithm private int algIndx = -1; - private boolean needsReinit = false; + private BoolWrapper needsReinit = new BoolWrapper(false); private boolean contextFromQueue = false; @@ -130,44 +131,28 @@ void getContext() throws OCKException { this.contextFromQueue = true; } } - this.needsReinit = false; + this.needsReinit.setValue(false); } - void releaseContext() throws OCKException { + /* end digest caching mechanism + * =========================================================================== + */ - if (this.digestId == 0) { - return; + /* This wrapper is used to pass a primitive variable as a parameter by reference instead of by value to the cleaner. */ + public class BoolWrapper { + boolean value; + public BoolWrapper(boolean value) { + this.value = value; } - // not SHA* algorithm - if (this.algIndx == -2) { - if (validId(this.digestId)) { - NativeInterface.DIGEST_delete(this.ockContext.getId(), - this.digestId); - this.digestId = 0; - } - } else { - if (this.contextFromQueue) { - // reset now to make sure all contexts in the queue are ready to use - this.reset(); - contexts[this.algIndx].add(this.digestId); - this.digestId = 0; - this.contextFromQueue = false; - } else { - // delete context - if (validId(this.digestId)) { - NativeInterface.DIGEST_delete(this.ockContext.getId(), - this.digestId); - this.digestId = 0; - } - } + public boolean getValue(){ + return this.value; } - this.digestId = 0; - } - /* end digest caching mechanism - * =========================================================================== - */ + public void setValue(boolean value) { + this.value = value; + } + } private OCKContext ockContext = null; private int digestLength = 0; @@ -178,7 +163,9 @@ void releaseContext() throws OCKException { private long digestId = 0; - public static Digest getInstance(OCKContext ockContext, String digestAlgo) throws OCKException { + private OpenJCEPlusProvider provider; + + public static Digest getInstance(OCKContext ockContext, String digestAlgo, OpenJCEPlusProvider provider) throws OCKException { if (ockContext == null) { throw new IllegalArgumentException("context is null"); } @@ -187,15 +174,23 @@ public static Digest getInstance(OCKContext ockContext, String digestAlgo) throw throw new IllegalArgumentException("digestAlgo is null/empty"); } - return new Digest(ockContext, digestAlgo); + return new Digest(ockContext, digestAlgo, provider); } - private Digest(OCKContext ockContext, String digestAlgo) throws OCKException { + private Digest(OCKContext ockContext, String digestAlgo, OpenJCEPlusProvider provider) throws OCKException { //final String methodName = "Digest(String)"; this.ockContext = ockContext; this.digestAlgo = digestAlgo; + this.provider = provider; getContext(); //OCKDebug.Msg(debPrefix, methodName, "digestAlgo :" + digestAlgo); + + if (provider == null) { + throw new IllegalArgumentException("Provider cannot be null."); + } + + this.provider.registerCleanable(this, cleanOCKResources(digestId, algIndx, + contextFromQueue, needsReinit, ockContext)); } private Digest() { @@ -238,7 +233,7 @@ public synchronized void update(byte[] input, int offset, int length) throws OCK if (errorCode < 0) { throwOCKException(errorCode); } - this.needsReinit = true; + this.needsReinit.setValue(true); } public synchronized byte[] digest() throws OCKException { @@ -260,7 +255,7 @@ public synchronized byte[] digest() throws OCKException { if (errorCode < 0) { throwOCKException(errorCode); } - this.needsReinit = false; + this.needsReinit.setValue(false); return digestBytes; } @@ -292,10 +287,10 @@ public synchronized void reset() throws OCKException { if (!validId(this.digestId)) { throw new OCKException(badIdMsg); } - if (this.needsReinit) { + if (this.needsReinit.getValue()) { NativeInterface.DIGEST_reset(this.ockContext.getId(), this.digestId); } - this.needsReinit = false; + this.needsReinit.setValue(false); } private synchronized void obtainDigestLength() throws OCKException { @@ -317,18 +312,6 @@ private synchronized void obtainDigestLength() throws OCKException { } } - @Override - protected synchronized void finalize() throws Throwable { - //final String methodName = "finalize"; - - try { - //OCKDebug.Msg(debPrefix, methodName, "digestId =" + this.digestId); - releaseContext(); - } finally { - super.finalize(); - } - } - /* At some point we may enhance this function to do other validations */ protected static boolean validId(long id) { //final String methodName = "validId"; @@ -348,9 +331,10 @@ public synchronized Object clone() throws CloneNotSupportedException { copy.digestLength = this.digestLength; copy.algIndx = this.algIndx; copy.digestAlgo = new String(this.digestAlgo); - copy.needsReinit = this.needsReinit; + copy.needsReinit.setValue(this.needsReinit.getValue()); copy.ockContext = this.ockContext; copy.contextFromQueue = false; + copy.provider = this.provider; // Allocate a new context for the digestId and copy all state information from our // original context into the copy. @@ -367,6 +351,41 @@ public synchronized Object clone() throws CloneNotSupportedException { .collect(Collectors.joining("\n")); throw new CloneNotSupportedException(stackTrace); } + + this.provider.registerCleanable(copy, cleanOCKResources(copy.digestId, copy.algIndx, + copy.contextFromQueue, copy.needsReinit, copy.ockContext)); return copy; } + + private Runnable cleanOCKResources(long digestId, int algIndx, boolean contextFromQueue, + BoolWrapper needsReinit, OCKContext ockContext) { + return () -> { + try { + if (digestId == 0) { + throw new OCKException("Digest Identifier is not valid"); + } + // not SHA* algorithm + if (algIndx == -2) { + if (validId(digestId)) { + NativeInterface.DIGEST_delete(ockContext.getId(), digestId); + } + } else { + if (contextFromQueue) { + // reset now to make sure all contexts in the queue are ready to use + if (needsReinit.getValue()) { + NativeInterface.DIGEST_reset(ockContext.getId(), digestId); + } + Digest.contexts[algIndx].add(digestId); + } else { + NativeInterface.DIGEST_delete(ockContext.getId(), digestId); + } + } + } catch (OCKException e) { + if (OpenJCEPlusProvider.getDebug() != null) { + OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning : " + e.getMessage()); + e.printStackTrace(); + } + } + }; + } } diff --git a/src/main/java/com/ibm/crypto/plus/provider/ock/Signature.java b/src/main/java/com/ibm/crypto/plus/provider/ock/Signature.java index ca5a1b595..5feee6212 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/ock/Signature.java +++ b/src/main/java/com/ibm/crypto/plus/provider/ock/Signature.java @@ -1,5 +1,5 @@ /* - * Copyright IBM Corp. 2023 + * Copyright IBM Corp. 2023, 2025 * * This code is free software; you can redistribute it and/or modify it * under the terms provided by IBM in the LICENSE file that accompanied @@ -8,6 +8,7 @@ package com.ibm.crypto.plus.provider.ock; +import com.ibm.crypto.plus.provider.OpenJCEPlusProvider; import java.security.InvalidKeyException; public final class Signature { @@ -20,19 +21,19 @@ public final class Signature { private final String badIdMsg = "Digest Identifier or PKey Identifier is not valid"; private final static String debPrefix = "SIGNATURE"; - public static Signature getInstance(OCKContext ockContext, String digestAlgo) + public static Signature getInstance(OCKContext ockContext, String digestAlgo, OpenJCEPlusProvider provider) throws OCKException { if (ockContext == null) { throw new IllegalArgumentException("context is null"); } - return new Signature(ockContext, digestAlgo); + return new Signature(ockContext, digestAlgo, provider); } - private Signature(OCKContext ockContext, String digestAlgo) throws OCKException { + private Signature(OCKContext ockContext, String digestAlgo, OpenJCEPlusProvider provider) throws OCKException { //final String methodName = "Signature(String)"; this.ockContext = ockContext; - this.digest = Digest.getInstance(ockContext, digestAlgo); + this.digest = Digest.getInstance(ockContext, digestAlgo, provider); //OCKDebug.Msg (debPrefix, methodName, "digestAlgo :" + digestAlgo); }