This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch dev_3.0
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit a8439661b0d5ee8e965f650546725065ea6442fd
Author: Thomas Wolf <[email protected]>
AuthorDate: Sat Sep 13 20:34:32 2025 +0200

    GH-585: Enable using java's built-in ed25519
    
    Add a key type detector for ed25519 keys. Different libraries return
    different algorithms from Key.getAlgorithm():
    
    * Bouncy Castle returns algorithm "Ed25519".
    * net.i2p returns algorithm "EdDSA", and we know it only does ed25519.
    * JDK returns algorithm "EdDSA", and we have to get the name of the
      NamedParameterSpec of the key to check whether it is indeed ed25519.
    
    Fix some tests, and make the Java15+ JcePublicKeyFactory also work
    (theoretically) for ed448.
    
    Use maven-failsafe-plugin instead of maven-surefire-plugin to test the
    MR JAR. We need to run the tests from the JAR, not from target/classes.
---
 sshd-common/pom.xml                                |  8 +++-
 .../sshd/common/config/keys/BuiltinIdentities.java |  3 ++
 .../apache/sshd/common/config/keys/KeyUtils.java   |  6 +--
 .../sshd/common/util/security/SecurityUtils.java   | 24 +++++-----
 .../eddsa/generic/EdDSAKeyTypeDetector.java        | 46 +++++++++++++++++++
 .../eddsa/generic/EdDSAKeyTypeDetector.java        | 52 ++++++++++++++++++++++
 .../security/eddsa/jce/JcePublicKeyFactory.java    | 12 ++---
 .../openssh/OpenSSHKeyPairResourceWriterTest.java  | 12 ++---
 .../util/security/eddsa/Ed25519VectorsTest.java    |  6 +--
 .../sshd/util/test/CommonTestSupportUtils.java     |  2 +-
 10 files changed, 135 insertions(+), 36 deletions(-)

diff --git a/sshd-common/pom.xml b/sshd-common/pom.xml
index d27a26add..3582c74f6 100644
--- a/sshd-common/pom.xml
+++ b/sshd-common/pom.xml
@@ -118,13 +118,14 @@
             <build>
                 <plugins>
                     <plugin>
+                        <!-- MR JAR needs to run tests from the built JAR, so 
use failsafe instead of surefire -->
                         <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-surefire-plugin</artifactId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
                         <executions>
                             <execution>
                                 <id>jce</id>
                                 <goals>
-                                    <goal>test</goal>
+                                    <goal>integration-test</goal>
                                 </goals>
                                 <configuration>
                                     
<redirectTestOutputToFile>true</redirectTestOutputToFile>
@@ -133,6 +134,9 @@
                                         
<org.apache.sshd.security.provider.EdDSA.enabled>false</org.apache.sshd.security.provider.EdDSA.enabled>
                                         
<org.apache.sshd.security.provider.BC.enabled>false</org.apache.sshd.security.provider.BC.enabled>
                                     </systemPropertyVariables>
+                                    <includes>
+                                        <include>**/*Test.java</include>
+                                    </includes>
                                 </configuration>
                             </execution>
                         </executions>
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
index f4afc957f..a43b9737b 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java
@@ -158,6 +158,9 @@ public enum BuiltinIdentities implements Identity {
     }
 
     private static BuiltinIdentities fromKeyType(String type) {
+        if (type == null) {
+            return null;
+        }
         return VALUES.stream().filter(b -> 
b.getSupportedKeyTypes().contains(type)).findFirst().orElse(null);
     }
 
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 3796e9f4f..b05ec3b8d 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
@@ -85,6 +85,7 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import 
org.apache.sshd.common.util.security.eddsa.generic.Ed25519PublicKeyDecoder;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAKeyTypeDetector;
 
 /**
  * Utility class for keys
@@ -856,10 +857,7 @@ public final class KeyUtils {
     }
 
     private static boolean isEd25519(Key k) {
-        return k != null
-                && (SecurityUtils.ED25519.equals(k.getAlgorithm()) //
-                        || 
(SecurityUtils.EDDSA.equalsIgnoreCase(k.getAlgorithm())
-                                && 
k.getClass().getCanonicalName().startsWith("net.i2p.")));
+        return EdDSAKeyTypeDetector.isEd25519(k);
     }
 
     public static boolean compareKeys(PublicKey k1, PublicKey k2) {
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 e9eff3a6f..2bf9881a3 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
@@ -94,12 +94,14 @@ public final class SecurityUtils {
     public static final String BOUNCY_CASTLE = "BC";
 
     /**
-     * EDDSA (net.i2p) {@link SecurityProviderRegistrar} name.
+     * EDDSA (net.i2p) {@link SecurityProviderRegistrar} name. Also the 
algorithm name that
+     * {@link java.security.Key#getAlgorithm()} returns for ed25519 keys 
generated by net.i2p or by SunEC.
      */
     public static final String EDDSA = "EdDSA";
 
     /**
-     * Key and signature algorithm name for ed25519.
+     * Key and signature algorithm name for ed25519. A Bouncy Castle Ed25519 
key returns this as
+     * {@link java.security.Key#getAlgorithm()}.
      */
     public static final String ED25519 = "Ed25519";
 
@@ -560,19 +562,13 @@ public final class SecurityUtils {
         Boolean isSupported = EDDSA_SUPPORT_PRESENT.get();
         if (isSupported == null) {
             register();
-            // Currently we can only support ed25519 through libraries and 
thus we require a registrar.
-            // TODO: GH-585 (needs a multi-release JAR)
-            boolean supported = false;
-            synchronized (REGISTERED_PROVIDERS) {
-                for (SecurityProviderRegistrar r : 
REGISTERED_PROVIDERS.values()) {
-                    supported = r.isEnabled() && r.isSupported() && 
r.isKeyFactorySupported(ED25519);
-                    if (supported) {
-                        break;
-                    }
-                }
+            try {
+                KeyFactory factory = getKeyFactory(ED25519);
+                isSupported = Boolean.valueOf(factory != null);
+            } catch (GeneralSecurityException e) {
+                isSupported = Boolean.FALSE;
             }
-            isSupported = Boolean.valueOf(supported);
-            if (!EDDSA_SUPPORT_PRESENT.compareAndSet(null, 
Boolean.valueOf(supported))) {
+            if (!EDDSA_SUPPORT_PRESENT.compareAndSet(null, isSupported)) {
                 isSupported = EDDSA_SUPPORT_PRESENT.get();
             }
         }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAKeyTypeDetector.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAKeyTypeDetector.java
new file mode 100644
index 000000000..f35d1a43b
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAKeyTypeDetector.java
@@ -0,0 +1,46 @@
+/*
+ * 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.security.Key;
+
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+public final class EdDSAKeyTypeDetector {
+
+    private EdDSAKeyTypeDetector() {
+        throw new IllegalStateException("No instantiation");
+    }
+
+    public static boolean isEd25519(Key key) {
+        if (key == null) {
+            return false;
+        }
+        String algorithm = key.getAlgorithm();
+        if (SecurityUtils.ED25519.equals(algorithm)) {
+            return true;
+        }
+        if (SecurityUtils.EDDSA.equalsIgnoreCase(algorithm)) {
+            if (key.getClass().getCanonicalName().startsWith("net.i2p.")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git 
a/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/generic/EdDSAKeyTypeDetector.java
 
b/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/generic/EdDSAKeyTypeDetector.java
new file mode 100644
index 000000000..bf6cc143d
--- /dev/null
+++ 
b/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/generic/EdDSAKeyTypeDetector.java
@@ -0,0 +1,52 @@
+/*
+ * 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.security.Key;
+import java.security.interfaces.EdECKey;
+import java.security.spec.NamedParameterSpec;
+
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+public final class EdDSAKeyTypeDetector {
+
+    private EdDSAKeyTypeDetector() {
+        throw new IllegalStateException("No instantiation");
+    }
+
+    public static boolean isEd25519(Key key) {
+        if (key == null) {
+            return false;
+        }
+        String algorithm = key.getAlgorithm();
+        if (SecurityUtils.ED25519.equals(algorithm)) {
+            return true;
+        }
+        if (SecurityUtils.EDDSA.equalsIgnoreCase(algorithm)) {
+            if (key.getClass().getCanonicalName().startsWith("net.i2p.")) {
+                return true;
+            }
+            if (key instanceof EdECKey) {
+                NamedParameterSpec params = ((EdECKey) key).getParams();
+                return SecurityUtils.ED25519.equals(params.getName());
+            }
+        }
+        return false;
+    }
+}
diff --git 
a/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/jce/JcePublicKeyFactory.java
 
b/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/jce/JcePublicKeyFactory.java
index 4b637b1e8..06fb1b9f5 100644
--- 
a/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/jce/JcePublicKeyFactory.java
+++ 
b/sshd-common/src/main/java15/org/apache/sshd/common/util/security/eddsa/jce/JcePublicKeyFactory.java
@@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
+import java.security.interfaces.EdECKey;
 import java.security.spec.NamedParameterSpec;
 
 import org.apache.sshd.common.util.security.PublicKeyFactory;
@@ -39,13 +40,14 @@ public class JcePublicKeyFactory implements 
PublicKeyFactory {
 
     @Override
     public PublicKey getPublicKey(PrivateKey key) {
-        if (SecurityUtils.ED25519.equals(key.getAlgorithm())) {
-            return recoverEd25519PublicKey(key);
+        if (SecurityUtils.EDDSA.equals(key.getAlgorithm()) && (key instanceof 
EdECKey)) {
+            NamedParameterSpec params = ((EdECKey) key).getParams();
+            return recoverEd25519PublicKey(key, params);
         }
         return null;
     }
 
-    private static PublicKey recoverEd25519PublicKey(PrivateKey key) {
+    private static PublicKey recoverEd25519PublicKey(PrivateKey key, 
NamedParameterSpec params) {
         byte[] rawPrivateKey = EdDSAUtils.getBytes(key);
         // An EdDSA private key is just cryptographically secure random data, 
which the JDK implementation obtains from
         // the given SecureRandom. By making that random generator return not 
new random bytes but the bytes of the
@@ -62,8 +64,8 @@ public class JcePublicKeyFactory implements PublicKeyFactory {
         //
         // Note that NamedParameterSpec was introduced in Java 11, and the 
ED25519 constant in Java 15.
         try {
-            KeyPairGenerator gen = KeyPairGenerator.getInstance("Ed25519");
-            gen.initialize(NamedParameterSpec.ED25519, new SecureRandom() {
+            KeyPairGenerator gen = 
KeyPairGenerator.getInstance(params.getName());
+            gen.initialize(params, new SecureRandom() {
 
                 private static final long serialVersionUID = 1L;
 
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
index 514f09f51..a123fec71 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/writer/openssh/OpenSSHKeyPairResourceWriterTest.java
@@ -58,12 +58,12 @@ class OpenSSHKeyPairResourceWriterTest extends 
JUnitTestSupport {
 
     static Collection<TestData> parameters() {
         List<TestData> result = new ArrayList<>();
-        result.add(new TestData("RSA", 1024));
-        result.add(new TestData("RSA", 2048));
-        result.add(new TestData("DSA", 1024));
-        result.add(new TestData("ECDSA", 256));
-        result.add(new TestData("ECDSA", 384));
-        result.add(new TestData("ECDSA", 521));
+        result.add(new TestData(KeyUtils.RSA_ALGORITHM, 1024));
+        result.add(new TestData(KeyUtils.RSA_ALGORITHM, 2048));
+        result.add(new TestData(KeyUtils.DSS_ALGORITHM, 1024));
+        result.add(new TestData(KeyUtils.EC_ALGORITHM, 256));
+        result.add(new TestData(KeyUtils.EC_ALGORITHM, 384));
+        result.add(new TestData(KeyUtils.EC_ALGORITHM, 521));
         if (SecurityUtils.isEDDSACurveSupported()) {
             result.add(new TestData(SecurityUtils.ED25519, -1));
         }
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 29ad12af8..afe1ff87e 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
@@ -77,10 +77,7 @@ class Ed25519VectorsTest extends JUnitTestSupport {
         if (registrar == null) {
             registrar = 
SecurityUtils.getRegisteredProvider(SecurityUtils.BOUNCY_CASTLE);
         }
-        if (registrar == null) {
-            throw new IllegalStateException("Neither net.i2p nor BC 
registered");
-        }
-        String supportClassName = registrar.getClass().getSimpleName();
+        String supportClassName = registrar != null ? 
registrar.getClass().getSimpleName() : "JCE";
         parameters.add(new Object[] {
                 supportClassName + " TEST1 - empty message",
                 
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
@@ -243,6 +240,7 @@ class Ed25519VectorsTest extends JUnitTestSupport {
             String name, String prvKey, String pubKey, String msg, String 
signature) throws Exception {
         initEd25519VectorsTest(name, prvKey, pubKey, msg, signature);
         PublicKey recoveredKey = 
SecurityUtils.recoverEDDSAPublicKey(privateKey);
+        assertNotNull(recoveredKey, "Public key could not be recovered");
         assertTrue(SecurityUtils.compareEDDSAPPublicKeys(publicKey, 
recoveredKey), "Recovered key is not equal");
         byte[] recoveredBytes = EdDSAUtils.getBytes(recoveredKey);
         assertArrayEquals(pubBytes, recoveredBytes, "Mismatched public seed 
value");
diff --git 
a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
 
b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
index 32a209157..45ad951da 100644
--- 
a/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
+++ 
b/sshd-common/src/test/java/org/apache/sshd/util/test/CommonTestSupportUtils.java
@@ -422,7 +422,7 @@ public final class CommonTestSupportUtils {
                 throw new InvalidKeySpecException("Unknown curve for key 
size=" + keySize);
             }
             gen.initialize(curve.getParameters());
-        } else {
+        } else if (!SecurityUtils.ED25519.equals(algorithm)) {
             gen.initialize(keySize);
         }
 

Reply via email to