[SSHD-673] "sendKexInit() no resolved signatures available" with key-type "EC"
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index 34587b7..dbc2359 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -24,8 +24,11 @@
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.EnumSet;
+import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@@ -124,6 +127,25 @@
}
});
+ public static final Comparator<ECCurves> BY_KEY_SIZE = new Comparator<ECCurves>() {
+ @Override
+ public int compare(ECCurves o1, ECCurves o2) {
+ int k1 = (o1 == null) ? Integer.MAX_VALUE : o1.getKeySize();
+ int k2 = (o2 == null) ? Integer.MAX_VALUE : o2.getKeySize();
+ return Integer.compare(k1, k2);
+ }
+ };
+
+ public static final List<ECCurves> SORTED_KEY_SIZE =
+ Collections.unmodifiableList(
+ new ArrayList<ECCurves>(VALUES) {
+ // Not serializing it
+ private static final long serialVersionUID = 1L;
+
+ {
+ Collections.sort(this, BY_KEY_SIZE);
+ }
+ });
private final String name;
private final String keyType;
private final ECParameterSpec params;
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index ba9f417..345c0b3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -45,6 +45,7 @@
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.ServiceFactory;
import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.helpers.AbstractFactoryManager;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoServiceFactory;
@@ -423,6 +424,7 @@
String provider;
boolean error = false;
String hostKeyType = AbstractGeneratorHostKeyProvider.DEFAULT_ALGORITHM;
+ int hostKeySize = 0;
Map<String, String> options = new LinkedHashMap<>();
int numArgs = GenericUtils.length(args);
@@ -440,6 +442,13 @@
break;
}
hostKeyType = args[++i].toUpperCase();
+ } else if ("-key-size".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
+ break;
+ }
+
+ hostKeySize = Integer.parseInt(args[++i]);
} else if ("-io".equals(argName)) {
if (i + 1 >= numArgs) {
System.err.println("option requires an argument: " + argName);
@@ -501,14 +510,20 @@
hostKeyProvider = new SimpleGeneratorHostKeyProvider(hostKeyFile);
}
hostKeyProvider.setAlgorithm(hostKeyType);
+ if (hostKeySize != 0) {
+ hostKeyProvider.setKeySize(hostKeySize);
+ }
List<KeyPair> keys = ValidateUtils.checkNotNullAndNotEmpty(hostKeyProvider.loadKeys(),
"Failed to load keys from %s", hostKeyFile);
KeyPair kp = keys.get(0);
PublicKey pubKey = kp.getPublic();
String keyAlgorithm = pubKey.getAlgorithm();
+ if ("ECDSA".equalsIgnoreCase(keyAlgorithm)) {
+ keyAlgorithm = KeyUtils.EC_ALGORITHM;
+ }
// force re-generation of host key if not same algorithm
- if (!Objects.equals(keyAlgorithm, hostKeyProvider.getAlgorithm())) {
+ if (!Objects.equals(keyAlgorithm, hostKeyType)) {
Files.deleteIfExists(hostKeyFile);
hostKeyProvider.clearLoadedKeys();
}
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-core/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
index 6a57763..d3b653b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java
@@ -33,8 +33,10 @@
import java.security.spec.AlgorithmParameterSpec;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
+import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
import org.apache.sshd.common.util.SecurityUtils;
@@ -151,32 +153,24 @@
}
protected KeyPair resolveKeyPair(Path keyPath) throws IOException, GeneralSecurityException {
+ String alg = getAlgorithm();
+ KeyPair kp;
if (keyPath != null) {
- LinkOption[] options = IoUtils.getLinkOptions(false);
- if (Files.exists(keyPath, options) && Files.isRegularFile(keyPath, options)) {
- try {
- KeyPair kp = readKeyPair(keyPath, IoUtils.EMPTY_OPEN_OPTIONS);
- if (kp != null) {
- if (log.isDebugEnabled()) {
- PublicKey key = kp.getPublic();
- log.debug("resolveKeyPair({}) loaded key={}-{}",
- keyPath, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
- }
- return kp;
- }
- } catch (Throwable e) {
- log.warn("resolveKeyPair({}) Failed ({}) to load: {}",
- keyPath, e.getClass().getSimpleName(), e.getMessage());
- if (log.isDebugEnabled()) {
- log.debug("resolveKeyPair(" + keyPath + ") load failure details", e);
- }
+ try {
+ kp = loadFromFile(alg, keyPath);
+ if (kp != null) {
+ return kp;
+ }
+ } catch (Throwable e) {
+ log.warn("resolveKeyPair({}) Failed ({}) to load: {}",
+ keyPath, e.getClass().getSimpleName(), e.getMessage());
+ if (log.isDebugEnabled()) {
+ log.debug("resolveKeyPair(" + keyPath + ") load failure details", e);
}
}
}
// either no file specified or no key in file
- String alg = getAlgorithm();
- KeyPair kp;
try {
kp = generateKeyPair(alg);
if (kp == null) {
@@ -213,6 +207,39 @@
return kp;
}
+ protected KeyPair loadFromFile(String alg, Path keyPath) throws IOException, GeneralSecurityException {
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ if ((!Files.exists(keyPath, options)) || (!Files.isRegularFile(keyPath, options))) {
+ return null;
+ }
+
+ KeyPair kp = readKeyPair(keyPath, IoUtils.EMPTY_OPEN_OPTIONS);
+ if (kp == null) {
+ return null;
+ }
+ PublicKey key = kp.getPublic();
+ String keyAlgorithm = key.getAlgorithm();
+ if ("ECDSA".equalsIgnoreCase(keyAlgorithm)) {
+ keyAlgorithm = KeyUtils.EC_ALGORITHM;
+ }
+
+ if (Objects.equals(alg, keyAlgorithm)) {
+ if (log.isDebugEnabled()) {
+ log.debug("resolveKeyPair({}) loaded key={}-{}",
+ keyPath, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
+ }
+ return kp;
+ }
+
+ // Not same algorithm - start again
+ if (log.isDebugEnabled()) {
+ log.debug("resolveKeyPair({}) mismatched loaded key algorithm: expected={}, loaded={}",
+ keyPath, alg, keyAlgorithm);
+ }
+ Files.deleteIfExists(keyPath);
+ return null;
+ }
+
protected KeyPair readKeyPair(Path keyPath, OpenOption... options) throws IOException, GeneralSecurityException {
try (InputStream inputStream = Files.newInputStream(keyPath, options)) {
return doReadKeyPair(keyPath.toString(), inputStream);
@@ -248,6 +275,11 @@
} else if (keySize != 0) {
generator.initialize(keySize);
log.info("generateKeyPair(" + algorithm + ") generating host key - size=" + keySize);
+ } else if (KeyUtils.EC_ALGORITHM.equals(algorithm)) {
+ // If left to our own devices choose the biggest key size possible
+ int numCurves = ECCurves.SORTED_KEY_SIZE.size();
+ ECCurves curve = ECCurves.SORTED_KEY_SIZE.get(numCurves - 1);
+ generator.initialize(curve.getParameters());
}
return generator.generateKeyPair();