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

Bouncy Castle BCFIPS provider fails native executable build after the BC FIPS version bump to 1.0.2.4 #37500

Open
michalvavrik opened this issue Dec 4, 2023 · 21 comments
Labels
area/native-image area/securepipeline issues related to ensure Quarkus can be used in a secure pipeline setups like FIPS or similar area/security kind/bug Something isn't working

Comments

@michalvavrik
Copy link
Contributor

michalvavrik commented Dec 4, 2023

Describe the bug

I have app with org.bouncycastle:bc-fips and org.bouncycastle:bctls-fips dependencies that I build to native executable. After #37354 build fails with exeception.

Expected behavior

Native executable is built.

Actual behavior

Native executable build fails:

GraalVM Native Image: Generating 'security-bouncycastle-fips-jsse-1.0.0-SNAPSHOT-runner' (executable)...
========================================================================================================================
For detailed information and explanations on the build output, visit:
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md
------------------------------------------------------------------------------------------------------------------------
[1/8] Initializing...                                                                                    (5.0s @ 0.14GB)
 Java version: 21.0.1+12-LTS, vendor version: Mandrel-23.1.1.0-Final
 Graal compiler: optimization level: 2, target machine: x86-64-v3
 C compiler: gcc (redhat, x86_64, 8.5.0)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 5 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
 - io.quarkus.runner.Feature: Auto-generated class by Quarkus from the existing extensions
 - io.quarkus.runtime.graal.DisableLoggingFeature: Disables INFO logging during the analysis phase
 - io.quarkus.security.BouncyCastleFeature
 - org.eclipse.angus.activation.nativeimage.AngusActivationFeature
------------------------------------------------------------------------------------------------------------------------
 6 experimental option(s) unlocked:
 - '-H:+AllowFoldMethods' (origin(s): command line)
 - '-H:BuildOutputJSONFile' (origin(s): command line)
 - '-H:-UseServiceLoaderFeature' (origin(s): command line)
 - '-H:AdditionalSecurityProviders' (origin(s): command line)
 - '-H:IncludeResources' (origin(s): command line)
 - '-H:ReflectionConfigurationResources' (origin(s): 'META-INF/native-image/io.netty/netty-transport/native-image.properties' in 'file:///project/lib/io.netty.netty-transport-4.1.100.Final.jar')
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 3.56GB of memory (11.4% of 31.10GB system memory, set via '-Xmx4g')
 - 12 thread(s) (100.0% of 12 available processor(s), determined at start)
19:22:14,397 INFO  [org.bou.jss.pro.PropertyUtils] Found boolean security property [keystore.type.compat]: true
19:22:15,309 INFO  [org.bou.jss.pro.PropertyUtils] Found string security property [jdk.tls.disabledAlgorithms]: SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, ECDH
19:22:15,310 INFO  [org.bou.jss.pro.PropertyUtils] Found string security property [jdk.certpath.disabledAlgorithms]: MD2, MD5, SHA1 jdkCA & usage TLSServer, RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224, SHA1 usage SignedJAR & denyAfter 2019-01-01
19:22:15,310 WARNING [org.bou.jss.pro.DisabledAlgorithmConstraints] Ignoring unsupported entry in 'jdk.certpath.disabledAlgorithms': SHA1 jdkCA & usage TLSServer
19:22:15,310 WARNING [org.bou.jss.pro.DisabledAlgorithmConstraints] Ignoring unsupported entry in 'jdk.certpath.disabledAlgorithms': SHA1 usage SignedJAR & denyAfter 2019-01-01
19:22:38,373 INFO  [org.bou.jss.pro.PropertyUtils] Found string system property [java.home]: /opt/mandrel
19:22:42,861 INFO  [org.bou.jss.pro.PropertyUtils] Found string system property [java.home]: /opt/mandrel
19:22:43,002 INFO  [org.bou.jss.pro.PropertyUtils] Found string system property [java.home]: /opt/mandrel
[2/8] Performing analysis...  [*****]                                                                   (36.6s @ 1.29GB)
   15,461 reachable types   (88.8% of   17,407 total)
   24,112 reachable fields  (61.9% of   38,931 total)
   77,354 reachable methods (59.9% of  129,172 total)
    4,580 types,   155 fields, and 3,669 methods registered for reflection
       61 types,    59 fields, and    55 methods registered for JNI access
        4 native libraries: dl, pthread, rt, z

Error: Unsupported features in 16 methods
Detailed message:
Error: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected. If these objects should not be stored in the image heap, you can use 

    '--trace-object-instantiation=org.bouncycastle.crypto.fips.FipsSecureRandom'

to find classes that instantiate these objects. Once you found such a class, you can mark it explicitly for run time initialization with 

    '--initialize-at-run-time=<culprit>'

to prevent the instantiation of the object.
The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image runtime by using the option --initialize-at-run-time=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point.
Trace: Object was reached by
  reading field java.util.concurrent.atomic.AtomicReference.value of constant 
    java.util.concurrent.atomic.AtomicReference@46f3c81e: RandomSpi
  indexing into array java.util.concurrent.atomic.AtomicReference[]@1476359e: [Ljava.util.concurrent.atomic.AtomicReference;@1476359e
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider.providerDefaultRandom of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider@ffc9672: org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProv...
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.providerDefaultSecureRandomProvider of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider@29120f33: BCFIPS version 1.000204
  reading field org.bouncycastle.jcajce.util.ProviderJcaJceHelper.provider of constant 
    org.bouncycastle.jcajce.util.ProviderJcaJceHelper@1782d427: org.bouncycastle.jcajce.util.ProviderJcaJceHelper@1782d427
  reading field org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider.helper of constant 
    org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider@27669d46: org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider@27669d46
  reading field org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1.val$cryptoProvider of constant 
    org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1@538fde3e: org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1@538fde3e
  reading field java.util.HashMap$Node.value of constant 
    java.util.HashMap$Node@5ef2c319: org.bouncycastle.jsse.provider.KeyManagerFactory=org.bouncycastle.jsse.provider....
  indexing into array java.util.HashMap$Node[]@cef2d6d: [Ljava.util.HashMap$Node;@cef2d6d
  reading field java.util.HashMap.table of constant 
    java.util.HashMap@55718cbb: {org.bouncycastle.jsse.provider.TrustManagerFactory=org.bouncycastle.jsse.provid...
  reading field org.bouncycastle.jsse.provider.BouncyCastleJsseProvider.creatorMap of constant 
    org.bouncycastle.jsse.provider.BouncyCastleJsseProvider@610628dc: BCJSSE version 1.0017
  reading field sun.security.jca.ProviderConfig.provider of constant 
    sun.security.jca.ProviderConfig@24446b40: BCJSSE
  indexing into array sun.security.jca.ProviderConfig[]@118b14af: [Lsun.security.jca.ProviderConfig;@118b14af
  reading field sun.security.jca.ProviderList.configs of constant 
    sun.security.jca.ProviderList@5e80e9e4: [BCJSSE]
  reading static field sun.security.jca.Providers.providerList
    at sun.security.jca.Providers.getSystemProviderList(Providers.java:195)
  parsing method sun.security.jca.Providers.getSystemProviderList(Providers.java:195) reachable via the parsing context
    at static root method.(Unknown Source)

Error: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected. If these objects should not be stored in the image heap, you can use 

    '--trace-object-instantiation=org.bouncycastle.crypto.fips.FipsSecureRandom'

to find classes that instantiate these objects. Once you found such a class, you can mark it explicitly for run time initialization with 

    '--initialize-at-run-time=<culprit>'

to prevent the instantiation of the object.
The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image runtime by using the option --initialize-at-run-time=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point.
Trace: Object was reached by
  reading field java.util.concurrent.atomic.AtomicReference.value of constant 
    java.util.concurrent.atomic.AtomicReference@52b20d5a: RandomSpi
  indexing into array java.util.concurrent.atomic.AtomicReference[]@1476359e: [Ljava.util.concurrent.atomic.AtomicReference;@1476359e
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider.providerDefaultRandom of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider@ffc9672: org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProv...
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.providerDefaultSecureRandomProvider of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider@29120f33: BCFIPS version 1.000204
  reading field org.bouncycastle.jcajce.util.ProviderJcaJceHelper.provider of constant 
    org.bouncycastle.jcajce.util.ProviderJcaJceHelper@1782d427: org.bouncycastle.jcajce.util.ProviderJcaJceHelper@1782d427
  reading field org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider.helper of constant 
    org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider@27669d46: org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider@27669d46
  reading field org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1.val$cryptoProvider of constant 
    org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1@538fde3e: org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1@538fde3e
  reading field java.util.HashMap$Node.value of constant 
    java.util.HashMap$Node@5ef2c319: org.bouncycastle.jsse.provider.KeyManagerFactory=org.bouncycastle.jsse.provider....
  indexing into array java.util.HashMap$Node[]@cef2d6d: [Ljava.util.HashMap$Node;@cef2d6d
  reading field java.util.HashMap.table of constant 
    java.util.HashMap@55718cbb: {org.bouncycastle.jsse.provider.TrustManagerFactory=org.bouncycastle.jsse.provid...
  reading field org.bouncycastle.jsse.provider.BouncyCastleJsseProvider.creatorMap of constant 
    org.bouncycastle.jsse.provider.BouncyCastleJsseProvider@610628dc: BCJSSE version 1.0017
  reading field sun.security.jca.ProviderConfig.provider of constant 
    sun.security.jca.ProviderConfig@24446b40: BCJSSE
  indexing into array sun.security.jca.ProviderConfig[]@118b14af: [Lsun.security.jca.ProviderConfig;@118b14af
  reading field sun.security.jca.ProviderList.configs of constant 
    sun.security.jca.ProviderList@5e80e9e4: [BCJSSE]
  reading static field sun.security.jca.Providers.providerList
    at sun.security.jca.Providers.getSystemProviderList(Providers.java:195)
  parsing method sun.security.jca.Providers.getSystemProviderList(Providers.java:195) reachable via the parsing context
    at static root method.(Unknown Source)

Error: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected. If these objects should not be stored in the image heap, you can use 

    '--trace-object-instantiation=org.bouncycastle.crypto.fips.FipsSecureRandom'

to find classes that instantiate these objects. Once you found such a class, you can mark it explicitly for run time initialization with 

    '--initialize-at-run-time=<culprit>'

to prevent the instantiation of the object.
The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image runtime by using the option --initialize-at-run-time=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point.
Trace: Object was reached by
  reading field java.util.concurrent.atomic.AtomicReference.value of constant 
    java.util.concurrent.atomic.AtomicReference@84a1a52: RandomSpi
  indexing into array java.util.concurrent.atomic.AtomicReference[]@1476359e: [Ljava.util.concurrent.atomic.AtomicReference;@1476359e
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider.providerDefaultRandom of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider@ffc9672: org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProv...
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.providerDefaultSecureRandomProvider of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider@29120f33: BCFIPS version 1.000204
  reading field org.bouncycastle.jcajce.util.ProviderJcaJceHelper.provider of constant 
    org.bouncycastle.jcajce.util.ProviderJcaJceHelper@1782d427: org.bouncycastle.jcajce.util.ProviderJcaJceHelper@1782d427
  reading field org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider.helper of constant 
    org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider@27669d46: org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider@27669d46
  reading field org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1.val$cryptoProvider of constant 
    org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1@538fde3e: org.bouncycastle.jsse.provider.BouncyCastleJsseProvider$1@538fde3e
  reading field java.util.HashMap$Node.value of constant 
    java.util.HashMap$Node@5ef2c319: org.bouncycastle.jsse.provider.KeyManagerFactory=org.bouncycastle.jsse.provider....
  indexing into array java.util.HashMap$Node[]@cef2d6d: [Ljava.util.HashMap$Node;@cef2d6d
  reading field java.util.HashMap.table of constant 
    java.util.HashMap@55718cbb: {org.bouncycastle.jsse.provider.TrustManagerFactory=org.bouncycastle.jsse.provid...
  reading field org.bouncycastle.jsse.provider.BouncyCastleJsseProvider.creatorMap of constant 
    org.bouncycastle.jsse.provider.BouncyCastleJsseProvider@610628dc: BCJSSE version 1.0017
  reading field sun.security.jca.ProviderConfig.provider of constant 
    sun.security.jca.ProviderConfig@24446b40: BCJSSE
  indexing into array sun.security.jca.ProviderConfig[]@118b14af: [Lsun.security.jca.ProviderConfig;@118b14af
  reading field sun.security.jca.ProviderList.configs of constant 
    sun.security.jca.ProviderList@5e80e9e4: [BCJSSE]
  reading static field sun.security.jca.Providers.providerList
    at sun.security.jca.Providers.getSystemProviderList(Providers.java:195)
  parsing method sun.security.jca.Providers.getSystemProviderList(Providers.java:195) reachable via the parsing context
    at static root method.(Unknown Source)

Error: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected. If these objects should not be stored in the image heap, you can use 

    '--trace-object-instantiation=org.bouncycastle.crypto.fips.FipsSecureRa

How to Reproduce?

Steps to reproduce:

  1. git clone git@github.com:quarkus-qe/quarkus-test-suite.git
  2. cd quarkus-test-suite/security/bouncycastle-fips/bcFipsJsse
  3. mvn clean verify -Dnative

Output of uname -a or ver

Fedora 38

Output of java -version

17

MANDREL 23.1.1.0 JDK 21.0.1+12-LTS

Quarkus version or git rev

999-SNAPSHOT

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.9.3

Additional information

No response

@quarkus-bot quarkus-bot bot added the area/securepipeline issues related to ensure Quarkus can be used in a secure pipeline setups like FIPS or similar label Dec 4, 2023
@quarkus-bot
Copy link

quarkus-bot bot commented Dec 4, 2023

/cc @Karm (mandrel,securepipeline), @galderz (mandrel), @jerboaa (securepipeline), @zakkak (mandrel,native-image)

@michalvavrik
Copy link
Contributor Author

/cc @sberyozkin

@sberyozkin
Copy link
Member

sberyozkin commented Dec 4, 2023

@michalvavrik native build passes in Quarkus, may be more substitutions are needed, I'm not seeing there where exactly that SecureRandom is set.

By the way it is not supported at the product level

@zakkak
Copy link
Contributor

zakkak commented Dec 5, 2023

@sberyozkin IIRC the same error is thrown in the bouncycastle-fips-jsse (last time I checked) integration test (I didn't update #14139 though...)

@sberyozkin
Copy link
Member

@michalvavrik, @zakkak, thanks Foivos, so can we have #14139 closed as a duplicate it looks like this issue refers to the same existing problem with the BC FIPS JSSE but via a different reproducer ?

@michalvavrik
Copy link
Contributor Author

@michalvavrik, @zakkak, thanks Foivos, so can we have #14139 closed as a duplicate it looks like this issue refers to the same existing problem with the BC FIPS JSSE but via a different reproducer ?

There must be some difference considering the reproducer I provided worked until last week and #14139 is opened for more than 2 years. I'm not sure you wouldn't loose some information by closing it.

@sberyozkin
Copy link
Member

@michalvavrik Sure but in both cases it is not BC FIPS on its own but the BC_FIPS and BC_TLS_FIPS combo which is specifically about securing the TLS connection as opposed to just dealing with the FIPS algorithms.

Let me adjust the title a bit

@sberyozkin sberyozkin changed the title Bouncy Castle FIPS breaks native executable build after latest bump QE Bouncy Castle FIPS JSSE native executable build fails after the BC FIPS version bump to 1.0.2.4 Dec 5, 2023
@sberyozkin
Copy link
Member

sberyozkin commented Dec 5, 2023

@michalvavrik https://quarkus.io/guides/security-customization#bouncy-castle-jsse-fips documents this is currently not supported in native, so a bit of a good news it does not break any user expectations, but I'll give it another try, as soon as possible, will try again @zakkak 's suggestions at #14139 etc

@michalvavrik
Copy link
Contributor Author

@michalvavrik https://quarkus.io/guides/security-customization#bouncy-castle-jsse-fips documents this is currently not supported in native, so a bit of a good news it does not break any user expectations, but I'll give it another try, as soon as possible, will try again @zakkak 's suggestions at #14139 etc

makes sense, I've since found out we only have @QuarkusTest for this, which is bit unusual and we only build native executable but don't test it. I suppose it is related. Thanks

@sberyozkin
Copy link
Member

sberyozkin commented Dec 8, 2023

@zakkak I've had a look today, with integration-tests/bouncycastle-fips-jsse (added a native test, https://github.com/quarkusio/quarkus/blob/2.10.0.Final/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseITCase.java), there is a new final field in BouncyCastleFipsProvider:

  // Field descriptor #467 Lorg/bouncycastle/crypto/SecureRandomProvider;
  private final org.bouncycastle.crypto.SecureRandomProvider providerDefaultSecureRandomProvider;

where SecureRandomProvider is an interface, and BouncyCastleFipsProvider$PooledSecuredRandomProvider seems to implement it which leads to

// Field descriptor #54 [Ljava/util/concurrent/atomic/AtomicReference;
  // Signature: [Ljava/util/concurrent/atomic/AtomicReference<Ljava/security/SecureRandom;>;
  private final java.util.concurrent.atomic.AtomicReference[] providerDefaultRandom;

and then during the native build:

Trace: Object was reached by
  reading field java.util.concurrent.atomic.AtomicReference.value of constant 
    java.util.concurrent.atomic.AtomicReference@374b8d8c: RandomSpi
  indexing into array java.util.concurrent.atomic.AtomicReference[]@3c425ce4: [Ljava.util.concurrent.atomic.AtomicReference;@3c425ce4
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider.providerDefaultRandom of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider@7722f86d: org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProv...
  reading field org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.providerDefaultSecureRandomProvider of constant 
    org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider@58156512: BCFIPS version 1.000204
  reading field org.bouncycastle.jcajce.util.ProviderJcaJceHelper.provider of constant 

I've tried 3 things:

  • Runtime reinitialize org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecuredRandomProvider
  • Reset that field:
@com.oracle.svm.core.annotate.TargetClass(className = "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecuredRandomProvider", onlyWith = BouncyCastleFips.class)
final class Target_org_bouncycastle_jcajce_provider_BouncyCastleFipsProvider$PooledSecuredRandomProvider {
    @Alias
    @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
    private java.util.concurrent.atomic.AtomicReference<java.security.SecureRandom>[] providerDefaultRandom;
}

in both cases having a similar message, here is the one when the substitution is used:

com.oracle.svm.core.util.UserError$UserException: Substitution target for io.quarkus.security.runtime.graal.Target_org_bouncycastle_jcajce_provider_BouncyCastleFipsProvider$PooledSecuredRandomProvider is not loaded. Use field `onlyWith` in the `TargetClass` annotation to make substitution only active when needed.

Please have a quick look next week if you can, keeping fingers crossed it will be an easy one for you :-)

Thanks

@zakkak
Copy link
Contributor

zakkak commented Dec 11, 2023

Runtime reinitialize org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecuredRandomProvider

That only works for static fields that are initialized at class initialization, it doesn't help for object instantiation.

Reset the field

Not sure why you are getting this error, but it again wouldn't solve the issue since it would essentially store a null as the value of that field which would then lead at run-time issues when the code would try to access it.

Please have a quick look next week if you can, keeping fingers crossed it will be an easy one for you :-)

Building with -Dquarkus.native.additional-build-args=--trace-object-instantiation=org.bouncycastle.crypto.fips.FipsSecureRandom I see the following class initializers causing the instantiation of FipsSecureRandom objects:

java.nio.file.TempFileHelper with stacktrace:

Error: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected. Try avoiding to initialize the class that caused initialization of the object.
The culprit object has been instantiated by the 'java.nio.file.TempFileHelper' class initializer with the following trace:
	at org.bouncycastle.crypto.fips.FipsSecureRandom.<init>(Unknown Source)
	at org.bouncycastle.crypto.fips.FipsDRBG$Builder.build(Unknown Source)
	at org.bouncycastle.crypto.fips.FipsDRBG$Builder.build(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider.get(Unknown Source)
	at org.bouncycastle.crypto.CryptoServicesRegistrar.getSecureRandomIfSet(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.getDefaultSecureRandom(Unknown Source)
	at org.bouncycastle.jcajce.provider.ProvRandom$1.createInstance(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$BcService.newInstance(Unknown Source)
	at java.security.SecureRandom.getDefaultPRNG(SecureRandom.java:298)
	at java.security.SecureRandom.<init>(SecureRandom.java:225)
	at java.nio.file.TempFileHelper.<clinit>(TempFileHelper.java:53)

sun.security.util.AnchorCertificates with stacktrace:

Error: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected. Try avoiding to initialize the class that caused initialization of the object.
The culprit object has been instantiated by the 'sun.security.util.AnchorCertificates' class initializer with the following trace:
	at org.bouncycastle.crypto.fips.FipsSecureRandom.<init>(Unknown Source)
	at org.bouncycastle.crypto.fips.FipsDRBG$Builder.build(Unknown Source)
	at org.bouncycastle.crypto.fips.FipsDRBG$Builder.build(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$PooledSecureRandomProvider.get(Unknown Source)
	at org.bouncycastle.crypto.CryptoServicesRegistrar.getSecureRandomIfSet(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.getDefaultSecureRandom(Unknown Source)
	at org.bouncycastle.jcajce.provider.ProvRandom$1.createInstance(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider$BcService.newInstance(Unknown Source)
	at java.security.SecureRandom.getDefaultPRNG(SecureRandom.java:298)
	at java.security.SecureRandom.<init>(SecureRandom.java:225)
	at org.bouncycastle.crypto.asymmetric.KeyUtils.validatedModulus(Unknown Source)
	at org.bouncycastle.crypto.asymmetric.KeyUtils.validated(Unknown Source)
	at org.bouncycastle.crypto.asymmetric.AsymmetricRSAPublicKey.<init>(Unknown Source)
	at org.bouncycastle.crypto.asymmetric.AsymmetricRSAPublicKey.<init>(Unknown Source)
	at org.bouncycastle.jcajce.provider.ProvRSA$RSAKeyFactory.generatePublic(Unknown Source)
	at org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider.getPublicKey(Unknown Source)
	at org.bouncycastle.jcajce.provider.X509CertificateObject.getPublicKey(Unknown Source)
	at sun.security.jca.JCAUtil.tryCommitCertEvent(JCAUtil.java:105)
	at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:356)
	at sun.security.pkcs12.PKCS12KeyStore.loadSafeContents(PKCS12KeyStore.java:2395)
	at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:2005)
	at sun.security.util.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:249)
	at java.security.KeyStore.load(KeyStore.java:1499)
	at sun.security.util.AnchorCertificates$1.run(AnchorCertificates.java:63)
	at sun.security.util.AnchorCertificates$1.run(AnchorCertificates.java:55)
	at java.security.AccessController.executePrivileged(AccessController.java:778)
	at java.security.AccessController.doPrivileged(AccessController.java:319)
	at sun.security.util.AnchorCertificates.<clinit>(AnchorCertificates.java:55)

However, passing --initialize-at-run-time=java.nio.file.TempFileHelper\\,sun.security.util.AnchorCertificates to quarkus.native.additional-build-args doesn't help, neither does setting the said classes to runtime re-init in io.quarkus.security.deployment.SecurityProcessor. Interestingly, java.nio.file.TempFileHelper is registered for runtime re-initialization by GraalVM itself in https://github.com/oracle/graal/blob/ddb574c618499d262dbcad5a2dd611c21177c231/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java#L49

We need to understand why GraalVM seems to ignore these instructions in this particular case.

@sberyozkin
Copy link
Member

Thanks @zakkak for analyzing it, indeed, I guessed afterwards re that the static init vs the instance one, though not sure I follow the comment about resetting fields since we have at least one substitution reset for another field, may be it is a different situation.
As far as these 2 classes are concerned, may be we should allow those 2 packages explicitly as I recall we did something similar for other (at least those which are internal) sun packages ?

@zakkak
Copy link
Contributor

zakkak commented Dec 12, 2023

though not sure I follow the comment about resetting fields since we have at least one substitution reset for another field, may be it is a different situation.

The substitution will only work for object instances in the image heap (i.e. for classes initialized and instantiated at build-time). We can have a short call if you want to discuss about it.

As far as these 2 classes are concerned, may be we should allow those 2 packages explicitly as I recall we did something similar for other (at least those which are internal) sun packages ?

I tried that and it still didn't work. Feel free to give it a go if you want though, as I might have missed something.

@sberyozkin
Copy link
Member

Thanks Foivos, @zakkak, I'd like to believe I understand now :-), but will be glad to have a call at some point to clarify a few things about native images, look forward to it.

As far as those 2 files are concerned, I'm not sure what I can do beyond all the tries you have done, I've searched around and I've only found TempFileHelper referenced in the stack trace (including it creating SecureRandom) in this closed issue:

oracle/graal#3522

So it looks like it is re-initialized correctly in some cases, I wonder if it is may be a Java 11 vs Java 17 thing

@zakkak
Copy link
Contributor

zakkak commented Dec 13, 2023

However, passing --initialize-at-run-time=java.nio.file.TempFileHelper\\,sun.security.util.AnchorCertificates to quarkus.native.additional-build-args doesn't help

The AnchorCertificates build-time-initialization is forced by GraalVM in https://github.com/oracle/graal/blob/5bdb1041af56f06a00515cd0bf3e107c628701ea/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java#L329

Removing it allows Quarkus to initialize it at runtime and also removes the traces showing java.nio.file.TempFileHelper as build-time initialized.

What's interesting though is that in the AnchorCertificates traces there is nothing FIPS-JSSE specific, we should observe the same issues in with just FIPS. According to my tests the SecurityServicesFeature is being used in both integration-tests/bouncycastle-fips and integration-tests/bouncycastle-fips-jsse.

To my understanding this happens because the two tests register the BCFIPS provider at a different index:

bouncycastel-fips:

image

bouncycastel-fips-jsse:

image

Relevant code segments:

@Recorder
public class SecurityProviderRecorder {
public void addBouncyCastleProvider(boolean inFipsMode) {
final String providerName = inFipsMode ? SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME
: SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME;
addProvider(loadProvider(providerName));
}
public void addBouncyCastleJsseProvider() {
Provider bc = loadProvider(SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME);
Provider bcJsse = loadProvider(SecurityProviderUtils.BOUNCYCASTLE_JSSE_PROVIDER_CLASS_NAME);
int sunJsseIndex = findProviderIndex(SecurityProviderUtils.SUN_JSSE_PROVIDER_NAME);
insertProvider(bc, sunJsseIndex);
insertProvider(bcJsse, sunJsseIndex + 1);
}
public void addBouncyCastleFipsJsseProvider() {
Provider bc = loadProvider(SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME);
int sunIndex = findProviderIndex(SecurityProviderUtils.SUN_PROVIDER_NAME);
insertProvider(bc, sunIndex);
Provider bcJsse = loadProviderWithParams(SecurityProviderUtils.BOUNCYCASTLE_JSSE_PROVIDER_CLASS_NAME,
new Class[] { boolean.class, Provider.class }, new Object[] { true, bc });
insertProvider(bcJsse, sunIndex + 1);
}

(In the failing fips-jsse case Security.insertProviderAt is used to add the provider at a specific index, while in fips case Security.addProvider is used instead to append the provider)

Similarly in

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));
}
} else {
// BC or BCFIPS
final String providerName = bouncyCastleProvider.get().isInFipsMode()
? SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME
: SecurityProviderUtils.BOUNCYCASTLE_PROVIDER_CLASS_NAME;
ResultHandle bcProvider = overallCatch.newInstance(MethodDescriptor.ofConstructor(providerName));
overallCatch.invokeStaticMethod(
MethodDescriptor.ofMethod(Security.class, "addProvider", int.class, Provider.class),
bcProvider);

@sberyozkin is there a reason you don't always use the same strategy? Which one is correct?

@sberyozkin
Copy link
Member

Hi @zakkak Sorry, I've totally missed your comments, with all code and doc PRs, thanks for testing it further.

As far as I recall for BC JSSE to be activated correctly, it had to be inserted before Sun JSSE, and since it depends on FIPS, FIPS provider had to come before it. This is the only combination which let me get BC JSSE+FIPS working correctly, if it is the only possible option, I'm not 100% sure, if you'd like please double check and insert the BC JSSE/FIPS at the end (using the code I used for adding BC FIPS only), I believe it will fail though.

Thanks

@sberyozkin
Copy link
Member

May be Sun JSSE provider has to be removed instead and BC JSSE/FIPS then added at the end. I might've tried it too but can't quite recall

@zakkak
Copy link
Contributor

zakkak commented Jan 17, 2024

@sberyozkin I was able to get the native image tests to pass with the following patch (places BCFIPS first and then BCJSSE without touching the rest of the providers):

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 d781085f835..5edc5572cb1 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
@@ -348,8 +348,6 @@ public void write(String s, byte[] bytes) {
                                     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));
@@ -360,13 +358,11 @@ public void write(String s, byte[] bytes) {
                             overallCatch.load(true), bcFipsProvider);
 
                     overallCatch.invokeStaticMethod(
-                            MethodDescriptor.ofMethod(Security.class, "insertProviderAt", int.class, Provider.class,
-                                    int.class),
-                            bcFipsProvider, overallCatch.load(sunIndex));
+                            MethodDescriptor.ofMethod(Security.class, "addProvider", int.class, Provider.class),
+                            bcFipsProvider);
                     overallCatch.invokeStaticMethod(
-                            MethodDescriptor.ofMethod(Security.class, "insertProviderAt", int.class, Provider.class,
-                                    int.class),
-                            bcJsseProvider, overallCatch.load(sunIndex + 1));
+                            MethodDescriptor.ofMethod(Security.class, "addProvider", int.class, Provider.class),
+                            bcJsseProvider);
                 }
             } else {
                 // BC or BCFIPS

However, when applying the equivalent for JVM-mode, i.e.:

diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderRecorder.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderRecorder.java
index 01eb8e2c6bc..4377ca6d387 100644
--- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderRecorder.java
+++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/SecurityProviderRecorder.java
@@ -28,10 +28,9 @@ public void addBouncyCastleJsseProvider() {
 
     public void addBouncyCastleFipsJsseProvider() {
         Provider bc = loadProvider(SecurityProviderUtils.BOUNCYCASTLE_FIPS_PROVIDER_CLASS_NAME);
-        int sunIndex = findProviderIndex(SecurityProviderUtils.SUN_PROVIDER_NAME);
-        insertProvider(bc, sunIndex);
+        addProvider(bc);
         Provider bcJsse = loadProviderWithParams(SecurityProviderUtils.BOUNCYCASTLE_JSSE_PROVIDER_CLASS_NAME,
                 new Class[] { boolean.class, Provider.class }, new Object[] { true, bc });
-        insertProvider(bcJsse, sunIndex + 1);
+        addProvider(bcJsse);
     }
 }
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 d34a25235ae..503e63639c7 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
@@ -8,9 +8,7 @@
 import io.quarkus.runtime.configuration.ConfigurationException;
 
 public final class SecurityProviderUtils {
-    public static final String SUN_PROVIDER_NAME = "SUN";
     public static final String SUN_JSSE_PROVIDER_NAME = "SunJSSE";
-    public static final String SUN_JSSE_PROVIDER_CLASS_NAME = "com.sun.net.ssl.internal.ssl.Provider";
     public static final String BOUNCYCASTLE_PROVIDER_NAME = "BC";
     public static final String BOUNCYCASTLE_JSSE_PROVIDER_NAME = BOUNCYCASTLE_PROVIDER_NAME + "JSSE";
     public static final String BOUNCYCASTLE_FIPS_PROVIDER_NAME = "BCFIPS";

JVM-mode fails with:

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 12.76 s <<< FAILURE! -- in io.quarkus.it.bouncycastle.BouncyCastleFipsJsseTestCase
[ERROR] io.quarkus.it.bouncycastle.BouncyCastleFipsJsseTestCase.testListProviders -- Time elapsed: 10.40 s <<< ERROR!
org.awaitility.core.ConditionTimeoutException: Assertion condition defined in protected void io.quarkus.it.bouncycastle.BouncyCastleFipsJsseTestCase.checkLog(boolean) Log file doesn't contain BouncyCastle JSSE client records, log: io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils - TLS client authentication is required, thus disabling insecure requests. You can switch to `redirect` by setting `quarkus.http.insecure-requests=redirect`./r/nio.quarkus.bootstrap.runner.Timing - quarkus-integration-test-bouncycastle-fips-jsse 999-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 1.267s. Listening on: https://localhost:8444/r/nio.quarkus.bootstrap.runner.Timing - Profile test activated. /r/nio.quarkus.bootstrap.runner.Timing - Installed features: [cdi, resteasy, security, smallrye-context-propagation, vertx]/r/n ==> expected: <true> but was: <false> within 10 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:985)
	at org.awaitility.core.ConditionFactory.untilAsserted(ConditionFactory.java:769)
	at io.quarkus.it.bouncycastle.BouncyCastleFipsJsseTestCase.checkLog(BouncyCastleFipsJsseTestCase.java:89)
	at io.quarkus.it.bouncycastle.BouncyCastleFipsJsseTestCase.testListProviders(BouncyCastleFipsJsseTestCase.java:45)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:1013)
	at io.quarkus.test.junit.QuarkusTestExtension.interceptTestMethod(QuarkusTestExtension.java:827)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.opentest4j.AssertionFailedError: Log file doesn't contain BouncyCastle JSSE client records, log: io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils - TLS client authentication is required, thus disabling insecure requests. You can switch to `redirect` by setting `quarkus.http.insecure-requests=redirect`./r/nio.quarkus.bootstrap.runner.Timing - quarkus-integration-test-bouncycastle-fips-jsse 999-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 1.267s. Listening on: https://localhost:8444/r/nio.quarkus.bootstrap.runner.Timing - Profile test activated. /r/nio.quarkus.bootstrap.runner.Timing - Installed features: [cdi, resteasy, security, smallrye-context-propagation, vertx]/r/n ==> expected: <true> but was: <false>
	at io.quarkus.it.bouncycastle.BouncyCastleFipsJsseTestCase$1.run(BouncyCastleFipsJsseTestCase.java:124)
	at org.awaitility.core.AssertionCondition.lambda$new$0(AssertionCondition.java:53)
	at org.awaitility.core.ConditionAwaiter$ConditionPoller.call(ConditionAwaiter.java:248)
	at org.awaitility.core.ConditionAwaiter$ConditionPoller.call(ConditionAwaiter.java:235)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:840)

Any ideas?

@zakkak
Copy link
Contributor

zakkak commented Jan 17, 2024

I also tried removing SUNJSSE but that didn't work as you predicted although according to https://downloads.bouncycastle.org/fips-java/BC-FJA-(D)TLSUserGuide-1.0.3.pdf the minimum config is:

security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
security.provider.3=sun.security.provider.Sun

@sberyozkin
Copy link
Member

Hey @zakkak Apologies I've missed it, thanks for your time. May be this is the solution, do it only in the native mode. I'll try to get back to this issue once I'll clear some backlog, the fact you have managed to get the tests passing in native mode is a good news indeed, thanks

@michalvavrik
Copy link
Contributor Author

I am removing JSSE from the PR title as issue seems to be in BCFIPS provider and BTW still there.

@michalvavrik michalvavrik changed the title QE Bouncy Castle FIPS JSSE native executable build fails after the BC FIPS version bump to 1.0.2.4 Bouncy Castle BCFIPS provider fails native executable build after the BC FIPS version bump to 1.0.2.4 May 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/native-image area/securepipeline issues related to ensure Quarkus can be used in a secure pipeline setups like FIPS or similar area/security kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants