/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sshd.common.util;
import java.io.IOException;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.sshd.common.cipher.BuiltinCiphers;
import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
import org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider;
import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.util.security.SecurityProviderRegistrar;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.util.test.BaseTestSupport;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SecurityUtilsTest extends BaseTestSupport {
public static final String BC_NAMED_USAGE_PROP =
SecurityProviderRegistrar.CONFIG_PROP_BASE
+ "." + SecurityUtils.BOUNCY_CASTLE
+ "." + SecurityProviderRegistrar.NAMED_PROVIDER_PROPERTY;
private static final String DEFAULT_PASSWORD = "super secret passphrase";
private static final FilePasswordProvider TEST_PASSWORD_PROVIDER = file -> DEFAULT_PASSWORD;
public SecurityUtilsTest() {
super();
}
// NOTE: Using the BouncyCastle provider instead of the name does not work as expected so we take no chances
@BeforeClass
public static void useNamedBouncyCastleProvider() {
System.setProperty(BC_NAMED_USAGE_PROP, Boolean.TRUE.toString());
}
@AfterClass
public static void unsetBouncyCastleProviderUsagePreference() {
System.clearProperty(BC_NAMED_USAGE_PROP);
}
@Test
public void testLoadEncryptedDESPrivateKey() throws Exception {
testLoadEncryptedRSAPrivateKey("DES-EDE3");
}
@Test
public void testLoadEncryptedAESPrivateKey() {
for (BuiltinCiphers c : new BuiltinCiphers[]{
BuiltinCiphers.aes128cbc, BuiltinCiphers.aes192cbc, BuiltinCiphers.aes256cbc
}) {
if (!c.isSupported()) {
System.out.println("Skip unsupported encryption scheme: " + c.getName());
continue;
}
try {
testLoadEncryptedRSAPrivateKey("AES-" + c.getKeySize());
} catch (Exception e) {
fail("Failed (" + e.getClass().getSimpleName() + " to load key for " + c.getName() + ": " + e.getMessage());
}
}
}
private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm) throws IOException, GeneralSecurityException {
return testLoadRSAPrivateKey(DEFAULT_PASSWORD.replace(' ', '-') + "-RSA-" + algorithm.toUpperCase() + "-key");
}
@Test
public void testLoadUnencryptedRSAPrivateKey() throws Exception {
testLoadRSAPrivateKey(getClass().getSimpleName() + "-RSA-KeyPair");
}
@Test
public void testLoadUnencryptedDSSPrivateKey() throws Exception {
testLoadDSSPrivateKey(getClass().getSimpleName() + "-DSA-KeyPair");
}
private KeyPair testLoadDSSPrivateKey(String name) throws Exception {
return testLoadPrivateKey(name, DSAPublicKey.class, DSAPrivateKey.class);
}
@Test
public void testLoadUnencryptedECPrivateKey() throws Exception {
Assume.assumeTrue("EC not supported", SecurityUtils.isECCSupported());
for (ECCurves c : ECCurves.VALUES) {
if (!c.isSupported()) {
System.out.println("Skip unsupported curve: " + c.getName());
continue;
}
testLoadECPrivateKey(getClass().getSimpleName() + "-EC-" + c.getKeySize() + "-KeyPair");
}
}
private KeyPair testLoadECPrivateKey(String name) throws IOException, GeneralSecurityException {
return testLoadPrivateKey(name, ECPublicKey.class, ECPrivateKey.class);
}
private KeyPair testLoadRSAPrivateKey(String name) throws IOException, GeneralSecurityException {
return testLoadPrivateKey(name, RSAPublicKey.class, RSAPrivateKey.class);
}
private KeyPair testLoadPrivateKey(String name, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType)
throws IOException, GeneralSecurityException {
Path folder = getClassResourcesFolder(TEST_SUBFOLDER);
Path file = folder.resolve(name);
KeyPair kpFile = testLoadPrivateKeyFile(file, pubType, prvType);
if (SecurityUtils.isBouncyCastleRegistered()) {
KeyPairResourceLoader bcLoader = SecurityUtils.getBouncycastleKeyPairResourceParser();
Collection<KeyPair> kpList = bcLoader.loadKeyPairs(file, TEST_PASSWORD_PROVIDER);
assertEquals(name + ": Mismatched loaded BouncyCastle keys count", 1, GenericUtils.size(kpList));
KeyPair kpBC = kpList.iterator().next();
assertTrue(name + ": Mismatched BouncyCastle vs. file values", KeyUtils.compareKeyPairs(kpFile, kpBC));
}
Class<?> clazz = getClass();
Package pkg = clazz.getPackage();
KeyPair kpResource = testLoadPrivateKeyResource(pkg.getName().replace('.', '/') + "/" + name, pubType, prvType);
assertTrue(name + ": Mismatched key file vs. resource values", KeyUtils.compareKeyPairs(kpFile, kpResource));
return kpResource;
}
private static KeyPair testLoadPrivateKeyResource(String name, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) {
return testLoadPrivateKey(name, new ClassLoadableResourceKeyPairProvider(name), pubType, prvType);
}
private static KeyPair testLoadPrivateKeyFile(Path file, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) {
return testLoadPrivateKey(file.toString(), new FileKeyPairProvider(file), pubType, prvType);
}
private static KeyPair testLoadPrivateKey(String resourceKey, AbstractResourceKeyPairProvider<?> provider,
Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType) {
provider.setPasswordFinder(TEST_PASSWORD_PROVIDER);
Iterable<KeyPair> iterator = provider.loadKeys();
List<KeyPair> pairs = new ArrayList<>();
for (KeyPair kp : iterator) {
pairs.add(kp);
}
assertEquals("Mismatched loaded pairs count for " + resourceKey, 1, pairs.size());
KeyPair kp = pairs.get(0);
PublicKey pub = kp.getPublic();
assertNotNull("No public key extracted", pub);
assertTrue("Not an " + pubType.getSimpleName() + " public key for " + resourceKey, pubType.isAssignableFrom(pub.getClass()));
PrivateKey prv = kp.getPrivate();
assertNotNull("No private key extracted", prv);
assertTrue("Not an " + prvType.getSimpleName() + " private key for " + resourceKey, prvType.isAssignableFrom(prv.getClass()));
return kp;
}
@Test
public void testSetMaxDHGroupExchangeKeySizeByProperty() {
try {
for (int expected = SecurityUtils.MIN_DHGEX_KEY_SIZE; expected <= SecurityUtils.MAX_DHGEX_KEY_SIZE; expected += 1024) {
SecurityUtils.setMaxDHGroupExchangeKeySize(0); // force detection
try {
System.setProperty(SecurityUtils.MAX_DHGEX_KEY_SIZE_PROP, Integer.toString(expected));
assertTrue("DH group not supported for key size=" + expected, SecurityUtils.isDHGroupExchangeSupported());
assertEquals("Mismatched values", expected, SecurityUtils.getMaxDHGroupExchangeKeySize());
} finally {
System.clearProperty(SecurityUtils.MAX_DHGEX_KEY_SIZE_PROP);
}
}
} finally {
SecurityUtils.setMaxDHGroupExchangeKeySize(0); // force detection
}
}
@Test
public void testSetMaxDHGroupExchangeKeySizeProgrammatically() {
try {
for (int expected = SecurityUtils.MIN_DHGEX_KEY_SIZE; expected <= SecurityUtils.MAX_DHGEX_KEY_SIZE; expected += 1024) {
SecurityUtils.setMaxDHGroupExchangeKeySize(expected);
assertTrue("DH group not supported for key size=" + expected, SecurityUtils.isDHGroupExchangeSupported());
assertEquals("Mismatched values", expected, SecurityUtils.getMaxDHGroupExchangeKeySize());
}
} finally {
SecurityUtils.setMaxDHGroupExchangeKeySize(0); // force detection
}
}
}