Skip to content

Instantly share code, notes, and snippets.

@marnix
Last active August 10, 2020 07:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marnix/834610d0fb92e53ce507edfce96bacb9 to your computer and use it in GitHub Desktop.
Save marnix/834610d0fb92e53ce507edfce96bacb9 to your computer and use it in GitHub Desktop.
/** 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