Add DefaultSecurityProviderConfig with Bouncy Castle disabled (#861)

* Added DefaultSecurityProviderConfig with Bouncy Castle disabled

* Upgrade test to junit jupiter

---------

Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
exceptionfactory
2023-07-20 01:43:25 -05:00
committed by GitHub
parent a3c9c61a09
commit 3069138482
7 changed files with 150 additions and 82 deletions

View File

@@ -19,8 +19,6 @@ import com.hierynomus.sshj.key.KeyAlgorithm;
import com.hierynomus.sshj.key.KeyAlgorithms; import com.hierynomus.sshj.key.KeyAlgorithms;
import net.schmizz.sshj.common.Factory; import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.random.JCERandom;
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
import java.util.Arrays; import java.util.Arrays;
@@ -34,7 +32,6 @@ public class AndroidConfig
SecurityUtils.registerSecurityProvider("org.spongycastle.jce.provider.BouncyCastleProvider"); SecurityUtils.registerSecurityProvider("org.spongycastle.jce.provider.BouncyCastleProvider");
} }
@Override @Override
protected void initKeyAlgorithms() { protected void initKeyAlgorithms() {
setKeyAlgorithms(Arrays.<Factory.Named<KeyAlgorithm>>asList( setKeyAlgorithms(Arrays.<Factory.Named<KeyAlgorithm>>asList(
@@ -43,10 +40,4 @@ public class AndroidConfig
KeyAlgorithms.SSHDSA() KeyAlgorithms.SSHDSA()
)); ));
} }
@Override
protected void initRandomFactory(boolean ignored) {
setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory()));
}
} }

View File

@@ -29,7 +29,6 @@ import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.keepalive.KeepAliveProvider; import net.schmizz.keepalive.KeepAliveProvider;
import net.schmizz.sshj.common.Factory; import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.LoggerFactory; import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.cipher.Cipher; import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.compression.NoneCompression; import net.schmizz.sshj.transport.compression.NoneCompression;
import net.schmizz.sshj.transport.kex.Curve25519SHA256; import net.schmizz.sshj.transport.kex.Curve25519SHA256;
@@ -43,7 +42,11 @@ import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile; import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.util.*; import java.util.Arrays;
import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Properties;
/** /**
* A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if * A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if
@@ -62,8 +65,6 @@ import java.util.*;
* <li>{@link net.schmizz.sshj.ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li> * <li>{@link net.schmizz.sshj.ConfigImpl#setVersion Client version}: {@code "NET_3_0"}</li>
* </ul> * </ul>
* <p/> * <p/>
* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This
* prevents using ciphers with strength greater than 128.
*/ */
public class DefaultConfig public class DefaultConfig
extends ConfigImpl { extends ConfigImpl {
@@ -73,11 +74,10 @@ public class DefaultConfig
public DefaultConfig() { public DefaultConfig() {
setLoggerFactory(LoggerFactory.DEFAULT); setLoggerFactory(LoggerFactory.DEFAULT);
setVersion(readVersionFromProperties()); setVersion(readVersionFromProperties());
final boolean bouncyCastleRegistered = SecurityUtils.isBouncyCastleRegistered(); initKeyExchangeFactories();
initKeyExchangeFactories(bouncyCastleRegistered);
initKeyAlgorithms(); initKeyAlgorithms();
initRandomFactory(bouncyCastleRegistered); initRandomFactory();
initFileKeyProviderFactories(bouncyCastleRegistered); initFileKeyProviderFactories();
initCipherFactories(); initCipherFactories();
initCompressionFactories(); initCompressionFactories();
initMACFactories(); initMACFactories();
@@ -102,35 +102,32 @@ public class DefaultConfig
log = loggerFactory.getLogger(getClass()); log = loggerFactory.getLogger(getClass());
} }
protected void initKeyExchangeFactories(boolean bouncyCastleRegistered) { protected void initKeyExchangeFactories() {
if (bouncyCastleRegistered) { setKeyExchangeFactories(
setKeyExchangeFactories( new Curve25519SHA256.Factory(),
new Curve25519SHA256.Factory(), new Curve25519SHA256.FactoryLibSsh(),
new Curve25519SHA256.FactoryLibSsh(), new DHGexSHA256.Factory(),
new DHGexSHA256.Factory(), new ECDHNistP.Factory521(),
new ECDHNistP.Factory521(), new ECDHNistP.Factory384(),
new ECDHNistP.Factory384(), new ECDHNistP.Factory256(),
new ECDHNistP.Factory256(), new DHGexSHA1.Factory(),
new DHGexSHA1.Factory(), DHGroups.Group1SHA1(),
DHGroups.Group1SHA1(), DHGroups.Group14SHA1(),
DHGroups.Group14SHA1(), DHGroups.Group14SHA256(),
DHGroups.Group14SHA256(), DHGroups.Group15SHA512(),
DHGroups.Group15SHA512(), DHGroups.Group16SHA512(),
DHGroups.Group16SHA512(), DHGroups.Group17SHA512(),
DHGroups.Group17SHA512(), DHGroups.Group18SHA512(),
DHGroups.Group18SHA512(), ExtendedDHGroups.Group14SHA256AtSSH(),
ExtendedDHGroups.Group14SHA256AtSSH(), ExtendedDHGroups.Group15SHA256(),
ExtendedDHGroups.Group15SHA256(), ExtendedDHGroups.Group15SHA256AtSSH(),
ExtendedDHGroups.Group15SHA256AtSSH(), ExtendedDHGroups.Group15SHA384AtSSH(),
ExtendedDHGroups.Group15SHA384AtSSH(), ExtendedDHGroups.Group16SHA256(),
ExtendedDHGroups.Group16SHA256(), ExtendedDHGroups.Group16SHA384AtSSH(),
ExtendedDHGroups.Group16SHA384AtSSH(), ExtendedDHGroups.Group16SHA512AtSSH(),
ExtendedDHGroups.Group16SHA512AtSSH(), ExtendedDHGroups.Group18SHA512AtSSH(),
ExtendedDHGroups.Group18SHA512AtSSH(), new ExtInfoClientFactory()
new ExtInfoClientFactory()); );
} else {
setKeyExchangeFactories(DHGroups.Group1SHA1(), new DHGexSHA1.Factory());
}
} }
protected void initKeyAlgorithms() { protected void initKeyAlgorithms() {
@@ -151,21 +148,19 @@ public class DefaultConfig
KeyAlgorithms.SSHDSA())); KeyAlgorithms.SSHDSA()));
} }
protected void initRandomFactory(boolean bouncyCastleRegistered) { protected void initRandomFactory() {
setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory())); setRandomFactory(new SingletonRandomFactory(new JCERandom.Factory()));
} }
protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) { protected void initFileKeyProviderFactories() {
if (bouncyCastleRegistered) { setFileKeyProviderFactories(
setFileKeyProviderFactories( new OpenSSHKeyV1KeyFile.Factory(),
new OpenSSHKeyV1KeyFile.Factory(), new PKCS8KeyFile.Factory(),
new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(),
new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory()
new PuTTYKeyFile.Factory()); );
}
} }
protected void initCipherFactories() { protected void initCipherFactories() {
List<Factory.Named<Cipher>> avail = new LinkedList<Factory.Named<Cipher>>(Arrays.<Factory.Named<Cipher>>asList( List<Factory.Named<Cipher>> avail = new LinkedList<Factory.Named<Cipher>>(Arrays.<Factory.Named<Cipher>>asList(
ChachaPolyCiphers.CHACHA_POLY_OPENSSH(), ChachaPolyCiphers.CHACHA_POLY_OPENSSH(),
@@ -203,27 +198,22 @@ public class DefaultConfig
StreamCiphers.Arcfour256()) StreamCiphers.Arcfour256())
); );
boolean warn = false; final ListIterator<Factory.Named<Cipher>> factories = avail.listIterator();
// Ref. https://issues.apache.org/jira/browse/SSHD-24 while (factories.hasNext()) {
// "AES256 and AES192 requires unlimited cryptography extension" final Factory.Named<Cipher> factory = factories.next();
for (Iterator<Factory.Named<Cipher>> i = avail.iterator(); i.hasNext(); ) {
final Factory.Named<Cipher> f = i.next();
try { try {
final Cipher c = f.create(); final Cipher cipher = factory.create();
final byte[] key = new byte[c.getBlockSize()]; final byte[] key = new byte[cipher.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()]; final byte[] iv = new byte[cipher.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv); cipher.init(Cipher.Mode.Encrypt, key, iv);
} catch (Exception e) { } catch (Exception e) {
warn = true; log.info("Cipher [{}] disabled: {}", factory.getName(), e.getCause().getMessage());
log.warn(e.getCause().getMessage()); factories.remove();
i.remove();
} }
} }
if (warn)
log.warn("Disabling high-strength ciphers: cipher strengths apparently limited by JCE policy");
setCipherFactories(avail); setCipherFactories(avail);
log.debug("Available cipher factories: {}", avail); log.debug("Available Ciphers {}", avail);
} }
protected void initMACFactories() { protected void initMACFactories() {

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.SecurityUtils;
/**
* SSHJ Configuration that uses the default Security Provider configuration from java.security and disables Bouncy Castle registration
*/
public class DefaultSecurityProviderConfig extends DefaultConfig {
static {
// Disable Bouncy Castle Provider registration prior to invoking constructors
SecurityUtils.setRegisterBouncyCastle(false);
}
}

View File

@@ -57,9 +57,6 @@ class ECDSAVariationsAdapter {
static PublicKey readPubKeyFromBuffer(Buffer<?> buf, String variation) throws GeneralSecurityException { static PublicKey readPubKeyFromBuffer(Buffer<?> buf, String variation) throws GeneralSecurityException {
String algorithm = BASE_ALGORITHM_NAME + variation; String algorithm = BASE_ALGORITHM_NAME + variation;
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + algorithm);
}
try { try {
// final String algo = buf.readString(); it has been already read // final String algo = buf.readString(); it has been already read
final String curveName = buf.readString(); final String curveName = buf.readString();

View File

@@ -259,6 +259,11 @@ public class SecurityUtils {
return false; return false;
} }
/**
* Configure whether to register the Bouncy Castle Security Provider. Must be called prior to other methods
*
* @param registerBouncyCastle Enable or disable Bouncy Castle Provider registration on subsequent method invocation
*/
public static synchronized void setRegisterBouncyCastle(boolean registerBouncyCastle) { public static synchronized void setRegisterBouncyCastle(boolean registerBouncyCastle) {
SecurityUtils.registerBouncyCastle = registerBouncyCastle; SecurityUtils.registerBouncyCastle = registerBouncyCastle;
registrationDone = false; registrationDone = false;

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj;
import net.schmizz.sshj.common.SecurityUtils;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.Optional;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class DefaultSecurityProviderConfigTest {
private static Provider bouncyCastleProvider;
@BeforeAll
public static void removeProviders() {
bouncyCastleProvider = Security.getProvider(SecurityUtils.BOUNCY_CASTLE);
if (bouncyCastleProvider != null) {
Security.removeProvider(SecurityUtils.BOUNCY_CASTLE);
}
}
@AfterAll
public static void addProviders() {
if (bouncyCastleProvider != null) {
Security.addProvider(bouncyCastleProvider);
}
}
@Test
public void testBouncyCastleNotRegistered() {
new DefaultSecurityProviderConfig();
assertBouncyCastleNotRegistered();
}
private void assertBouncyCastleNotRegistered() {
final Optional<String> bouncyCastleFound = Arrays.stream(Security.getProviders())
.map(Provider::getName)
.filter(SecurityUtils.BOUNCY_CASTLE::contentEquals)
.findFirst();
assertFalse(bouncyCastleFound.isPresent());
}
}

View File

@@ -19,7 +19,6 @@ import com.hierynomus.sshj.common.KeyDecryptionFailedException;
import com.hierynomus.sshj.userauth.certificate.Certificate; import com.hierynomus.sshj.userauth.certificate.Certificate;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.KeyType;
import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
import net.schmizz.sshj.userauth.password.PasswordFinder; import net.schmizz.sshj.userauth.password.PasswordFinder;
@@ -448,13 +447,6 @@ public class OpenSSHKeyFileTest {
assertThrows(IOException.class, keyProvider::getPrivate, "This key is not in 'openssh-key-v1' format"); assertThrows(IOException.class, keyProvider::getPrivate, "This key is not in 'openssh-key-v1' format");
} }
@BeforeEach
public void checkBCRegistration() {
if (!SecurityUtils.isBouncyCastleRegistered()) {
throw new AssertionError("bouncy castle needed");
}
}
private String readFile(String pathname) private String readFile(String pathname)
throws IOException { throws IOException {
StringBuilder fileContents = new StringBuilder(); StringBuilder fileContents = new StringBuilder();