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();