Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[native image] Bouncycastle JSSE fails with GraalVM 21.1 #17046

Closed
zakkak opened this issue May 6, 2021 · 7 comments · Fixed by #23527
Closed

[native image] Bouncycastle JSSE fails with GraalVM 21.1 #17046

zakkak opened this issue May 6, 2021 · 7 comments · Fixed by #23527
Assignees
Milestone

Comments

@zakkak
Copy link
Contributor

zakkak commented May 6, 2021

Describe the bug

Bouncycastle JSSE fails (at runtime) with GraalVM 21.1

Expected behavior

The test should build and run

Actual behavior

The test compiles but fails with:

Executing [/home/runner/work/quarkus/quarkus/integration-tests/bouncycastle-jsse/target/quarkus-integration-test-bouncycastle-jsse-999-SNAPSHOT-runner, -Dquarkus.http.port=8081, -Dquarkus.http.ssl-port=8444, -Dtest.url=http://localhost:8081, -Dquarkus.log.file.path=/home/runner/work/quarkus/quarkus/integration-tests/bouncycastle-jsse/target/quarkus.log, -Dquarkus.log.file.enable=true]
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-05-05 15:42:46,264 FINE  [org.bou.jss.pro.PropertyUtils] (vert.x-eventloop-thread-1) Boolean system property [org.bouncycastle.jsse.ec.disableChar2] defaulted to: false
2021-05-05 15:42:46,268 FINE  [org.bou.jss.pro.PropertyUtils] (vert.x-eventloop-thread-1) Boolean system property [org.bouncycastle.ec.disable_f2m] defaulted to: false
2021-05-05 15:42:46,269 FINE  [org.bou.jss.pro.PropertyUtils] (vert.x-eventloop-thread-1) Boolean system property [jsse.enableFFDHE] defaulted to: true
2021-05-05 15:42:46,278 INFO  [io.quarkus] (main) quarkus-integration-test-bouncycastle-jsse 999-SNAPSHOT native (powered by Quarkus 999-SNAPSHOT) started in 0.052s. Listening on: http://0.0.0.0:8081 and https://0.0.0.0:8444
2021-05-05 15:42:46,279 INFO  [io.quarkus] (main) Profile prod activated. 
2021-05-05 15:42:46,279 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, security, smallrye-context-propagation]
2021-05-05 15:42:48,516 FINE  [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server selected protocol version: TLSv1.3
2021-05-05 15:42:48,516 FINE  [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server did not specify a session ID
2021-05-05 15:42:48,516 FINER [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server (1.3) found no credentials for signature scheme 'ecdsa_secp256r1_sha256(0x403)' (keyType 'EC')
2021-05-05 15:42:48,516 FINE  [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server (1.3) did not select any credentials
2021-05-05 15:42:48,516 FINER [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server found no credentials for cipher suite: TLS_AES_256_GCM_SHA384
2021-05-05 15:42:48,516 FINE  [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server (1.3) did not select any credentials
2021-05-05 15:42:48,516 FINER [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server found no credentials for cipher suite: TLS_AES_128_GCM_SHA256
2021-05-05 15:42:48,516 INFO  [org.bou.jss.pro.ProvTlsServer] (vert.x-eventloop-thread-1) Server raised fatal(2) handshake_failure(40) alert: Failed to process record: org.bouncycastle.tls.TlsFatalAlert: handshake_failure(40)
	at org.bouncycastle.tls.AbstractTlsServer.getSelectedCipherSuite(AbstractTlsServer.java:418)
	at org.bouncycastle.jsse.provider.ProvTlsServer.getSelectedCipherSuite(ProvTlsServer.java:417)
	at org.bouncycastle.tls.TlsServerProtocol.generate13ServerHello(TlsServerProtocol.java:254)
	at org.bouncycastle.tls.TlsServerProtocol.generateServerHello(TlsServerProtocol.java:461)
	at org.bouncycastle.tls.TlsServerProtocol.handleHandshakeMessage(TlsServerProtocol.java:900)
	at org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(TlsProtocol.java:611)
	at org.bouncycastle.tls.TlsProtocol.processRecord(TlsProtocol.java:511)
	at org.bouncycastle.tls.RecordStream.readFullRecord(RecordStream.java:207)
	at org.bouncycastle.tls.TlsProtocol.safeReadFullRecord(TlsProtocol.java:806)
	at org.bouncycastle.tls.TlsProtocol.offerInput(TlsProtocol.java:1179)
	at org.bouncycastle.tls.TlsProtocol.offerInput(TlsProtocol.java:1147)
	at org.bouncycastle.jsse.provider.ProvSSLEngine.unwrap(ProvSSLEngine.java:464)
	at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:637)
	at io.netty.handler.ssl.JdkSslEngine.unwrap(JdkSslEngine.java:92)
	at io.netty.handler.ssl.JdkAlpnSslEngine.unwrap(JdkAlpnSslEngine.java:143)
	at io.netty.handler.ssl.SslHandler$SslEngineType$3.unwrap(SslHandler.java:283)
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1388)
	at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1283)
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1330)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:829)
	at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:553)
	at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192)

Error:  Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 8.067 s <<< FAILURE! - in io.quarkus.it.bouncycastle.BouncyCastleJsseITCase
Error:  io.quarkus.it.bouncycastle.BouncyCastleJsseITCase.testListProviders  Time elapsed: 1.73 s  <<< ERROR!
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:336)
	at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
	at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:185)
	at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
	at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1418)
	at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1324)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:440)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:411)
	at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:570)
	at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:554)
	at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:415)
	at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
	at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:326)
	at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:605)
	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:440)
	at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
	at org.apache.http.client.HttpClient$execute$0.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148)
	at io.restassured.internal.RequestSpecificationImpl$RestAssuredHttpBuilder.doRequest(RequestSpecificationImpl.groovy:2055)
	at io.restassured.internal.http.HTTPBuilder.doRequest(HTTPBuilder.java:495)
	at io.restassured.internal.http.HTTPBuilder.request(HTTPBuilder.java:452)
	at io.restassured.internal.http.HTTPBuilder$request$2.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:166)
	at io.restassured.internal.RequestSpecificationImpl.sendHttpRequest(RequestSpecificationImpl.groovy:1451)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1268)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:819)
	at groovy.lang.GroovyObject.invokeMethod(GroovyObject.java:39)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:45)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:55)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:171)
	at io.restassured.internal.RequestSpecificationImpl.sendRequest(RequestSpecificationImpl.groovy:1200)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1268)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:819)
	at groovy.lang.GroovyObject.invokeMethod(GroovyObject.java:39)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:45)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:166)
	at io.restassured.internal.filter.SendRequestFilter.filter(SendRequestFilter.groovy:30)
	at io.restassured.filter.Filter$filter$0.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at io.restassured.filter.Filter$filter.call(Unknown Source)
	at io.restassured.internal.filter.FilterContextImpl.next(FilterContextImpl.groovy:72)
	at io.restassured.filter.time.TimingFilter.filter(TimingFilter.java:56)
	at io.restassured.filter.Filter$filter.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:157)
	at io.restassured.internal.filter.FilterContextImpl.next(FilterContextImpl.groovy:72)
	at io.restassured.filter.FilterContext$next.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148)
	at io.restassured.internal.RequestSpecificationImpl.applyPathParamsAndSendRequest(RequestSpecificationImpl.groovy:1655)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1268)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:819)
	at groovy.lang.GroovyObject.invokeMethod(GroovyObject.java:39)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:45)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:55)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:171)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:203)
	at io.restassured.internal.RequestSpecificationImpl.applyPathParamsAndSendRequest(RequestSpecificationImpl.groovy:1661)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1268)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1035)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:819)
	at groovy.lang.GroovyObject.invokeMethod(GroovyObject.java:39)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:45)
	at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:55)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:51)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:171)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:203)
	at io.restassured.internal.RequestSpecificationImpl.get(RequestSpecificationImpl.groovy:171)
	at io.restassured.internal.RequestSpecificationImpl.get(RequestSpecificationImpl.groovy)
	at io.quarkus.it.bouncycastle.BouncyCastleJsseTestCase.doTestListProviders(BouncyCastleJsseTestCase.java:52)
	at io.quarkus.it.bouncycastle.BouncyCastleJsseITCase.testListProviders(BouncyCastleJsseITCase.java:12)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at io.quarkus.test.junit.QuarkusTestExtension.interceptTestMethod(QuarkusTestExtension.java:866)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:188)
	at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:154)
	at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:128)
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:428)
	at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
	at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:562)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:548)

See https://github.com/zakkak/quarkus/runs/2510421681?check_suite_focus=true

To Reproduce

Steps to reproduce the behavior:

./mvnw clean verify -Dnative -Dnative.surefire.skip -Dquarkus.native.container-build=true \
    -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:21.1-java11 \
    -pl integration-tests/bouncycastle-jsse     

Environment (please complete the following information):

Key Value
uname -a Linux slimhat 5.11.15-300.fc34.x86_64 #1 SMP Fri Apr 16 13:41:48 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
java -version openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment 18.9 (build 11.0.11+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.11+9, mixed mode, sharing)
mvnw --version Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: /home/zakkak/.m2/wrapper/dists/apache-maven-3.8.1-bin/2l5mhf2pq2clrde7f7qp1rdt5m/apache-maven-3.8.1
Java version: 11.0.11, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-11-openjdk-11.0.11.0.9-0.fc34.x86_64
Default locale: en_IE, platform encoding: UTF-8
OS name: "linux", version: "5.11.15-300.fc34.x86_64", arch: "amd64", family: "unix"
quarkus-plugin.version null object or invalid expression
quarkus.platform.artifact-id null object or invalid expression
quarkus.platform.group-id null object or invalid expression
quarkus.platform.version null object or invalid expression
@zakkak zakkak added the kind/bug Something isn't working label May 6, 2021
zakkak added a commit to zakkak/quarkus that referenced this issue May 6, 2021
@sberyozkin sberyozkin self-assigned this May 7, 2021
@zakkak
Copy link
Contributor Author

zakkak commented May 13, 2021

@sberyozkin I tried testing this with oracle/graal@a0ea2cd reverted and

./mvnw clean verify -Dnative -Dnative.surefire.skip -Dquarkus.native.additional-build-args='--enable-all-security-services' -pl integration-tests/bouncycastle-jsse

but I am getting:

[INFO] Running io.quarkus.it.bouncycastle.BouncyCastleJsseITCase
Executing [/home/zakkak/code/quarkus/integration-tests/bouncycastle-jsse/target/quarkus-integration-test-bouncycastle-jsse-999-SNAPSHOT-runner, -Dquarkus.http.port=8081, -Dquarkus.http.ssl-port=8444, -Dtest.url=http://localhost:8081, -Dquarkus.log.file.path=/home/zakkak/code/quarkus/integration-tests/bouncycastle-jsse/target/quarkus.log, -Dquarkus.log.file.enable=true]
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-05-13 08:05:01,971 ERROR [io.qua.run.Application] (main) Failed to start application (with profile prod): java.nio.file.NoSuchFileException: server-keystore.jks
	at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:219)
	at java.nio.file.Files.newByteChannel(Files.java:371)
	at java.nio.file.Files.newByteChannel(Files.java:422)
	at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:420)
	at java.nio.file.Files.newInputStream(Files.java:156)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder.getFileContent(VertxHttpRecorder.java:689)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder.createSslOptions(VertxHttpRecorder.java:630)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder.doServerStart(VertxHttpRecorder.java:469)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder.startServer(VertxHttpRecorder.java:251)
	at io.quarkus.deployment.steps.VertxHttpProcessor$openSocket-2064782366.deploy_0(VertxHttpProcessor$openSocket-2064782366.zig:166)
	at io.quarkus.deployment.steps.VertxHttpProcessor$openSocket-2064782366.deploy(VertxHttpProcessor$openSocket-2064782366.zig:40)
	at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:601)
	at io.quarkus.runtime.Application.start(Application.java:101)
	at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:101)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:66)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:42)
	at io.quarkus.runtime.Quarkus.run(Quarkus.java:119)
	at io.quarkus.runner.GeneratedMain.main(GeneratedMain.zig:29)

while server-keystore.jks appears to be present

$ find integration-tests/bouncycastle-jsse -name "server-keystore.jks" 
integration-tests/bouncycastle-jsse/src/main/resources/server-keystore.jks
integration-tests/bouncycastle-jsse/target/classes/server-keystore.jks

do you have any clue why it might be failing to find server-keystore.jks?

@zakkak
Copy link
Contributor Author

zakkak commented Jul 13, 2021

do you have any clue why it might be failing to find server-keystore.jks?

Well, that was apparently because -Dquarkus.native.additional-build-args='--enable-all-security-services' on the command line was overriding the quarkus.native.additional-build-args value from the properties file...

So after including the resources, reverting oracle/graal@a0ea2cd does indeed make the test pass with 21.1.

The issue persists in the upcoming 21.2 version, even when explicitly passing all security providers using -H:AdditionalSecurityProviders=....

Looking a bit more into this it looks like some BouncyCastle security services are not getting registered despite explicitly adding the providers we need. Looking further into it...

@zakkak
Copy link
Contributor Author

zakkak commented Jul 14, 2021

The issue appears to be related to the way that we add the "BC" and "BCJSSE" providers. The way we currently do it is through a static initializer:

void recordBouncyCastleProviders(SecurityProviderRecorder recorder,
Optional<BouncyCastleProviderBuildItem> bouncyCastleProvider,
Optional<BouncyCastleJsseProviderBuildItem> bouncyCastleJsseProvider) {
if (bouncyCastleJsseProvider.isPresent()) {
if (bouncyCastleJsseProvider.get().isInFipsMode()) {
recorder.addBouncyCastleFipsJsseProvider();
} else {
recorder.addBouncyCastleJsseProvider();
}
} else if (bouncyCastleProvider.isPresent()) {
recorder.addBouncyCastleProvider(bouncyCastleProvider.get().isInFipsMode());
}

This static initializer however appears to be called after the before-analysis phase were the available services are calculated:

https://github.com/oracle/graal/blob/811c0deb7cbc91323467898d39508cf65b5ae756/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java#L411

This essentially means that when calculating the available services we have not yet registered the bouncycastle providers.
As a result native-image fails to register the services coming with the bouncycastle providers.

The solution should be to move the addition of the bouncycastle providers in the after-registration phase, e.g.:

@AutomaticFeature
public class BouncyCastleFeature implements Feature {

    @Override
    public void afterRegistration(AfterRegistrationAccess access) {
        Security.addProvider(new BouncyCastleProvider());
    }
}

@sberyozkin the following "prototype" makes the test pass. Do you think you can take it from here?

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java
index 3b933d2522..111016f716 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java
@@ -1,5 +1,6 @@
 package io.quarkus.deployment.steps;
 
+import static io.quarkus.gizmo.MethodDescriptor.ofConstructor;
 import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
 
 import java.io.ObjectStreamClass;
@@ -10,6 +11,8 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
+import java.security.Provider;
+import java.security.Security;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -79,10 +82,13 @@ public class NativeImageAutoFeatureStep {
             "ignoreResources", void.class, String.class);
     static final String RUNTIME_REFLECTION = RuntimeReflection.class.getName();
     static final String JNI_RUNTIME_ACCESS = "com.oracle.svm.core.jni.JNIRuntimeAccess";
+    static final String AFTER_REGISTRATION_ACCESS = Feature.AfterRegistrationAccess.class.getName();
     static final String BEFORE_ANALYSIS_ACCESS = Feature.BeforeAnalysisAccess.class.getName();
     static final String DYNAMIC_PROXY_REGISTRY = "com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry";
     static final String LEGACY_LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.LocalizationFeature";
     static final String LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.localization.LocalizationFeature";
+    static final String BOUNCYCASTLE_PROVIDER = "org.bouncycastle.jce.provider.BouncyCastleProvider";
+    static final String BOUNCYCASTLE_JSSE_PROVIDER = "org.bouncycastle.jsse.provider.BouncyCastleJsseProvider";
 
     @BuildStep
     GeneratedResourceBuildItem generateNativeResourcesList(List<NativeImageResourceBuildItem> resources,
@@ -125,7 +131,13 @@ public class NativeImageAutoFeatureStep {
                 Object.class.getName(), Feature.class.getName());
         file.addAnnotation("com.oracle.svm.core.annotate.AutomaticFeature");
 
-        //MethodCreator afterReg = file.getMethodCreator("afterRegistration", void.class, "org.graalvm.nativeimage.Feature$AfterRegistrationAccess");
+        MethodCreator afterReg = file.getMethodCreator("afterRegistration", "V", AFTER_REGISTRATION_ACCESS);
+        afterReg.invokeStaticMethod(ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, int.class),
+                afterReg.newInstance(ofConstructor(BOUNCYCASTLE_PROVIDER)), afterReg.load(4));
+        afterReg.invokeStaticMethod(ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, int.class),
+                afterReg.newInstance(ofConstructor(BOUNCYCASTLE_JSSE_PROVIDER)), afterReg.load(5));
+        afterReg.returnValue(null);
+
         MethodCreator beforeAn = file.getMethodCreator("beforeAnalysis", "V", BEFORE_ANALYSIS_ACCESS);
         TryBlock overallCatch = beforeAn.tryBlock();
         //TODO: at some point we are going to need to break this up, as if it get too big it will hit the method size limit
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 99e3bb2856..23a120e5d3 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
@@ -188,21 +188,21 @@ public class SecurityProcessor {
 
     }
 
-    @BuildStep
-    @Record(ExecutionTime.STATIC_INIT)
-    void recordBouncyCastleProviders(SecurityProviderRecorder recorder,
-            Optional<BouncyCastleProviderBuildItem> bouncyCastleProvider,
-            Optional<BouncyCastleJsseProviderBuildItem> bouncyCastleJsseProvider) {
-        if (bouncyCastleJsseProvider.isPresent()) {
-            if (bouncyCastleJsseProvider.get().isInFipsMode()) {
-                recorder.addBouncyCastleFipsJsseProvider();
-            } else {
-                recorder.addBouncyCastleJsseProvider();
-            }
-        } else if (bouncyCastleProvider.isPresent()) {
-            recorder.addBouncyCastleProvider(bouncyCastleProvider.get().isInFipsMode());
-        }
-    }
+//    @BuildStep
+//    @Record(ExecutionTime.STATIC_INIT)
+//    void recordBouncyCastleProviders(SecurityProviderRecorder recorder,
+//            Optional<BouncyCastleProviderBuildItem> bouncyCastleProvider,
+//            Optional<BouncyCastleJsseProviderBuildItem> bouncyCastleJsseProvider) {
+//        if (bouncyCastleJsseProvider.isPresent()) {
+//            if (bouncyCastleJsseProvider.get().isInFipsMode()) {
+//                recorder.addBouncyCastleFipsJsseProvider();
+//            } else {
+//                recorder.addBouncyCastleJsseProvider();
+//            }
+//        } else if (bouncyCastleProvider.isPresent()) {
+//            recorder.addBouncyCastleProvider(bouncyCastleProvider.get().isInFipsMode());
+//        }
+//    }
 
     @BuildStep
     void addBouncyCastleProvidersToNativeImage(BuildProducer<NativeImageSecurityProviderBuildItem> additionalProviders,
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 dd0942d42c..28fc7c1e55 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,12 @@ package io.quarkus.it.bouncycastle;
 
 import org.junit.jupiter.api.Test;
 
-import io.quarkus.test.junit.DisabledOnNativeImage;
 import io.quarkus.test.junit.NativeImageTest;
 
 @NativeImageTest
 public class BouncyCastleJsseITCase extends BouncyCastleJsseTestCase {
     @Test
-    @DisabledOnNativeImage
+    //    @DisabledOnNativeImage
     @Override
     public void testListProviders() {
         doTestListProviders();

@sberyozkin
Copy link
Member

@zakkak Hi - I'm sorry - I keep missing the notifications and I've no idea what is causing it. Let me look into after PTO, thanks for spending your time on this issue, it is appreciated

@sberyozkin
Copy link
Member

sberyozkin commented Jul 29, 2021

@zakkak Quick question about this code:

+        MethodCreator afterReg = file.getMethodCreator("afterRegistration", "V", AFTER_REGISTRATION_ACCESS);
+        afterReg.invokeStaticMethod(ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, int.class),
+                afterReg.newInstance(ofConstructor(BOUNCYCASTLE_PROVIDER)), afterReg.load(4));
+        afterReg.invokeStaticMethod(ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, int.class),
+                afterReg.newInstance(ofConstructor(BOUNCYCASTLE_JSSE_PROVIDER)), afterReg.load(5));
+        afterReg.returnValue(null);
+

I think it can not be done directly in NativeImageAutoFeatureStep#generateFeature so I wonder is it possible to do it in SecurityProcessor's own generateFeature by doing again

ClassCreator file = new ClassCreator(new ClassOutput() {
            @Override
            public void write(String s, byte[] bytes) {
                nativeImageClass.produce(new GeneratedNativeImageClassBuildItem(s, bytes));
            }
        }, GRAAL_AUTOFEATURE, null,
                Object.class.getName(), Feature.class.getName());
        file.addAnnotation("com.oracle.svm.core.annotate.AutomaticFeature");

        //MethodCreator afterReg = file.getMethodCreator("afterRegistration", void.class, "org.graalvm.nativeimage.Feature$AfterRegistrationAccess");
// do the after registration BC related updates as in your prototype
f.close()

?
If that it is not possible then I can probably introduce a build item which will pass parameters to support the after reg updates from SecurityProcessor, ex:

//afterReg.invokeStaticMethod(ofMethod(Security.class, "insertProviderAt", int.class, Provider.class, int.class),
//afterReg.newInstance(ofConstructor(BOUNCYCASTLE_PROVIDER)), afterReg.load(4));
afterReg.invokeStaticMethod(ofMethod(myBuildItem.getClassTarget(), //Securiy.class 
myBuildItem.getMethodName(), //"insertProviderAt"
myBuildItem.getMethodParameterTypes()//int.class, Provider.class, int.class)
  afterReg.newInstance(ofConstructor(myBuildItem.getProviderClass()//BC)), afterReg.load(4));

Update: prototyping around the 2nd approach

etc ?

Also, in your prototype you start with a 4 index with afterReg.load(4) etc. What is 4, is it an initial offset of something ?
Update: please ignore the last question - these are the provider indexes that should be calculated
Update: Hmm... but these indexes can not be calculated at the build time (they may be known for a given version of GraalVM but may change across different versions)

Thanks

@zakkak
Copy link
Contributor Author

zakkak commented Aug 11, 2021

Hi @sberyozkin sorry for the delay, I was on PTO.

... I think it can not be done directly in NativeImageAutoFeatureStep#generateFeature so I wonder is it possible to do it in SecurityProcessor's...? own generateFeature by doing again

I guess so, not sure to be honest.

Update: prototyping around the 2nd approach
Update: Hmm... but these indexes can not be calculated at the build time (they may be known for a given version of GraalVM but may change across different versions)

What's the status of this? Is the only problem the provider indices?

To my understanding the index is calculated at build time even without the patch.
io.quarkus.security.deployment.SecurityProcessor#recordBouncyCastleProviders invokes io.quarkus.security.runtime.SecurityProviderRecorder#addBouncyCastleJsseProvider which uses io.quarkus.security.runtime.SecurityProviderUtils#findProviderIndex to get the index so we should be able to do something similar when generating the feature.

WDYT?

@sberyozkin
Copy link
Member

sberyozkin commented Aug 19, 2021

Hi @zakkak np, I've just got back from PTO as well :-)

I haven't looked into it since the time of my last comment; but this index even if it is made available to the build code it would have to be wrapped in RuntimeValue which can only be passed to the recorder again, so not sure it works

@quarkus-bot quarkus-bot bot added this to the 2.8 - main milestone Mar 8, 2022
@gsmet gsmet modified the milestones: 2.8 - main, 2.7.4.Final Mar 8, 2022
luca-digrazia pushed a commit to luca-digrazia/DatasetCommitsDiffSearch that referenced this issue Sep 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants