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 a63c2cbbb0fcc3024a99c58a955b972d2ab8a183
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Sep 25 21:46:17 2025 +0200

    GH-803: use JDK built-in ML-KEM on Java24+
    
    Add KEMs to SecurityUtils and SecurityEntityFactories. Configure the
    "SunJCEWrapper" SecurityProviderRegistrar to include support for ML-KEM
    if it is available in Java's SunJCE security provider.
    
    Rename interface KeyEncapsulationMethod to KEM: this serves now as a
    wrapper around different KEM implementations from Bouncy Castle or from
    the JDK. Because the class name is user-visible in the security provider
    registrar configuration, using KEM minimizes confusions.
    
    Move the Bouncy Castle implementations from sshd-core to sshd-common
    where the rest of BC-specific stuff lives. Add a JceKEM factory to
    create KEM instances if supported by the platform. This default factory
    always throws NoSuchAlgorithmException.
    
    In the multi-release section for Java 24, add a real JceKEM factory that
    returns instances of our own KEM wrapper class that internally use
    javax.crypto.KEM.
    
    Change the Github workflows to use Java 25 for compiling, and run the
    tests on Java 8, 17, and 25. (Previously we tested on 8, 11, and 17.)
    
    The new JDK ML-KEM support is tested in the "multirelease" execution of
    the maven-failsafe-plugin in bundle sshd-test in test OpenSshMlKemTest.
---
 .github/workflows/build.yml                        |   6 +-
 .github/workflows/master-build.yml                 |   2 +-
 .github/workflows/next-build.yml                   |   2 +-
 CHANGES.md                                         |   4 +
 pom.xml                                            |   2 +-
 sshd-common/pom.xml                                |  13 ++
 .../apache/sshd/common/util/security/JceKEM.java   |  37 ++++
 .../org/apache/sshd/common/util/security/KEM.java  |  12 +-
 .../sshd/common/util/security/KEMFactory.java      |  41 ++++
 .../util/security/SecurityEntityFactory.java       |   9 +
 .../util/security/SecurityProviderRegistrar.java   |   9 +
 .../sshd/common/util/security/SecurityUtils.java   |   5 +
 .../security/SunJCESecurityProviderRegistrar.java  |  30 +++
 .../bouncycastle/BouncyCastleAccessor.java         |  18 --
 .../security/bouncycastle/BouncyCastleKEM.java     |  42 ++++
 .../bouncycastle/BouncyCastleKEMAccessor.java      |  51 +++++
 .../BouncyCastleSecurityProviderRegistrar.java     |  33 +++-
 .../common/util/security/bouncycastle}/MLKEM.java  |  57 ++++--
 .../util/security/bouncycastle}/SNTRUP761.java     |  24 ++-
 .../eddsa/EdDSASecurityProviderRegistrar.java      |   6 +-
 .../apache/sshd/common/util/security/JceKEM.java   | 216 +++++++++++++++++++++
 .../java/org/apache/sshd/client/kex/DHGClient.java |   6 +-
 .../org/apache/sshd/common/kex/AbstractDH.java     |   3 +-
 .../apache/sshd/common/kex/BuiltinDHFactories.java |   9 +-
 .../org/apache/sshd/common/kex/BuiltinKEM.java     |  88 +++------
 .../java/org/apache/sshd/server/kex/DHGServer.java |   6 +-
 sshd-test/pom.xml                                  |   6 -
 .../apache/sshd/client/kex/OpenSshMlKemTest.java   |  13 +-
 28 files changed, 614 insertions(+), 136 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 76a938e09..4989264da 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -35,7 +35,7 @@ jobs:
     strategy:
       matrix:
         os: [ ubuntu-latest ]
-        java: [ '17' ]
+        java: [ '25' ]
     steps:
       - uses: actions/checkout@v4
 
@@ -66,7 +66,7 @@ jobs:
     strategy:
       matrix:
         os: [ ubuntu-latest, windows-latest ]
-        java: [ '8', '11', '17' ]
+        java: [ '8', '17', '25' ]
     steps:
       - uses: actions/checkout@v4
 
@@ -76,7 +76,7 @@ jobs:
           distribution: temurin
           java-version: |
             ${{ matrix.java }} 
-            17
+            25
 
       - uses: actions/cache@v4
         with:
diff --git a/.github/workflows/master-build.yml 
b/.github/workflows/master-build.yml
index 97cd03174..d4a43c524 100644
--- a/.github/workflows/master-build.yml
+++ b/.github/workflows/master-build.yml
@@ -52,7 +52,7 @@ jobs:
         uses: actions/setup-java@v4
         with:
           distribution: temurin
-          java-version: '17'
+          java-version: '25'
           # Create a ~/.m2/settings.xml referencing these environment variable 
names
           server-id: 'apache.snapshots.https'
           server-username: NEXUS_USERNAME
diff --git a/.github/workflows/next-build.yml b/.github/workflows/next-build.yml
index 12d2d94e0..9a0272cf3 100644
--- a/.github/workflows/next-build.yml
+++ b/.github/workflows/next-build.yml
@@ -52,7 +52,7 @@ jobs:
         uses: actions/setup-java@v4
         with:
           distribution: temurin
-          java-version: '17'
+          java-version: '25'
           # Create a ~/.m2/settings.xml referencing these environment variable 
names
           server-id: 'apache.snapshots.https'
           server-username: NEXUS_USERNAME
diff --git a/CHANGES.md b/CHANGES.md
index 8a84b3012..7c4231877 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -66,3 +66,7 @@ Complete refactoring of the SSH transport protocol. New 
feature: support for cli
   the version used in SSH. Since the Poly1305 MAC in Java is not accessible 
separately, Apache MINA SSHD still
   has to use its own implementation for that part.)
 
+* [GH-803](https://github.com/apache/mina-sshd/issues/803) Support the JDK 
built-in ML-KEMs on Java24+
+
+  Use the ML-KEM implementations from SunJCE if run on Java >= 24. For Java < 
24, The Bouncy Castle implementations
+  are used. The SunJCE ML-KEMs are advertised in the 
`SunJCESecurityProviderRegistrar`.
diff --git a/pom.xml b/pom.xml
index 64cf9700c..8985b14fb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,7 +72,7 @@
 
     <properties>
         <japicmp-sshd-last-release>2.16.0</japicmp-sshd-last-release>
-        <minimalJavaBuildVersion>17</minimalJavaBuildVersion>
+        <minimalJavaBuildVersion>24</minimalJavaBuildVersion>
         <surefireJdk>[${minimalJavaBuildVersion},)</surefireJdk>
         <minimalMavenBuildVersion>3.9.8</minimalMavenBuildVersion>
 
diff --git a/sshd-common/pom.xml b/sshd-common/pom.xml
index b16433e1b..f66fa54be 100644
--- a/sshd-common/pom.xml
+++ b/sshd-common/pom.xml
@@ -200,6 +200,19 @@
                             <release>15</release>
                         </configuration>
                     </execution>
+                    <execution>
+                        <id>compile-24</id>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                        <configuration>
+                            <compileSourceRoots>
+                                
<compileSourceRoot>${project.basedir}/src/main/java24</compileSourceRoot>
+                            </compileSourceRoots>
+                            <multiReleaseOutput>true</multiReleaseOutput>
+                            <release>24</release>
+                        </configuration>
+                    </execution>
                 </executions>
             </plugin>
         </plugins>
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/JceKEM.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/JceKEM.java
new file mode 100644
index 000000000..d1a9afbc2
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/JceKEM.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+
+enum JceKEM implements KEMFactory {
+
+    INSTANCE;
+
+    @Override
+    public KEM get(String algorithm, Provider provider) throws 
NoSuchAlgorithmException {
+        throw new NoSuchAlgorithmException();
+    }
+
+    @Override
+    public boolean isSupported(String algorithm) {
+        return false;
+    }
+}
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/kex/KeyEncapsulationMethod.java
 b/sshd-common/src/main/java/org/apache/sshd/common/util/security/KEM.java
similarity index 92%
rename from 
sshd-core/src/main/java/org/apache/sshd/common/kex/KeyEncapsulationMethod.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/security/KEM.java
index d03cfb4c9..10f176655 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/kex/KeyEncapsulationMethod.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/KEM.java
@@ -16,12 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.kex;
+package org.apache.sshd.common.util.security;
+
+import org.apache.sshd.common.OptionalFeature;
 
 /**
  * General interface for key encapsulation methods (KEM).
  */
-public interface KeyEncapsulationMethod {
+public interface KEM extends OptionalFeature {
+
+    String ML_KEM_768 = "ML-KEM-768";
+
+    String ML_KEM_1024 = "ML-KEM-1024";
+
+    String SNTRUP_761 = "SNTRUP-761";
 
     /**
      * Client-side KEM operations.
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/KEMFactory.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/KEMFactory.java
new file mode 100644
index 000000000..249ffd7d7
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/KEMFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+
+public interface KEMFactory {
+
+    default KEM get(String algorithm) throws NoSuchAlgorithmException {
+        return get(algorithm, null);
+    }
+
+    KEM get(String algorithm, Provider provider) throws 
NoSuchAlgorithmException;
+
+    default boolean isSupported(String algorithm) {
+        try {
+            KEM kem = get(algorithm);
+            return kem.isSupported();
+        } catch (Throwable t) {
+            return false;
+        }
+    }
+
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
index eab3f0cec..569abb345 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityEntityFactory.java
@@ -76,6 +76,10 @@ public interface SecurityEntityFactory {
         throw new NoSuchAlgorithmException("Algorithm '" + algorithm + "' not 
supported (default)");
     }
 
+    default KEM createKEM(String algorithm) throws GeneralSecurityException {
+        throw new NoSuchAlgorithmException("Algorithm '" + algorithm + "' not 
supported (default)");
+    }
+
     class Named implements SecurityEntityFactory {
 
         private final String name;
@@ -232,5 +236,10 @@ public interface SecurityEntityFactory {
         public SecureRandom createSecureRandom(String algorithm) throws 
GeneralSecurityException {
             return SecureRandom.getInstance(algorithm);
         }
+
+        @Override
+        public KEM createKEM(String algorithm) throws GeneralSecurityException 
{
+            return JceKEM.INSTANCE.get(algorithm);
+        }
     }
 }
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 4df4f8022..3a4e0f419 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
@@ -190,6 +190,15 @@ public interface SecurityProviderRegistrar extends 
SecurityProviderChoice, Optio
         return isSecurityEntitySupported(CertificateFactory.class, type);
     }
 
+    /**
+     * @param  algorithm The {@link KEM} algorithm
+     * @return           {@code true} if this security provider supports the 
algorithm
+     * @see              #isSecurityEntitySupported(Class, String)
+     */
+    default boolean isKEMSupported(String algorithm) {
+        return isSecurityEntitySupported(KEM.class, algorithm);
+    }
+
     @Override
     default PublicKey getPublicKey(PrivateKey key) {
         return null;
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 dcb93c20c..a2bbe89c2 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
@@ -760,4 +760,9 @@ public final class SecurityUtils {
                 = resolveSecurityEntityFactory(CertificateFactory.class, type, 
r -> r.isCertificateFactorySupported(type));
         return factory.createCertificateFactory(type);
     }
+
+    public static KEM getKEM(String algorithm) throws GeneralSecurityException 
{
+        SecurityEntityFactory factory = 
resolveSecurityEntityFactory(KEM.class, algorithm, r -> 
r.isKEMSupported(algorithm));
+        return factory.createKEM(algorithm);
+    }
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunJCESecurityProviderRegistrar.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunJCESecurityProviderRegistrar.java
index b17c2df40..91d236bfd 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunJCESecurityProviderRegistrar.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SunJCESecurityProviderRegistrar.java
@@ -18,6 +18,9 @@
  */
 package org.apache.sshd.common.util.security;
 
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
 import java.security.Security;
 import java.util.HashMap;
@@ -57,6 +60,23 @@ public class SunJCESecurityProviderRegistrar extends 
AbstractSecurityProviderReg
         String baseName = getBasePropertyName();
         defaultProperties.put(baseName + ".Cipher", "AES");
         defaultProperties.put(baseName + ".Mac", 
"HmacSha1,HmacSha224,HmacSha256,HmacSha384,HmacSha512");
+        if (isSupported()) {
+            if (haveKEM(getSecurityProvider())) {
+                String kems = KEM.ML_KEM_1024 + ',' + KEM.ML_KEM_768;
+                defaultProperties.put(baseName + ".KEM", kems);
+                defaultProperties.put(baseName + ".KeyPairGenerator", kems);
+                defaultProperties.put(baseName + ".KeyFactory", kems);
+            }
+        }
+    }
+
+    private static boolean haveKEM(Provider provider) {
+        try {
+            KeyFactory factory = KeyFactory.getInstance(KEM.ML_KEM_768, 
provider);
+            return factory != null;
+        } catch (NoSuchAlgorithmException e) {
+            return false;
+        }
     }
 
     @Override
@@ -104,4 +124,14 @@ public class SunJCESecurityProviderRegistrar extends 
AbstractSecurityProviderReg
         return getSecurityProvider() != null;
     }
 
+    @Override
+    public SecurityEntityFactory getFactory() {
+        return new SecurityEntityFactory.ByProvider(getSecurityProvider()) {
+
+            @Override
+            public KEM createKEM(String algorithm) throws 
GeneralSecurityException {
+                return JceKEM.INSTANCE.get(algorithm);
+            }
+        };
+    }
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleAccessor.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleAccessor.java
index e7d333049..e36ceca9e 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleAccessor.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleAccessor.java
@@ -21,7 +21,6 @@ package org.apache.sshd.common.util.security.bouncycastle;
 import java.security.Provider;
 
 import org.apache.sshd.common.util.ReflectionUtils;
-import org.bouncycastle.jcajce.interfaces.EdDSAKey;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 final class BouncyCastleAccessor {
@@ -48,14 +47,6 @@ final class BouncyCastleAccessor {
         }
     }
 
-    public boolean isSupported() {
-        try {
-            return Inner.isSupported();
-        } catch (Throwable t) {
-            return false;
-        }
-    }
-
     private static final class Inner {
 
         private Inner() {
@@ -91,15 +82,6 @@ final class BouncyCastleAccessor {
             }
             return null;
         }
-
-        static boolean isSupported() {
-            try {
-                // Just something that forces class loading.
-                return EdDSAKey.class != null;
-            } catch (Throwable t) {
-                return false;
-            }
-        }
     }
 
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEM.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEM.java
new file mode 100644
index 000000000..bb83d4e14
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEM.java
@@ -0,0 +1,42 @@
+/*
+ * 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.bouncycastle;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+
+import org.apache.sshd.common.util.security.KEM;
+import org.apache.sshd.common.util.security.KEMFactory;
+
+enum BouncyCastleKEM implements KEMFactory {
+
+    INSTANCE;
+
+    @Override
+    public KEM get(String algorithm, Provider provider) throws 
NoSuchAlgorithmException {
+        if (KEM.ML_KEM_768.equalsIgnoreCase(algorithm)) {
+            return MLKEM.ML_KEM_768;
+        } else if (KEM.ML_KEM_1024.equalsIgnoreCase(algorithm)) {
+            return MLKEM.ML_KEM_1024;
+        } else if (KEM.SNTRUP_761.equalsIgnoreCase(algorithm)) {
+            return SNTRUP761.INSTANCE;
+        }
+        throw new NoSuchAlgorithmException("KEM '" + algorithm + "' unknown");
+    }
+}
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
new file mode 100644
index 000000000..209a23541
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
@@ -0,0 +1,51 @@
+/*
+ * 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.bouncycastle;
+
+import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
+
+final class BouncyCastleKEMAccessor {
+
+    static final BouncyCastleKEMAccessor INSTANCE = new 
BouncyCastleKEMAccessor();
+
+    private BouncyCastleKEMAccessor() {
+        super();
+    }
+
+    public boolean isSupported() {
+        try {
+            return Inner.isSupported();
+        } catch (Throwable t) {
+            return false;
+        }
+    }
+
+    private static final class Inner {
+
+        private Inner() {
+            super();
+        }
+
+        static boolean isSupported() {
+            // Just something that forces class loading.
+            return MLKEMParameters.ml_kem_768 != null;
+        }
+
+    }
+}
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 a6a1bb8d0..82d3aa9e5 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
@@ -18,6 +18,7 @@
  */
 package org.apache.sshd.common.util.security.bouncycastle;
 
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
@@ -30,6 +31,8 @@ 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.KEM;
+import org.apache.sshd.common.util.security.SecurityEntityFactory;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
@@ -105,18 +108,42 @@ public class BouncyCastleSecurityProviderRegistrar 
extends AbstractSecurityProvi
             return false;
         }
 
+        boolean supported = true;
         if (KeyPairGenerator.class.isAssignableFrom(entityType)
                 || KeyFactory.class.isAssignableFrom(entityType)) {
             if (SecurityUtils.ED25519.equalsIgnoreCase(name)) {
-                return isEdDSASupported();
+                supported = isEdDSASupported();
             }
         } else if (Signature.class.isAssignableFrom(entityType)) {
             if (SecurityUtils.ED25519.equalsIgnoreCase(name)) {
-                return isEdDSASupported();
+                supported = isEdDSASupported();
             }
+        } else if (KEM.class.isAssignableFrom(entityType)) {
+            supported = BouncyCastleKEMAccessor.INSTANCE.isSupported();
+            supported = supported && 
BouncyCastleKEM.INSTANCE.isSupported(name);
         }
 
-        return super.isSecurityEntitySupported(entityType, name);
+        return supported && super.isSecurityEntitySupported(entityType, name);
+    }
+
+    @Override
+    public SecurityEntityFactory getFactory() {
+        if (isNamedProviderUsed()) {
+            return new SecurityEntityFactory.Named(getProviderName()) {
+
+                @Override
+                public KEM createKEM(String algorithm) throws 
GeneralSecurityException {
+                    return BouncyCastleKEM.INSTANCE.get(algorithm);
+                }
+            };
+        }
+        return new SecurityEntityFactory.ByProvider(getSecurityProvider()) {
+
+            @Override
+            public KEM createKEM(String algorithm) throws 
GeneralSecurityException {
+                return BouncyCastleKEM.INSTANCE.get(algorithm);
+            }
+        };
     }
 
     @Override
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/MLKEM.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
similarity index 85%
rename from sshd-core/src/main/java/org/apache/sshd/common/kex/MLKEM.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
index 4ef2847f9..3dad32c07 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/MLKEM.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
@@ -16,13 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.kex;
+package org.apache.sshd.common.util.security.bouncycastle;
 
 import java.util.Arrays;
 import java.util.Objects;
 
 import org.apache.sshd.common.OptionalFeature;
 import org.apache.sshd.common.random.JceRandom;
+import org.apache.sshd.common.util.security.KEM;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.SecretWithEncapsulation;
 import org.bouncycastle.pqc.crypto.mlkem.MLKEMExtractor;
@@ -42,7 +43,43 @@ import 
org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
  *
  * @see <a 
href="https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf";>NIST FIPS 
203</a>
  */
-final class MLKEM {
+enum MLKEM implements KEM {
+
+    ML_KEM_768 {
+
+        @Override
+        public Client getClient() {
+            return new Client(Parameters.mlkem768);
+        }
+
+        @Override
+        public Server getServer() {
+            return new Server(Parameters.mlkem768);
+        }
+
+        @Override
+        public boolean isSupported() {
+            return Parameters.mlkem768.isSupported();
+        }
+    },
+
+    ML_KEM_1024 {
+
+        @Override
+        public Client getClient() {
+            return new Client(Parameters.mlkem1024);
+        }
+
+        @Override
+        public Server getServer() {
+            return new Server(Parameters.mlkem1024);
+        }
+
+        @Override
+        public boolean isSupported() {
+            return Parameters.mlkem1024.isSupported();
+        }
+    };
 
     enum Parameters implements OptionalFeature {
         // For key sizes see NIST FIPS 203, section 8, table 3. Bouncy Castle 
does not expose the
@@ -88,19 +125,7 @@ final class MLKEM {
         }
     }
 
-    private MLKEM() {
-        // No instantiation
-    }
-
-    static KeyEncapsulationMethod.Client getClient(Parameters parameters) {
-        return new Client(parameters);
-    }
-
-    static KeyEncapsulationMethod.Server getServer(Parameters parameters) {
-        return new Server(parameters);
-    }
-
-    private static class Client implements KeyEncapsulationMethod.Client {
+    private static class Client implements KEM.Client {
 
         private final Parameters parameters;
 
@@ -140,7 +165,7 @@ final class MLKEM {
         }
     }
 
-    private static class Server implements KeyEncapsulationMethod.Server {
+    private static class Server implements KEM.Server {
 
         private final Parameters parameters;
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/SNTRUP761.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/SNTRUP761.java
similarity index 89%
rename from sshd-core/src/main/java/org/apache/sshd/common/kex/SNTRUP761.java
rename to 
sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/SNTRUP761.java
index 68a20b921..2b05f00cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/SNTRUP761.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/SNTRUP761.java
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.kex;
+package org.apache.sshd.common.util.security.bouncycastle;
 
 import java.util.Arrays;
 
 import org.apache.sshd.common.random.JceRandom;
+import org.apache.sshd.common.util.security.KEM;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.SecretWithEncapsulation;
@@ -35,13 +36,16 @@ import 
org.bouncycastle.pqc.crypto.ntruprime.SNTRUPrimePublicKeyParameters;
 /**
  * A Bouncy Castle implementation of the sntrup761 key encapsulation method 
(KEM).
  */
-final class SNTRUP761 {
+final class SNTRUP761 implements KEM {
+
+    static final SNTRUP761 INSTANCE = new SNTRUP761();
 
     private SNTRUP761() {
         // No instantiation
     }
 
-    static boolean isSupported() {
+    @Override
+    public boolean isSupported() {
         if (SecurityUtils.isFipsMode()) {
             return false;
         }
@@ -52,7 +56,17 @@ final class SNTRUP761 {
         }
     }
 
-    static class Client implements KeyEncapsulationMethod.Client {
+    @Override
+    public Client getClient() {
+        return new Client();
+    }
+
+    @Override
+    public Server getServer() {
+        return new Server();
+    }
+
+    static class Client implements KEM.Client {
 
         private SNTRUPrimeKEMExtractor extractor;
         private SNTRUPrimePublicKeyParameters publicKey;
@@ -89,7 +103,7 @@ final class SNTRUP761 {
         }
     }
 
-    static class Server implements KeyEncapsulationMethod.Server {
+    static class Server implements KEM.Server {
 
         private SecretWithEncapsulation value;
 
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 3c2e1de3d..3ef3853a0 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
@@ -123,7 +123,7 @@ public class EdDSASecurityProviderRegistrar extends 
AbstractSecurityProviderRegi
 
     @Override
     public SecurityEntityFactory getFactory() {
-        return new DelegatingSecurityEntityProvider(super.getFactory());
+        return new DelegatingSecurityEntityFactory(super.getFactory());
     }
 
     @Override
@@ -135,11 +135,11 @@ public class EdDSASecurityProviderRegistrar extends 
AbstractSecurityProviderRegi
         return super.getPublicKey(key);
     }
 
-    private static class DelegatingSecurityEntityProvider implements 
SecurityEntityFactory {
+    private static class DelegatingSecurityEntityFactory implements 
SecurityEntityFactory {
 
         private SecurityEntityFactory delegate;
 
-        DelegatingSecurityEntityProvider(SecurityEntityFactory delegate) {
+        DelegatingSecurityEntityFactory(SecurityEntityFactory delegate) {
             this.delegate = delegate;
         }
 
diff --git 
a/sshd-common/src/main/java24/org/apache/sshd/common/util/security/JceKEM.java 
b/sshd-common/src/main/java24/org/apache/sshd/common/util/security/JceKEM.java
new file mode 100644
index 000000000..721ba8dcf
--- /dev/null
+++ 
b/sshd-common/src/main/java24/org/apache/sshd/common/util/security/JceKEM.java
@@ -0,0 +1,216 @@
+/*
+ * 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;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+import javax.crypto.DecapsulateException;
+import javax.crypto.KEM.Decapsulator;
+import javax.crypto.KEM.Encapsulated;
+import javax.crypto.KEM.Encapsulator;
+
+enum JceKEM implements KEMFactory {
+
+    INSTANCE;
+
+    // See 
https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-11
+
+    // Sequence, length 1202 (3 bytes), Sequence, length 11, OID, length 9, 9 
bytes OID, Bit String, length 1185 (3
+    // bytes), zero unused bits. OID = 2.16.840.101.3.4.4.2
+    private static final byte[] ML768_X509_PREFIX = { 0x30, (byte) 0x82, 0x04, 
(byte) 0xb2, 0x30, 0x0b, 0x06, 0x09, 0x60,
+            (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02, 0x03, 
(byte) 0x82, 0x04, (byte) 0xa1, 0x00 };
+    // Sequence, length 1586 (3 bytes), Sequence, length 11, OID, length 9, 9 
bytes OID, Bit String, length 1569 (3
+    // bytes), zero unused bits. OID = 2.16.840.101.3.4.4.3
+    private static final byte[] ML1024_X509_PREFIX = { 0x30, (byte) 0x82, 
0x06, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86,
+            0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03, 0x03, (byte) 0x82, 0x06, 
0x21, 0x00 };
+
+    @Override
+    public KEM get(String algorithm, Provider provider) throws 
NoSuchAlgorithmException {
+        javax.crypto.KEM kem = provider == null
+                ? javax.crypto.KEM.getInstance(algorithm)
+                : javax.crypto.KEM.getInstance(algorithm, provider);
+        if (KEM.ML_KEM_768.equalsIgnoreCase(algorithm)) {
+            return new KEMWrapper(kem, KEM.ML_KEM_768, provider, 1184, 1088, 
ML768_X509_PREFIX);
+        } else if (KEM.ML_KEM_1024.equalsIgnoreCase(algorithm)) {
+            return new KEMWrapper(kem, KEM.ML_KEM_1024, provider, 1568, 1568, 
ML1024_X509_PREFIX);
+        }
+        throw new NoSuchAlgorithmException(algorithm + " not supported");
+    }
+
+    @Override
+    public boolean isSupported(String algorithm) {
+        if (KEM.ML_KEM_768.equalsIgnoreCase(algorithm) || 
KEM.ML_KEM_1024.equalsIgnoreCase(algorithm)) {
+            try {
+                return javax.crypto.KEM.getInstance(algorithm) != null;
+            } catch (NoSuchAlgorithmException e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    private static class KEMWrapper implements KEM {
+
+        private final javax.crypto.KEM kem;
+
+        private final String algorithm;
+
+        private final Provider provider;
+
+        private final int encapKeyLength;
+
+        private final int cipherTextLength;
+
+        private final byte[] prefix;
+
+        KEMWrapper(javax.crypto.KEM kem, String algorithm, Provider provider, 
int encapKeyLength, int cipherTextLength,
+                byte[] prefix) {
+            this.algorithm = algorithm;
+            this.provider = provider;
+            this.encapKeyLength = encapKeyLength;
+            this.cipherTextLength = cipherTextLength;
+            this.prefix = prefix;
+            this.kem = kem;
+        }
+
+        @Override
+        public String toString() {
+            return kem.getClass().getName();
+        }
+
+        @Override
+        public boolean isSupported() {
+            return true;
+        }
+
+        @Override
+        public Client getClient() {
+            return new Client();
+        }
+
+        @Override
+        public Server getServer() {
+            return new Server();
+        }
+
+        private class Client implements KEM.Client {
+
+            private Decapsulator dec;
+
+            private byte[] pubKey;
+
+            Client() {
+                super();
+            }
+
+            private byte[] extractFromX509(byte[] x509, byte[] prefix, int 
length) {
+                return Arrays.copyOfRange(x509, prefix.length, prefix.length + 
length);
+            }
+
+            @Override
+            public void init() {
+                try {
+                    KeyPairGenerator generator = provider == null
+                            ? KeyPairGenerator.getInstance(algorithm)
+                            : KeyPairGenerator.getInstance(algorithm, 
provider);
+                    KeyPair kp = generator.generateKeyPair();
+                    dec = kem.newDecapsulator(kp.getPrivate());
+                    pubKey = extractFromX509(kp.getPublic().getEncoded(), 
prefix, encapKeyLength);
+                } catch (GeneralSecurityException e) {
+                    throw new IllegalStateException(e.getMessage(), e);
+                }
+            }
+
+            @Override
+            public byte[] getPublicKey() {
+                return pubKey;
+            }
+
+            @Override
+            public byte[] extractSecret(byte[] encapsulated) {
+                try {
+                    return dec.decapsulate(encapsulated).getEncoded();
+                } catch (DecapsulateException e) {
+                    throw new IllegalArgumentException(e.getMessage(), e);
+                }
+            }
+
+            @Override
+            public int getEncapsulationLength() {
+                return cipherTextLength;
+            }
+        }
+
+        private class Server implements KEM.Server {
+
+            private Encapsulated encapsulation;
+
+            Server() {
+                super();
+            }
+
+            private PublicKey createKey(byte[] raw, int from, int length) 
throws GeneralSecurityException {
+                KeyFactory factory = provider == null
+                        ? KeyFactory.getInstance(algorithm)
+                        : KeyFactory.getInstance(algorithm, provider);
+                byte[] x509 = Arrays.copyOf(prefix, prefix.length + length);
+                System.arraycopy(raw, from, x509, prefix.length, length);
+                return factory.generatePublic(new X509EncodedKeySpec(x509));
+            }
+
+            @Override
+            public int getPublicKeyLength() {
+                return encapKeyLength;
+            }
+
+            @Override
+            public byte[] init(byte[] publicKey) {
+                int pkBytes = getPublicKeyLength();
+                if (publicKey.length < pkBytes) {
+                    throw new IllegalArgumentException("KEM public key too 
short: " + publicKey.length);
+                }
+                try {
+                    Encapsulator enc = 
kem.newEncapsulator(createKey(publicKey, 0, pkBytes));
+                    encapsulation = enc.encapsulate();
+                    return Arrays.copyOfRange(publicKey, pkBytes, 
publicKey.length);
+                } catch (GeneralSecurityException e) {
+                    throw new IllegalArgumentException(e.getMessage(), e);
+                }
+            }
+
+            @Override
+            public byte[] getSecret() {
+                return encapsulation.key().getEncoded();
+            }
+
+            @Override
+            public byte[] getEncapsulation() {
+                return encapsulation.encapsulation();
+            }
+        }
+    }
+}
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index cc9bdbe7d..e0b8b21f3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -36,7 +36,6 @@ import org.apache.sshd.common.kex.AbstractDH;
 import org.apache.sshd.common.kex.CurveSizeIndicator;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.kex.KexProposalOption;
-import org.apache.sshd.common.kex.KeyEncapsulationMethod;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.common.session.Session;
@@ -46,6 +45,7 @@ import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.common.util.security.KEM;
 import org.apache.sshd.core.CoreModuleProperties;
 
 /**
@@ -58,7 +58,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
     protected final DHFactory factory;
     protected AbstractDH dh;
 
-    private KeyEncapsulationMethod.Client kemClient;
+    private KEM.Client kemClient;
 
     protected DHGClient(DHFactory factory, Session session) {
         super(session);
@@ -100,7 +100,7 @@ public class DHGClient extends AbstractDHClientKeyExchange {
         hash = dh.getHash();
         hash.init();
 
-        KeyEncapsulationMethod kem = dh.getKeyEncapsulation();
+        KEM kem = dh.getKeyEncapsulation();
         byte[] e;
         if (kem == null) {
             e = updateE(dh.getE());
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/AbstractDH.java 
b/sshd-core/src/main/java/org/apache/sshd/common/kex/AbstractDH.java
index b9de78f52..cb709992c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/AbstractDH.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/AbstractDH.java
@@ -23,6 +23,7 @@ import javax.crypto.KeyAgreement;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.security.KEM;
 
 /**
  * Base class for the Diffie-Hellman key agreement.
@@ -118,7 +119,7 @@ public abstract class AbstractDH {
 
     public abstract Digest getHash() throws Exception;
 
-    public KeyEncapsulationMethod getKeyEncapsulation() {
+    public KEM getKeyEncapsulation() {
         return null;
     }
 
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java 
b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
index bbe82da0c..fd2209d71 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
@@ -39,6 +39,7 @@ import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.digest.Digest;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.KEM;
 import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
@@ -315,7 +316,7 @@ public enum BuiltinDHFactories implements DHFactory {
             return new XDH(MontgomeryCurve.x25519, true) {
 
                 @Override
-                public KeyEncapsulationMethod getKeyEncapsulation() {
+                public KEM getKeyEncapsulation() {
                     return BuiltinKEM.mlkem768;
                 }
 
@@ -345,7 +346,7 @@ public enum BuiltinDHFactories implements DHFactory {
             return new ECDH(ECCurves.nistp256, true) {
 
                 @Override
-                public KeyEncapsulationMethod getKeyEncapsulation() {
+                public KEM getKeyEncapsulation() {
                     return BuiltinKEM.mlkem768;
                 }
 
@@ -370,7 +371,7 @@ public enum BuiltinDHFactories implements DHFactory {
             return new ECDH(ECCurves.nistp384, true) {
 
                 @Override
-                public KeyEncapsulationMethod getKeyEncapsulation() {
+                public KEM getKeyEncapsulation() {
                     return BuiltinKEM.mlkem1024;
                 }
 
@@ -395,7 +396,7 @@ public enum BuiltinDHFactories implements DHFactory {
             return new XDH(MontgomeryCurve.x25519, true) {
 
                 @Override
-                public KeyEncapsulationMethod getKeyEncapsulation() {
+                public KEM getKeyEncapsulation() {
                     return BuiltinKEM.sntrup761;
                 }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinKEM.java 
b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinKEM.java
index b8f2af753..5beab1763 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinKEM.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinKEM.java
@@ -18,75 +18,37 @@
  */
 package org.apache.sshd.common.kex;
 
+import java.security.GeneralSecurityException;
+
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.OptionalFeature;
+import org.apache.sshd.common.util.security.KEM;
+import org.apache.sshd.common.util.security.SecurityUtils;
 
 /**
  * All built in key encapsulation methods (KEM).
  */
-public enum BuiltinKEM implements KeyEncapsulationMethod, NamedResource, 
OptionalFeature {
-
-    mlkem768("mlkem768") {
-
-        @Override
-        public Client getClient() {
-            return MLKEM.getClient(MLKEM.Parameters.mlkem768);
-        }
-
-        @Override
-        public Server getServer() {
-            return MLKEM.getServer(MLKEM.Parameters.mlkem768);
-        }
-
-        @Override
-        public boolean isSupported() {
-            return MLKEM.Parameters.mlkem768.isSupported();
-        }
-
-    },
-
-    mlkem1024("mlkem1024") {
-
-        @Override
-        public Client getClient() {
-            return MLKEM.getClient(MLKEM.Parameters.mlkem1024);
-        }
-
-        @Override
-        public Server getServer() {
-            return MLKEM.getServer(MLKEM.Parameters.mlkem1024);
-        }
-
-        @Override
-        public boolean isSupported() {
-            return MLKEM.Parameters.mlkem1024.isSupported();
-        }
+public enum BuiltinKEM implements KEM, NamedResource, OptionalFeature {
 
-    },
+    mlkem768("mlkem768", KEM.ML_KEM_768),
 
-    sntrup761("sntrup761") {
+    mlkem1024("mlkem1024", KEM.ML_KEM_1024),
 
-        @Override
-        public Client getClient() {
-            return new SNTRUP761.Client();
-        }
-
-        @Override
-        public Server getServer() {
-            return new SNTRUP761.Server();
-        }
-
-        @Override
-        public boolean isSupported() {
-            return SNTRUP761.isSupported();
-        }
-
-    };
+    sntrup761("sntrup761", KEM.SNTRUP_761);
 
     private String name;
 
-    BuiltinKEM(String name) {
+    private KEM kem;
+
+    BuiltinKEM(String name, String algorithm) {
         this.name = name;
+        KEM k;
+        try {
+            k = SecurityUtils.getKEM(algorithm);
+        } catch (GeneralSecurityException e) {
+            k = null;
+        }
+        this.kem = k;
     }
 
     @Override
@@ -94,4 +56,18 @@ public enum BuiltinKEM implements KeyEncapsulationMethod, 
NamedResource, Optiona
         return name;
     }
 
+    @Override
+    public boolean isSupported() {
+        return kem != null && kem.isSupported();
+    }
+
+    @Override
+    public Client getClient() {
+        return kem.getClient();
+    }
+
+    @Override
+    public Server getServer() {
+        return kem.getServer();
+    }
 }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java 
b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java
index 42dcf906d..33d260b49 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java
@@ -30,7 +30,6 @@ import org.apache.sshd.common.kex.AbstractDH;
 import org.apache.sshd.common.kex.CurveSizeIndicator;
 import org.apache.sshd.common.kex.DHFactory;
 import org.apache.sshd.common.kex.KexProposalOption;
-import org.apache.sshd.common.kex.KeyEncapsulationMethod;
 import org.apache.sshd.common.kex.KeyExchange;
 import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.common.session.Session;
@@ -39,6 +38,7 @@ import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.security.KEM;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
@@ -103,13 +103,13 @@ public class DHGServer extends 
AbstractDHServerKeyExchange {
         }
 
         byte[] e = updateE(buffer);
-        KeyEncapsulationMethod kem = dh.getKeyEncapsulation();
+        KEM kem = dh.getKeyEncapsulation();
         if (kem == null) {
             dh.setF(e);
             k = normalize(dh.getK());
         } else {
             try {
-                KeyEncapsulationMethod.Server kemServer = kem.getServer();
+                KEM.Server kemServer = kem.getServer();
 
                 if (dh instanceof CurveSizeIndicator) {
                     int expectedLength = kemServer.getPublicKeyLength() + 
((CurveSizeIndicator) dh).getByteLength();
diff --git a/sshd-test/pom.xml b/sshd-test/pom.xml
index cfbb2fd42..65ec8e838 100644
--- a/sshd-test/pom.xml
+++ b/sshd-test/pom.xml
@@ -51,12 +51,6 @@
         </dependency>
 
         <!-- test dependencies -->
-                <dependency>
-                    <groupId>org.apache.sshd</groupId>
-                    <artifactId>sshd-mina</artifactId>
-                    <version>${project.version}</version>
-                    <scope>test</scope>
-                </dependency>
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcpg-jdk18on</artifactId>
diff --git 
a/sshd-test/src/test/java/org/apache/sshd/client/kex/OpenSshMlKemTest.java 
b/sshd-test/src/test/java/org/apache/sshd/client/kex/OpenSshMlKemTest.java
index f9a715cd8..bebb61c32 100644
--- a/sshd-test/src/test/java/org/apache/sshd/client/kex/OpenSshMlKemTest.java
+++ b/sshd-test/src/test/java/org/apache/sshd/client/kex/OpenSshMlKemTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sshd.client.kex;
 
-import java.security.Security;
 import java.util.Collections;
 
 import org.apache.sshd.AbstractContainerTestBase;
@@ -28,10 +27,10 @@ import org.apache.sshd.client.future.AuthFuture;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.kex.BuiltinDHFactories;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.common.util.security.KEM;
+import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -76,16 +75,10 @@ class OpenSshMlKemTest extends AbstractContainerTestBase {
             .waitingFor(Wait.forLogMessage(".*Server listening on.*port 
22.*\\n", 1)) //
             .withLogConsumer(new Slf4jLogConsumer(LOG));
 
-    @BeforeAll
-    static void registerBouncyCastleProviderIfNecessary() {
-        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
-            Security.addProvider(new BouncyCastleProvider());
-        }
-    }
-
     @Test
     void mlkem768x25519() throws Exception {
         
Assumptions.assumeTrue(BuiltinDHFactories.mlkem768x25519.isSupported());
+        LOG.info("Java {} using KEM {}", System.getProperty("java.version"), 
SecurityUtils.getKEM(KEM.ML_KEM_768));
         FileKeyPairProvider keyPairProvider = CommonTestSupportUtils
                 .createTestKeyPairProvider(TEST_KEYS + "/user01_ed25519");
         SshClient client = setupTestClient();

Reply via email to