You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tw...@apache.org on 2025/09/21 10:52:15 UTC

(mina-sshd) 13/19: Also enable X25519 and X448 in SunECSecurityProviderRegistrar

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 0598b834a78d3f800e8227548a0e001dfb883d49
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Fri Sep 19 21:37:34 2025 +0200

    Also enable X25519 and X448 in SunECSecurityProviderRegistrar
    
    These algorithms are available since Java 11. If present, use them from
    SunEC even if Bouncy Castle is also present.
    
    Also enable it by default.
    
    Fix the two ed25519 PublicKeyEntryDecoders to not call
    KeyPairGenerator.initialize(int): the SunEC edDSA key pair generator
    throws an exception if called with value 256; it accepts 255 and 448.[1]
    
    [1] https://github.com/openjdk/jdk/blob/b03b6f54c5/src/java.base/share/classes/sun/security/ec/ed/EdDSAParameters.java#L275
---
 sshd-common/pom.xml                                |  9 ++-
 .../security/SunECSecurityProviderRegistrar.java   | 79 +++++++++++++++++-----
 .../eddsa/generic/Ed25519PublicKeyDecoder.java     |  6 ++
 .../OpenSSHEd25519PrivateKeyEntryDecoder.java      |  6 ++
 sshd-core/pom.xml                                  | 46 ++++++++-----
 sshd-test/pom.xml                                  | 57 +++++++++-------
 6 files changed, 146 insertions(+), 57 deletions(-)

diff --git a/sshd-common/pom.xml b/sshd-common/pom.xml
index 5d0fef547..b16433e1b 100644
--- a/sshd-common/pom.xml
+++ b/sshd-common/pom.xml
@@ -124,17 +124,21 @@
                         <configuration>
                             <redirectTestOutputToFile>true</redirectTestOutputToFile>
                             <reportsDirectory>${project.build.directory}/surefire-reports-common</reportsDirectory>
+                            <systemPropertyVariables>
+                                <org.apache.sshd.security.provider.SunECWrapper.enabled>false</org.apache.sshd.security.provider.SunECWrapper.enabled>
+                            </systemPropertyVariables>
                         </configuration>
                     </execution>
                     <execution>
-                        <id>no-net-i2p</id>
+                        <id>bc</id>
                         <goals>
                             <goal>test</goal>
                         </goals>
                         <configuration>
                             <redirectTestOutputToFile>true</redirectTestOutputToFile>
-                            <reportsDirectory>${project.build.directory}/surefire-reports-no-net-i2p</reportsDirectory>
+                            <reportsDirectory>${project.build.directory}/surefire-reports-bc</reportsDirectory>
                             <systemPropertyVariables>
+                                <org.apache.sshd.security.provider.SunECWrapper.enabled>false</org.apache.sshd.security.provider.SunECWrapper.enabled>
                                 <org.apache.sshd.security.provider.EdDSA.enabled>false</org.apache.sshd.security.provider.EdDSA.enabled>
                             </systemPropertyVariables>
                         </configuration>
@@ -200,4 +204,5 @@
             </plugin>
         </plugins>
     </build>
+
 </project>
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunECSecurityProviderRegistrar.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunECSecurityProviderRegistrar.java
index f0fea7c32..702c29db7 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunECSecurityProviderRegistrar.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunECSecurityProviderRegistrar.java
@@ -37,37 +37,84 @@ import org.apache.sshd.common.util.GenericUtils;
  * be set correctly. Mixing SunEC keys with Bouncy Castle signatures won't work.
  * </p>
  * <p>
- * This registrar is <em>disabled</em> by default. It can be enabled via a system property
- * {@code org.apache.sshd.security.provider.SunECWrapper.enabled=true}.
+ * This registrar is <em>enabled</em> by default. It can be disabled via a system property
+ * {@code org.apache.sshd.security.provider.SunECWrapper.enabled=false}.
  * </p>
  * <p>
- * The registrar can be configured as usual. By default it has only the "Ed25519" {@code KeyFactory},
- * {@code KeyPairGenerator}, and {@code Signature} enabled; everything else is disabled.
+ * The registrar can be configured as usual. By default are enabled:
+ * </p>
+ * <dl>
+ * <dt>"Ed25519"</dt>
+ * <dd>{@code KeyFactory}, {@code KeyPairGenerator}, and {@code Signature}, if "Ed25519" is supported by SunEC</dd>
+ * <dt>"X25519"</dt>
+ * <dd>{@code KeyAgreement}, {@code KeyFactory}, and {@code KeyPairGenerator}, if "X25519" is supported by SunEC</dd>
+ * <dt>"X448"</dt>
+ * <dd>{@code KeyAgreement}, {@code KeyFactory}, and {@code KeyPairGenerator}, if "X448" is supported by SunEC</dd>
+ * </dl>
+ * <p>
+ * Everything else is disabled.
  * </p>
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class SunECSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
 
+    private static final String X25519 = "X25519";
+
+    private static final String X448 = "X448";
+
     private final Map<String, String> defaultProperties = new HashMap<>();
 
     public SunECSecurityProviderRegistrar() {
         super("SunECWrapper");
-        String baseName = getBasePropertyName();
-        defaultProperties.put(baseName + ".enabled", "false");
         if (isSupported()) {
-            boolean haveEd25519;
-            try {
-                KeyFactory factory = KeyFactory.getInstance(SecurityUtils.ED25519, getSecurityProvider());
-                haveEd25519 = factory != null;
-            } catch (NoSuchAlgorithmException e) {
-                haveEd25519 = false;
-            }
+            Provider provider = getSecurityProvider();
+
+            boolean haveEd25519 = have(SecurityUtils.ED25519, provider);
+            boolean haveX25519 = have("X25519", provider);
+            boolean haveX448 = have("X448", provider);
+
+            String keyAgreement = null;
+            String generator = null;
+            String factory = null;
+            String signature = null;
             if (haveEd25519) {
-                defaultProperties.put(baseName + ".KeyPairGenerator", "Ed25519");
-                defaultProperties.put(baseName + ".KeyFactory", "Ed25519");
-                defaultProperties.put(baseName + ".Signature", "Ed25519");
+                generator = SecurityUtils.ED25519;
+                factory = SecurityUtils.ED25519;
+                signature = SecurityUtils.ED25519;
+            }
+            if (haveX25519) {
+                keyAgreement = X25519;
+                generator = generator == null ? X25519 : (generator + ',' + X25519);
+                factory = factory == null ? X25519 : (factory + ',' + X25519);
+            }
+            if (haveX448) {
+                keyAgreement = keyAgreement == null ? X448 : (keyAgreement + ',' + X448);
+                generator = generator == null ? X448 : (generator + ',' + X448);
+                factory = factory == null ? X448 : (factory + ',' + X448);
             }
+            String baseName = getBasePropertyName();
+            if (keyAgreement != null) {
+                defaultProperties.put(baseName + ".KeyAgreement", keyAgreement);
+            }
+            if (generator != null) {
+                defaultProperties.put(baseName + ".KeyPairGenerator", generator);
+            }
+            if (factory != null) {
+                defaultProperties.put(baseName + ".KeyFactory", factory);
+            }
+            if (signature != null) {
+                defaultProperties.put(baseName + ".Signature", signature);
+            }
+        }
+    }
+
+    private static boolean have(String algorithm, Provider provider) {
+        try {
+            KeyFactory factory = KeyFactory.getInstance(algorithm, provider);
+            return factory != null;
+        } catch (NoSuchAlgorithmException e) {
+            return false;
         }
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java
index 712defeac..e5f1941a8 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/Ed25519PublicKeyDecoder.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
+import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PublicKey;
 import java.util.Collections;
@@ -55,6 +56,11 @@ public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder
         return SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
     }
 
+    @Override
+    public KeyPair generateKeyPair(int keySize) throws GeneralSecurityException {
+        return getKeyPairGenerator().generateKeyPair();
+    }
+
     @Override
     public String encodePublicKey(OutputStream s, PublicKey key) throws IOException {
         Objects.requireNonNull(key, "No public key provided");
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java
index aafd8a717..3634ab053 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/OpenSSHEd25519PrivateKeyEntryDecoder.java
@@ -24,6 +24,7 @@ import java.io.InputStream;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
+import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -157,6 +158,11 @@ public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntr
         return SecurityUtils.getKeyPairGenerator(SecurityUtils.ED25519);
     }
 
+    @Override
+    public KeyPair generateKeyPair(int keySize) throws GeneralSecurityException {
+        return getKeyPairGenerator().generateKeyPair();
+    }
+
     @Override
     public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
         return SecurityUtils.getKeyFactory(SecurityUtils.ED25519);
diff --git a/sshd-core/pom.xml b/sshd-core/pom.xml
index c315235cf..3f3dfa97f 100644
--- a/sshd-core/pom.xml
+++ b/sshd-core/pom.xml
@@ -189,28 +189,42 @@
                         <configuration>
                             <redirectTestOutputToFile>true</redirectTestOutputToFile>
                             <reportsDirectory>${project.build.directory}/surefire-reports-nio2</reportsDirectory>
+                            <systemPropertyVariables>
+                                <org.apache.sshd.security.provider.SunECWrapper.enabled>false</org.apache.sshd.security.provider.SunECWrapper.enabled>
+                            </systemPropertyVariables>
                         </configuration>
                     </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <configuration>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <reportsDirectory>${project.build.directory}/surefire-reports-jce</reportsDirectory>
+                    <systemPropertyVariables>
+                        <!-- Enable using deprecated ssh-rsa signature keys with JSch 0.2.x -->
+                        <jsch.server_host_key>ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256,ssh-rsa</jsch.server_host_key>
+                        <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>
+                    <excludes>
+                        <!-- These tests fail inexplicably without Bouncycastle -->
+                        <exclude>**/*LoadTest.java</exclude>
+                        <exclude>**/SinglePublicKeyAuthTest.java</exclude>
+                        <exclude>**/ClientTest.java</exclude>
+                    </excludes>
+                </configuration>
+                <executions>
                     <execution>
                         <id>jce</id>
                         <goals>
-                            <goal>test</goal>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
                         </goals>
-                        <configuration>
-                            <redirectTestOutputToFile>true</redirectTestOutputToFile>
-                            <reportsDirectory>${project.build.directory}/surefire-reports-jce</reportsDirectory>
-                            <systemPropertyVariables>
-                                <!-- Enable using deprecated ssh-rsa signature keys with JSch 0.2.x -->
-                                <jsch.server_host_key>ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256,ssh-rsa</jsch.server_host_key>
-                                <org.apache.sshd.security.provider.BC.enabled>false</org.apache.sshd.security.provider.BC.enabled>
-                            </systemPropertyVariables>
-                            <excludes>
-                                <!-- These tests fail inexplicably without Bouncycastle -->
-                                <exclude>**/*LoadTest.java</exclude>
-                                <exclude>**/SinglePublicKeyAuthTest.java</exclude>
-                                <exclude>**/ClientTest.java</exclude>
-                            </excludes>
-                        </configuration>
                     </execution>
                 </executions>
             </plugin>
diff --git a/sshd-test/pom.xml b/sshd-test/pom.xml
index 24ae7e6fb..3ac83a5e1 100644
--- a/sshd-test/pom.xml
+++ b/sshd-test/pom.xml
@@ -132,33 +132,44 @@
                     </systemPropertyVariables>
                 </configuration>
             </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-failsafe-plugin</artifactId>
-                <configuration>
-                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
-                    <reportsDirectory>${project.build.directory}/surefire-reports-nio2</reportsDirectory>
-                    <systemPropertyVariables>
-                        <org.apache.sshd.security.provider.SunECWrapper.enabled>true</org.apache.sshd.security.provider.SunECWrapper.enabled>
-                        <org.apache.sshd.common.io.IoServiceFactoryFactory>org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory</org.apache.sshd.common.io.IoServiceFactoryFactory>
-                    </systemPropertyVariables>
-                    <includes>
-                        <include>**/*Test.java</include>
-                    </includes>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>integration-test</goal>
-                            <goal>verify</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
         </plugins>
     </build>
 
     <profiles>
+        <profile>
+            <id>multirelease</id>
+            <activation>
+                <jdk>[11,)</jdk>
+            </activation>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <configuration>
+                            <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                            <reportsDirectory>${project.build.directory}/surefire-reports-mr</reportsDirectory>
+                            <systemPropertyVariables>
+                                <org.apache.sshd.common.io.IoServiceFactoryFactory>org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory</org.apache.sshd.common.io.IoServiceFactoryFactory>
+                            </systemPropertyVariables>
+                            <includes>
+                                <include>**/*Test.java</include>
+                            </includes>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
         <profile>
             <id>test-mina</id>
             <activation>