You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2020/05/22 13:00:40 UTC
[mina-sshd] 04/05: [SSHD-989] Added verification that
parsed/recovered key pairs can be used for signing
This is an automated email from the ASF dual-hosted git repository.
lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 1b2e34a11636ad62ae9092dc30e2c320d5e23dcf
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri May 22 08:59:15 2020 +0300
[SSHD-989] Added verification that parsed/recovered key pairs can be used for signing
---
CHANGES.md | 4 +-
.../loader/pem/ECDSAPEMResourceKeyPairParser.java | 5 ++
.../sshd/common/signature/AbstractSignature.java | 23 ++++--
.../apache/sshd/common/signature/SignatureDSA.java | 2 +-
.../sshd/common/signature/SignatureECDSA.java | 2 +-
.../apache/sshd/common/signature/SignatureRSA.java | 22 ++++--
.../security/eddsa/EdDSASecurityProviderUtils.java | 7 +-
.../util/security/eddsa/SignatureEd25519.java | 3 +-
.../OpenSSHKeyPairResourceParserDecodingTest.java | 2 +
.../OpenSSHKeyPairResourceParserPasswordTest.java | 1 +
.../pem/PKCS8PEMResourceKeyPairParserTest.java | 36 +++++++++
.../common/signature/SignatureRSASHA1Test.java | 3 +-
.../common/util/security/SecurityUtilsTest.java | 10 +--
.../sshd/util/test/CommonTestSupportUtils.java | 86 +++++++++++++++++++++-
.../apache/sshd/util/test/JUnitTestSupport.java | 9 +++
15 files changed, 191 insertions(+), 24 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 8e644be..1495950 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -53,10 +53,8 @@ as much of the available functionality as possible.
* [SSHD-992](https://issues.apache.org/jira/browse/SSHD-984) - Provide more hooks into the SFTP server subsystem via SftpFileSystemAccessor
-<<<<<<< HEAD
* [SSHD-997](https://issues.apache.org/jira/browse/SSHD-997) - Fixed OpenSSH private key decoders for RSA and Ed25519
* [SSHD-998](https://issues.apache.org/jira/browse/SSHD-998) - Take into account SFTP version preference when establishing initial channel
-=======
+
* [SSHD-989](https://issues.apache.org/jira/browse/SSHD-989) - Implement ECDSA public key recovery for PKCS8 encoded data
->>>>>>> [SSHD-989] Implement ECDSA public key recovery from PKCS#8 encoded data
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
index 44d3d51..13b3f91 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java
@@ -224,6 +224,11 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
throw new StreamCorruptedException("Missing named curve parameter");
}
+ /*
+ * SSHD-989 - if object type is BIT STRING then this is the public key - in which case we need to figure out
+ * some other way to recover the curve parameters
+ */
+
curveOID = namedCurve.asOID();
}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java
index 8381d32..d167788 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/AbstractSignature.java
@@ -25,9 +25,13 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.Map;
import java.util.Objects;
+import java.util.function.Predicate;
import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;
@@ -109,12 +113,17 @@ public abstract class AbstractSignature implements Signature {
/**
* Makes an attempt to detect if the signature is encoded or pure data
*
- * @param sig The original signature
- * @return A {@link SimpleImmutableEntry} where first value is the key type and second value is the data -
- * {@code null} if not encoded
+ * @param sig The original signature
+ * @param expectedTypes The expected encoded key types
+ * @return A {@link SimpleImmutableEntry} where first value is the key type and second value is the
+ * data - {@code null} if not encoded
*/
- protected SimpleImmutableEntry<String, byte[]> extractEncodedSignature(byte[] sig) {
- final int dataLen = NumberUtils.length(sig);
+ protected Map.Entry<String, byte[]> extractEncodedSignature(byte[] sig, Collection<String> expectedTypes) {
+ return GenericUtils.isEmpty(expectedTypes) ? null : extractEncodedSignature(sig, k -> expectedTypes.contains(k));
+ }
+
+ protected Map.Entry<String, byte[]> extractEncodedSignature(byte[] sig, Predicate<? super String> typeSelector) {
+ int dataLen = NumberUtils.length(sig);
// if it is encoded then we must have at least 2 UINT32 values
if (dataLen < (2 * Integer.BYTES)) {
return null;
@@ -141,6 +150,10 @@ public abstract class AbstractSignature implements Signature {
}
String keyType = new String(sig, keyTypeStartPos, (int) keyTypeLen, StandardCharsets.UTF_8);
+ if (!typeSelector.test(keyType)) {
+ return null;
+ }
+
byte[] data = new byte[(int) dataBytesLen];
System.arraycopy(sig, keyTypeEndPos + Integer.BYTES, data, 0, (int) dataBytesLen);
return new SimpleImmutableEntry<>(keyType, data);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java
index d44d222..4ee5785 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureDSA.java
@@ -102,7 +102,7 @@ public class SignatureDSA extends AbstractSignature {
if (sigLen != DSA_SIGNATURE_LENGTH) {
// probably some encoded data
- Map.Entry<String, byte[]> encoding = extractEncodedSignature(sig);
+ Map.Entry<String, byte[]> encoding = extractEncodedSignature(sig, k -> KeyPairProvider.SSH_DSS.equalsIgnoreCase(k));
if (encoding != null) {
String keyType = encoding.getKey();
ValidateUtils.checkTrue(
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java
index e8e705b..33e4251 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureECDSA.java
@@ -103,7 +103,7 @@ public class SignatureECDSA extends AbstractSignature {
@Override
public boolean verify(SessionContext session, byte[] sig) throws Exception {
byte[] data = sig;
- Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+ Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, ECCurves.KEY_TYPES);
if (encoding != null) {
String keyType = encoding.getKey();
ECCurves curve = ECCurves.fromKeyType(keyType);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
index a41a89b..0639a45 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
@@ -21,7 +21,12 @@ package org.apache.sshd.common.signature;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.interfaces.RSAKey;
+import java.util.Collections;
import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
@@ -35,6 +40,16 @@ import org.apache.sshd.common.util.ValidateUtils;
* @see <A HREF="https://tools.ietf.org/html/rfc4253#section-6.6">RFC4253 section 6.6</A>
*/
public abstract class SignatureRSA extends AbstractSignature {
+ public static final NavigableSet<String> SUPPORTED_KEY_TYPES = Collections.unmodifiableNavigableSet(
+ Stream.of(
+ KeyPairProvider.SSH_RSA,
+ KeyPairProvider.SSH_RSA_CERT,
+ KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS,
+ KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS,
+ KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS,
+ KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS)
+ .collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))));
+
private int verifierSignatureSize = -1;
protected SignatureRSA(String algorithm) {
@@ -64,7 +79,7 @@ public abstract class SignatureRSA extends AbstractSignature {
@Override
public boolean verify(SessionContext session, byte[] sig) throws Exception {
byte[] data = sig;
- Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+ Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, SUPPORTED_KEY_TYPES);
if (encoding != null) {
String keyType = encoding.getKey();
/*
@@ -76,10 +91,7 @@ public abstract class SignatureRSA extends AbstractSignature {
* another variant that corresponds to a good-faith implementation and is considered safe to accept.
*/
String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
- if ((!KeyPairProvider.SSH_RSA.equals(canonicalName))
- && (!KeyPairProvider.SSH_RSA_CERT.equals(canonicalName))) {
- throw new IllegalArgumentException("Mismatched key type: " + keyType);
- }
+ ValidateUtils.checkTrue(SUPPORTED_KEY_TYPES.contains(canonicalName), "Mismatched key type: %s", keyType);
data = encoding.getValue();
}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
index 25dab72..a142196 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java
@@ -48,6 +48,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
public final class EdDSASecurityProviderUtils {
// See EdDSANamedCurveTable
public static final String CURVE_ED25519_SHA512 = "Ed25519";
+ public static final int KEY_SIZE = 256;
private EdDSASecurityProviderUtils() {
throw new UnsupportedOperationException("No instance");
@@ -61,8 +62,12 @@ public final class EdDSASecurityProviderUtils {
return EdDSAPrivateKey.class;
}
+ public static boolean isEDDSAKey(Key key) {
+ return getEDDSAKeySize(key) == KEY_SIZE;
+ }
+
public static int getEDDSAKeySize(Key key) {
- return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? 256 : -1;
+ return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? KEY_SIZE : -1;
}
public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
index 16d8cf8..f5dcba5 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java
@@ -37,7 +37,8 @@ public class SignatureEd25519 extends AbstractSignature {
@Override
public boolean verify(SessionContext session, byte[] sig) throws Exception {
byte[] data = sig;
- Map.Entry<String, byte[]> encoding = extractEncodedSignature(data);
+ Map.Entry<String, byte[]> encoding
+ = extractEncodedSignature(data, k -> KeyPairProvider.SSH_ED25519.equalsIgnoreCase(k));
if (encoding != null) {
String keyType = encoding.getKey();
ValidateUtils.checkTrue(
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
index fd16ef2..a41cd93 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserDecodingTest.java
@@ -91,6 +91,8 @@ public class OpenSSHKeyPairResourceParserDecodingTest extends OpenSSHKeyPairReso
fail("Unsupported key type name (" + pubName + "): " + supportedTypeNames);
}
+ validateKeyPairSignable(pubName, kp);
+
@SuppressWarnings("rawtypes")
PrivateKeyEntryDecoder decoder = OpenSSHKeyPairResourceParser.getPrivateKeyEntryDecoder(prvKey);
assertNotNull("No private key decoder", decoder);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java
index 60acbce..b5033c4 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserPasswordTest.java
@@ -163,6 +163,7 @@ public class OpenSSHKeyPairResourceParserPasswordTest extends OpenSSHKeyPairReso
case RETRY:
assertEquals("Mismatched pairs count", 1, GenericUtils.size(pairs));
assertEquals("Mismatched retries count", MAX_RETRIES, retriesCount.getAndSet(0));
+ validateKeyPairSignable(resourceKey, GenericUtils.head(pairs));
break;
default:
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
index f078c23..4dbdcaf 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java
@@ -21,10 +21,12 @@ package org.apache.sshd.common.config.keys.loader.pem;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -39,7 +41,9 @@ import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
import org.apache.sshd.util.test.JUnitTestSupport;
import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.Assume;
import org.junit.FixMethodOrder;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
@@ -110,6 +114,38 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport {
}
}
+ // see https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b
+ // openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -out
+ // pkcs8-ecdsa-256.pem
+ // openssl ecparam -genkey -name prime256v1 -noout -out pkcs8-ec-256.key
+ // openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in pkcs8-ec-256.key -out pkcs8-ec-256.pem
+
+ // openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:1024 -out pkcs8-rsa-1024.pem
+
+ // openssl asn1parse -inform PEM -in ...file... -dump
+ @Test // see SSHD-989
+ @Ignore("WIP")
+ public void testPKCS8FileParsing() throws Exception {
+ String resourceKey = "pkcs8-" + algorithm.toLowerCase() + "-" + keySize + ".pem";
+ URL url = getClass().getResource(resourceKey);
+ Assume.assumeTrue("No test file=" + resourceKey, url != null);
+
+ Collection<KeyPair> pairs = PKCS8PEMResourceKeyPairParser.INSTANCE.loadKeyPairs(null, url, null);
+ assertEquals("Mismatched extract keys count", 1, GenericUtils.size(pairs));
+
+ assertSignatureMatch("Cannot sign with recovered key pair", GenericUtils.head(pairs));
+ }
+
+ private static void assertSignatureMatch(String message, KeyPair kp) throws GeneralSecurityException {
+ assertSignatureMatch(message, kp.getPrivate(), kp.getPublic());
+ }
+
+ private static void assertSignatureMatch(
+ String message, PrivateKey privateKey, PublicKey publicKey)
+ throws GeneralSecurityException {
+ // TODO
+ }
+
@Override
public String toString() {
return getClass().getSimpleName() + "[" + algorithm + "/" + keySize + "]";
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
index 9fb12dd..7bb3a15 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/signature/SignatureRSASHA1Test.java
@@ -118,7 +118,8 @@ public class SignatureRSASHA1Test extends JUnitTestSupport {
assertTrue("Verifier signature size not initialized", vSize > 0);
// make sure padding is required
- Map.Entry<String, byte[]> encoding = rsa.extractEncodedSignature(TEST_SIGNATURE);
+ Map.Entry<String, byte[]> encoding = rsa.extractEncodedSignature(
+ TEST_SIGNATURE, SignatureRSA.SUPPORTED_KEY_TYPES);
assertNotNull("Signature is not encoded", encoding);
byte[] data = encoding.getValue();
assertTrue("Signature data size (" + data.length + ") not below verifier size (" + vSize + ")", data.length < vSize);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java
index 2d7ea79..ecf7ac8 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/SecurityUtilsTest.java
@@ -108,8 +108,7 @@ public class SecurityUtilsTest extends JUnitTestSupport {
}
}
- private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm)
- throws IOException, GeneralSecurityException {
+ private KeyPair testLoadEncryptedRSAPrivateKey(String algorithm) throws Exception {
return testLoadRSAPrivateKey(DEFAULT_PASSWORD.replace(' ', '-') + "-RSA-" + algorithm.toUpperCase() + "-key");
}
@@ -140,17 +139,17 @@ public class SecurityUtilsTest extends JUnitTestSupport {
}
}
- private KeyPair testLoadECPrivateKey(String name) throws IOException, GeneralSecurityException {
+ private KeyPair testLoadECPrivateKey(String name) throws Exception {
return testLoadPrivateKey(name, ECPublicKey.class, ECPrivateKey.class);
}
- private KeyPair testLoadRSAPrivateKey(String name) throws IOException, GeneralSecurityException {
+ private KeyPair testLoadRSAPrivateKey(String name) throws Exception {
return testLoadPrivateKey(name, RSAPublicKey.class, RSAPrivateKey.class);
}
private KeyPair testLoadPrivateKey(
String name, Class<? extends PublicKey> pubType, Class<? extends PrivateKey> prvType)
- throws IOException, GeneralSecurityException {
+ throws Exception {
Path folder = getTestResourcesFolder();
Path file = folder.resolve(name);
KeyPair kpFile = testLoadPrivateKeyFile(file, pubType, prvType);
@@ -167,6 +166,7 @@ public class SecurityUtilsTest extends JUnitTestSupport {
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));
+ validateKeyPairSignable(name, kpResource);
return kpResource;
}
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
index 43f635a..d39e76c 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
@@ -26,17 +26,25 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
import java.security.CodeSource;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
import java.security.ProtectionDomain;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Collection;
@@ -45,9 +53,11 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.keys.KeyUtils;
@@ -56,6 +66,8 @@ import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
@@ -532,7 +544,7 @@ public final class CommonTestSupportUtils {
}
}
- private static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
+ public static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
Objects.requireNonNull(provider, "No provider");
// get the I/O out of the way
@@ -571,4 +583,76 @@ public final class CommonTestSupportUtils {
return bytes;
}
}
+
+ /**
+ * Checks that the key pair can be used to successfully validate a signature
+ *
+ * @param kp The {@link KeyPair}
+ * @return An {@link Optional} holding the verification result - if empty then no appropriate signer was
+ * found for the keys.
+ * @throws Exception If failed to generate the signature
+ */
+ public static Optional<Boolean> verifySignatureMatch(KeyPair kp) throws Exception {
+ return verifySignatureMatch(kp.getPrivate(), kp.getPublic());
+ }
+
+ public static Optional<Boolean> verifySignatureMatch(
+ PrivateKey privateKey, PublicKey publicKey)
+ throws Exception {
+ Objects.requireNonNull(privateKey, "No private key provided");
+ Objects.requireNonNull(publicKey, "No public key provided");
+
+ // Use check only the private key so we can detect if "mixed" keys are used by failing the verification
+ if (privateKey instanceof RSAPrivateKey) {
+ return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.rsa));
+ } else if (privateKey instanceof DSAPrivateKey) {
+ return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.dsa));
+ } else if (SecurityUtils.isECCSupported() && (privateKey instanceof ECKey)) {
+ ECCurves curve = ECCurves.fromECKey((ECKey) privateKey);
+ ValidateUtils.checkNotNull(curve, "Unsupported EC key: %s", privateKey);
+ switch (curve) {
+ case nistp256:
+ return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.nistp256));
+ case nistp384:
+ return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.nistp384));
+ case nistp521:
+ return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.nistp521));
+ default: // ignore
+ }
+ } else if (SecurityUtils.isEDDSACurveSupported() && (privateKey instanceof EdDSAPrivateKey)) {
+ return Optional.of(verifySignatureMatch(privateKey, publicKey, BuiltinSignatures.ed25519));
+ }
+
+ return Optional.empty();
+ }
+
+ public static boolean verifySignatureMatch(
+ PrivateKey privateKey, PublicKey publicKey, Factory<? extends Signature> factory)
+ throws Exception {
+ Signature signer = factory.create();
+ signer.initSigner(null, privateKey);
+
+ byte[] msg = ("[" + privateKey + "][" + publicKey + "]@" + signer).getBytes(StandardCharsets.UTF_8);
+ signer.update(null, msg);
+ byte[] signature = signer.sign(null);
+
+ Signature verifier = factory.create();
+ verifier.initVerifier(null, publicKey);
+ verifier.update(null, msg);
+ return verifier.verify(null, signature);
+ }
+
+ // clears the sensitive data regardless of success/failure
+ public static void writeSensitiveDataToFile(Path file, byte[] sensitiveData)
+ throws IOException {
+ try (ByteChannel out = Files.newByteChannel(file,
+ StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
+ ByteBuffer buf = ByteBuffer.wrap(sensitiveData);
+ while (buf.hasRemaining()) {
+ out.write(buf);
+ }
+ } finally {
+ Arrays.fill(sensitiveData, (byte) 0);
+ }
+ }
}
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 6db9657..aede6de 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -52,6 +52,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
@@ -440,6 +441,14 @@ public abstract class JUnitTestSupport extends Assert {
}
}
+ public static KeyPair validateKeyPairSignable(Object hint, KeyPair kp) throws Exception {
+ assertNotNull(hint + ": no key pair provided", kp);
+ Optional<Boolean> signable = CommonTestSupportUtils.verifySignatureMatch(kp);
+ // if no result then assume "OK"
+ assertTrue(hint + ": Failed to validate signature", signable.orElse(Boolean.TRUE));
+ return kp;
+ }
+
public static String resolveEffectiveAlgorithm(String algorithm) {
if (GenericUtils.isEmpty(algorithm)) {
return algorithm;