Skip to content

Commit

Permalink
implement fips compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
artoonie committed Oct 30, 2023
1 parent 9966ec2 commit ee4fd99
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.3'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
implementation 'org.bouncycastle:bc-fips:1.0.2.4'
}

// ### Application plugin settings
Expand Down
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
requires java.xml;
requires org.apache.poi.ooxml;
requires java.xml.crypto;
requires org.bouncycastle.fips.core;
// enable reflexive calls from network.brightspots.rcv into javafx.fxml
opens network.brightspots.rcv;
// our main module
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/network/brightspots/rcv/HartCvrReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ boolean verifyHashIfNeeded(File cvrXml) {
boolean isHashVerified = false;

if (SecurityConfig.isIsHartSignatureValidationEnabled()) {
if (!SecurityConfig.haveProvidersBeenCulled()) {
Logger.severe("RCTab security error. Could not meet FIPS compliance.");
}

File signatureXml = new File(cvrXml.getAbsolutePath() + ".sig.xml");
if (signatureXml.exists()) {
try {
Expand Down
78 changes: 78 additions & 0 deletions src/main/java/network/brightspots/rcv/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import static network.brightspots.rcv.SecurityXmlParsers.RsaKeyValue;

import java.security.NoSuchAlgorithmException;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;

class SecurityConfig {
// Only the unit test modules should ever set this to false, if it is initially set as true.
// Note: On some builds, this will be configured to false by default. We will need some
Expand All @@ -37,6 +40,18 @@ class SecurityConfig {
// The RsaKeyValue is lazily-initialized. It is set on the first call to getRsaPublicKey().
private static RsaKeyValue rsaKeyValue = null;

private static final String expectedSecureRandomProvider = "SUN";

private static final String expectedXmlCanonicalizationProvider = "XMLDSig";

static {
try {
SetupAndVerifyFipsCompliance();
} catch (SecurityConfigurationException e) {
throw new RuntimeException(e);
}
}

public static boolean isIsHartSignatureValidationEnabled() {
return IS_HART_SIGNATURE_VALIDATION_ENABLED;
}
Expand Down Expand Up @@ -65,4 +80,67 @@ public static void disableValidationForUnitTests() {

IS_HART_SIGNATURE_VALIDATION_ENABLED = false;
}

private static void SetupAndVerifyFipsCompliance() throws SecurityConfigurationException {
// First, verify that the SecureRandom provider is what we expect.
try {
String randomProviderName =
java.security.SecureRandom.getInstanceStrong().getProvider().getName();
if (!randomProviderName.equals(expectedSecureRandomProvider)) {
throw new SecurityConfigurationException("Unexpected SecureRandom provider"
+ randomProviderName);
}
} catch (NoSuchAlgorithmException e) {
throw new SecurityConfigurationException("No SecureRandom algorithm found.");
}

java.security.Security.insertProviderAt(new BouncyCastleFipsProvider(), 1);
// Remove all providers except for the SecureRandom provider and the XMLDSig provider.
// XMLDsig is only used for canonicalizaton, not for anything related to cryptography.
java.security.Provider[] providers = java.security.Security.getProviders();
int numSecureRandomProviders = 0;
int numXmlCanonicalizationProviders = 0;
for (java.security.Provider provider : providers) {
String name = provider.getName();
if (name.equals(expectedSecureRandomProvider)) {
numSecureRandomProviders++;
} else if (name.equals(expectedXmlCanonicalizationProvider)) {
numXmlCanonicalizationProviders++;
} else {
java.security.Security.removeProvider(name);
}
}

if (numSecureRandomProviders != 1) {
throw new SecurityConfigurationException(
"Must have exactly one SecureRandom provider, but found "
+ numSecureRandomProviders);
}
if (numXmlCanonicalizationProviders != 1) {
throw new SecurityConfigurationException(
"Must have exactly one XML Canonicalization provider, but found "
+ numXmlCanonicalizationProviders);
}

// Note: position 1 is "most preferred", not 0
java.security.Security.insertProviderAt(new BouncyCastleFipsProvider(), 1);

Logger.info("Security Providers: ");
for (java.security.Provider provider : java.security.Security.getProviders()) {
Logger.info(provider.getName() + "\n\t" + provider.getInfo());
}
}

/*
* Sanity check to ensure SetupAndVerifyFipsCompliance has been run.
*/
public static boolean haveProvidersBeenCulled() {
return java.security.Security.getProviders().length == 3;
}

static class SecurityConfigurationException extends Exception {
SecurityConfigurationException(String message) {
super(message);
}
}
}
12 changes: 12 additions & 0 deletions src/test/java/network/brightspots/rcv/SecurityTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,16 @@ void testUnexpectedPublicKey() {
DEFAULT_DATA_FILE)
);
}

@Test
@DisplayName("Ensure FIPS Compliance check is run")
void testFipsComplianceWasCorrectlySetUp() {
Assertions.assertTrue(SecurityConfig.haveProvidersBeenCulled());

java.security.Provider[] providers = java.security.Security.getProviders();
Assertions.assertEquals(3, providers.length);
Assertions.assertEquals("BCFIPS", providers[0].getName());
Assertions.assertEquals("SUN", providers[1].getName());
Assertions.assertEquals("XMLDSig", providers[2].getName());
}
}

0 comments on commit ee4fd99

Please sign in to comment.