Skip to content

Commit

Permalink
Add BouncyCastle providers to generated AutoFeature
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Mar 7, 2022
1 parent bfb30a8 commit af17d0e
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -149,10 +158,15 @@ private static void prepareBouncyCastleProvider(BuildProducer<ReflectiveClassBui
isFipsMode ? SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME
: SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME));
reflection.produce(new ReflectiveClassBuildItem(true, true,
"org.bouncycastle.jcajce.provider.symmetric.AES",
"org.bouncycastle.jcajce.provider.symmetric.AES$CBC",
"org.bouncycastle.crypto.paddings.PKCS7Padding",
"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi",
"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi$EC",
"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi$ECDSA",
"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi",
"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi$EC",
"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi$ECDSA",
"org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi",
"org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyPairGeneratorSpi",
"org.bouncycastle.jcajce.provider.asymmetric.rsa.PSSSignatureSpi",
Expand All @@ -169,13 +183,6 @@ private static void prepareBouncyCastleProvider(BuildProducer<ReflectiveClassBui
} else {
reflection.produce(new ReflectiveClassBuildItem(true, true, true, "org.bouncycastle.crypto.general.AES"));
runtimeReInitialized.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.crypto.general.AES"));
runtimeReInitialized
.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.math.ec.custom.sec.SecP521R1Curve"));
runtimeReInitialized
.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.math.ec.custom.sec.SecP384R1Curve"));
runtimeReInitialized
.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.math.ec.custom.sec.SecP256R1Curve"));
runtimeReInitialized.produce(new RuntimeReinitializedClassBuildItem("org.bouncycastle.math.ec.ECPoint"));
runtimeReInitialized
.produce(new RuntimeReinitializedClassBuildItem(
"org.bouncycastle.crypto.asymmetric.NamedECDomainParameters"));
Expand Down Expand Up @@ -221,28 +228,105 @@ void recordBouncyCastleProviders(SecurityProviderRecorder recorder,
}

@BuildStep
void addBouncyCastleProvidersToNativeImage(BuildProducer<NativeImageSecurityProviderBuildItem> additionalProviders,
void addBouncyCastleProvidersToNativeImage(
BuildProducer<GeneratedNativeImageClassBuildItem> nativeImageClass,
BuildProducer<NativeImageSecurityProviderBuildItem> additionalProviders,
List<BouncyCastleProviderBuildItem> bouncyCastleProviders,
List<BouncyCastleJsseProviderBuildItem> bouncyCastleJsseProviders) {
Optional<BouncyCastleJsseProviderBuildItem> 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<BouncyCastleProviderBuildItem> 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<GeneratedNativeImageClassBuildItem> 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<JPMSExportBuildItem> jpmsExports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,22 +39,24 @@ public class BouncyCastleFipsJsseTestCase {
@TestHTTPResource(ssl = true)
URL url;

@Inject
Vertx vertx;

@Test
public void testListProviders() throws Exception {
doTestListProviders();
checkLog(false);
}

protected void doTestListProviders() throws Exception {
WebClientOptions options = createWebClientOptions();
WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options);
HttpResponse<io.vertx.mutiny.core.buffer.Buffer> 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<io.vertx.mutiny.core.buffer.Buffer> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down

0 comments on commit af17d0e

Please sign in to comment.