From 2897872d364ffdb5e41ed117c47cbca48d4807b0 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Sun, 20 Oct 2024 23:01:21 +0100 Subject: [PATCH 01/10] Add support for Ed25519 via Bouncy Castle --- .../sshd/common/config/keys/KeyUtils.java | 5 +- .../pem/PKCS8PEMResourceKeyPairParser.java | 6 +- .../sshd/common/util/buffer/Buffer.java | 2 +- .../security/SecurityProviderRegistrar.java | 12 +- .../common/util/security/SecurityUtils.java | 102 ++++++---- ...BouncyCastleSecurityProviderRegistrar.java | 56 +++--- .../eddsa/Ed25519PEMResourceKeyParser.java | 6 +- .../eddsa/EdDSASecurityProviderRegistrar.java | 10 + .../eddsa/EdDSASecurityProviderUtils.java | 3 +- .../util/security/eddsa/I2pEdDSASupport.java | 102 ++++++++++ .../util/security/eddsa/SignatureEd25519.java | 24 +-- .../BouncyCastleEd25519PublicKeyDecoder.java | 105 ++++++++++ .../BouncyCastleEdDSASupport.java | 132 ++++++++++++ ...eOpenSSHEd25519PrivateKeyEntryDecoder.java | 189 ++++++++++++++++++ .../security/eddsa/generic/EdDSASupport.java | 93 +++++++++ .../generic/GenericSignatureEd25519.java | 50 +++++ .../AbstractGeneratorHostKeyProvider.java | 2 +- 17 files changed, 801 insertions(+), 98 deletions(-) create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java index 9f58f0046..e8be1b575 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java @@ -874,7 +874,7 @@ public static String getKeyType(Key key) { } else { return curve.getKeyType(); } - } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) { + } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm()) || SecurityUtils.ED25519.equalsIgnoreCase(key.getAlgorithm())) { return KeyPairProvider.SSH_ED25519; } @@ -1073,6 +1073,9 @@ public static boolean compareKeys(PublicKey k1, PublicKey k2) { } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm()) && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) { return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); + } else if ((k1 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k1.getAlgorithm()) + && (k2 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k2.getAlgorithm())) { + return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); } else if ((k1 instanceof SkED25519PublicKey) && (k2 instanceof SkED25519PublicKey)) { return compareSkEd25519Keys(SkED25519PublicKey.class.cast(k1), SkED25519PublicKey.class.cast(k2)); } else if ((k1 instanceof OpenSshCertificate) && (k2 instanceof OpenSshCertificate)) { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java index fbb5d233f..8d86aa0bc 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java @@ -49,7 +49,7 @@ import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.security.Decryptor; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.eddsa.Ed25519PEMResourceKeyParser; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author Apache MINA SSHD Project @@ -147,9 +147,9 @@ public Collection extractKeyPairs(byte[] encBytes, PKCS8PrivateKeyInfo kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser); } } else if (SecurityUtils.isEDDSACurveSupported() - && Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) { + && EdDSASupport.ED25519_OID.endsWith(oid)) { ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes(); - kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes()); + kp = EdDSASupport.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes()); } else { PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes); PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey), diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java index 275280880..bb7bfc2d8 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java @@ -1042,7 +1042,7 @@ public void putRawPublicKeyBytes(PublicKey key) { byte[] ecPoint = ECCurves.encodeECPoint(ecKey.getW(), ecParams); putString(curve.getName()); putBytes(ecPoint); - } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm())) { + } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm()) || SecurityUtils.ED25519.equals(key.getAlgorithm())) { SecurityUtils.putRawEDDSAPublicKey(this, key); } else if (key instanceof SecurityKeyPublicKey) { putRawPublicKeyBytes(((SecurityKeyPublicKey) key).getDelegatePublicKey()); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java index a62c27e1f..b1fd549a2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java @@ -26,12 +26,7 @@ import java.security.Security; import java.security.Signature; import java.security.cert.CertificateFactory; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Predicate; import javax.crypto.Cipher; @@ -45,6 +40,7 @@ import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.IgnoringEmptyMap; import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author Apache MINA SSHD Project @@ -188,6 +184,10 @@ default boolean isCertificateFactorySupported(String type) { return isSecurityEntitySupported(CertificateFactory.class, type); } + default Optional> getEdDSASupport() { + return Optional.empty(); + } + /** * @param entityType The requested entity type - its simple name serves to build the configuration property name. * @return Configuration value to use if no specific configuration provided - default=empty diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java index c45510c7d..2289d8b8f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -35,17 +35,7 @@ import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateFactory; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -77,7 +67,7 @@ import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleGeneratorHostKeyProvider; import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleKeyPairResourceParser; import org.apache.sshd.common.util.security.bouncycastle.BouncyCastleRandomFactory; -import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider; import org.slf4j.Logger; @@ -103,6 +93,8 @@ public final class SecurityUtils { // See EdDSAEngine.SIGNATURE_ALGORITHM public static final String CURVE_ED25519_SHA512 = "NONEwithEdDSA"; + public static final String ED25519 = "Ed25519"; + /** * System property used to configure the value for the minimum supported Diffie-Hellman Group Exchange key size. If * not set, then an internal auto-discovery mechanism is employed. If set to negative value then Diffie-Hellman @@ -606,67 +598,93 @@ public static RandomFactory getRandomFactory() { * @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported */ public static boolean isEDDSACurveSupported() { + return getEdDSASupport().isPresent(); + } + + public static Optional> getEdDSASupport() { if (isFipsMode()) { - return false; + return Optional.empty(); } register(); - SecurityProviderRegistrar r = getRegisteredProvider(EDDSA); - return (r != null) && r.isEnabled() && r.isSupported(); + synchronized (REGISTERED_PROVIDERS) { + for (Map.Entry entry : REGISTERED_PROVIDERS.entrySet()) { + Optional> support = entry.getValue().getEdDSASupport(); + if (support.isPresent()) { + return support; + } + } + } + return Optional.empty(); } /* -------------------------------------------------------------------- */ public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider N/A"); } - return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder(); + return support.get().getEDDSAPublicKeyEntryDecoder(); } public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider N/A"); } - return EdDSASecurityProviderUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder(); + return support.get().getOpenSSHEDDSAPrivateKeyEntryDecoder(); } public static org.apache.sshd.common.signature.Signature getEDDSASigner() { - if (isEDDSACurveSupported()) { - return EdDSASecurityProviderUtils.getEDDSASignature(); + Optional> support = getEdDSASupport(); + if (support.isPresent()) { + return support.get().getEDDSASigner(); } throw new UnsupportedOperationException(EDDSA + " Signer not available"); } public static int getEDDSAKeySize(Key key) { - return EdDSASecurityProviderUtils.getEDDSAKeySize(key); + Optional> support = getEdDSASupport(); + return support.map(edDSASupport -> edDSASupport.getEDDSAKeySize(key)).orElse(-1); } public static Class getEDDSAPublicKeyType() { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class; + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { + return PublicKey.class; + } + return support.get().getEDDSAPublicKeyType(); } public static Class getEDDSAPrivateKeyType() { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPrivateKeyType() : PrivateKey.class; + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { + return PrivateKey.class; + } + return support.get().getEDDSAPrivateKeyType(); } public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2) : false; + Optional> support = getEdDSASupport(); + return support.map(edDSASupport -> edDSASupport.compareEDDSAPPublicKeys(k1, k2)).orElse(false); } public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2) : false; + Optional> support = getEdDSASupport(); + return support.map(edDSASupport -> edDSASupport.compareEDDSAPrivateKeys(k1, k2)).orElse(false); } public static PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); + return support.get().recoverEDDSAPublicKey(key); } public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException { @@ -674,19 +692,34 @@ public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) thro throw new InvalidKeyException("Unsupported key type: " + keyType); } - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { + throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); + } + + return support.get().generateEDDSAPublicKey(seed); + } + + public static PrivateKey generateEDDSAPrivateKey(String keyType, byte[] seed) throws GeneralSecurityException, IOException { + if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { + throw new InvalidKeyException("Unsupported key type: " + keyType); + } + + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); + return support.get().generateEDDSAPrivateKey(seed); } public static B putRawEDDSAPublicKey(B buffer, PublicKey key) { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); + return support.get().putRawEDDSAPublicKey(buffer, key); } public static B putEDDSAKeyPair(B buffer, KeyPair kp) { @@ -694,11 +727,12 @@ public static B putEDDSAKeyPair(B buffer, KeyPair kp) { } public static B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { - if (!isEDDSACurveSupported()) { + Optional> support = getEdDSASupport(); + if (!support.isPresent()) { throw new UnsupportedOperationException(EDDSA + " provider not supported"); } - return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); + return support.get().putEDDSAKeyPair(buffer, pubKey, prvKey); } public static KeyPair extractEDDSAKeyPair(Buffer buffer, String keyType) throws GeneralSecurityException { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java index f5cf4c549..298e942f2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java @@ -19,18 +19,16 @@ package org.apache.sshd.common.util.security.bouncycastle; import java.lang.reflect.Field; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; import java.security.Provider; import java.security.Security; -import java.security.Signature; -import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import org.apache.sshd.common.util.ExceptionUtils; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; /** @@ -43,10 +41,12 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi private static final String BCFIPS_PROVIDER_NAME = "BCFIPS"; private static final String BC_PROVIDER_NAME = "BC"; private static final String NAME_FIELD = "PROVIDER_NAME"; + private static final String ED_DSA_KEY_CLASS_NAME = "org.bouncycastle.jcajce.interfaces.EdDSAKey"; // Do not define a static registrar instance to minimize class loading issues private final AtomicReference supportHolder = new AtomicReference<>(null); private final AtomicReference allSupportHolder = new AtomicReference<>(); + private final AtomicReference edDSASupportHolder = new AtomicReference<>(null); private String providerClass; private String providerName; @@ -103,27 +103,6 @@ public String getDefaultSecurityEntitySupportValue(Class entityType) { return allValue; } - @Override - public boolean isSecurityEntitySupported(Class entityType, String name) { - if (!isSupported()) { - return false; - } - - // Some known values it does not support - if (KeyPairGenerator.class.isAssignableFrom(entityType) - || KeyFactory.class.isAssignableFrom(entityType)) { - if (Objects.compare(name, SecurityUtils.EDDSA, String.CASE_INSENSITIVE_ORDER) == 0) { - return false; - } - } else if (Signature.class.isAssignableFrom(entityType)) { - if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0) { - return false; - } - } - - return super.isSecurityEntitySupported(entityType, name); - } - @Override public boolean isSupported() { Boolean supported; @@ -176,4 +155,31 @@ public boolean isSupported() { return supported.booleanValue(); } + + + @Override + public Optional> getEdDSASupport() { + if (!isEdDSASupported()) { + return Optional.empty(); + } + return Optional.of(new org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport()); + } + + private boolean isEdDSASupported() { + if (!isSupported()) { + return false; + } + + Boolean edDSASupported; + synchronized (edDSASupportHolder) { + edDSASupported = edDSASupportHolder.get(); + if (edDSASupported != null) { + return edDSASupported.booleanValue(); + } + Class clazz = ThreadUtils.resolveDefaultClass(getClass(), ED_DSA_KEY_CLASS_NAME); + edDSASupported = Boolean.valueOf(clazz != null); + edDSASupportHolder.set(edDSASupported); + return edDSASupported; + } + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java index 846757413..0fef86e5e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java @@ -48,6 +48,7 @@ import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.io.input.NoCloseInputStream; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author Apache MINA SSHD Project @@ -67,10 +68,7 @@ public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParse public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY"; public static final List ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER); - /** - * @see RFC8412 section 3 - */ - public static final String ED25519_OID = "1.3.101.112"; + public static final String ED25519_OID = EdDSASupport.ED25519_OID; public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java index 8fbd28c7d..6c14046ed 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java @@ -23,11 +23,13 @@ import java.security.Provider; import java.security.Signature; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import org.apache.sshd.common.util.ExceptionUtils; import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.threads.ThreadUtils; /** @@ -100,4 +102,12 @@ public boolean isSupported() { return supported.booleanValue(); } + + @Override + public Optional> getEdDSASupport() { + if (!isSupported()) { + return Optional.empty(); + } + return Optional.of(new I2pEdDSASupport()); + } } 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 058472cfd..4e6cfb3b2 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 @@ -41,6 +41,7 @@ import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; /** * @author Apache MINA SSHD Project @@ -48,7 +49,7 @@ public final class EdDSASecurityProviderUtils { // See EdDSANamedCurveTable public static final String CURVE_ED25519_SHA512 = "Ed25519"; - public static final int KEY_SIZE = 256; + public static final int KEY_SIZE = EdDSASupport.KEY_SIZE; private EdDSASecurityProviderUtils() { throw new UnsupportedOperationException("No instance"); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java new file mode 100644 index 000000000..f1c876f2b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java @@ -0,0 +1,102 @@ +/* + * 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.security.eddsa; + +import java.security.*; + +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.EdDSAPublicKey; +import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; + +public class I2pEdDSASupport implements EdDSASupport { + + public I2pEdDSASupport() { + } + + @Override + public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { + return Ed25519PublicKeyDecoder.INSTANCE; + } + + @Override + public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { + return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; + } + + @Override + public Signature getEDDSASigner() { + return EdDSASecurityProviderUtils.getEDDSASignature(); + } + + @Override + public int getEDDSAKeySize(Key key) { + return EdDSASecurityProviderUtils.getEDDSAKeySize(key); + } + + @Override + public Class getEDDSAPublicKeyType() { + return EdDSASecurityProviderUtils.getEDDSAPublicKeyType(); + } + + @Override + public Class getEDDSAPrivateKeyType() { + return EdDSASecurityProviderUtils.getEDDSAPrivateKeyType(); + } + + @Override + public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { + return EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2); + } + + @Override + public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { + return EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2); + } + + @Override + public PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); + } + + @Override + public PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { + return EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); + } + + @Override + public PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException { + return Ed25519PEMResourceKeyParser.generateEdDSAPrivateKey(seed); + } + + @Override + public B putRawEDDSAPublicKey(B buffer, PublicKey key) { + return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); + } + + @Override + public B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { + return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); + } + +} 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 bba78751d..4aaab148c 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 @@ -18,34 +18,14 @@ */ package org.apache.sshd.common.util.security.eddsa; -import java.util.Map; - import net.i2p.crypto.eddsa.EdDSAEngine; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.signature.AbstractSignature; -import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; /** * @author Apache MINA SSHD Project */ -public class SignatureEd25519 extends AbstractSignature { +public class SignatureEd25519 extends GenericSignatureEd25519 { public SignatureEd25519() { super(EdDSAEngine.SIGNATURE_ALGORITHM); } - - @Override - public boolean verify(SessionContext session, byte[] sig) throws Exception { - byte[] data = sig; - Map.Entry encoding - = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase); - if (encoding != null) { - String keyType = encoding.getKey(); - ValidateUtils.checkTrue( - KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType); - data = encoding.getValue(); - } - - return doVerify(data); - } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java new file mode 100644 index 000000000..a530314cf --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java @@ -0,0 +1,105 @@ +/* + * 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.security.eddsa.bouncycastle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; +import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; +import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; +import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; + +/** + * @author Apache MINA SSHD Project + */ +public final class BouncyCastleEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { + public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this + + public static final BouncyCastleEd25519PublicKeyDecoder INSTANCE = new BouncyCastleEd25519PublicKeyDecoder(); + + private BouncyCastleEd25519PublicKeyDecoder() { + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, + Collections.unmodifiableList( + Collections.singletonList( + KeyPairProvider.SSH_ED25519))); + } + + @Override + public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePublicKey(new RawEncodedKeySpec(key.getPointEncoding())); + } + } + + @Override + public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePrivateKey(new OpenSSHPrivateKeySpec(key.getEncoded())); + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); + } + + @Override + public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException { + Objects.requireNonNull(key, "No public key provided"); + KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519); + byte[] seed = getSeedValue(key); + KeyEntryResolver.writeRLEBytes(s, seed); + return KeyPairProvider.SSH_ED25519; + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); + } + + @Override + public EdDSAPublicKey decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map headers) + throws IOException, GeneralSecurityException { + byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); + return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed)); + } + + public static byte[] getSeedValue(EdDSAPublicKey key) { + // a bit of reverse-engineering on the EdDSAPublicKeySpec + return (key == null) ? null : key.getPointEncoding(); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java new file mode 100644 index 000000000..63abfa98f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -0,0 +1,132 @@ +/* + * 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.security.eddsa.bouncycastle; + +import java.io.IOException; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; + +import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.util.PrivateKeyInfoFactory; +import org.bouncycastle.jcajce.interfaces.EdDSAKey; +import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; +import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; +import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; + +public class BouncyCastleEdDSASupport implements EdDSASupport { + + public BouncyCastleEdDSASupport() { + } + + @Override + public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { + return BouncyCastleEd25519PublicKeyDecoder.INSTANCE; + } + + @Override + public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { + return BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; + } + + @Override + public Signature getEDDSASigner() { + return new GenericSignatureEd25519(SecurityUtils.EDDSA); + } + + @Override + public int getEDDSAKeySize(Key key) { + return key instanceof EdDSAKey ? KEY_SIZE : -1; + } + + @Override + public Class getEDDSAPublicKeyType() { + return EdDSAPublicKey.class; + } + + @Override + public Class getEDDSAPrivateKeyType() { + return EdDSAPrivateKey.class; + } + + @Override + public boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { + if (!(k1 instanceof EdDSAPublicKey) || !(k2 instanceof EdDSAPublicKey)) { + return false; + } + + return k1.equals(k2); + } + + @Override + public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { + if (!(k1 instanceof EdDSAPrivateKey) || !(k2 instanceof EdDSAPrivateKey)) { + return false; + } + + return k1.equals(k2); + } + + @Override + public PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + if (!(key instanceof EdDSAPrivateKey)) { + throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA); + } + EdDSAPrivateKey edDSAKey = (EdDSAPrivateKey) key; + return edDSAKey.getPublicKey(); + } + + @Override + public PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { + RawEncodedKeySpec keySpec = new RawEncodedKeySpec(seed); + KeyFactory factory = SecurityUtils.getKeyFactory("Ed25519"); + return factory.generatePublic(keySpec); + } + + @Override + public PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException { + Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(seed); + PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters); + KeyFactory factory = SecurityUtils.getKeyFactory("Ed25519"); + return factory.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded())); + } + + @Override + public B putRawEDDSAPublicKey(B buffer, PublicKey key) { + EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key); + buffer.putBytes(edKey.getPointEncoding()); + return buffer; + } + + @Override + public B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { + ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey); + ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey); + throw new UnsupportedOperationException("Full SSHD-440 implementation N/A"); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java new file mode 100644 index 000000000..67cf112dc --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java @@ -0,0 +1,189 @@ +/* + * 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.security.eddsa.bouncycastle; + +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.util.PrivateKeyFactory; +import org.bouncycastle.crypto.util.PrivateKeyInfoFactory; +import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; +import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; +import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; +import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; + +/** + * @author Apache MINA SSHD Project + */ +public class BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder + extends AbstractPrivateKeyEntryDecoder { + public static final BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder INSTANCE + = new BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder(); + private static final int PK_SIZE = 32; + private static final int SK_SIZE = 32; + private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; + + public BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder() { + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, + Collections.unmodifiableList( + Collections.singletonList( + KeyPairProvider.SSH_ED25519))); + } + + @Override + public EdDSAPrivateKey decodePrivateKey( + SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) + throws IOException, GeneralSecurityException { + if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { + throw new InvalidKeyException("Unsupported key type: " + keyType); + } + + if (!SecurityUtils.isEDDSACurveSupported()) { + throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); + } + + // ed25519 bernstein naming: pk .. public key, sk .. secret key + // we expect to find two byte arrays with the following structure (type:size): + // [pk:32], [sk:32,pk:32] + + byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; + byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; + try { + pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); + keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); + if (pk.length != PK_SIZE) { + throw new InvalidKeyException( + String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); + } + + if (keypair.length != KEYPAIR_SIZE) { + throw new InvalidKeyException( + String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, + KEYPAIR_SIZE)); + } + + // verify that the keypair contains the expected pk + // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface + if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { + throw new InvalidKeyException("Keypair did not contain the public key."); + } + + byte[] sk = Arrays.copyOf(keypair, SK_SIZE); + Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(sk); + PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters); + EdDSAPrivateKey privateKey = generatePrivateKey(new PKCS8EncodedKeySpec(info.getEncoded())); + + // the private key class contains the calculated public key (Abyte) + // pointers to the corresponding code: + // EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a); + // EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray(); + + // we can now verify the generated pk matches the one we read + if (!Arrays.equals(privateKey.getPublicKey().getPointEncoding(), pk)) { + throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); + } + + return privateKey; + } finally { + // get rid of sensitive data a.s.a.p + Arrays.fill(pk, (byte) 0); + Arrays.fill(keypair, (byte) 0); + } + } + + @Override + public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey key, EdDSAPublicKey pubKey) + throws IOException { + Objects.requireNonNull(key, "No private key provided"); + + // ed25519 bernstein naming: pk .. public key, sk .. secret key + // we are expected to write the following arrays (type:size): + // [pk:32], [sk:32,pk:32] + + Ed25519PrivateKeyParameters parameters = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(key.getEncoded()); + byte[] sk = parameters.getEncoded(); + byte[] pk = key.getPublicKey().getPointEncoding(); + + Objects.requireNonNull(sk, "No seed"); + + byte[] keypair = new byte[KEYPAIR_SIZE]; + System.arraycopy(sk, 0, keypair, 0, SK_SIZE); + System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE); + + KeyEntryResolver.writeRLEBytes(s, pk); + KeyEntryResolver.writeRLEBytes(s, keypair); + + return KeyPairProvider.SSH_ED25519; + } + + @Override + public boolean isPublicKeyRecoverySupported() { + return true; + } + + @Override + public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException { + return (EdDSAPublicKey) SecurityUtils.recoverEDDSAPublicKey(prvKey); + } + + @Override + public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePublicKey(new RawEncodedKeySpec(key.getPointEncoding())); + } + } + + @Override + public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + return generatePrivateKey(new OpenSSHPrivateKeySpec(key.getEncoded())); + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java new file mode 100644 index 000000000..609b2fe6c --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -0,0 +1,93 @@ +/* + * 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.security.eddsa.generic; + +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.security.*; + +import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.io.der.ASN1Object; +import org.apache.sshd.common.util.io.der.ASN1Type; +import org.apache.sshd.common.util.io.der.DERParser; +import org.apache.sshd.common.util.security.SecurityUtils; + +public interface EdDSASupport { + + int KEY_SIZE = 256; + + /** + * @see RFC8412 section 3 + */ + String ED25519_OID = "1.3.101.112"; + + static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { + PrivateKey privateKey = decodeEdDSAPrivateKey(keyData); + PublicKey publicKey = SecurityUtils.recoverEDDSAPublicKey(privateKey); + return new KeyPair(publicKey, privateKey); + } + + static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { + try (DERParser parser = new DERParser(keyData)) { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data container"); + } + + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.OCTET_STRING) { + throw new StreamCorruptedException("Mismatched key data container type: " + objType); + } + + return SecurityUtils.generateEDDSAPrivateKey(KeyPairProvider.SSH_ED25519, obj.getValue()); + } + } + + PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder(); + + PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder(); + + Signature getEDDSASigner(); + + int getEDDSAKeySize(Key key); + + Class getEDDSAPublicKeyType(); + + Class getEDDSAPrivateKeyType(); + + boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2); + + boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2); + + PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; + + PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException; + + PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException; + + B putRawEDDSAPublicKey(B buffer, PublicKey key); + + B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey); + +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java new file mode 100644 index 000000000..99c54fa79 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericSignatureEd25519.java @@ -0,0 +1,50 @@ +/* + * 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.security.eddsa.generic; + +import java.util.Map; + +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.signature.AbstractSignature; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @author Apache MINA SSHD Project + */ +public class GenericSignatureEd25519 extends AbstractSignature { + public GenericSignatureEd25519(String algorithm) { + super(algorithm); + } + + @Override + public boolean verify(SessionContext session, byte[] sig) throws Exception { + byte[] data = sig; + Map.Entry encoding + = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase); + if (encoding != null) { + String keyType = encoding.getKey(); + ValidateUtils.checkTrue( + KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType); + data = encoding.getValue(); + } + + return doVerify(data); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java index 5bb209a47..6acfc081c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java @@ -254,7 +254,7 @@ protected Iterable loadFromFile(SessionContext session, String alg, Pat if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) { keyAlgorithm = KeyUtils.EC_ALGORITHM; } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyAlgorithm)) { - keyAlgorithm = SecurityUtils.EDDSA; +// keyAlgorithm = SecurityUtils.EDDSA; } if (Objects.equals(alg, keyAlgorithm)) { From 19808605ef56bebb3b7726b3fe0603f24b498835 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Sun, 20 Oct 2024 23:34:24 +0100 Subject: [PATCH 02/10] Refactor public key decoder --- .../eddsa/Ed25519PublicKeyDecoder.java | 70 +------------------ .../util/security/eddsa/I2pEdDSASupport.java | 23 +++++- .../BouncyCastleEdDSASupport.java | 32 +++++++-- .../security/eddsa/generic/EdDSASupport.java | 11 ++- .../GenericEd25519PublicKeyDecoder.java} | 53 ++++++-------- 5 files changed, 80 insertions(+), 109 deletions(-) rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/{bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java => generic/GenericEd25519PublicKeyDecoder.java} (64%) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java index 9decb2b31..566e9d58a 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java @@ -18,84 +18,20 @@ */ package org.apache.sshd.common.util.security.eddsa; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; /** * @author Apache MINA SSHD Project */ -public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { - public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this +public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder { public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); private Ed25519PublicKeyDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); - } - - @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams())); - } - } - - @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams())); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); - } - - @Override - public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException { - Objects.requireNonNull(key, "No public key provided"); - KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519); - byte[] seed = getSeedValue(key); - KeyEntryResolver.writeRLEBytes(s, seed); - return KeyPairProvider.SSH_ED25519; - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - } - - @Override - public EdDSAPublicKey decodePublicKey( - SessionContext session, String keyType, InputStream keyData, Map headers) - throws IOException, GeneralSecurityException { - byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); - return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed)); + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new I2pEdDSASupport()); } public static byte[] getSeedValue(EdDSAPublicKey key) { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java index f1c876f2b..eb948a0f4 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java @@ -20,9 +20,12 @@ package org.apache.sshd.common.util.security.eddsa; import java.security.*; +import java.security.spec.KeySpec; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; import org.apache.sshd.common.signature.Signature; @@ -80,12 +83,12 @@ public PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityExc } @Override - public PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { - return EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); + public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { + return (EdDSAPublicKey) EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); } @Override - public PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException { + public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException { return Ed25519PEMResourceKeyParser.generateEdDSAPrivateKey(seed); } @@ -99,4 +102,18 @@ public B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateK return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); } + @Override + public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) { + return new EdDSAPublicKeySpec(publicKey.getA(), publicKey.getParams()); + } + + @Override + public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { + return new EdDSAPrivateKeySpec(privateKey.getSeed(), privateKey.getParams()); + } + + @Override + public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { + return publicKey == null ? null : publicKey.getAbyte(); + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java index 63abfa98f..bb666a452 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.security.*; +import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; @@ -30,6 +31,7 @@ import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; @@ -37,6 +39,7 @@ import org.bouncycastle.jcajce.interfaces.EdDSAKey; import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; +import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; public class BouncyCastleEdDSASupport implements EdDSASupport { @@ -46,7 +49,7 @@ public BouncyCastleEdDSASupport() { @Override public PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - return BouncyCastleEd25519PublicKeyDecoder.INSTANCE; + return new GenericEd25519PublicKeyDecoder<>(EdDSAPublicKey.class, EdDSAPrivateKey.class, this); } @Override @@ -102,18 +105,18 @@ public PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityExc } @Override - public PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { + public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { RawEncodedKeySpec keySpec = new RawEncodedKeySpec(seed); - KeyFactory factory = SecurityUtils.getKeyFactory("Ed25519"); - return factory.generatePublic(keySpec); + KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); + return (EdDSAPublicKey) factory.generatePublic(keySpec); } @Override - public PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException { + public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException { Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(seed); PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters); - KeyFactory factory = SecurityUtils.getKeyFactory("Ed25519"); - return factory.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded())); + KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); + return (EdDSAPrivateKey) factory.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded())); } @Override @@ -129,4 +132,19 @@ public B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateK ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey); throw new UnsupportedOperationException("Full SSHD-440 implementation N/A"); } + + @Override + public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) { + return new RawEncodedKeySpec(publicKey.getPointEncoding()); + } + + @Override + public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { + return new OpenSSHPrivateKeySpec(privateKey.getEncoded()); + } + + @Override + public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { + return publicKey == null ? null : publicKey.getPointEncoding(); + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java index 609b2fe6c..17cb21a3e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.StreamCorruptedException; import java.security.*; +import java.security.spec.KeySpec; import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; @@ -82,12 +83,18 @@ static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, Gene PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; - PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException; + PUB generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException; - PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException; + PRV generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException; B putRawEDDSAPublicKey(B buffer, PublicKey key); B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey); + KeySpec createPublicKeySpec(PUB publicKey); + + KeySpec createPrivateKeySpec(PRV privateKey); + + byte[] getPublicKeyData(PUB publicKey); + } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java similarity index 64% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java index a530314cf..92496b740 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEd25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java @@ -16,17 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.sshd.common.util.security.eddsa.bouncycastle; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; +package org.apache.sshd.common.util.security.eddsa.generic; import org.apache.sshd.common.config.keys.KeyEntryResolver; import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; @@ -38,36 +28,43 @@ import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + /** * @author Apache MINA SSHD Project */ -public final class BouncyCastleEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { +public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this - public static final BouncyCastleEd25519PublicKeyDecoder INSTANCE = new BouncyCastleEd25519PublicKeyDecoder(); + protected final EdDSASupport edDSASupport; - private BouncyCastleEd25519PublicKeyDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); + public GenericEd25519PublicKeyDecoder(Class pubType, Class prvType, EdDSASupport edDSASupport) { + super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + this.edDSASupport = edDSASupport; } @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { + public PUB clonePublicKey(PUB key) throws GeneralSecurityException { if (key == null) { return null; } else { - return generatePublicKey(new RawEncodedKeySpec(key.getPointEncoding())); + return generatePublicKey(edDSASupport.createPublicKeySpec(key)); } } @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { + public PRV clonePrivateKey(PRV key) throws GeneralSecurityException { if (key == null) { return null; } else { - return generatePrivateKey(new OpenSSHPrivateKeySpec(key.getEncoded())); + return generatePrivateKey(edDSASupport.createPrivateKeySpec(key)); } } @@ -77,10 +74,10 @@ public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { } @Override - public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException { + public String encodePublicKey(OutputStream s, PUB key) throws IOException { Objects.requireNonNull(key, "No public key provided"); KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519); - byte[] seed = getSeedValue(key); + byte[] seed = edDSASupport.getPublicKeyData(key); KeyEntryResolver.writeRLEBytes(s, seed); return KeyPairProvider.SSH_ED25519; } @@ -91,15 +88,11 @@ public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { } @Override - public EdDSAPublicKey decodePublicKey( + public PUB decodePublicKey( SessionContext session, String keyType, InputStream keyData, Map headers) throws IOException, GeneralSecurityException { byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); - return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed)); + return edDSASupport.generateEDDSAPublicKey(seed); } - public static byte[] getSeedValue(EdDSAPublicKey key) { - // a bit of reverse-engineering on the EdDSAPublicKeySpec - return (key == null) ? null : key.getPointEncoding(); - } } From a50d0f5f2cdac0957312503d5b092535738130aa Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Mon, 21 Oct 2024 09:16:54 +0100 Subject: [PATCH 03/10] Refactor private key decoder --- .../util/security/eddsa/I2pEdDSASupport.java | 8 +- .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 156 +----------------- .../BouncyCastleEdDSASupport.java | 12 +- .../security/eddsa/generic/EdDSASupport.java | 4 +- ...OpenSSHEd25519PrivateKeyEntryDecoder.java} | 59 +++---- 5 files changed, 43 insertions(+), 196 deletions(-) rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/{bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java => generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java} (66%) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java index eb948a0f4..cd16e400f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.util.security.eddsa; +import java.io.IOException; import java.security.*; import java.security.spec.KeySpec; @@ -78,7 +79,7 @@ public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { } @Override - public PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(key); } @@ -116,4 +117,9 @@ public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { return publicKey == null ? null : publicKey.getAbyte(); } + + @Override + public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { + return privateKey.getSeed(); + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java index 478f97dd1..dc321a399 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -19,168 +19,18 @@ package org.apache.sshd.common.util.security.eddsa; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; -import java.util.Objects; - import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; -import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream; -import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder; /** * @author Apache MINA SSHD Project */ -public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { +public class OpenSSHEd25519PrivateKeyEntryDecoder extends GenericOpenSSHEd25519PrivateKeyEntryDecoder { public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); - private static final int PK_SIZE = 32; - private static final int SK_SIZE = 32; - private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; public OpenSSHEd25519PrivateKeyEntryDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); - } - - @Override - public EdDSAPrivateKey decodePrivateKey( - SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) - throws IOException, GeneralSecurityException { - if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { - throw new InvalidKeyException("Unsupported key type: " + keyType); - } - - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); - } - - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we expect to find two byte arrays with the following structure (type:size): - // [pk:32], [sk:32,pk:32] - - byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; - byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; - try { - pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); - keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); - if (pk.length != PK_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); - } - - if (keypair.length != KEYPAIR_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, - KEYPAIR_SIZE)); - } - - // verify that the keypair contains the expected pk - // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface - if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { - throw new InvalidKeyException("Keypair did not contain the public key."); - } - - byte[] sk = Arrays.copyOf(keypair, SK_SIZE); - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512); - EdDSAPrivateKey privateKey = generatePrivateKey(new EdDSAPrivateKeySpec(sk, params)); - - // the private key class contains the calculated public key (Abyte) - // pointers to the corresponding code: - // EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a); - // EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray(); - - // we can now verify the generated pk matches the one we read - if (!Arrays.equals(privateKey.getAbyte(), pk)) { - throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); - } - - return privateKey; - } finally { - // get rid of sensitive data a.s.a.p - Arrays.fill(pk, (byte) 0); - Arrays.fill(keypair, (byte) 0); - } + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new I2pEdDSASupport()); } - @Override - public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey key, EdDSAPublicKey pubKey) - throws IOException { - Objects.requireNonNull(key, "No private key provided"); - - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we are expected to write the following arrays (type:size): - // [pk:32], [sk:32,pk:32] - - byte[] sk = key.getSeed(); - byte[] pk = key.getAbyte(); - - Objects.requireNonNull(sk, "No seed"); - - byte[] keypair = new byte[KEYPAIR_SIZE]; - System.arraycopy(sk, 0, keypair, 0, SK_SIZE); - System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE); - - KeyEntryResolver.writeRLEBytes(s, pk); - KeyEntryResolver.writeRLEBytes(s, keypair); - - return KeyPairProvider.SSH_ED25519; - } - - @Override - public boolean isPublicKeyRecoverySupported() { - return true; - } - - @Override - public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException { - return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(prvKey); - } - - @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams())); - } - } - - @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams())); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java index bb666a452..0f7ef9e74 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -32,9 +32,11 @@ import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; +import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder; import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; +import org.bouncycastle.crypto.util.PrivateKeyFactory; import org.bouncycastle.crypto.util.PrivateKeyInfoFactory; import org.bouncycastle.jcajce.interfaces.EdDSAKey; import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; @@ -54,7 +56,7 @@ public PublicKeyEntryDecoder getEDDSAPublicKeyE @Override public PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - return BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; + return new GenericOpenSSHEd25519PrivateKeyEntryDecoder<>(EdDSAPublicKey.class, EdDSAPrivateKey.class, this); } @Override @@ -96,7 +98,7 @@ public boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { } @Override - public PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { + public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { if (!(key instanceof EdDSAPrivateKey)) { throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA); } @@ -147,4 +149,10 @@ public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { return publicKey == null ? null : publicKey.getPointEncoding(); } + + @Override + public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { + Ed25519PrivateKeyParameters parameters = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded()); + return parameters.getEncoded(); + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java index 17cb21a3e..bd09fa451 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -81,7 +81,7 @@ static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, Gene boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2); - PublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; + PUB recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; PUB generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException; @@ -97,4 +97,6 @@ static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, Gene byte[] getPublicKeyData(PUB publicKey); + byte[] getPrivateKeyData(PRV privateKey) throws IOException; + } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java similarity index 66% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java index 67cf112dc..686a1fcf4 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java @@ -17,12 +17,11 @@ * under the License. */ -package org.apache.sshd.common.util.security.eddsa.bouncycastle; +package org.apache.sshd.common.util.security.eddsa.generic; import java.io.IOException; import java.io.InputStream; import java.security.*; -import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; import java.util.Collections; import java.util.Locale; @@ -36,35 +35,25 @@ import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.output.SecureByteArrayOutputStream; import org.apache.sshd.common.util.security.SecurityUtils; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; -import org.bouncycastle.crypto.util.PrivateKeyFactory; -import org.bouncycastle.crypto.util.PrivateKeyInfoFactory; -import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; -import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; -import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; -import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; /** * @author Apache MINA SSHD Project */ -public class BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder - extends AbstractPrivateKeyEntryDecoder { - public static final BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder INSTANCE - = new BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder(); +public class GenericOpenSSHEd25519PrivateKeyEntryDecoder + extends AbstractPrivateKeyEntryDecoder { private static final int PK_SIZE = 32; private static final int SK_SIZE = 32; private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; - public BouncyCastleOpenSSHEd25519PrivateKeyEntryDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); + protected final EdDSASupport edDSASupport; + + public GenericOpenSSHEd25519PrivateKeyEntryDecoder(Class pubType, Class prvType, EdDSASupport edDSASupport) { + super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + this.edDSASupport = edDSASupport; } @Override - public EdDSAPrivateKey decodePrivateKey( + public PRV decodePrivateKey( SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) throws IOException, GeneralSecurityException { if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { @@ -102,17 +91,10 @@ public EdDSAPrivateKey decodePrivateKey( } byte[] sk = Arrays.copyOf(keypair, SK_SIZE); - Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(sk); - PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters); - EdDSAPrivateKey privateKey = generatePrivateKey(new PKCS8EncodedKeySpec(info.getEncoded())); - - // the private key class contains the calculated public key (Abyte) - // pointers to the corresponding code: - // EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a); - // EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray(); + PRV privateKey = edDSASupport.generateEDDSAPrivateKey(sk); // we can now verify the generated pk matches the one we read - if (!Arrays.equals(privateKey.getPublicKey().getPointEncoding(), pk)) { + if (!Arrays.equals(edDSASupport.getPublicKeyData(recoverPublicKey(privateKey)), pk)) { throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); } @@ -125,7 +107,7 @@ public EdDSAPrivateKey decodePrivateKey( } @Override - public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey key, EdDSAPublicKey pubKey) + public String encodePrivateKey(SecureByteArrayOutputStream s, PRV key, PUB pubKey) throws IOException { Objects.requireNonNull(key, "No private key provided"); @@ -133,9 +115,8 @@ public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey ke // we are expected to write the following arrays (type:size): // [pk:32], [sk:32,pk:32] - Ed25519PrivateKeyParameters parameters = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(key.getEncoded()); - byte[] sk = parameters.getEncoded(); - byte[] pk = key.getPublicKey().getPointEncoding(); + byte[] sk = edDSASupport.getPrivateKeyData(key); + byte[] pk = edDSASupport.getPublicKeyData(pubKey); Objects.requireNonNull(sk, "No seed"); @@ -155,25 +136,25 @@ public boolean isPublicKeyRecoverySupported() { } @Override - public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException { - return (EdDSAPublicKey) SecurityUtils.recoverEDDSAPublicKey(prvKey); + public PUB recoverPublicKey(PRV prvKey) throws GeneralSecurityException { + return edDSASupport.recoverEDDSAPublicKey(prvKey); } @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { + public PUB clonePublicKey(PUB key) throws GeneralSecurityException { if (key == null) { return null; } else { - return generatePublicKey(new RawEncodedKeySpec(key.getPointEncoding())); + return generatePublicKey(edDSASupport.createPublicKeySpec(key)); } } @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { + public PRV clonePrivateKey(PRV key) throws GeneralSecurityException { if (key == null) { return null; } else { - return generatePrivateKey(new OpenSSHPrivateKeySpec(key.getEncoded())); + return generatePrivateKey(edDSASupport.createPrivateKeySpec(key)); } } From 8b4c553bfc701e4b32d95c64c3382d3260706289 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Mon, 21 Oct 2024 20:24:50 +0100 Subject: [PATCH 04/10] Generalise generating host key provider --- .../sshd/common/config/keys/KeyUtils.java | 5 +++-- ...BouncyCastleSecurityProviderRegistrar.java | 5 ++--- .../eddsa/Ed25519PublicKeyDecoder.java | 3 +-- .../eddsa/EdDSASecurityProviderRegistrar.java | 2 +- ...ort.java => NetI2pCryptoEdDSASupport.java} | 5 +++-- .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 5 +++-- .../BouncyCastleEdDSASupport.java | 4 +++- .../GenericEd25519PublicKeyDecoder.java | 20 ++++++++----------- ...cOpenSSHEd25519PrivateKeyEntryDecoder.java | 3 ++- .../AbstractGeneratorHostKeyProvider.java | 10 +++++++--- 10 files changed, 33 insertions(+), 29 deletions(-) rename sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/{I2pEdDSASupport.java => NetI2pCryptoEdDSASupport.java} (96%) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java index e8be1b575..28883cb8d 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java @@ -874,7 +874,8 @@ public static String getKeyType(Key key) { } else { return curve.getKeyType(); } - } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm()) || SecurityUtils.ED25519.equalsIgnoreCase(key.getAlgorithm())) { + } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm()) + || SecurityUtils.ED25519.equalsIgnoreCase(key.getAlgorithm())) { return KeyPairProvider.SSH_ED25519; } @@ -1074,7 +1075,7 @@ public static boolean compareKeys(PublicKey k1, PublicKey k2) { && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) { return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); } else if ((k1 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k1.getAlgorithm()) - && (k2 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k2.getAlgorithm())) { + && (k2 != null) && SecurityUtils.ED25519.equalsIgnoreCase(k2.getAlgorithm())) { return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); } else if ((k1 instanceof SkED25519PublicKey) && (k2 instanceof SkED25519PublicKey)) { return compareSkEd25519Keys(SkED25519PublicKey.class.cast(k1), SkED25519PublicKey.class.cast(k2)); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java index 298e942f2..4a9e4bcc6 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java @@ -41,7 +41,7 @@ public class BouncyCastleSecurityProviderRegistrar extends AbstractSecurityProvi private static final String BCFIPS_PROVIDER_NAME = "BCFIPS"; private static final String BC_PROVIDER_NAME = "BC"; private static final String NAME_FIELD = "PROVIDER_NAME"; - private static final String ED_DSA_KEY_CLASS_NAME = "org.bouncycastle.jcajce.interfaces.EdDSAKey"; + private static final String EDDSA_KEY_CLASS_NAME = "org.bouncycastle.jcajce.interfaces.EdDSAKey"; // Do not define a static registrar instance to minimize class loading issues private final AtomicReference supportHolder = new AtomicReference<>(null); @@ -156,7 +156,6 @@ public boolean isSupported() { return supported.booleanValue(); } - @Override public Optional> getEdDSASupport() { if (!isEdDSASupported()) { @@ -176,7 +175,7 @@ private boolean isEdDSASupported() { if (edDSASupported != null) { return edDSASupported.booleanValue(); } - Class clazz = ThreadUtils.resolveDefaultClass(getClass(), ED_DSA_KEY_CLASS_NAME); + Class clazz = ThreadUtils.resolveDefaultClass(getClass(), EDDSA_KEY_CLASS_NAME); edDSASupported = Boolean.valueOf(clazz != null); edDSASupportHolder.set(edDSASupported); return edDSASupported; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java index 566e9d58a..a9244942c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java @@ -18,7 +18,6 @@ */ package org.apache.sshd.common.util.security.eddsa; - import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; @@ -31,7 +30,7 @@ public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecode public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); private Ed25519PublicKeyDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new I2pEdDSASupport()); + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new NetI2pCryptoEdDSASupport()); } public static byte[] getSeedValue(EdDSAPublicKey key) { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java index 6c14046ed..edcb55bf0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java @@ -108,6 +108,6 @@ public boolean isSupported() { if (!isSupported()) { return Optional.empty(); } - return Optional.of(new I2pEdDSASupport()); + return Optional.of(new NetI2pCryptoEdDSASupport()); } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java similarity index 96% rename from sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java rename to sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java index cd16e400f..61b1ee093 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/I2pEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java @@ -33,9 +33,10 @@ import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; -public class I2pEdDSASupport implements EdDSASupport { +public class NetI2pCryptoEdDSASupport implements EdDSASupport { - public I2pEdDSASupport() { + public NetI2pCryptoEdDSASupport() { + super(); } @Override diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java index dc321a399..aa673bd34 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -26,11 +26,12 @@ /** * @author Apache MINA SSHD Project */ -public class OpenSSHEd25519PrivateKeyEntryDecoder extends GenericOpenSSHEd25519PrivateKeyEntryDecoder { +public class OpenSSHEd25519PrivateKeyEntryDecoder + extends GenericOpenSSHEd25519PrivateKeyEntryDecoder { public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); public OpenSSHEd25519PrivateKeyEntryDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new I2pEdDSASupport()); + super(EdDSAPublicKey.class, EdDSAPrivateKey.class, new NetI2pCryptoEdDSASupport()); } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java index 0f7ef9e74..9cbed278e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -47,6 +47,7 @@ public class BouncyCastleEdDSASupport implements EdDSASupport { public BouncyCastleEdDSASupport() { + super(); } @Override @@ -152,7 +153,8 @@ public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { @Override public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { - Ed25519PrivateKeyParameters parameters = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded()); + Ed25519PrivateKeyParameters parameters + = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded()); return parameters.getEncoded(); } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java index 92496b740..ecfd701d8 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java @@ -18,29 +18,25 @@ */ package org.apache.sshd.common.util.security.eddsa.generic; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; -import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; -import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; -import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.*; -import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Objects; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.security.SecurityUtils; + /** * @author Apache MINA SSHD Project */ -public class GenericEd25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { +public class GenericEd25519PublicKeyDecoder + extends AbstractPublicKeyEntryDecoder { public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this protected final EdDSASupport edDSASupport; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java index 686a1fcf4..c62f6ddf7 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java @@ -47,7 +47,8 @@ public class GenericOpenSSHEd25519PrivateKeyEntryDecoder edDSASupport; - public GenericOpenSSHEd25519PrivateKeyEntryDecoder(Class pubType, Class prvType, EdDSASupport edDSASupport) { + public GenericOpenSSHEd25519PrivateKeyEntryDecoder(Class pubType, Class prvType, + EdDSASupport edDSASupport) { super(pubType, prvType, Collections.singletonList(KeyPairProvider.SSH_ED25519)); this.edDSASupport = edDSASupport; } diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java index 6acfc081c..f922114a2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java @@ -253,11 +253,9 @@ protected Iterable loadFromFile(SessionContext session, String alg, Pat String keyAlgorithm = key.getAlgorithm(); if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) { keyAlgorithm = KeyUtils.EC_ALGORITHM; - } else if (BuiltinIdentities.Constants.ED25519.equalsIgnoreCase(keyAlgorithm)) { -// keyAlgorithm = SecurityUtils.EDDSA; } - if (Objects.equals(alg, keyAlgorithm)) { + if (Objects.equals(alg, keyAlgorithm) || edDsaAlgorithmsMatch(alg, keyAlgorithm)) { if (log.isDebugEnabled()) { log.debug("resolveKeyPair({}) loaded key={}-{}", keyPath, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); @@ -396,4 +394,10 @@ protected KeyPair generateKeyPair(String algorithm) throws GeneralSecurityExcept return generator.generateKeyPair(); } + + boolean edDsaAlgorithmsMatch(String expectedAlgorithm, String keyAlgorithm) { + return (expectedAlgorithm.equalsIgnoreCase(SecurityUtils.EDDSA) + || expectedAlgorithm.equalsIgnoreCase(SecurityUtils.ED25519)) && + (keyAlgorithm.equalsIgnoreCase(SecurityUtils.EDDSA) || keyAlgorithm.equalsIgnoreCase(SecurityUtils.ED25519)); + } } From cfa874acbaf6e2469b60cd271e01c8ac382ccd6e Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Mon, 21 Oct 2024 20:28:12 +0100 Subject: [PATCH 05/10] Consistent naming --- .../server/keyprovider/AbstractGeneratorHostKeyProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java index f922114a2..69d3254fc 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java @@ -255,7 +255,7 @@ protected Iterable loadFromFile(SessionContext session, String alg, Pat keyAlgorithm = KeyUtils.EC_ALGORITHM; } - if (Objects.equals(alg, keyAlgorithm) || edDsaAlgorithmsMatch(alg, keyAlgorithm)) { + if (Objects.equals(alg, keyAlgorithm) || edDSAAlgorithmsMatch(alg, keyAlgorithm)) { if (log.isDebugEnabled()) { log.debug("resolveKeyPair({}) loaded key={}-{}", keyPath, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); @@ -395,7 +395,7 @@ protected KeyPair generateKeyPair(String algorithm) throws GeneralSecurityExcept return generator.generateKeyPair(); } - boolean edDsaAlgorithmsMatch(String expectedAlgorithm, String keyAlgorithm) { + boolean edDSAAlgorithmsMatch(String expectedAlgorithm, String keyAlgorithm) { return (expectedAlgorithm.equalsIgnoreCase(SecurityUtils.EDDSA) || expectedAlgorithm.equalsIgnoreCase(SecurityUtils.ED25519)) && (keyAlgorithm.equalsIgnoreCase(SecurityUtils.EDDSA) || keyAlgorithm.equalsIgnoreCase(SecurityUtils.ED25519)); From f2d2ab0b8002610cdcc227ae1e14e2f86fb7d7f4 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Sat, 23 Nov 2024 14:45:51 +0000 Subject: [PATCH 06/10] Ensure net.i2p.crypto provider is used if it's available --- .../common/util/security/SecurityUtils.java | 14 ++++++++++ ...BouncyCastleSecurityProviderRegistrar.java | 26 +++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java index 2289d8b8f..bfa9bfe48 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -601,6 +601,11 @@ public static boolean isEDDSACurveSupported() { return getEdDSASupport().isPresent(); } + public static boolean isNetI2pCryptoEdDSARegistered() { + register(); + return isProviderRegistered(EDDSA); + } + public static Optional> getEdDSASupport() { if (isFipsMode()) { return Optional.empty(); @@ -608,6 +613,15 @@ public static boolean isEDDSACurveSupported() { register(); synchronized (REGISTERED_PROVIDERS) { + // Prefer the net.i2p.crypto provider if it's available for backwards compatibility + SecurityProviderRegistrar netI2pCryptoProvider = REGISTERED_PROVIDERS.get(EDDSA); + if (netI2pCryptoProvider != null) { + Optional> support = netI2pCryptoProvider.getEdDSASupport(); + if (support.isPresent()) { + return support; + } + } + for (Map.Entry entry : REGISTERED_PROVIDERS.entrySet()) { Optional> support = entry.getValue().getEdDSASupport(); if (support.isPresent()) { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java index 4a9e4bcc6..6285204bf 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java @@ -19,8 +19,8 @@ package org.apache.sshd.common.util.security.bouncycastle; import java.lang.reflect.Field; -import java.security.Provider; -import java.security.Security; +import java.security.*; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; @@ -103,6 +103,28 @@ public String getDefaultSecurityEntitySupportValue(Class entityType) { return allValue; } + @Override + public boolean isSecurityEntitySupported(Class entityType, String name) { + if (!isSupported()) { + return false; + } + + if (KeyPairGenerator.class.isAssignableFrom(entityType) + || KeyFactory.class.isAssignableFrom(entityType)) { + if (Objects.compare(name, SecurityUtils.EDDSA, String.CASE_INSENSITIVE_ORDER) == 0 + && SecurityUtils.isNetI2pCryptoEdDSARegistered()) { + return false; + } + } else if (Signature.class.isAssignableFrom(entityType)) { + if (Objects.compare(name, SecurityUtils.CURVE_ED25519_SHA512, String.CASE_INSENSITIVE_ORDER) == 0 + && SecurityUtils.isNetI2pCryptoEdDSARegistered()) { + return false; + } + } + + return super.isSecurityEntitySupported(entityType, name); + } + @Override public boolean isSupported() { Boolean supported; From d00d68823dfc4d21158fa65317b2f6ba9e364696 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Sat, 23 Nov 2024 16:03:53 +0000 Subject: [PATCH 07/10] Add some JavaDoc --- .../security/SecurityProviderRegistrar.java | 3 + .../eddsa/Ed25519PEMResourceKeyParser.java | 139 +--------------- .../eddsa/Ed25519PublicKeyDecoder.java | 3 + .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 3 + .../util/security/eddsa/SignatureEd25519.java | 2 + .../security/eddsa/generic/EdDSASupport.java | 87 ++++++++++ .../GenericEd25519PEMResourceKeyParser.java | 151 ++++++++++++++++++ 7 files changed, 256 insertions(+), 132 deletions(-) create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java index b1fd549a2..ecbda0a6a 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java @@ -184,6 +184,9 @@ default boolean isCertificateFactorySupported(String type) { return isSecurityEntitySupported(CertificateFactory.class, type); } + /** + * @return the EdDSA support implementation associated with the security provider (if applicable) + */ default Optional> getEdDSASupport() { return Optional.empty(); } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java index 0fef86e5e..ab64ea720 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java @@ -20,159 +20,34 @@ package org.apache.sshd.common.util.security.eddsa; import java.io.IOException; -import java.io.InputStream; -import java.io.StreamCorruptedException; -import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; -import java.security.KeyPair; import java.security.NoSuchAlgorithmException; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import net.i2p.crypto.eddsa.EdDSAKey; import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import org.apache.sshd.common.NamedResource; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.io.der.ASN1Object; -import org.apache.sshd.common.util.io.der.ASN1Type; -import org.apache.sshd.common.util.io.der.DERParser; -import org.apache.sshd.common.util.io.input.NoCloseInputStream; import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PEMResourceKeyParser; /** + * An implementation of {@link GenericEd25519PEMResourceKeyParser} tied to the {@code net.i2p.crypto} EdDSA security + * provider. + * * @author Apache MINA SSHD Project */ -public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { - - // From an early draft of RFC 8410 (https://datatracker.ietf.org/doc/html/draft-ietf-curdle-pkix-eddsa-00). - // The final RFC 8410 dropped the "EDDSA" and moved to PKCS#8 using just "BEGIN PRIVATE KEY". - public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY"; - - // Some (older) OpenSSL versions used this if "traditional" (RFC 1421) PEM format was chosen: - public static final String BEGIN_ED25519_MARKER = "BEGIN ED25519 PRIVATE KEY"; - - public static final List BEGINNERS = GenericUtils.unmodifiableList(BEGIN_MARKER, BEGIN_ED25519_MARKER); - - public static final String END_MARKER = "END EDDSA PRIVATE KEY"; - public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY"; - public static final List ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER); - - public static final String ED25519_OID = EdDSASupport.ED25519_OID; +public class Ed25519PEMResourceKeyParser extends GenericEd25519PEMResourceKeyParser { public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); public Ed25519PEMResourceKeyParser() { - super(EdDSAKey.KEY_ALGORITHM, ED25519_OID, BEGINNERS, ENDERS); - } - - @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, String beginMarker, - String endMarker, FilePasswordProvider passwordProvider, - InputStream stream, Map headers) - throws IOException, GeneralSecurityException { - KeyPair kp = parseEd25519KeyPair(stream, false); - return Collections.singletonList(kp); - } - - public static KeyPair parseEd25519KeyPair( - InputStream inputStream, boolean okToClose) - throws IOException, GeneralSecurityException { - try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { - return parseEd25519KeyPair(parser); - } - } - - /* - * See https://tools.ietf.org/html/rfc8410#section-7 - * - * SEQUENCE { - * INTEGER 0x00 (0 decimal) - * SEQUENCE { - * OBJECTIDENTIFIER 1.3.101.112 - * } - * OCTETSTRING keyData - * } - * - * NOTE: there is another variant that also has some extra parameters - * but it has the same "prefix" structure so we don't care - */ - public static KeyPair parseEd25519KeyPair(DERParser parser) throws IOException, GeneralSecurityException { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing version value"); - } - - BigInteger version = obj.asInteger(); - if (!BigInteger.ZERO.equals(version)) { - throw new StreamCorruptedException("Invalid version: " + version); - } - - obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing OID container"); - } - - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.SEQUENCE) { - throw new StreamCorruptedException("Unexpected OID object type: " + objType); - } - - List curveOid; - try (DERParser oidParser = obj.createParser()) { - obj = oidParser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing OID value"); - } - - curveOid = obj.asOID(); - } - - String oid = GenericUtils.join(curveOid, '.'); - // TODO modify if more curves supported - if (!ED25519_OID.equals(oid)) { - throw new StreamCorruptedException("Unsupported curve OID: " + oid); - } - - obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data"); - } - - return decodeEd25519KeyPair(obj.getValue()); - } - - public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { - EdDSAPrivateKey privateKey = decodeEdDSAPrivateKey(keyData); - EdDSAPublicKey publicKey = EdDSASecurityProviderUtils.recoverEDDSAPublicKey(privateKey); - return new KeyPair(publicKey, privateKey); + super(); } public static EdDSAPrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { - try (DERParser parser = new DERParser(keyData)) { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data container"); - } - - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.OCTET_STRING) { - throw new StreamCorruptedException("Mismatched key data container type: " + objType); - } - - return generateEdDSAPrivateKey(obj.getValue()); - } + return EdDSAPrivateKey.class.cast(EdDSASupport.decodeEdDSAPrivateKey(keyData)); } public static EdDSAPrivateKey generateEdDSAPrivateKey(byte[] seed) throws GeneralSecurityException { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java index a9244942c..7c5ac4d5a 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java @@ -23,6 +23,9 @@ import org.apache.sshd.common.util.security.eddsa.generic.GenericEd25519PublicKeyDecoder; /** + * An implementation of {@link GenericEd25519PublicKeyDecoder} tied to the {@code net.i2p.crypto} EdDSA security + * provider. + * * @author Apache MINA SSHD Project */ public final class Ed25519PublicKeyDecoder extends GenericEd25519PublicKeyDecoder { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java index aa673bd34..b4012a90f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -24,6 +24,9 @@ import org.apache.sshd.common.util.security.eddsa.generic.GenericOpenSSHEd25519PrivateKeyEntryDecoder; /** + * An implementation of {@link GenericOpenSSHEd25519PrivateKeyEntryDecoder} tied to the {@code net.i2p.crypto} EdDSA + * security provider + * * @author Apache MINA SSHD Project */ public class OpenSSHEd25519PrivateKeyEntryDecoder 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 4aaab148c..1c49809ea 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 @@ -22,6 +22,8 @@ import org.apache.sshd.common.util.security.eddsa.generic.GenericSignatureEd25519; /** + * An implementation of {@link GenericSignatureEd25519} tied to the {@code net.i2p.crypto} EdDSA security provider. + * * @author Apache MINA SSHD Project */ public class SignatureEd25519 extends GenericSignatureEd25519 { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java index bd09fa451..5e26e0507 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -34,6 +34,13 @@ import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.security.SecurityUtils; +/** + * Provides generic operations required of a security provider to support EdDSA and Ed25519. + * + * @param type of the public key supported by the security provider + * @param type of the private key supported by the security provider + * @author Apache MINA SSHD Project + */ public interface EdDSASupport { int KEY_SIZE = 256; @@ -43,12 +50,20 @@ public interface EdDSASupport { */ String ED25519_OID = "1.3.101.112"; + /** + * @param keyData the raw private key bytes. + * @return a {@link KeyPair} from the given raw private key data. + */ static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { PrivateKey privateKey = decodeEdDSAPrivateKey(keyData); PublicKey publicKey = SecurityUtils.recoverEDDSAPublicKey(privateKey); return new KeyPair(publicKey, privateKey); } + /** + * @param keyData the raw private key bytes. + * @return the associated private key. + */ static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { try (DERParser parser = new DERParser(keyData)) { ASN1Object obj = parser.readObject(); @@ -65,38 +80,110 @@ static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, Gene } } + /** + * @return the public key entry decoder implementation associated with the security provider. + */ PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder(); + /** + * @return the private key entry decoder implementation associated with the security provider. + */ PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder(); + /** + * @return the signature implementation associated with the security provider. + */ Signature getEDDSASigner(); + /** + * @param key the key to get the size of. + * @return the size of the key if it is an EdDSA key, -1 otherwise. + */ int getEDDSAKeySize(Key key); + /** + * @return the public key class type associated with the security provider. + */ Class getEDDSAPublicKeyType(); + /** + * @return the private key class type associated with the security provider. + */ Class getEDDSAPrivateKeyType(); + /** + * @param k1 the first key + * @param k2 the second key + * @return {@code true} if both keys are instances of the public key type associated with the security provider + * and they are equal. + */ boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2); + /** + * @param k1 the first key + * @param k2 the second key + * @return {@code true} if both keys are instances of the private key type associated with the security provider + * and they are equal. + */ boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2); + /** + * @param key the private key + * @return the public key associated with the private key. + */ PUB recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException; + /** + * @param seed the raw public key bytes + * @return the associated public key + */ PUB generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException; + /** + * @param seed the raw private key bytes + * @return the associated private key + */ PRV generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException; + /** + * @param buffer the buffer to insert the public key into + * @param key the public key to be inserted into the buffer + * @return the buffer that was passed in + * @param type of the buffer + */ B putRawEDDSAPublicKey(B buffer, PublicKey key); + /** + * @param buffer the buffer to insert the keys into + * @param pubKey the public key to be inserted into the buffer + * @param prvKey the private key to be inserted into the buffer + * @return the buffer that was passed in + * @param type of the buffer + */ B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey); + /** + * @param publicKey the public key + * @return a key spec from the public key + */ KeySpec createPublicKeySpec(PUB publicKey); + /** + * @param privateKey the private key + * @return a key spec from the private key + */ KeySpec createPrivateKeySpec(PRV privateKey); + /** + * @param publicKey the public key + * @return the raw public key bytes associated with the key + */ byte[] getPublicKeyData(PUB publicKey); + /** + * @param privateKey the private key + * @return the raw private key bytes associated with the key + */ byte[] getPrivateKeyData(PRV privateKey) throws IOException; } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java new file mode 100644 index 000000000..9e4c64511 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PEMResourceKeyParser.java @@ -0,0 +1,151 @@ +/* + * 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.security.eddsa.generic; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.der.ASN1Object; +import org.apache.sshd.common.util.io.der.ASN1Type; +import org.apache.sshd.common.util.io.der.DERParser; +import org.apache.sshd.common.util.io.input.NoCloseInputStream; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author Apache MINA SSHD Project + */ +public class GenericEd25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { + + // From an early draft of RFC 8410 (https://datatracker.ietf.org/doc/html/draft-ietf-curdle-pkix-eddsa-00). + // The final RFC 8410 dropped the "EDDSA" and moved to PKCS#8 using just "BEGIN PRIVATE KEY". + public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY"; + + // Some (older) OpenSSL versions used this if "traditional" (RFC 1421) PEM format was chosen: + public static final String BEGIN_ED25519_MARKER = "BEGIN ED25519 PRIVATE KEY"; + + public static final List BEGINNERS = GenericUtils.unmodifiableList(BEGIN_MARKER, BEGIN_ED25519_MARKER); + + public static final String END_MARKER = "END EDDSA PRIVATE KEY"; + public static final String END_ED25519_MARKER = "END ED25519 PRIVATE KEY"; + public static final List ENDERS = GenericUtils.unmodifiableList(END_MARKER, END_ED25519_MARKER); + + public static final String ED25519_OID = EdDSASupport.ED25519_OID; + + public static final GenericEd25519PEMResourceKeyParser INSTANCE = new GenericEd25519PEMResourceKeyParser(); + + public GenericEd25519PEMResourceKeyParser() { + super(SecurityUtils.EDDSA, ED25519_OID, BEGINNERS, ENDERS); + } + + @Override + public Collection extractKeyPairs( + SessionContext session, NamedResource resourceKey, String beginMarker, + String endMarker, FilePasswordProvider passwordProvider, + InputStream stream, Map headers) + throws IOException, GeneralSecurityException { + KeyPair kp = parseEd25519KeyPair(stream, false); + return Collections.singletonList(kp); + } + + public static KeyPair parseEd25519KeyPair( + InputStream inputStream, boolean okToClose) + throws IOException, GeneralSecurityException { + try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { + return parseEd25519KeyPair(parser); + } + } + + /* + * See https://tools.ietf.org/html/rfc8410#section-7 + * + * SEQUENCE { + * INTEGER 0x00 (0 decimal) + * SEQUENCE { + * OBJECTIDENTIFIER 1.3.101.112 + * } + * OCTETSTRING keyData + * } + * + * NOTE: there is another variant that also has some extra parameters + * but it has the same "prefix" structure so we don't care + */ + public static KeyPair parseEd25519KeyPair(DERParser parser) throws IOException, GeneralSecurityException { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing version value"); + } + + BigInteger version = obj.asInteger(); + if (!BigInteger.ZERO.equals(version)) { + throw new StreamCorruptedException("Invalid version: " + version); + } + + obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing OID container"); + } + + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.SEQUENCE) { + throw new StreamCorruptedException("Unexpected OID object type: " + objType); + } + + List curveOid; + try (DERParser oidParser = obj.createParser()) { + obj = oidParser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing OID value"); + } + + curveOid = obj.asOID(); + } + + String oid = GenericUtils.join(curveOid, '.'); + // TODO modify if more curves supported + if (!ED25519_OID.equals(oid)) { + throw new StreamCorruptedException("Unsupported curve OID: " + oid); + } + + obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data"); + } + + return decodeEd25519KeyPair(obj.getValue()); + } + + public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { + return EdDSASupport.decodeEd25519KeyPair(keyData); + } + +} From cc101547af4eb42f06047e447d9346139fbe59da Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Mon, 25 Nov 2024 14:08:55 +0000 Subject: [PATCH 08/10] Replace wildcard imports --- .../util/security/SecurityProviderRegistrar.java | 8 +++++++- .../sshd/common/util/security/SecurityUtils.java | 13 ++++++++++++- .../BouncyCastleSecurityProviderRegistrar.java | 6 +++++- .../security/eddsa/NetI2pCryptoEdDSASupport.java | 5 ++++- .../bouncycastle/BouncyCastleEdDSASupport.java | 7 ++++++- .../util/security/eddsa/generic/EdDSASupport.java | 6 +++++- .../generic/GenericEd25519PublicKeyDecoder.java | 6 +++++- ...GenericOpenSSHEd25519PrivateKeyEntryDecoder.java | 8 +++++++- 8 files changed, 51 insertions(+), 8 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java index ecbda0a6a..a9cf72ab2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityProviderRegistrar.java @@ -26,7 +26,13 @@ import java.security.Security; import java.security.Signature; import java.security.cert.CertificateFactory; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.function.Predicate; import javax.crypto.Cipher; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java index bfa9bfe48..97fabfdb5 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -35,7 +35,18 @@ import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateFactory; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java index 6285204bf..8a3830bcb 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleSecurityProviderRegistrar.java @@ -19,7 +19,11 @@ package org.apache.sshd.common.util.security.bouncycastle; import java.lang.reflect.Field; -import java.security.*; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.security.Security; +import java.security.Signature; import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java index 61b1ee093..e4e4c19d6 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java @@ -20,7 +20,10 @@ package org.apache.sshd.common.util.security.eddsa; import java.io.IOException; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.KeySpec; import net.i2p.crypto.eddsa.EdDSAPrivateKey; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java index 9cbed278e..8c42f9088 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -20,7 +20,12 @@ package org.apache.sshd.common.util.security.eddsa.bouncycastle; import java.io.IOException; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java index 5e26e0507..8d45f649e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -21,7 +21,11 @@ import java.io.IOException; import java.io.StreamCorruptedException; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.KeySpec; import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java index ecfd701d8..99622264b 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericEd25519PublicKeyDecoder.java @@ -21,7 +21,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.Collections; import java.util.Map; import java.util.Objects; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java index c62f6ddf7..078a6572b 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/GenericOpenSSHEd25519PrivateKeyEntryDecoder.java @@ -21,7 +21,13 @@ import java.io.IOException; import java.io.InputStream; -import java.security.*; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.Arrays; import java.util.Collections; import java.util.Locale; From 615fab4e2dc769adbe3c6a5d98e1814730c034c1 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Sun, 22 Dec 2024 15:11:23 +0000 Subject: [PATCH 09/10] Add some tests --- .../eddsa/NetI2pCryptoEdDSASupport.java | 6 + .../BouncyCastleEdDSASupport.java | 12 +- .../security/eddsa/generic/EdDSASupport.java | 5 + .../security/eddsa/Ed25519VectorsTest.java | 331 +++++++++++------- 4 files changed, 217 insertions(+), 137 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java index e4e4c19d6..e88f5bdc0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/NetI2pCryptoEdDSASupport.java @@ -34,6 +34,7 @@ import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; import org.apache.sshd.common.signature.Signature; import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; public class NetI2pCryptoEdDSASupport implements EdDSASupport { @@ -126,4 +127,9 @@ public byte[] getPublicKeyData(EdDSAPublicKey publicKey) { public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { return privateKey.getSeed(); } + + @Override + public String getKeyFactoryAlgorithm() { + return SecurityUtils.EDDSA; + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java index 8c42f9088..6663c2e45 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/bouncycastle/BouncyCastleEdDSASupport.java @@ -46,7 +46,6 @@ import org.bouncycastle.jcajce.interfaces.EdDSAKey; import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey; import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey; -import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec; import org.bouncycastle.jcajce.spec.RawEncodedKeySpec; public class BouncyCastleEdDSASupport implements EdDSASupport { @@ -115,7 +114,7 @@ public EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecuri @Override public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { RawEncodedKeySpec keySpec = new RawEncodedKeySpec(seed); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); + KeyFactory factory = SecurityUtils.getKeyFactory(getKeyFactoryAlgorithm()); return (EdDSAPublicKey) factory.generatePublic(keySpec); } @@ -123,7 +122,7 @@ public EdDSAPublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurity public EdDSAPrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException, IOException { Ed25519PrivateKeyParameters parameters = new Ed25519PrivateKeyParameters(seed); PrivateKeyInfo info = PrivateKeyInfoFactory.createPrivateKeyInfo(parameters); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.ED25519); + KeyFactory factory = SecurityUtils.getKeyFactory(getKeyFactoryAlgorithm()); return (EdDSAPrivateKey) factory.generatePrivate(new PKCS8EncodedKeySpec(info.getEncoded())); } @@ -148,7 +147,7 @@ public KeySpec createPublicKeySpec(EdDSAPublicKey publicKey) { @Override public KeySpec createPrivateKeySpec(EdDSAPrivateKey privateKey) { - return new OpenSSHPrivateKeySpec(privateKey.getEncoded()); + return new PKCS8EncodedKeySpec(privateKey.getEncoded()); } @Override @@ -162,4 +161,9 @@ public byte[] getPrivateKeyData(EdDSAPrivateKey privateKey) throws IOException { = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded()); return parameters.getEncoded(); } + + @Override + public String getKeyFactoryAlgorithm() { + return SecurityUtils.ED25519; + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java index 8d45f649e..a8ed9ac54 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSASupport.java @@ -190,4 +190,9 @@ static PrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, Gene */ byte[] getPrivateKeyData(PRV privateKey) throws IOException; + /** + * @return the algorithm name used by the provider's {@link java.security.KeyFactory}. + */ + String getKeyFactoryAlgorithm(); + } diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java index 8b42895ce..4847e2b81 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/Ed25519VectorsTest.java @@ -19,19 +19,23 @@ package org.apache.sshd.common.util.security.eddsa; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.spec.KeySpec; import java.util.ArrayList; -import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.EdDSAPublicKey; import org.apache.sshd.common.signature.Signature; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.common.util.security.eddsa.bouncycastle.BouncyCastleEdDSASupport; +import org.apache.sshd.common.util.security.eddsa.generic.EdDSASupport; import org.apache.sshd.util.test.JUnitTestSupport; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; @@ -41,9 +45,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * @author Apache MINA SSHD Project * @see EdDSA and Ed25519 @@ -51,131 +52,145 @@ */ @TestMethodOrder(MethodName.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests @Tag("NoIoTestCase") -public class Ed25519VectorsTest extends JUnitTestSupport { +public class Ed25519VectorsTest extends JUnitTestSupport { private byte[] prvBytes; - private PrivateKey privateKey; + private PRV privateKey; private byte[] pubBytes; - private PublicKey publicKey; + private PUB publicKey; private byte[] msgBytes; private byte[] expSignature; - public void initEd25519VectorsTest(String name, String prvKey, String pubKey, String msg, String signature) - throws GeneralSecurityException { + public void initEd25519VectorsTest( + String name, EdDSASupport support, String prvKey, String pubKey, String msg, String signature) + throws GeneralSecurityException, IOException { prvBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, prvKey); - privateKey = EdDSASecurityProviderUtils.generateEDDSAPrivateKey(prvBytes.clone()); + privateKey = support.generateEDDSAPrivateKey(prvBytes.clone()); pubBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, pubKey); - publicKey = EdDSASecurityProviderUtils.generateEDDSAPublicKey(pubBytes.clone()); + publicKey = support.generateEDDSAPublicKey(pubBytes.clone()); msgBytes = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, msg); expSignature = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, signature); } - @SuppressWarnings("checkstyle:anoninnerlength") public static List parameters() { - return new ArrayList<>( - Arrays.asList( - new Object[] { - "TEST1 - empty message", - "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", - "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", - "", - "e5564300c360ac729086e2cc806e828a" - + "84877f1eb8e5d974d873e06522490155" - + "5fb8821590a33bacc61e39701cf9b46b" - + "d25bf5f0595bbe24655141438e7a100b" - }, - new Object[] { - "TEST2 - one byte", - "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", - "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", - "72", - "92a009a9f0d4cab8720e820b5f642540" - + "a2b27b5416503f8fb3762223ebdb69da" - + "085ac1e43e15996e458f3613d0f11d8c" - + "387b2eaeb4302aeeb00d291612bb0c00" - }, - new Object[] { - "TEST3 - 2 bytes", - "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", - "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", - "af82", - "6291d657deec24024827e69c3abe01a3" - + "0ce548a284743a445e3680d7db5ac3ac" - + "18ff9b538d16f290ae67f760984dc659" - + "4a7c15e9716ed28dc027beceea1ec40a" - }, - new Object[] { - "TEST1024 - large message", - "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5", - "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", - "08b8b2b733424243760fe426a4b54908" - + "632110a66c2f6591eabd3345e3e4eb98" - + "fa6e264bf09efe12ee50f8f54e9f77b1" - + "e355f6c50544e23fb1433ddf73be84d8" - + "79de7c0046dc4996d9e773f4bc9efe57" - + "38829adb26c81b37c93a1b270b20329d" - + "658675fc6ea534e0810a4432826bf58c" - + "941efb65d57a338bbd2e26640f89ffbc" - + "1a858efcb8550ee3a5e1998bd177e93a" - + "7363c344fe6b199ee5d02e82d522c4fe" - + "ba15452f80288a821a579116ec6dad2b" - + "3b310da903401aa62100ab5d1a36553e" - + "06203b33890cc9b832f79ef80560ccb9" - + "a39ce767967ed628c6ad573cb116dbef" - + "efd75499da96bd68a8a97b928a8bbc10" - + "3b6621fcde2beca1231d206be6cd9ec7" - + "aff6f6c94fcd7204ed3455c68c83f4a4" - + "1da4af2b74ef5c53f1d8ac70bdcb7ed1" - + "85ce81bd84359d44254d95629e9855a9" - + "4a7c1958d1f8ada5d0532ed8a5aa3fb2" - + "d17ba70eb6248e594e1a2297acbbb39d" - + "502f1a8c6eb6f1ce22b3de1a1f40cc24" - + "554119a831a9aad6079cad88425de6bd" - + "e1a9187ebb6092cf67bf2b13fd65f270" - + "88d78b7e883c8759d2c4f5c65adb7553" - + "878ad575f9fad878e80a0c9ba63bcbcc" - + "2732e69485bbc9c90bfbd62481d9089b" - + "eccf80cfe2df16a2cf65bd92dd597b07" - + "07e0917af48bbb75fed413d238f5555a" - + "7a569d80c3414a8d0859dc65a46128ba" - + "b27af87a71314f318c782b23ebfe808b" - + "82b0ce26401d2e22f04d83d1255dc51a" - + "ddd3b75a2b1ae0784504df543af8969b" - + "e3ea7082ff7fc9888c144da2af58429e" - + "c96031dbcad3dad9af0dcbaaaf268cb8" - + "fcffead94f3c7ca495e056a9b47acdb7" - + "51fb73e666c6c655ade8297297d07ad1" - + "ba5e43f1bca32301651339e22904cc8c" - + "42f58c30c04aafdb038dda0847dd988d" - + "cda6f3bfd15c4b4c4525004aa06eeff8" - + "ca61783aacec57fb3d1f92b0fe2fd1a8" - + "5f6724517b65e614ad6808d6f6ee34df" - + "f7310fdc82aebfd904b01e1dc54b2927" - + "094b2db68d6f903b68401adebf5a7e08" - + "d78ff4ef5d63653a65040cf9bfd4aca7" - + "984a74d37145986780fc0b16ac451649" - + "de6188a7dbdf191f64b5fc5e2ab47b57" - + "f7f7276cd419c17a3ca8e1b939ae49e4" - + "88acba6b965610b5480109c8b17b80e1" - + "b7b750dfc7598d5d5011fd2dcc5600a3" - + "2ef5b52a1ecc820e308aa342721aac09" - + "43bf6686b64b2579376504ccc493d97e" - + "6aed3fb0f9cd71a43dd497f01f17c0e2" - + "cb3797aa2a2f256656168e6c496afc5f" - + "b93246f6b1116398a346f1a641f3b041" - + "e989f7914f90cc2c7fff357876e506b5" - + "0d334ba77c225bc307ba537152f3f161" - + "0e4eafe595f6d9d90d11faa933a15ef1" - + "369546868a7f3a45a96768d40fd9d034" - + "12c091c6315cf4fde7cb68606937380d" - + "b2eaaa707b4c4185c32eddcdd306705e" - + "4dc1ffc872eeee475a64dfac86aba41c" - + "0618983f8741c5ef68d3a101e8a3b8ca" - + "c60c905c15fc910840b94c00a0b9d0", - "0aab4c900501b3e24d7cdf4663326a3a" - + "87df5e4843b2cbdb67cbf6e460fec350" - + "aa5371b1508f9f4528ecea23c436d94b" - + "5e8fcd4f681e30a6ac00a9704a188a03" - })); + List parameters = new ArrayList<>(); + Map> supportedSecurityProviders = new LinkedHashMap<>(); + supportedSecurityProviders.put(SecurityUtils.EDDSA, new NetI2pCryptoEdDSASupport()); + supportedSecurityProviders.put(SecurityUtils.BOUNCY_CASTLE, new BouncyCastleEdDSASupport()); + for (Map.Entry> entry : supportedSecurityProviders.entrySet()) { + String supportClassName = entry.getValue().getClass().getSimpleName(); + parameters.add(new Object[] { + supportClassName + " TEST1 - empty message", + entry.getValue(), + entry.getKey(), + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", + "", + "e5564300c360ac729086e2cc806e828a" + + "84877f1eb8e5d974d873e06522490155" + + "5fb8821590a33bacc61e39701cf9b46b" + + "d25bf5f0595bbe24655141438e7a100b" + }); + parameters.add(new Object[] { + supportClassName + " TEST2 - one byte", + entry.getValue(), + entry.getKey(), + "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", + "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + "72", + "92a009a9f0d4cab8720e820b5f642540" + + "a2b27b5416503f8fb3762223ebdb69da" + + "085ac1e43e15996e458f3613d0f11d8c" + + "387b2eaeb4302aeeb00d291612bb0c00" + }); + parameters.add(new Object[] { + supportClassName + " TEST3 - 2 bytes", + entry.getValue(), + entry.getKey(), + "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", + "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", + "af82", + "6291d657deec24024827e69c3abe01a3" + + "0ce548a284743a445e3680d7db5ac3ac" + + "18ff9b538d16f290ae67f760984dc659" + + "4a7c15e9716ed28dc027beceea1ec40a" + }); + parameters.add(new Object[] { + supportClassName + " TEST1024 - large message", + entry.getValue(), + entry.getKey(), + "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5", + "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", + "08b8b2b733424243760fe426a4b54908" + + "632110a66c2f6591eabd3345e3e4eb98" + + "fa6e264bf09efe12ee50f8f54e9f77b1" + + "e355f6c50544e23fb1433ddf73be84d8" + + "79de7c0046dc4996d9e773f4bc9efe57" + + "38829adb26c81b37c93a1b270b20329d" + + "658675fc6ea534e0810a4432826bf58c" + + "941efb65d57a338bbd2e26640f89ffbc" + + "1a858efcb8550ee3a5e1998bd177e93a" + + "7363c344fe6b199ee5d02e82d522c4fe" + + "ba15452f80288a821a579116ec6dad2b" + + "3b310da903401aa62100ab5d1a36553e" + + "06203b33890cc9b832f79ef80560ccb9" + + "a39ce767967ed628c6ad573cb116dbef" + + "efd75499da96bd68a8a97b928a8bbc10" + + "3b6621fcde2beca1231d206be6cd9ec7" + + "aff6f6c94fcd7204ed3455c68c83f4a4" + + "1da4af2b74ef5c53f1d8ac70bdcb7ed1" + + "85ce81bd84359d44254d95629e9855a9" + + "4a7c1958d1f8ada5d0532ed8a5aa3fb2" + + "d17ba70eb6248e594e1a2297acbbb39d" + + "502f1a8c6eb6f1ce22b3de1a1f40cc24" + + "554119a831a9aad6079cad88425de6bd" + + "e1a9187ebb6092cf67bf2b13fd65f270" + + "88d78b7e883c8759d2c4f5c65adb7553" + + "878ad575f9fad878e80a0c9ba63bcbcc" + + "2732e69485bbc9c90bfbd62481d9089b" + + "eccf80cfe2df16a2cf65bd92dd597b07" + + "07e0917af48bbb75fed413d238f5555a" + + "7a569d80c3414a8d0859dc65a46128ba" + + "b27af87a71314f318c782b23ebfe808b" + + "82b0ce26401d2e22f04d83d1255dc51a" + + "ddd3b75a2b1ae0784504df543af8969b" + + "e3ea7082ff7fc9888c144da2af58429e" + + "c96031dbcad3dad9af0dcbaaaf268cb8" + + "fcffead94f3c7ca495e056a9b47acdb7" + + "51fb73e666c6c655ade8297297d07ad1" + + "ba5e43f1bca32301651339e22904cc8c" + + "42f58c30c04aafdb038dda0847dd988d" + + "cda6f3bfd15c4b4c4525004aa06eeff8" + + "ca61783aacec57fb3d1f92b0fe2fd1a8" + + "5f6724517b65e614ad6808d6f6ee34df" + + "f7310fdc82aebfd904b01e1dc54b2927" + + "094b2db68d6f903b68401adebf5a7e08" + + "d78ff4ef5d63653a65040cf9bfd4aca7" + + "984a74d37145986780fc0b16ac451649" + + "de6188a7dbdf191f64b5fc5e2ab47b57" + + "f7f7276cd419c17a3ca8e1b939ae49e4" + + "88acba6b965610b5480109c8b17b80e1" + + "b7b750dfc7598d5d5011fd2dcc5600a3" + + "2ef5b52a1ecc820e308aa342721aac09" + + "43bf6686b64b2579376504ccc493d97e" + + "6aed3fb0f9cd71a43dd497f01f17c0e2" + + "cb3797aa2a2f256656168e6c496afc5f" + + "b93246f6b1116398a346f1a641f3b041" + + "e989f7914f90cc2c7fff357876e506b5" + + "0d334ba77c225bc307ba537152f3f161" + + "0e4eafe595f6d9d90d11faa933a15ef1" + + "369546868a7f3a45a96768d40fd9d034" + + "12c091c6315cf4fde7cb68606937380d" + + "b2eaaa707b4c4185c32eddcdd306705e" + + "4dc1ffc872eeee475a64dfac86aba41c" + + "0618983f8741c5ef68d3a101e8a3b8ca" + + "c60c905c15fc910840b94c00a0b9d0", + "0aab4c900501b3e24d7cdf4663326a3a" + + "87df5e4843b2cbdb67cbf6e460fec350" + + "aa5371b1508f9f4528ecea23c436d94b" + + "5e8fcd4f681e30a6ac00a9704a188a03" + }); + } + return parameters; } @BeforeAll @@ -185,31 +200,38 @@ static void checkEDDSASupported() { @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void publicKeyBytes(String name, String prvKey, String pubKey, String msg, String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); - byte[] publicSeed = Ed25519PublicKeyDecoder.getSeedValue((EdDSAPublicKey) publicKey); + public void publicKeyBytes( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + byte[] publicSeed = support.getPublicKeyData(publicKey); assertArrayEquals(pubBytes, publicSeed, "Mismatched public seed value"); } @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void privateKeyBytes(String name, String prvKey, String pubKey, String msg, String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); - assertArrayEquals(prvBytes, ((EdDSAPrivateKey) privateKey).getSeed(), "Mismatched private seed value"); + public void privateKeyBytes( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + byte[] privateSeed = support.getPrivateKeyData(privateKey); + assertArrayEquals(prvBytes, privateSeed, "Mismatched private seed value"); } @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void signature(String name, String prvKey, String pubKey, String msg, String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); - Signature signer = EdDSASecurityProviderUtils.getEDDSASignature(); + public void signature( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + Signature signer = support.getEDDSASigner(); signer.initSigner(null, privateKey); signer.update(null, msgBytes.clone()); byte[] actSignature = signer.sign(null); assertArrayEquals(expSignature, actSignature, "Mismatched signature"); - Signature verifier = EdDSASecurityProviderUtils.getEDDSASignature(); + Signature verifier = support.getEDDSASigner(); verifier.initVerifier(null, publicKey); verifier.update(null, msgBytes.clone()); assertTrue(verifier.verify(null, expSignature), "Verification failed"); @@ -217,9 +239,11 @@ public void signature(String name, String prvKey, String pubKey, String msg, Str @MethodSource("parameters") @ParameterizedTest(name = "{0}") - public void partialBufferSignature(String name, String prvKey, String pubKey, String msg, String signature) + public void partialBufferSignature( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { - initEd25519VectorsTest(name, prvKey, pubKey, msg, signature); + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); byte[] extraData = getCurrentTestName().getBytes(StandardCharsets.UTF_8); byte[] dataBuf = new byte[msgBytes.length + extraData.length]; int offset = extraData.length / 2; @@ -227,16 +251,57 @@ public void partialBufferSignature(String name, String prvKey, String pubKey, St System.arraycopy(msgBytes, 0, dataBuf, offset, msgBytes.length); System.arraycopy(extraData, offset, dataBuf, offset + msgBytes.length, extraData.length - offset); - Signature signer = EdDSASecurityProviderUtils.getEDDSASignature(); + Signature signer = support.getEDDSASigner(); signer.initSigner(null, privateKey); signer.update(null, dataBuf.clone(), offset, msgBytes.length); byte[] actSignature = signer.sign(null); assertArrayEquals(expSignature, actSignature, "Mismatched signature"); - Signature verifier = EdDSASecurityProviderUtils.getEDDSASignature(); + Signature verifier = support.getEDDSASigner(); verifier.initVerifier(null, publicKey); verifier.update(null, dataBuf.clone(), offset, msgBytes.length); assertTrue(verifier.verify(null, expSignature), "Verification failed"); } + + @MethodSource("parameters") + @ParameterizedTest(name = "{0}") + public void recoverEDDSAPublicKey( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + PUB recoveredKey = support.recoverEDDSAPublicKey(privateKey); + assertTrue(support.compareEDDSAPPublicKeys(publicKey, recoveredKey), "Recovered key is not equal"); + byte[] recoveredBytes = support.getPublicKeyData(recoveredKey); + assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed value"); + } + + @MethodSource("parameters") + @ParameterizedTest(name = "{0}") + public void createPublicKeySpec( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + KeySpec keySpec = support.createPublicKeySpec(publicKey); + KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider); + PUB generatedKey = (PUB) keyFactory.generatePublic(keySpec); + assertTrue(support.compareEDDSAPPublicKeys(publicKey, generatedKey), "Generated key is not equal"); + byte[] generatedBytes = support.getPublicKeyData(generatedKey); + assertArrayEquals(pubBytes, generatedBytes, "Mismatched public seed value"); + } + + @MethodSource("parameters") + @ParameterizedTest(name = "{0}") + public void createPrivateKeySpec( + String name, EdDSASupport support, String provider, String prvKey, String pubKey, String msg, + String signature) throws Exception { + initEd25519VectorsTest(name, support, prvKey, pubKey, msg, signature); + KeySpec keySpec = support.createPrivateKeySpec(privateKey); + KeyFactory keyFactory = KeyFactory.getInstance(support.getKeyFactoryAlgorithm(), provider); + PRV generatedKey = (PRV) keyFactory.generatePrivate(keySpec); + assertTrue(support.compareEDDSAPrivateKeys(privateKey, generatedKey), "Generated key is not equal"); + byte[] generatedBytes = support.getPrivateKeyData(generatedKey); + assertArrayEquals(prvBytes, generatedBytes, "Mismatched private seed value"); + } + } From 9c49609e58fb3a0376c0b3100cc7388dd0d1e268 Mon Sep 17 00:00:00 2001 From: Ian O'Neill Date: Sun, 22 Dec 2024 19:39:19 +0000 Subject: [PATCH 10/10] Update docs --- docs/dependencies.md | 10 ++++------ docs/files-parsing.md | 4 ++-- docs/standards.md | 3 ++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/dependencies.md b/docs/dependencies.md index 39aa4f5e5..04331b6b4 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -93,10 +93,10 @@ implementation. This is also an **optional** dependency and must be add explicit ## [ed25519-java](https://github.com/str4d/ed25519-java) -Required for supporting [ssh-ed25519](https://tools.ietf.org/html/draft-bjh21-ssh-ed25519-02) keys -and [ed25519-sha-512](https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-02) signatures. **Note:** -the required Maven module(s) are defined as `optional` so must be added as an **explicit** dependency in -order to be included in the classpath: +Can optionally be provided to support [ssh-ed25519](https://tools.ietf.org/html/draft-bjh21-ssh-ed25519-02) keys +and [ed25519-sha-512](https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-02) signatures where [Bouncy Castle](#bouncy-castle) is not suitable. **Note:** +use of this dependency is not recommended, but it can be added as an **explicit** dependency in +order to provide Ed25519 support as follows: ```xml @@ -108,5 +108,3 @@ order to be included in the classpath: ``` - -The code contains support for reading _ed25519_ [OpenSSH formatted private keys](https://issues.apache.org/jira/browse/SSHD-703). diff --git a/docs/files-parsing.md b/docs/files-parsing.md index c8c8368e4..a26b6eea1 100644 --- a/docs/files-parsing.md +++ b/docs/files-parsing.md @@ -18,8 +18,8 @@ and `HostConfigEntry#readHostConfigEntries`. ### PEM/OpenSSH The common code contains built-in support for parsing PEM and/or _OpenSSH_ formatted key files and using them for authentication purposes. -As mentioned previously, it can leverage _Bouncycastle_ if available, but can do most of the work without it as well. For _ed25519_ support, -one must provide the _eddsa_ artifact dependency. +As mentioned previously, it can leverage _Bouncy Castle_ if available, but can do most of the work without it as well. For _ed25519_ support, +one must provide either `net.i2p.crypto.eddsa` or _Bouncy Castle_ as a dependency; if both are present `net.i2p.crypto.eddsa` is used. ### [PUTTY](https://www.putty.org/) diff --git a/docs/standards.md b/docs/standards.md index 58314f57e..34cf56a56 100644 --- a/docs/standards.md +++ b/docs/standards.md @@ -110,7 +110,8 @@ mlkem1024nistp384-sha384. ### Signatures/Keys * ssh-dss, ssh-rsa, rsa-sha2-256, rsa-sha2-512, nistp256, nistp384, nistp521 -, ssh-ed25519 (requires `eddsa` optional module), sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com +, ssh-ed25519 (requires Bouncy Castle or `net.i2p.crypto.eddsa` as an optional dependency - if both are present, `net.i2p.crypto.eddsa` is used) +, sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com , ssh-rsa-cert-v01@openssh.com, ssh-dss-cert-v01@openssh.com, ssh-ed25519-cert-v01@openssh.com , ecdsa-sha2-nistp256-cert-v01@openssh.com, ecdsa-sha2-nistp384-cert-v01@openssh.com , ecdsa-sha2-nistp521-cert-v01@openssh.com