From 0da9675ba48dcbb7bd4222e0ce1e159dfd6a4cc1 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 25 Sep 2026 11:28:33 +0300 Subject: [PATCH 1/2] [#1081] Wrap with a 2048-bit key in the key wrapping check, and report a provider refusing with an Error The crypto manager checks its key wrapping transformation at every start by wrapping a MAC key with a hard-coded certificate. Its 1024-bit RSA key is refused by BC-FIPS in approved-only mode with FipsUnapprovedOperationError, which escaped catch (Exception) and kept the server from starting. - Use a 2048-bit RSA certificate (SHA256withRSA) for the check. - Use a constant key identifier there instead of an MD5 digest of it. - Report an Error from a provider during the cipher, MAC and key wrapping checks as a refusal of the configuration; rethrow VirtualMachineError. Fixes #1081 --- .../server/crypto/CryptoManagerImpl.java | 63 ++++--- .../server/crypto/CryptoManagerTestCase.java | 167 ++++++++++++++++-- 2 files changed, 196 insertions(+), 34 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java index 4ab3d15b3b..ea1ec1878e 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java @@ -351,7 +351,8 @@ public boolean isConfigurationChangeAcceptable( requestedCipherTransformation, requestedCipherTransformationKeyLengthBits); } - catch (Exception ex) { + catch (Exception | Error ex) { + rethrowIfVirtualMachineError(ex); logger.traceException(ex); unacceptableReasons.add( ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_ENCRYPTION_CIPHER.get( @@ -373,7 +374,8 @@ public boolean isConfigurationChangeAcceptable( requestedMACAlgorithm, requestedMACAlgorithmKeyLengthBits); } - catch (Exception ex) { + catch (Exception | Error ex) { + rethrowIfVirtualMachineError(ex); logger.traceException(ex); unacceptableReasons.add( ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_MAC_ENGINE.get( @@ -400,28 +402,36 @@ else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformatio /* Note that the TrustStoreBackend not available at initial, CryptoManager configuration, hence a "dummy" certificate must be used to validate the choice of secret key wrapping cipher. Otherwise, call - getInstanceKeyCertificateFromLocalTruststore() */ + getInstanceKeyCertificateFromLocalTruststore(). Its key has 2048 bits, + the least a FIPS approved-only provider wraps with, and the key + identifier is not computed from it: the wrapped key is thrown away. */ final String certificateBase64 = - "MIIB2jCCAUMCBEb7wpYwDQYJKoZIhvcNAQEEBQAwNDEbMBkGA1UEChMST3B" + - "lbkRTIENlcnRpZmljYXRlMRUwEwYDVQQDEwwxMC4wLjI0OC4yNTEwHhcNMD" + - "cwOTI3MTQ0NzUwWhcNMjcwOTIyMTQ0NzUwWjA0MRswGQYDVQQKExJPcGVuR" + - "FMgQ2VydGlmaWNhdGUxFTATBgNVBAMTDDEwLjAuMjQ4LjI1MTCBnzANBgkq" + - "hkiG9w0BAQEFAAOBjQAwgYkCgYEAnIm6ELyuNVbpaacBQ7fzHlHMmQO/CYJ" + - "b2gPTdb9n1HLOBqh2lmLLHvt2SgBeN5TSa1PAHW8zJy9LDhpWKZvsUOIdQD" + - "8Ula/0d/jvMEByEj/hr00P6yqgLXk+EudPgOkFXHA+IfkkOSghMooWc/L8H" + - "nD1REdqeZuxp+ARNU+cc/ECAwEAATANBgkqhkiG9w0BAQQFAAOBgQBemyCU" + - "jucN34MZwvzbmFHT/leUu3/cpykbGM9HL2QUX7iKvv2LJVqexhj7CLoXxZP" + - "oNL+HHKW0vi5/7W5KwOZsPqKI2SdYV7nDqTZklm5ZP0gmIuNO6mTqBRtC2D" + - "lplX1Iq+BrQJAmteiPtwhdZD+EIghe51CaseImjlLlY2ZK8w=="; + "MIIDGTCCAgGgAwIBAgIICGFHa+OJNiMwDQYJKoZIhvcNAQELBQAwOjEbMBkG" + + "A1UEChMST3BlbkRKIENlcnRpZmljYXRlMRswGQYDVQQDExJLZXkgd3JhcHBpbmcg" + + "Y2hlY2swIBcNMjYwOTI1MDYzNTAwWhgPMjEyNjA5MDEwNjM1MDBaMDoxGzAZBgNV" + + "BAoTEk9wZW5ESiBDZXJ0aWZpY2F0ZTEbMBkGA1UEAxMSS2V5IHdyYXBwaW5nIGNo" + + "ZWNrMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkLf2hk4cc4FiL0lG" + + "1efFX7hZ/RB5pvb5bKfQAlE3l/YYHmQNjdM+JgIP7t5l/vveoWkkgwSjWj2sh10H" + + "VDpXBxDBdoLNScoKrlDryY+FKO9nDogPJDtQRaTs3ntQDtRR90qASUuw/+gmjitY" + + "fNPHhWy1o/tiwjyz2df/y/pqGEb+VPL0zoyxat+TjCprfmYOwstlsjVhrZhbe96W" + + "WYF8qOMiqx8lu/L9fPJcKg2zSyMnLk0KZJ9iVKWuhyojKdmHSpqSEAzjKaG15qfF" + + "ykotuYMh+gdyMjbdvmSyRZV+XK8/2w26f3Cve3ivOPAmse6Z2aDC4AiIoFsyvAJT" + + "dI2c4QIDAQABoyEwHzAdBgNVHQ4EFgQU0ElK+Aaz5nAV/mTc5zT2//fGtEswDQYJ" + + "KoZIhvcNAQELBQADggEBAA0+IjrK0HWw+0nHdl4f0JI5pvyIotUbbYgZrwYWqc8V" + + "GrHu2RzhsUDTlg/o1L/8f5rM8vKFgg73gmIGHtS16UpBp5PuKi9UXtpZ1G11yH8/" + + "P+4PkmlWl5XNFD6sTy8sOyt0Lv3aVCXt2tkQKu5HFhoXTfLn7JsrSWp52I+QTfYT" + + "KjB2J0IB2AsLtKeAU8r1CepS3YS+/npq4bvwjo0z7kwt6NNXbD2frC1AVVFTUNar" + + "nop82WUyMl94WXHWCe5Q0h67a1RB8i/KTS8ro0pEhMmoHHPc8zY/hyp5Of/m9pJ5" + + "ThBjQlyDccG+81IemDAcwmCqMnEJUcceEmy7VEZT/y4="; final byte[] certificate = Base64.decode(certificateBase64).toByteArray(); - final String keyID = getInstanceKeyID(certificate); final SecretKey macKey = macCryptoManager.generateKeyEntry( requestedMACAlgorithm, requestedMACAlgorithmKeyLengthBits).getSecretKey(); encodeSymmetricKeyAttribute(requestedKeyWrappingTransformation, - keyID, certificate, macKey); + "key-wrapping-check", certificate, macKey); } - catch (Exception ex) { + catch (Exception | Error ex) { + rethrowIfVirtualMachineError(ex); logger.traceException(ex); unacceptableReasons.add( ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.get( @@ -433,11 +443,24 @@ else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformatio return isAcceptable; } + /** + * Rethrows what no configuration can be refused for. The checks ask the JCE providers for keys + * and ciphers, and a provider may refuse with an Error rather than an exception: the BC-FIPS + * provider in approved-only mode throws its FipsUnapprovedOperationError. + */ + private static void rethrowIfVirtualMachineError(final Throwable t) + { + if (t instanceof VirtualMachineError) + { + throw (VirtualMachineError) t; + } + } + /** * Checks that this Java runtime provides the key wrapping transformation. Only a refusal here - * names the key-wrapping-transformation property: the wrap which follows it also needs an MD5 - * digest and a 1024-bit RSA key, and changing the property does not help when one of those is - * what the runtime refuses. + * names the key-wrapping-transformation property: the wrap which follows it also needs a MAC + * key of the mac-algorithm property, and changing the key wrapping property does not help when + * that is what the runtime refuses. */ private static boolean isKeyWrappingTransformationSupported( final String transformation, final List unacceptableReasons) diff --git a/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java index 0642f4cb4e..9dbee3787e 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java @@ -40,14 +40,21 @@ import java.nio.file.Path; import java.security.MessageDigest; import java.security.Provider; +import java.security.Security; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.TreeSet; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; import javax.crypto.Mac; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.ldap.Attribute; @@ -324,12 +331,12 @@ public void testUnsupportedKeyWrappingTransformationIsRefusedNamingTheProperty() } /** - A transformation the runtime provides, refused because of the rest of the check (here the - MD5 digest of the instance key identifier), is not reported as a matter of the property: - changing key-wrapping-transformation would not help. + The check wraps with a key which a provider running in FIPS approved-only mode accepts: BC-FIPS + in that mode refuses RSA keys under 2048 bits with an Error, and the check runs at every start, + so a smaller key keeps the server from starting whatever the transformation. */ @Test - public void testKeyWrappingRefusalForAnotherCauseDoesNotNameTheProperty() throws Exception + public void testKeyWrappingCheckPassesUnderApprovedOnlyBcFips() throws Exception { final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); @@ -337,22 +344,154 @@ public void testKeyWrappingRefusalForAnotherCauseDoesNotNameTheProperty() throws assertThat(supported).isNotEqualTo(cfg.getKeyWrappingTransformation()); final List why = new ArrayList<>(); - // Withdrawing MD5 withdraws the SUN provider, whose SHA-1 digest the OAEP cipher still needs. - final Provider sha1Only = new Provider("Sha1OnlyDigest", "1.0", "SHA-1 digest only") {}; - sha1Only.put("MessageDigest.SHA-1", "sun.security.provider.SHA"); - sha1Only.put("Alg.Alias.MessageDigest.SHA1", "SHA-1"); + final boolean acceptable = inApprovedOnlyBcFipsThread(() -> cm.isConfigurationChangeAcceptable( + withProperty(withMacKeyFromSunJce(cfg), "getKeyWrappingTransformation", supported), why)); + + assertThat(why).isEmpty(); + assertThat(acceptable).isTrue(); + } + + /** + A provider refusing the wrap with an Error, as BC-FIPS in approved-only mode refuses PKCS#1 v1.5 + encryption, is reported as a refusal of the configuration rather than escaping from the check. + */ + @Test + public void testKeyWrappingRefusedWithAnErrorIsReported() throws Exception + { + final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); + final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); + final String unapproved = "RSA/ECB/PKCS1Padding"; + final List why = new ArrayList<>(); + + final boolean acceptable = inApprovedOnlyBcFipsThread(() -> cm.isConfigurationChangeAcceptable( + withProperty(withMacKeyFromSunJce(cfg), "getKeyWrappingTransformation", unapproved), why)); + + assertThat(acceptable).isFalse(); + assertThat(why).hasSize(1); + final String reason = why.get(0).toString(); + assertThat(why.get(0).ordinal()).as(reason).isEqualTo(ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.ordinal()); + assertThat(reason).contains("PKCS1.5"); + } + + /** + A provider refusing to generate a MAC key with an Error, as BC-FIPS in approved-only mode + refuses a random generator it has not approved, is reported as a refusal of the MAC algorithm. + */ + @Test + public void testMacKeyGenerationRefusedWithAnErrorIsReported() throws Exception + { + final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); + final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); + final List why = new ArrayList<>(); + + final boolean acceptable = inApprovedOnlyBcFipsThread(() -> cm.isConfigurationChangeAcceptable( + withProperty(cfg, "getMacKeyLength", cfg.getMacKeyLength() + 64), why)); + + assertThat(acceptable).isFalse(); + assertThat(why).hasSize(1); + final String reason = why.get(0).toString(); + assertThat(why.get(0).ordinal()).as(reason).isEqualTo(ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_MAC_ENGINE.ordinal()); + assertThat(reason).contains("unapproved RNG"); + } + + /** + A provider refusing to generate a cipher key with an Error is reported as a refusal of the + cipher transformation. + */ + @Test + public void testCipherKeyGenerationRefusedWithAnErrorIsReported() throws Exception + { + final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); + final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); + final String transformation = "AES/CTR/NoPadding"; + assertThat(transformation).isNotEqualTo(cfg.getCipherTransformation()); + final List why = new ArrayList<>(); + + final boolean acceptable = inApprovedOnlyBcFipsThread(() -> cm.isConfigurationChangeAcceptable( + withProperty(cfg, "getCipherTransformation", transformation), why)); + + assertThat(acceptable).isFalse(); + assertThat(why).hasSize(1); + final String reason = why.get(0).toString(); + assertThat(why.get(0).ordinal()).as(reason).isEqualTo(ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_ENCRYPTION_CIPHER.ordinal()); + assertThat(reason).contains("unapproved RNG"); + } + + /** The check does not need an MD5 digest, which a restricted runtime may not offer. */ + @Test + public void testKeyWrappingCheckDoesNotNeedMd5() throws Exception + { + final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); + final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); + final String supported = "RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING"; + assertThat(supported).isNotEqualTo(cfg.getKeyWrappingTransformation()); + final List why = new ArrayList<>(); + + // Withdrawing MD5 withdraws the SUN provider, and with it what the rest of the check needs of + // that provider: the SHA-1 digest of the OAEP cipher and of the default random generator the + // cipher is initialized with, the SHA-256 digest of the HmacSHA256 MAC of SunJCE, and the + // X.509 certificate factory. + final Provider sunWithoutMd5 = new Provider("SunWithoutMd5", "1.0", "SUN services the check needs") {}; + sunWithoutMd5.put("MessageDigest.SHA-1", "sun.security.provider.SHA"); + sunWithoutMd5.put("Alg.Alias.MessageDigest.SHA1", "SHA-1"); + sunWithoutMd5.put("Alg.Alias.MessageDigest.SHA", "SHA-1"); + sunWithoutMd5.put("MessageDigest.SHA-256", "sun.security.provider.SHA2$SHA256"); + sunWithoutMd5.put("CertificateFactory.X.509", "sun.security.provider.X509Factory"); withoutJceService("MessageDigest", "MD5", () -> { assertThat(cm.isConfigurationChangeAcceptable(withProperty(cfg, "getKeyWrappingTransformation", supported), why)) - .isFalse(); + .as("%s", why).isTrue(); return null; - }, sha1Only); + }, sunWithoutMd5); + } - assertThat(why).hasSize(1); - final String reason = why.get(0).toString(); - assertThat(why.get(0).ordinal()).as(reason).isEqualTo(ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.ordinal()); - assertThat(reason).contains("MD5").doesNotContain("key-wrapping-transformation"); + /** + Returns the crypto manager configuration with a MAC algorithm which BC-FIPS does not offer in + approved-only mode, so that SunJCE generates the MAC key the check wraps. The crypto manager + generates keys with a random generator of the provider that came first when it was loaded, SUN + in the test JVM, and BC-FIPS in approved-only mode refuses to generate a key with it. + */ + private static CryptoManagerCfg withMacKeyFromSunJce(final CryptoManagerCfg cfg) + { + return withProperty(cfg, "getMacAlgorithm", "HmacMD5"); + } + + /** + Runs {@code action} in a thread of its own which BC-FIPS serves in approved-only mode (a mode + a thread cannot leave), with that provider installed first for the duration. + */ + private static T inApprovedOnlyBcFipsThread(final Callable action) throws Exception + { + final List installed = Arrays.asList(Security.getProviders()); + final Provider previous = Security.getProvider("BCFIPS"); + Security.removeProvider("BCFIPS"); + Security.insertProviderAt(previous != null ? previous : new BouncyCastleFipsProvider(), 1); + try + { + final FutureTask task = new FutureTask<>(() -> + { + assertThat(CryptoServicesRegistrar.setApprovedOnlyMode(true)).isTrue(); + return action.call(); + }); + new Thread(task, "approved-only BC-FIPS").start(); + try + { + return task.get(1, TimeUnit.MINUTES); + } + catch (ExecutionException e) + { + throw new AssertionError("the action failed in approved-only mode", e.getCause()); + } + } + finally + { + Security.removeProvider("BCFIPS"); + if (previous != null) + { + Security.insertProviderAt(previous, installed.indexOf(previous) + 1); + } + } } /** From cf821bd67d322bee43168fb4b78ef1e5af2dbcfc Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 25 Sep 2026 17:28:17 +0300 Subject: [PATCH 2/2] [#1081] Rethrow a LinkageError from a provider as well, and pin the rethrow Review round 1 of #1104. - A LinkageError from a broken provider jar (ExceptionInInitializerError, NoClassDefFoundError) is not a refusal either: reported as one, it lost its cause, since a refusal carries only a message. The helper, renamed rethrowIfNotARefusal, rethrows it along with VirtualMachineError. The BC-FIPS refusals extend AssertionError and stay refusals. - testErrorWhichIsNotARefusalPropagates fails the cipher, MAC and key wrapping checks through a probe provider with a StackOverflowError and an ExceptionInInitializerError, and expects each to propagate. --- .../server/crypto/CryptoManagerImpl.java | 16 +- .../server/crypto/CryptoManagerTestCase.java | 168 ++++++++++++++++++ 2 files changed, 177 insertions(+), 7 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java index ea1ec1878e..a2eb090654 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java @@ -352,7 +352,7 @@ public boolean isConfigurationChangeAcceptable( requestedCipherTransformationKeyLengthBits); } catch (Exception | Error ex) { - rethrowIfVirtualMachineError(ex); + rethrowIfNotARefusal(ex); logger.traceException(ex); unacceptableReasons.add( ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_ENCRYPTION_CIPHER.get( @@ -375,7 +375,7 @@ public boolean isConfigurationChangeAcceptable( requestedMACAlgorithmKeyLengthBits); } catch (Exception | Error ex) { - rethrowIfVirtualMachineError(ex); + rethrowIfNotARefusal(ex); logger.traceException(ex); unacceptableReasons.add( ERR_CRYPTOMGR_CANNOT_GET_REQUESTED_MAC_ENGINE.get( @@ -431,7 +431,7 @@ else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformatio "key-wrapping-check", certificate, macKey); } catch (Exception | Error ex) { - rethrowIfVirtualMachineError(ex); + rethrowIfNotARefusal(ex); logger.traceException(ex); unacceptableReasons.add( ERR_CRYPTOMGR_CANNOT_GET_PREFERRED_KEY_WRAPPING_CIPHER.get( @@ -446,13 +446,15 @@ else if (!isKeyWrappingTransformationSupported(requestedKeyWrappingTransformatio /** * Rethrows what no configuration can be refused for. The checks ask the JCE providers for keys * and ciphers, and a provider may refuse with an Error rather than an exception: the BC-FIPS - * provider in approved-only mode throws its FipsUnapprovedOperationError. + * provider in approved-only mode throws its FipsUnapprovedOperationError. A VirtualMachineError + * is not a refusal, and neither is a LinkageError, which a broken provider jar throws: reported + * as a refusal, it would lose its cause, since a refusal carries only a message. */ - private static void rethrowIfVirtualMachineError(final Throwable t) + private static void rethrowIfNotARefusal(final Throwable t) { - if (t instanceof VirtualMachineError) + if (t instanceof VirtualMachineError || t instanceof LinkageError) { - throw (VirtualMachineError) t; + throw (Error) t; } } diff --git a/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java index 9dbee3787e..699993498e 100644 --- a/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java +++ b/opendj-server-legacy/src/test/java/org/opends/server/crypto/CryptoManagerTestCase.java @@ -38,9 +38,13 @@ import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Path; +import java.security.AlgorithmParameters; +import java.security.Key; import java.security.MessageDigest; import java.security.Provider; +import java.security.SecureRandom; import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -51,7 +55,10 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; +import javax.crypto.CipherSpi; +import javax.crypto.KeyGeneratorSpi; import javax.crypto.Mac; +import javax.crypto.SecretKey; import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; @@ -446,6 +453,167 @@ public void testKeyWrappingCheckDoesNotNeedMd5() throws Exception }, sunWithoutMd5); } + /** The error the probe provider fails with, set by the case using it. */ + private static volatile Error probeError; + + @DataProvider + public Object[][] errorsWhichAreNotRefusals() + { + final List cases = new ArrayList<>(); + for (final String[] check : new String[][] { + { "getCipherTransformation", "ErrorProbe/CBC/PKCS5Padding" }, + { "getMacAlgorithm", "ErrorProbe" }, + { "getKeyWrappingTransformation", "ErrorProbeWrap/ECB/NoPadding" } }) + { + cases.add(new Object[] { check[0], check[1], new StackOverflowError("probe") }); + cases.add(new Object[] { check[0], check[1], new ExceptionInInitializerError(new IllegalStateException("probe")) }); + } + return cases.toArray(new Object[0][]); + } + + /** + An Error which is not a refusal of the configuration, of the virtual machine or of a broken + provider jar, propagates out of the cipher, MAC and key wrapping checks with its cause. + */ + @Test(dataProvider = "errorsWhichAreNotRefusals") + public void testErrorWhichIsNotARefusalPropagates(final String getter, final String value, final Error error) + throws Exception + { + final CryptoManagerImpl cm = DirectoryServer.getCryptoManager(); + final CryptoManagerCfg cfg = getServerContext().getRootConfig().getCryptoManager(); + final Provider probe = new Provider("ErrorProbe", "1.0", "Fails with the error of the case") {}; + probe.put("KeyGenerator.ErrorProbe", FailingKeyGenerator.class.getName()); + probe.put("Cipher.ErrorProbeWrap", FailingCipher.class.getName()); + probeError = error; + Security.insertProviderAt(probe, 1); + try + { + final List why = new ArrayList<>(); + assertThatThrownBy(() -> cm.isConfigurationChangeAcceptable(withProperty(cfg, getter, value), why)) + .isSameAs(error); + assertThat(why).isEmpty(); + } + finally + { + Security.removeProvider("ErrorProbe"); + probeError = null; + } + } + + /** A key generator failing with the error of the case. */ + public static final class FailingKeyGenerator extends KeyGeneratorSpi + { + @Override + protected void engineInit(final SecureRandom random) + { + throw probeError; + } + + @Override + protected void engineInit(final AlgorithmParameterSpec params, final SecureRandom random) + { + throw probeError; + } + + @Override + protected void engineInit(final int keySize, final SecureRandom random) + { + throw probeError; + } + + @Override + protected SecretKey engineGenerateKey() + { + throw probeError; + } + } + + /** A cipher which is found, and fails with the error of the case once initialized. */ + public static final class FailingCipher extends CipherSpi + { + @Override + protected void engineSetMode(final String mode) + { + // Any mode. + } + + @Override + protected void engineSetPadding(final String padding) + { + // Any padding. + } + + @Override + protected int engineGetBlockSize() + { + return 0; + } + + @Override + protected int engineGetOutputSize(final int inputLen) + { + return 0; + } + + @Override + protected byte[] engineGetIV() + { + return null; + } + + @Override + protected AlgorithmParameters engineGetParameters() + { + return null; + } + + @Override + protected void engineInit(final int opmode, final Key key, final SecureRandom random) + { + throw probeError; + } + + @Override + protected void engineInit(final int opmode, final Key key, final AlgorithmParameterSpec params, + final SecureRandom random) + { + throw probeError; + } + + @Override + protected void engineInit(final int opmode, final Key key, final AlgorithmParameters params, + final SecureRandom random) + { + throw probeError; + } + + @Override + protected byte[] engineUpdate(final byte[] input, final int inputOffset, final int inputLen) + { + throw probeError; + } + + @Override + protected int engineUpdate(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, + final int outputOffset) + { + throw probeError; + } + + @Override + protected byte[] engineDoFinal(final byte[] input, final int inputOffset, final int inputLen) + { + throw probeError; + } + + @Override + protected int engineDoFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, + final int outputOffset) + { + throw probeError; + } + } + /** Returns the crypto manager configuration with a MAC algorithm which BC-FIPS does not offer in approved-only mode, so that SunJCE generates the MAC key the check wraps. The crypto manager