Skip to content

Commit 540fe14

Browse files
authored
Merge pull request #46307 from michalvavrik/feature/fix-bc-provider-in-netty-ssl
Workaround Netty SSL Bouncycastle issue in native mode
2 parents 863abd5 + d22295c commit 540fe14

File tree

6 files changed

+116
-2
lines changed

6 files changed

+116
-2
lines changed

extensions/security/runtime/src/main/java/io/quarkus/security/runtime/graal/BouncyCastleSubstitutions.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.quarkus.security.runtime.graal;
22

3+
import java.security.Provider;
34
import java.security.SecureRandom;
5+
import java.security.Security;
46
import java.util.Arrays;
57
import java.util.Set;
68
import java.util.function.BooleanSupplier;
@@ -9,13 +11,18 @@
911
import com.oracle.svm.core.annotate.Alias;
1012
import com.oracle.svm.core.annotate.RecomputeFieldValue;
1113

14+
import io.quarkus.security.runtime.SecurityProviderUtils;
15+
1216
final class BouncyCastlePackages {
1317
static final String ORG_BOUNCYCASTLE_CRYPTO_PACKAGE = "org.bouncycastle.crypto";
1418
static final String ORG_BOUNCYCASTLE_CRYPTO_FIPS_PACKAGE = "org.bouncycastle.crypto.fips";
1519
static final String ORG_BOUNCYCASTLE_CRYPTO_INTERNAL_PACKAGE = "org.bouncycastle.crypto.internal";
1620
static final String ORG_BOUNCYCASTLE_CRYPTO_GENERAL_PACKAGE = "org.bouncycastle.crypto.general";
21+
static final String ORG_BOUNCYCASTLE_OPENSSL_PACKAGE = "org.bouncycastle.openssl";
1722
static final Set<String> PACKAGES = Arrays.asList(Package.getPackages()).stream()
18-
.map(p -> p.getName()).filter(p -> p.startsWith(ORG_BOUNCYCASTLE_CRYPTO_PACKAGE)).collect(Collectors.toSet());
23+
.map(Package::getName)
24+
.filter(p -> p.startsWith(ORG_BOUNCYCASTLE_CRYPTO_PACKAGE) || p.startsWith(ORG_BOUNCYCASTLE_OPENSSL_PACKAGE))
25+
.collect(Collectors.toSet());
1926
}
2027

2128
@com.oracle.svm.core.annotate.TargetClass(className = "org.bouncycastle.crypto.general.DSA$1", onlyWith = BouncyCastleCryptoGeneral.class)
@@ -104,6 +111,59 @@ final class Target_org_bouncycastle_math_ec_ECPoint {
104111
private static SecureRandom testRandom;
105112
}
106113

114+
// TODO: this should be removed when https://github.com/netty/netty/issues/14826 is addressed
115+
// this substitution can be removed when io.quarkus.it.bouncycastle.BouncyCastleITCase#loadNettySslContext passes
116+
@com.oracle.svm.core.annotate.TargetClass(className = "io.netty.handler.ssl.BouncyCastlePemReader", onlyWith = NettySslBountyCastleSupportRequired.class)
117+
final class Target_io_netty_handler_ssl_BouncyCastlePemReader {
118+
@Alias
119+
private static volatile Provider bcProvider;
120+
@Alias
121+
private static volatile boolean attemptedLoading;
122+
@Alias
123+
private static volatile Throwable unavailabilityCause;
124+
125+
@com.oracle.svm.core.annotate.Substitute
126+
public static boolean isAvailable() {
127+
if (!attemptedLoading) {
128+
// do what io.netty.handler.ssl.BouncyCastlePemReader.tryLoading does
129+
// however Netty creates a new provider instance that doesn't have all the services
130+
// while we take already created provider with all registered services
131+
bcProvider = Security.getProvider(SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_NAME);
132+
if (bcProvider == null) {
133+
bcProvider = Security.getProvider(SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_NAME);
134+
}
135+
if (bcProvider == null) {
136+
tryLoading();
137+
} else {
138+
attemptedLoading = true;
139+
}
140+
}
141+
return unavailabilityCause == null;
142+
}
143+
144+
@Alias
145+
private static void tryLoading() {
146+
147+
}
148+
}
149+
150+
class NettySslBountyCastleSupportRequired implements BooleanSupplier {
151+
@Override
152+
public boolean getAsBoolean() {
153+
// this package is used by the BouncyCastlePemReader and present in 'org.bouncycastle:bcpkix-jdk18on'
154+
if (BouncyCastlePackages.PACKAGES.contains(BouncyCastlePackages.ORG_BOUNCYCASTLE_OPENSSL_PACKAGE)) {
155+
try {
156+
Class.forName("io.netty.handler.ssl.BouncyCastlePemReader", false,
157+
Thread.currentThread().getContextClassLoader());
158+
return true;
159+
} catch (Throwable e) {
160+
// class not available
161+
}
162+
}
163+
return false;
164+
}
165+
}
166+
107167
class BouncyCastleCryptoFips implements BooleanSupplier {
108168
@Override
109169
public boolean getAsBoolean() {
@@ -123,4 +183,4 @@ class BouncyCastleCryptoInternal implements BooleanSupplier {
123183
public boolean getAsBoolean() {
124184
return BouncyCastlePackages.PACKAGES.contains(BouncyCastlePackages.ORG_BOUNCYCASTLE_CRYPTO_INTERNAL_PACKAGE);
125185
}
126-
}
186+
}

integration-tests/bouncycastle/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
<artifactId>quarkus-security</artifactId>
2525
</dependency>
2626

27+
<!-- dependency for Netty SSL context loading test -->
28+
<dependency>
29+
<groupId>org.bouncycastle</groupId>
30+
<artifactId>bcpkix-jdk18on</artifactId>
31+
</dependency>
32+
2733
<!-- test dependencies -->
2834
<dependency>
2935
<groupId>org.bouncycastle</groupId>

integration-tests/bouncycastle/src/main/java/io/quarkus/it/bouncycastle/BouncyCastleEndpoint.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.bouncycastle.util.io.pem.PemObject;
2121
import org.bouncycastle.util.io.pem.PemReader;
2222

23+
import io.netty.handler.ssl.SslContextBuilder;
24+
2325
@Path("/jca")
2426
public class BouncyCastleEndpoint {
2527

@@ -103,4 +105,15 @@ public String readRsaPrivatePemKey() throws Exception {
103105
return ecPrivateKey.getPrivateExponent() != null ? "success" : "failure";
104106
}
105107
}
108+
109+
@GET
110+
@Path("loadNettySslContext")
111+
public String loadNettySslContext() throws Exception {
112+
var classLoader = Thread.currentThread().getContextClassLoader();
113+
try (var privateKey = classLoader.getResourceAsStream("pkcs1-key.pem");
114+
var certificate = classLoader.getResourceAsStream("certificate.pem")) {
115+
var sslcontext = SslContextBuilder.forClient().keyManager(certificate, privateKey).build();
116+
return Arrays.toString(sslcontext.cipherSuites().toArray());
117+
}
118+
}
106119
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICbDCCAdWgAwIBAgIJALeUXoWyGYBYMA0GCSqGSIb3DQEBBQUAMCoxGzAZBgNV
3+
BAMMEmh4NTA5IFRlc3QgUm9vdCBDQTELMAkGA1UEBhMCU0UwHhcNMDcxMTE1MDY1
4+
ODU2WhcNMTcxMTEyMDY1ODU2WjAqMRswGQYDVQQDDBJoeDUwOSBUZXN0IFJvb3Qg
5+
Q0ExCzAJBgNVBAYTAlNFMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHcvJb
6+
yJXPhM9HHq1hU6d2Cu1fW9o1CvObirn1SNZg+pTnQgO9Lv4VjQQfltNK0aovyLJa
7+
UdbAbsRCfH+79YY2tU76x8aXpUri0DfUv5PGscIZzW7WULaaXxBgHo1owzmhc1Qj
8+
F9JDEurJXGFEZaDsPcEwY40RjrKDL8SXzEoEwwIDAQABo4GZMIGWMB0GA1UdDgQW
9+
BBSM5w21xd5phXUsCKHeUxUwnKHoADBaBgNVHSMEUzBRgBSM5w21xd5phXUsCKHe
10+
UxUwnKHoAKEupCwwKjEbMBkGA1UEAwwSaHg1MDkgVGVzdCBSb290IENBMQswCQYD
11+
VQQGEwJTRYIJALeUXoWyGYBYMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgHmMA0G
12+
CSqGSIb3DQEBBQUAA4GBAIBa6mq1aytlbhixD6q4PROg7P1OGX6nr5CkC96CC+Xp
13+
5UTLZEVIddkrBswNAAS0p5eEorO8xD9eT5ztZ0oYITymsO1sEIfDLks+LhdBoyF7
14+
TX24INRwjlqsC8UlbRFoClxIMNhrMwcC3oZ4oLddV2OmA0IOG6yHXvEOQq0sTotr
15+
-----END CERTIFICATE-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIBdVHnnzZmJm+Z1HAYYOZlvnB8Dj8kVx9XBH+6UCWlGUoAoGCCqGSM49
3+
AwEHoUQDQgAEThPp/xgEov0mKg2s0GII76VkZAcCc//3quAqzg+PuFKXgruaF7Kn
4+
3tuQVWHBlyZX56oOstUYQh3418Z3Gb1+yw==
5+
-----END EC PRIVATE KEY-----

integration-tests/bouncycastle/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleTestCase.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.hamcrest.Matchers.equalTo;
44

5+
import org.hamcrest.Matchers;
56
import org.junit.jupiter.api.Test;
67

78
import io.quarkus.test.junit.QuarkusTest;
@@ -89,4 +90,18 @@ public void readRsaPrivatePemKey() {
8990
.statusCode(200)
9091
.body(equalTo("success"));
9192
}
93+
94+
@Test
95+
public void loadNettySslContext() {
96+
// this tests that io.netty.handler.ssl.BouncyCastlePemReader used by Netty SSL context
97+
// works in native; it is used when 'org.bouncycastle:bcpkix-jdk18on' dependency is present
98+
// even for other standards, not just PKCS1 used by this test, however for these the test could pass
99+
// because of Netty SSL context has other strategies, not just BC
100+
RestAssured.given()
101+
.when()
102+
.get("/jca/loadNettySslContext")
103+
.then()
104+
.statusCode(200)
105+
.body(Matchers.notNullValue());
106+
}
92107
}

0 commit comments

Comments
 (0)