Last active
August 10, 2020 07:02
-
-
Save marnix/834610d0fb92e53ce507edfce96bacb9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** This reproduction scenario by Marnix Klooster is public domain. */ | |
package marnix.experiment.bcfips; | |
import java.net.URL; | |
import java.security.KeyStore; | |
import java.security.Security; | |
import javax.net.ssl.SSLContext; | |
import javax.net.ssl.TrustManager; | |
import javax.net.ssl.TrustManagerFactory; | |
import org.bouncycastle.crypto.CryptoServicesRegistrar; | |
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; | |
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; | |
/** | |
* Reproduction scenario for unexplained approved/unapproved exception. Run with | |
* bc-fips-1.0.2.jar and bctls-fips-1.0.10.jar. Run on a standard Java 8 JRE (I | |
* used Corretto), without any lib/security modifications or lib/ext additions, | |
* no SecurityManager, and not setting the 'approved-only mode' JVM property. | |
* | |
* As recommended in the bc-fips-1.0.2 user guide for Java 9+, this attempts to | |
* run without the Oracle/OpenJDK SunJSSE, and instead use BCJSSE from | |
* bctsl-fips. | |
* | |
* However, that combination (1) seems to require manual trust manager creation, | |
* and (2) inexplicably fails with an error about mixing approved/unapproved | |
* modes. | |
* | |
* Specifically, it seems that a DigestOutputStream object is created in one | |
* mode, then stored statically somewhere, and then used in the other mode... | |
* | |
* This seems related to the discussion in this thread: | |
* http://bouncy-castle.1462172.n4.nabble.com/Error-quot-Attempt-to-use-unapproved-implementation-in-approved-thread-quot-on-Tomcat-8-5-9-with-SSL-td4658537.html | |
*/ | |
public class MainForBCFIPSMixedApprovedOnlyIssue { | |
@SuppressWarnings("restriction") | |
public static void main(String[] args) throws Throwable { | |
// workaround for just this experiment, so that we don't have to have cacerts in | |
// BCFKS mode... | |
System.setProperty("org.bouncycastle.jca.enable_jks", "true"); | |
Security.insertProviderAt(new BouncyCastleFipsProvider(), 1); // insert at front to make sure it's preferred | |
Security.removeProvider("SunJSSE"); | |
// The following workaround for an SNI restriction in BCJSSE is from | |
// https://github.com/bcgit/bc-java/issues/460#issuecomment-461085449 | |
// and the bctls 1.0.10 user guide, section 3.5.1. | |
// There they offer a better solution, which seems a lot more involved. | |
System.setProperty("jdk.tls.trustNameService", "true"); | |
Security.insertProviderAt(new BouncyCastleJsseProvider("fips:BCFIPS"), 2); | |
for (boolean approvedOnlyMode : new boolean[] { false, true }) { // order of 'false' and 'true' doesn't matter | |
Throwable threadException[] = new Throwable[1]; | |
Thread thread = new Thread(() -> { | |
try { | |
CryptoServicesRegistrar.setApprovedOnlyMode(approvedOnlyMode); | |
assert approvedOnlyMode == CryptoServicesRegistrar.isInApprovedOnlyMode(); | |
// It looks like this is needed, to make the below HTTPS connection work. | |
TrustManagerFactory tmf = TrustManagerFactory | |
.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | |
tmf.init((KeyStore) null); | |
TrustManager[] trustManagers = tmf.getTrustManagers(); | |
SSLContext context = SSLContext.getInstance("TLS"); | |
context.init(null, trustManagers, null); | |
SSLContext.setDefault(context); // fails if SunJSSE.isFIPS() == true | |
// The following fails the second time, with | |
// | |
// Caused by: org.bouncycastle.crypto.fips.FipsUnapprovedOperationError: Attempt to use unapproved implementation in approved thread: SHA-512 | |
// at org.bouncycastle.crypto.internal.io.Utils.approvedModeCheck(Unknown Source) | |
// at org.bouncycastle.crypto.internal.io.DigestOutputStream.write(Unknown Source) | |
// at org.bouncycastle.crypto.UpdateOutputStream.update(Unknown Source) | |
// at org.bouncycastle.jcajce.provider.BaseMessageDigest.engineUpdate(Unknown Source) | |
// at java.security.MessageDigest.update(MessageDigest.java:335) | |
// at org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider$NonceEntropySource$NonceEntropySourceSpi.runDigest(Unknown Source) | |
// at org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider$NonceEntropySource$NonceEntropySourceSpi.engineNextBytes(Unknown Source) | |
// at java.security.SecureRandom.nextBytes(SecureRandom.java:468) | |
// at org.bouncycastle.tls.crypto.impl.jcajce.JcaNonceGenerator.<init>(Unknown Source) | |
// at ... | |
// | |
// (or with "...approved implementation in unapproved thread...", | |
// if true, false is used above). | |
assert new URL("https://www.google.com").openConnection().getInputStream().read(new byte[4096]) != -1; | |
} catch (Throwable e) { | |
threadException[0] = e; | |
} | |
}, "Thread-approvedOnlyMode-" + approvedOnlyMode); | |
thread.start(); | |
thread.join(); | |
if (threadException[0] != null) { | |
// to make this unit test fail | |
throw new RuntimeException("error in approvedOnlyMode==" + approvedOnlyMode + ": " + threadException[0], | |
threadException[0]); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment