From af17d0efbf14882858210e6fc40c7ed949ef9a3e Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 24 Jan 2022 16:56:34 +0000 Subject: [PATCH] Add BouncyCastle providers to generated AutoFeature --- .../deployment/SecurityProcessor.java | 110 +++++++++++++++--- .../runtime/SecurityProviderUtils.java | 8 +- .../graal/BouncyCastleSubstitutions.java | 22 ++++ .../BouncyCastleFipsJsseTestCase.java | 22 ++-- .../bouncycastle/BouncyCastleJsseITCase.java | 2 - .../it/bouncycastle/BouncyCastleEndpoint.java | 16 +++ .../it/bouncycastle/BouncyCastleTestCase.java | 20 ++++ 7 files changed, 172 insertions(+), 28 deletions(-) diff --git a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java index 657a8f65f898aa..b09adcbe32a364 100644 --- a/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java +++ b/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java @@ -1,5 +1,8 @@ package io.quarkus.security.deployment; +import static io.quarkus.gizmo.MethodDescriptor.ofMethod; +import static io.quarkus.security.runtime.SecurityProviderUtils.findProviderIndex; + import java.io.IOException; import java.lang.reflect.Modifier; import java.net.MalformedURLException; @@ -42,12 +45,18 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.GeneratedNativeImageClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.JPMSExportBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem; +import io.quarkus.gizmo.CatchBlockCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import io.quarkus.runtime.RuntimeValue; import io.quarkus.security.runtime.IdentityProviderManagerCreator; import io.quarkus.security.runtime.SecurityBuildTimeConfig; @@ -149,10 +158,15 @@ private static void prepareBouncyCastleProvider(BuildProducer additionalProviders, + void addBouncyCastleProvidersToNativeImage( + BuildProducer nativeImageClass, + BuildProducer additionalProviders, List bouncyCastleProviders, List bouncyCastleJsseProviders) { Optional bouncyCastleJsseProvider = getOne(bouncyCastleJsseProviders); if (bouncyCastleJsseProvider.isPresent()) { additionalProviders.produce( new NativeImageSecurityProviderBuildItem(SecurityProviderUtils.BOUNCYCASTLE_JSSE_PROVIDER_CLASS_NAME)); - final String providerName = bouncyCastleJsseProvider.get().isInFipsMode() - ? SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME - : SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME; - additionalProviders.produce(new NativeImageSecurityProviderBuildItem(providerName)); + + ClassCreator file = createBouncyCastleFeatureCreator(nativeImageClass); + MethodCreator beforeAn = createBeforeAnalysisMethod(file); + TryBlock overallCatch = beforeAn.tryBlock(); + + if (!bouncyCastleJsseProvider.get().isInFipsMode()) { + final int sunJsseIndex = findProviderIndex(SecurityProviderUtils.SUN_JSSE_PROVIDER_NAME); + + ResultHandle bcProvider = overallCatch + .newInstance( + MethodDescriptor.ofConstructor(SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME)); + ResultHandle bcJsseProvider = overallCatch.newInstance( + MethodDescriptor.ofConstructor(SecurityProviderUtils.BOUNCYCASTLE_JSSE_PROVIDER_CLASS_NAME)); + + overallCatch.invokeStaticMethod( + MethodDescriptor.ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, + int.class), + bcProvider, overallCatch.load(sunJsseIndex)); + overallCatch.invokeStaticMethod( + MethodDescriptor.ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, + int.class), + bcJsseProvider, overallCatch.load(sunJsseIndex + 1)); + } else { + final int sunIndex = findProviderIndex(SecurityProviderUtils.SUN_PROVIDER_NAME); + + ResultHandle bcFipsProvider = overallCatch + .newInstance(MethodDescriptor + .ofConstructor(SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME)); + MethodDescriptor bcJsseProviderConstructor = MethodDescriptor.ofConstructor( + SecurityProviderUtils.BOUNCYCASTLE_JSSE_PROVIDER_CLASS_NAME, "boolean", + Provider.class.getName()); + ResultHandle bcJsseProvider = overallCatch.newInstance(bcJsseProviderConstructor, + overallCatch.load(true), bcFipsProvider); + + overallCatch.invokeStaticMethod( + MethodDescriptor.ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, + int.class), + bcFipsProvider, overallCatch.load(sunIndex)); + overallCatch.invokeStaticMethod( + MethodDescriptor.ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, + int.class), + bcJsseProvider, overallCatch.load(sunIndex + 1)); + } + completeBouncyCastleFeature(file, beforeAn, overallCatch); + } else { Optional bouncyCastleProvider = getOne(bouncyCastleProviders); if (bouncyCastleProvider.isPresent()) { final String providerName = bouncyCastleProvider.get().isInFipsMode() ? SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME : SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME; - additionalProviders.produce(new NativeImageSecurityProviderBuildItem(providerName)); + + ClassCreator file = createBouncyCastleFeatureCreator(nativeImageClass); + MethodCreator beforeAn = createBeforeAnalysisMethod(file); + TryBlock overallCatch = beforeAn.tryBlock(); + + ResultHandle bcProvider = overallCatch.newInstance(MethodDescriptor.ofConstructor(providerName)); + overallCatch.invokeStaticMethod( + MethodDescriptor.ofMethod(Security.class, "addProvider", int.class, Provider.class), + bcProvider); + + completeBouncyCastleFeature(file, beforeAn, overallCatch); } } } + private ClassCreator createBouncyCastleFeatureCreator(BuildProducer nativeImageClass) { + ClassCreator file = new ClassCreator(new ClassOutput() { + @Override + public void write(String s, byte[] bytes) { + nativeImageClass.produce(new GeneratedNativeImageClassBuildItem(s, bytes)); + } + }, "io.quarkus.security.BouncyCastleFeature", null, Object.class.getName(), + org.graalvm.nativeimage.hosted.Feature.class.getName()); + file.addAnnotation("com.oracle.svm.core.annotate.AutomaticFeature"); + return file; + } + + private static MethodCreator createBeforeAnalysisMethod(ClassCreator file) { + return file.getMethodCreator("beforeAnalysis", "V", + org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess.class.getName()); + } + + private static void completeBouncyCastleFeature(ClassCreator file, MethodCreator beforeAn, TryBlock overallCatch) { + CatchBlockCreator print = overallCatch.addCatch(Throwable.class); + print.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), print.getCaughtException()); + beforeAn.returnValue(null); + file.close(); + } + // Work around https://github.com/quarkusio/quarkus/issues/21374 @BuildStep void addBouncyCastleExportsToNativeImage(BuildProducer jpmsExports, diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderUtils.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderUtils.java index 856bac45d8d7c5..32370b8c110240 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderUtils.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderUtils.java @@ -24,7 +24,9 @@ private SecurityProviderUtils() { public static void addProvider(Provider provider) { try { - Security.addProvider(provider); + if (Security.getProvider(provider.getName()) == null) { + Security.addProvider(provider); + } } catch (Exception t) { final String errorMessage = String.format("Security provider %s can not be added", provider.getName()); throw new ConfigurationException(errorMessage, t); @@ -33,7 +35,9 @@ public static void addProvider(Provider provider) { public static void insertProvider(Provider provider, int index) { try { - Security.insertProviderAt(provider, index); + if (Security.getProvider(provider.getName()) == null) { + Security.insertProviderAt(provider, index); + } } catch (Exception t) { final String errorMessage = String.format("Security provider %s can not be inserted", provider.getName()); throw new ConfigurationException(errorMessage, t); diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java index c22895906b594e..b647e93b36ccc2 100644 --- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java +++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java @@ -1,10 +1,14 @@ package io.quarkus.security.runtime.graal; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Set; import java.util.function.BooleanSupplier; import java.util.stream.Collectors; +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; + final class BouncyCastlePackages { static final String ORG_BOUNCYCASTLE_CRYPTO_PACKAGE = "org.bouncycastle.crypto"; static final String ORG_BOUNCYCASTLE_CRYPTO_FIPS_PACKAGE = "org.bouncycastle.crypto.fips"; @@ -86,6 +90,24 @@ final class Target_org_bouncycastle_crypto_internal_AsymmetricCipherKeyPair { final class Target_org_bouncycastle_crypto_fips_RsaBlindedEngine { } +@com.oracle.svm.core.annotate.TargetClass(className = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider", onlyWith = BouncyCastleCryptoFips.class) +final class Target_org_bouncycastle_jcajce_provider_BouncyCastleFipsProvider { + @Alias + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + private SecureRandom entropySource; + + @Alias + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + private SecureRandom providerDefaultRandom; +} + +@com.oracle.svm.core.annotate.TargetClass(className = "org.bouncycastle.math.ec.ECPoint", onlyWith = BouncyCastleCryptoFips.class) +final class Target_org_bouncycastle_math_ec_ECPoint { + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + private static SecureRandom testRandom; +} + class BouncyCastleCryptoFips implements BooleanSupplier { @Override public boolean getAsBoolean() { diff --git a/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java b/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java index f60e9b3ec1213f..ad3375320991b7 100644 --- a/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java +++ b/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java @@ -16,8 +16,6 @@ import java.nio.file.Paths; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; - import org.awaitility.core.ThrowingRunnable; import org.jboss.logging.Logger; import org.junit.jupiter.api.Assertions; @@ -41,9 +39,6 @@ public class BouncyCastleFipsJsseTestCase { @TestHTTPResource(ssl = true) URL url; - @Inject - Vertx vertx; - @Test public void testListProviders() throws Exception { doTestListProviders(); @@ -51,12 +46,17 @@ public void testListProviders() throws Exception { } protected void doTestListProviders() throws Exception { - WebClientOptions options = createWebClientOptions(); - WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); - HttpResponse resp = webClient.get("/jsse/listProviders").send().await() - .indefinitely(); - String providers = resp.bodyAsString(); - assertTrue(providers.contains("BCFIPS,BCJSSE")); + Vertx vertx = Vertx.vertx(); + try { + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + HttpResponse resp = webClient.get("/jsse/listProviders").send().await() + .indefinitely(); + String providers = resp.bodyAsString(); + assertTrue(providers.contains("BCFIPS,BCJSSE")); + } finally { + vertx.close(); + } } private WebClientOptions createWebClientOptions() throws Exception { diff --git a/integration-tests/bouncycastle-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleJsseITCase.java b/integration-tests/bouncycastle-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleJsseITCase.java index dc7f16cc2e4541..370e9ba9334750 100644 --- a/integration-tests/bouncycastle-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleJsseITCase.java +++ b/integration-tests/bouncycastle-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleJsseITCase.java @@ -2,13 +2,11 @@ import org.junit.jupiter.api.Test; -import io.quarkus.test.junit.DisabledOnNativeImage; import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest public class BouncyCastleJsseITCase extends BouncyCastleJsseTestCase { @Test - @DisabledOnNativeImage @Override public void testListProviders() { doTestListProviders(); diff --git a/integration-tests/bouncycastle/src/main/java/io/quarkus/it/bouncycastle/BouncyCastleEndpoint.java b/integration-tests/bouncycastle/src/main/java/io/quarkus/it/bouncycastle/BouncyCastleEndpoint.java index 51bfeda246ac76..1fd1a0850548b3 100644 --- a/integration-tests/bouncycastle/src/main/java/io/quarkus/it/bouncycastle/BouncyCastleEndpoint.java +++ b/integration-tests/bouncycastle/src/main/java/io/quarkus/it/bouncycastle/BouncyCastleEndpoint.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.stream.Collectors; +import javax.crypto.Cipher; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -45,6 +46,14 @@ public String generateEcKeyPair() throws Exception { return "success"; } + @GET + @Path("generateEcDsaKeyPair") + public String generateEcDsaKeyPair() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "BC"); + keyPairGenerator.generateKeyPair(); + return "success"; + } + @GET @Path("generateRsaKeyPair") public String generateRsaKeyPair() throws Exception { @@ -53,6 +62,13 @@ public String generateRsaKeyPair() throws Exception { return "success"; } + @GET + @Path("checkAesCbcPKCS7PaddingCipher") + public String checkAesCbcPKCS7PaddingCipher() throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC"); + return cipher.getAlgorithm(); + } + @GET @Path("readEcPrivatePemKey") public String readEcPrivatePemKey() throws Exception { diff --git a/integration-tests/bouncycastle/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleTestCase.java b/integration-tests/bouncycastle/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleTestCase.java index de70dbe2986331..283e11d8505195 100644 --- a/integration-tests/bouncycastle/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleTestCase.java +++ b/integration-tests/bouncycastle/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleTestCase.java @@ -40,6 +40,16 @@ public void testGenerateEcKeyPair() { .body(equalTo("success")); } + @Test + public void testGenerateEcDsaKeyPair() { + RestAssured.given() + .when() + .get("/jca/generateEcDsaKeyPair") + .then() + .statusCode(200) + .body(equalTo("success")); + } + @Test public void testGenerateRsaKeyPair() { RestAssured.given() @@ -50,6 +60,16 @@ public void testGenerateRsaKeyPair() { .body(equalTo("success")); } + @Test + public void testAesCbcPKCS7PaddingCipher() { + RestAssured.given() + .when() + .get("/jca/checkAesCbcPKCS7PaddingCipher") + .then() + .statusCode(200) + .body(equalTo("AES/CBC/PKCS7Padding")); + } + @Test public void readEcPrivatePemKey() { RestAssured.given()