Skip to content

Commit

Permalink
apacheGH-590: provide a single flag to enable FIPS mode
Browse files Browse the repository at this point in the history
Add a flag in SecurityUtils to enable FIPS mode. In FIPS mode,
algorithms known to be not FIPS-compliant are had disabled and not
available. The BouncyCastleSecurityRegistrar only considers bc-fips,
and the SunJCESecurityRegistrar and the EdDSASecurityRegistrar are
disabled.

The ChaCha20-Poly1305 cipher is disabled, ed25519 signatures are
disabled, the bcrypt KDF used in OpenSSH-format encrypted private
keys[1] is disabled, and the curve25519 and curve448 key exchange
methods are disabled. Also disabled is the post-quantum
sntrup761x25519-sha512 key exchange method.

These disabled algorithms are not approved in FIPS 140.

The flag can be set via a system property or by calling
SecurityUtils.setFipsMode(). The system property is
"org.apache.sshd.security.fipsEnabled" and takes the boolean
value "true". Any other value does not enable FIPS mode.

[1] https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
  • Loading branch information
tomaswolf committed Sep 3, 2024
1 parent 139ff01 commit 56571c6
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -39,6 +40,7 @@
import org.apache.sshd.common.config.NamedFactoriesListParseResult;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.security.SecurityUtils;

/**
* Provides easy access to the currently implemented ciphers
Expand All @@ -51,6 +53,11 @@ public enum BuiltinCiphers implements CipherFactory {
public Cipher create() {
return new CipherNone();
}

@Override
public boolean isSupported() {
return !SecurityUtils.isFipsMode();
}
},
aes128cbc(Constants.AES128_CBC, 16, 0, 16, "AES", 128, "AES/CBC/NoPadding", 16) {
@Override
Expand Down Expand Up @@ -149,6 +156,11 @@ public Cipher create() {
public Cipher create() {
return new ChaCha20Cipher();
}

@Override
public boolean isSupported() {
return !SecurityUtils.isFipsMode();
}
},
/**
* @deprecated
Expand All @@ -175,7 +187,7 @@ public Cipher create() {
private final int blkSize;
private final String algorithm;
private final String transformation;
private final boolean supported;
private final AtomicReference<Boolean> supported = new AtomicReference<>();

BuiltinCiphers(
String factoryName, int ivsize, int authSize, int kdfSize,
Expand All @@ -188,13 +200,6 @@ public Cipher create() {
this.algorithm = algorithm;
this.transformation = transformation;
this.blkSize = blkSize;
/*
* This can be done once since in order to change the support the JVM needs to be stopped, some
* unlimited-strength files need be installed and then the JVM re-started. Therefore, the answer is not going to
* change while the JVM is running
*/
this.supported = Constants.NONE.equals(factoryName) || Constants.CC20P1305_OPENSSH.equals(factoryName)
|| Cipher.checkSupported(this.transformation, this.keysize);
}

@Override
Expand All @@ -213,7 +218,14 @@ public final String toString() {
*/
@Override
public boolean isSupported() {
return supported;
Boolean value = supported.get();
if (value == null) {
value = Cipher.checkSupported(this.transformation, this.keysize);
if (!supported.compareAndSet(null, value)) {
value = supported.get();
}
}
return value.booleanValue();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ protected OpenSSHKdfOptions resolveKdfOptions(
OpenSSHKdfOptions options;
// TODO define a factory class where users can register extra KDF options
if (BCryptKdfOptions.NAME.equalsIgnoreCase(kdfName)) {
if (SecurityUtils.isFipsMode()) {
throw new NoSuchAlgorithmException(BCryptKdfOptions.NAME + " is disabled in FIPS mode");
}
options = new BCryptKdfOptions();
} else {
options = new RawKdfOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
Expand All @@ -50,8 +51,11 @@
import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCrypt;
import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
import org.apache.sshd.common.config.keys.writer.KeyPairResourceWriter;
import org.apache.sshd.common.random.JceRandomFactory;
import org.apache.sshd.common.random.Random;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils;

/**
* A {@link KeyPairResourceWriter} for writing keys in the modern OpenSSH format, using the OpenBSD bcrypt KDF for
Expand Down Expand Up @@ -272,14 +276,18 @@ public byte[] getKdfOptions() {
@Override
protected byte[] deriveEncryptionKey(PrivateKeyEncryptionContext context, int keyLength)
throws IOException, GeneralSecurityException {
if (SecurityUtils.isFipsMode()) {
throw new NoSuchAlgorithmException(BCryptKdfOptions.NAME + " is disabled in FIPS mode");
}

byte[] iv = context.getInitVector();
if (iv == null) {
iv = generateInitializationVector(context);
}

byte[] salt = new byte[BCRYPT_SALT_LENGTH];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
Random random = JceRandomFactory.INSTANCE.create();
random.fill(salt);

byte[] kdfOutput = new byte[keyLength + iv.length];
BCrypt bcrypt = new BCrypt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ public final class SecurityUtils {

public static final String PROP_DEFAULT_SECURITY_PROVIDER = "org.apache.sshd.security.defaultProvider";

/**
* A boolean system property that can be set to {@code "true"} to enable FIPS mode. In FIPS mode, crypto-algorithms
* not approved in FIPS-140 will not be available.
* <p>
* <b>Note:</b> if this system property is not {@code "true"}, it can be overridden via {@link #setFipsMode()}.
* </p>
*/
public static final String FIPS_ENABLED = "org.apache.sshd.security.fipsEnabled";

private static final AtomicInteger MIN_DHG_KEY_SIZE_HOLDER = new AtomicInteger(0);
private static final AtomicInteger MAX_DHG_KEY_SIZE_HOLDER = new AtomicInteger(0);

Expand All @@ -181,12 +190,44 @@ public final class SecurityUtils {

private static final AtomicReference<SecurityProviderChoice> DEFAULT_PROVIDER_HOLDER = new AtomicReference<>();

private static final AtomicReference<Boolean> FIPS_MODE = new AtomicReference<>();

private static Boolean hasEcc;

private SecurityUtils() {
throw new UnsupportedOperationException("No instance");
}

/**
* Unconditionally set FIPS mode, overriding the {@link #FIPS_ENABLED} system property.
*
* @throws IllegalStateException if a call to {@link #isFipsMode()} had already occurred and returned {@code false}.
*/
public static void setFipsMode() {
if (!FIPS_MODE.compareAndSet(null, Boolean.TRUE)) {
if (!Boolean.TRUE.equals(FIPS_MODE.get())) {
throw new IllegalStateException("FIPS mode was already set to FALSE");
}
}
}

/**
* Tells whether FIPS mode is enabled, either through the system property {@link #FIPS_ENABLED} or via
* {@link #setFipsMode()}.
*
* @return {@code true} if FIPS mode is enabled, {@code false} otherwise.
*/
public static boolean isFipsMode() {
Boolean value = FIPS_MODE.get();
if (FIPS_MODE.get() == null) {
value = Boolean.getBoolean(FIPS_ENABLED);
if (!FIPS_MODE.compareAndSet(null, value)) {
value = FIPS_MODE.get();
}
}
return value;
}

/**
* @param name The provider's name - never {@code null}/empty
* @return {@code true} if the provider is marked as disabled a-priori
Expand Down Expand Up @@ -565,6 +606,9 @@ public static RandomFactory getRandomFactory() {
* @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported
*/
public static boolean isEDDSACurveSupported() {
if (isFipsMode()) {
return false;
}
register();

SecurityProviderRegistrar r = getRegisteredProvider(EDDSA);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public SunJCESecurityProviderRegistrar() {

@Override
public boolean isEnabled() {
if (!super.isEnabled()) {
if (SecurityUtils.isFipsMode() || !super.isEnabled()) {
return false;
}
return isSupported();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ public boolean isSupported() {
if (supported != null) {
return supported.booleanValue();
}

Class<?> clazz = ThreadUtils.resolveDefaultClass(getClass(), PROVIDER_CLASS);
boolean requireFips = SecurityUtils.isFipsMode();
Class<?> clazz = null;
if (!requireFips) {
clazz = ThreadUtils.resolveDefaultClass(getClass(), PROVIDER_CLASS);
}
if (clazz == null) {
clazz = ThreadUtils.resolveDefaultClass(getClass(), FIPS_PROVIDER_CLASS);
}
Expand All @@ -145,7 +148,7 @@ public boolean isSupported() {
Provider provider = Security.getProvider(BCFIPS_PROVIDER_NAME);
if (provider != null) {
providerName = BCFIPS_PROVIDER_NAME;
} else {
} else if (!requireFips) {
provider = Security.getProvider(BC_PROVIDER_NAME);
if (provider != null) {
providerName = BC_PROVIDER_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public EdDSASecurityProviderRegistrar() {

@Override
public boolean isEnabled() {
if (!super.isEnabled()) {
if (SecurityUtils.isFipsMode() || !super.isEnabled()) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public int getKeySize() {

@Override
public boolean isSupported() {
return supported;
return supported && !SecurityUtils.isFipsMode();
}

public KeyAgreement createKeyAgreement() throws GeneralSecurityException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.security.SecureRandom;
import java.util.Arrays;

import org.apache.sshd.common.util.security.SecurityUtils;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.pqc.crypto.ntruprime.SNTRUPrimeKEMExtractor;
Expand All @@ -41,6 +42,9 @@ private SNTRUP761() {
}

static boolean isSupported() {
if (SecurityUtils.isFipsMode()) {
return false;
}
try {
return SNTRUPrimeParameters.sntrup761.getSessionKeySize() == 256; // BC < 1.78 had only 128
} catch (Throwable e) {
Expand Down

0 comments on commit 56571c6

Please sign in to comment.