diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index e86039fe..9b497ae9 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: ['8', '11', '17', '21', '22'] + java: ['8', '11', '17', '21', '23'] steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/ChangeLog.md b/ChangeLog.md index 3d1f3721..62957d49 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,11 +1,17 @@ +* [0.2.22](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.22) + * Add support for sntrup761x25519-sha512 KEX algorithm. + * Add support for mlkem768x25519-sha256, mlkem768nistp256-sha256 & mlkem1024nistp384-sha384 KEX algorithms. +* [0.2.21](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.21) + * [#692](https://github.com/mwiede/jsch/pull/692) Update Deflate.java by @mjmst74. * [0.2.20](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.20) * [#529](https://github.com/mwiede/jsch/pull/529) Update `Hostkey.getFingerprint()` method to output more moden format first introduced with OpenSSH 6.8. * [#622](https://github.com/mwiede/jsch/pull/622) Add stack trace to log message when an exception occurs during authentication. + * [#649](https://github.com/mwiede/jsch/pull/649) Incorrect Oid for service name in GSSAPI authentication. * [0.2.19](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.19) * Enforce DHGEX prime modulus bit length meets configured constraints. - * #604 Fix possible rekeying timeouts. + * [#604](https://github.com/mwiede/jsch/issues/604) Fix possible rekeying timeouts. * [0.2.18](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.18) - * Handle negated patterns according to ssh_config(5) by @bmiddaugh in https://github.com/mwiede/jsch/pull/565 + * [#565](https://github.com/mwiede/jsch/pull/565) Handle negated patterns according to ssh_config(5) by @bmiddaugh. * [0.2.17](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.17) * Add PBKDF2-HMAC-SHA512/256 & PBKDF2-HMAC-SHA512/224, which are both supported as of Java 21. * [0.2.16](https://github.com/mwiede/jsch/releases/tag/jsch-0.2.16) diff --git a/pom.xml b/pom.xml index e861222e..736c1bdf 100644 --- a/pom.xml +++ b/pom.xml @@ -423,7 +423,7 @@ Import-Package: \ com.sun.jna*;version="${range;[=0,+)}";resolution:=optional,\ org.apache.logging.log4j*;version="${range;[=0,4)}";resolution:=optional,\ - org.bouncycastle*;version="[1.76,${versionmask;+})";resolution:=optional,\ + org.bouncycastle*;version="[1.79,${versionmask;+})";resolution:=optional,\ org.slf4j*;version="[1.7,${versionmask;+})";resolution:=optional,\ org.newsclub.net.unix;resolution:=optional,\ org.ietf.jgss;resolution:=optional,\ diff --git a/src/main/java/com/jcraft/jsch/DH25519MLKEM768.java b/src/main/java/com/jcraft/jsch/DH25519MLKEM768.java new file mode 100644 index 00000000..c4caf388 --- /dev/null +++ b/src/main/java/com/jcraft/jsch/DH25519MLKEM768.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch; + +class DH25519MLKEM768 extends DHXECKEM { + public DH25519MLKEM768() { + kem_name = "mlkem768"; + sha_name = "sha-256"; + curve_name = "X25519"; + kem_pubkey_len = 1184; + kem_encap_len = 1088; + xec_key_len = 32; + } +} diff --git a/src/main/java/com/jcraft/jsch/DHEC256MLKEM768.java b/src/main/java/com/jcraft/jsch/DHEC256MLKEM768.java new file mode 100644 index 00000000..17da0f69 --- /dev/null +++ b/src/main/java/com/jcraft/jsch/DHEC256MLKEM768.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch; + +class DHEC256MLKEM768 extends DHECNKEM { + public DHEC256MLKEM768() { + kem_name = "mlkem768"; + sha_name = "sha-256"; + kem_pubkey_len = 1184; + kem_encap_len = 1088; + ecdh_key_size = 256; + ecdh_key_len = 65; + } +} diff --git a/src/main/java/com/jcraft/jsch/DHEC384MLKEM1024.java b/src/main/java/com/jcraft/jsch/DHEC384MLKEM1024.java new file mode 100644 index 00000000..62a7ea8d --- /dev/null +++ b/src/main/java/com/jcraft/jsch/DHEC384MLKEM1024.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch; + +class DHEC384MLKEM1024 extends DHECNKEM { + public DHEC384MLKEM1024() { + kem_name = "mlkem1024"; + sha_name = "sha-384"; + kem_pubkey_len = 1568; + kem_encap_len = 1568; + ecdh_key_size = 384; + ecdh_key_len = 97; + } +} diff --git a/src/main/java/com/jcraft/jsch/DHECNKEM.java b/src/main/java/com/jcraft/jsch/DHECNKEM.java new file mode 100644 index 00000000..0efc080f --- /dev/null +++ b/src/main/java/com/jcraft/jsch/DHECNKEM.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch; + +abstract class DHECNKEM extends KeyExchange { + + private static final int SSH_MSG_KEX_HYBRID_INIT = 30; + private static final int SSH_MSG_KEX_HYBRID_REPLY = 31; + private int state; + + byte[] C_INIT; + + byte[] V_S; + byte[] V_C; + byte[] I_S; + byte[] I_C; + + byte[] e; + + private Buffer buf; + private Packet packet; + + private KEM kem; + private ECDH ecdh; + + protected String kem_name; + protected String sha_name; + protected int kem_pubkey_len; + protected int kem_encap_len; + protected int ecdh_key_size; + protected int ecdh_key_len; + + @Override + public void init(Session session, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) + throws Exception { + this.V_S = V_S; + this.V_C = V_C; + this.I_S = I_S; + this.I_C = I_C; + + try { + Class c = Class.forName(session.getConfig(sha_name)).asSubclass(HASH.class); + sha = c.getDeclaredConstructor().newInstance(); + sha.init(); + } catch (Exception e) { + throw new JSchException(e.toString(), e); + } + + buf = new Buffer(); + packet = new Packet(buf); + + packet.reset(); + // command + string len + C_INIT len + buf.checkFreeSize(1 + 4 + kem_pubkey_len + ecdh_key_len); + buf.putByte((byte) SSH_MSG_KEX_HYBRID_INIT); + + try { + Class k = Class.forName(session.getConfig(kem_name)).asSubclass(KEM.class); + kem = k.getDeclaredConstructor().newInstance(); + kem.init(); + + Class c = + Class.forName(session.getConfig("ecdh-sha2-nistp")).asSubclass(ECDH.class); + ecdh = c.getDeclaredConstructor().newInstance(); + ecdh.init(ecdh_key_size); + + byte[] kem_public_key_C = kem.getPublicKey(); + byte[] ecdh_public_key_C = ecdh.getQ(); + C_INIT = new byte[kem_pubkey_len + ecdh_key_len]; + System.arraycopy(kem_public_key_C, 0, C_INIT, 0, kem_pubkey_len); + System.arraycopy(ecdh_public_key_C, 0, C_INIT, kem_pubkey_len, ecdh_key_len); + buf.putString(C_INIT); + } catch (Exception e) { + throw new JSchException(e.toString(), e); + } + + if (V_S == null) { // This is a really ugly hack for Session.checkKexes ;-( + return; + } + + session.write(packet); + + if (session.getLogger().isEnabled(Logger.INFO)) { + session.getLogger().log(Logger.INFO, "SSH_MSG_KEX_HYBRID_INIT sent"); + session.getLogger().log(Logger.INFO, "expecting SSH_MSG_KEX_HYBRID_REPLY"); + } + + state = SSH_MSG_KEX_HYBRID_REPLY; + } + + @Override + public boolean next(Buffer _buf) throws Exception { + int i, j; + switch (state) { + case SSH_MSG_KEX_HYBRID_REPLY: + // The server responds with: + // byte SSH_MSG_KEX_HYBRID_REPLY + // string K_S, server's public host key + // string S_REPLY + // string the signature on the exchange hash + j = _buf.getInt(); + j = _buf.getByte(); + j = _buf.getByte(); + if (j != SSH_MSG_KEX_HYBRID_REPLY) { + if (session.getLogger().isEnabled(Logger.ERROR)) { + session.getLogger().log(Logger.ERROR, "type: must be SSH_MSG_KEX_HYBRID_REPLY " + j); + } + return false; + } + + K_S = _buf.getString(); + + byte[] S_REPLY = _buf.getString(); + if (S_REPLY.length != kem_encap_len + ecdh_key_len) { + return false; + } + + byte[] encapsulation = new byte[kem_encap_len]; + byte[] ecdh_public_key_S = new byte[ecdh_key_len]; + System.arraycopy(S_REPLY, 0, encapsulation, 0, kem_encap_len); + System.arraycopy(S_REPLY, kem_encap_len, ecdh_public_key_S, 0, ecdh_key_len); + + byte[][] r_s = KeyPairECDSA.fromPoint(ecdh_public_key_S); + + // RFC 5656, + // 4. ECDH Key Exchange + // All elliptic curve public keys MUST be validated after they are + // received. An example of a validation algorithm can be found in + // Section 3.2.2 of [SEC1]. If a key fails validation, + // the key exchange MUST fail. + if (!ecdh.validate(r_s[0], r_s[1])) { + return false; + } + + byte[] tmp = null; + try { + tmp = kem.decapsulate(encapsulation); + sha.update(tmp, 0, tmp.length); + } finally { + Util.bzero(tmp); + } + try { + tmp = normalize(ecdh.getSecret(r_s[0], r_s[1])); + sha.update(tmp, 0, tmp.length); + } finally { + Util.bzero(tmp); + } + K = encodeAsString(sha.digest()); + + byte[] sig_of_H = _buf.getString(); + + // draft-kampanakis-curdle-ssh-pq-ke-04, + // 2.5. Key Derivation + // + // The PQ/T Hybrid key exchange hash H is the result of computing the + // HASH, where HASH is the hash algorithm specified in the named PQ/T + // Hybrid key exchange method name, over the concatenation of the + // following: + // string V_C, client's identification string (CR and LF excluded) + // string V_S, server's identification string (CR and LF excluded) + // string I_C, payload of the client's SSH_MSG_KEXINIT + // string I_S, payload of the server's SSH_MSG_KEXINIT + // string K_S, server's public host key + // string C_INIT, client message octet string + // string S_REPLY, server message octet string + // string K, SSH shared secret + // + // K, the shared secret used in H, was traditionally encoded as an + // integer (mpint) as per [RFC4253], [RFC5656], and [RFC8731]. In this + // specification, K is the hash output of the two concatenated byte + // arrays (Section 2.4) which is not an integer. Thus, K is encoded as a + // string using the process described in Section 5 of [RFC4251] and is + // then fed along with other data in H to the key exchange method's HASH + // function to generate encryption keys. + buf.reset(); + buf.putString(V_C); + buf.putString(V_S); + buf.putString(I_C); + buf.putString(I_S); + buf.putString(K_S); + buf.putString(C_INIT); + buf.putString(S_REPLY); + byte[] foo = new byte[buf.getLength()]; + buf.getByte(foo); + + sha.update(foo, 0, foo.length); + sha.update(K, 0, K.length); + H = sha.digest(); + + i = 0; + j = 0; + j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) + | ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff); + String alg = Util.byte2str(K_S, i, j); + i += j; + + boolean result = verify(alg, K_S, i, sig_of_H); + + state = STATE_END; + return result; + } + return false; + } + + @Override + public int getState() { + return state; + } +} diff --git a/src/main/java/com/jcraft/jsch/JSch.java b/src/main/java/com/jcraft/jsch/JSch.java index b5d642d3..c09cff5d 100644 --- a/src/main/java/com/jcraft/jsch/JSch.java +++ b/src/main/java/com/jcraft/jsch/JSch.java @@ -102,8 +102,14 @@ public class JSch { config.put("curve25519-sha256", "com.jcraft.jsch.DH25519"); config.put("curve25519-sha256@libssh.org", "com.jcraft.jsch.DH25519"); config.put("curve448-sha512", "com.jcraft.jsch.DH448"); + config.put("mlkem768x25519-sha256", "com.jcraft.jsch.DH25519MLKEM768"); + config.put("mlkem768nistp256-sha256", "com.jcraft.jsch.DHEC256MLKEM768"); + config.put("mlkem1024nistp384-sha384", "com.jcraft.jsch.DHEC384MLKEM1024"); + config.put("sntrup761x25519-sha512", "com.jcraft.jsch.DH25519SNTRUP761"); config.put("sntrup761x25519-sha512@openssh.com", "com.jcraft.jsch.DH25519SNTRUP761"); + config.put("mlkem768", "com.jcraft.jsch.bc.MLKEM768"); + config.put("mlkem1024", "com.jcraft.jsch.bc.MLKEM1024"); config.put("sntrup761", "com.jcraft.jsch.bc.SNTRUP761"); config.put("dh", "com.jcraft.jsch.jce.DH"); @@ -243,7 +249,7 @@ public class JSch { Util.getSystemProperty("jsch.check_ciphers", "chacha20-poly1305@openssh.com")); config.put("CheckMacs", Util.getSystemProperty("jsch.check_macs", "")); config.put("CheckKexes", Util.getSystemProperty("jsch.check_kexes", - "sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,curve448-sha512")); + "mlkem768x25519-sha256,mlkem768nistp256-sha256,mlkem1024nistp384-sha384,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,curve448-sha512")); config.put("CheckSignatures", Util.getSystemProperty("jsch.check_signatures", "ssh-ed25519,ssh-ed448")); config.put("FingerprintHash", Util.getSystemProperty("jsch.fingerprint_hash", "sha256")); diff --git a/src/main/java/com/jcraft/jsch/bc/MLKEM.java b/src/main/java/com/jcraft/jsch/bc/MLKEM.java new file mode 100644 index 00000000..13742fc9 --- /dev/null +++ b/src/main/java/com/jcraft/jsch/bc/MLKEM.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch.bc; + +import com.jcraft.jsch.KEM; +import java.security.SecureRandom; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.pqc.crypto.mlkem.MLKEMExtractor; +import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyGenerationParameters; +import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyPairGenerator; +import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters; +import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters; +import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters; + +abstract class MLKEM implements KEM { + protected MLKEMParameters params; + MLKEMExtractor extractor; + MLKEMPublicKeyParameters publicKey; + + @Override + public void init() throws Exception { + MLKEMKeyPairGenerator kpg = new MLKEMKeyPairGenerator(); + kpg.init(new MLKEMKeyGenerationParameters(new SecureRandom(), params)); + AsymmetricCipherKeyPair kp = kpg.generateKeyPair(); + extractor = new MLKEMExtractor((MLKEMPrivateKeyParameters) kp.getPrivate()); + publicKey = (MLKEMPublicKeyParameters) kp.getPublic(); + } + + @Override + public byte[] getPublicKey() throws Exception { + return publicKey.getEncoded(); + } + + @Override + public byte[] decapsulate(byte[] encapsulation) throws Exception { + return extractor.extractSecret(encapsulation); + } +} diff --git a/src/main/java/com/jcraft/jsch/bc/MLKEM1024.java b/src/main/java/com/jcraft/jsch/bc/MLKEM1024.java new file mode 100644 index 00000000..6fa6affe --- /dev/null +++ b/src/main/java/com/jcraft/jsch/bc/MLKEM1024.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch.bc; + +import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters; + +public class MLKEM1024 extends MLKEM { + public MLKEM1024() { + params = MLKEMParameters.ml_kem_1024; + } +} diff --git a/src/main/java/com/jcraft/jsch/bc/MLKEM768.java b/src/main/java/com/jcraft/jsch/bc/MLKEM768.java new file mode 100644 index 00000000..6697bb20 --- /dev/null +++ b/src/main/java/com/jcraft/jsch/bc/MLKEM768.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. The names of the authors may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jcraft.jsch.bc; + +import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters; + +public class MLKEM768 extends MLKEM { + public MLKEM768() { + params = MLKEMParameters.ml_kem_768; + } +} diff --git a/src/test/java/com/jcraft/jsch/Algorithms2IT.java b/src/test/java/com/jcraft/jsch/Algorithms2IT.java index 6c9e37d0..b2fa3004 100644 --- a/src/test/java/com/jcraft/jsch/Algorithms2IT.java +++ b/src/test/java/com/jcraft/jsch/Algorithms2IT.java @@ -107,38 +107,40 @@ public static void afterAll() { sshdLogger.clearAll(); } - @Test + @ParameterizedTest + @ValueSource(strings = {"mlkem768nistp256-sha256", "mlkem1024nistp384-sha384", "curve448-sha512"}) @EnabledForJreRange(min = JAVA_11) - public void testJava11KEXs() throws Exception { + public void testJava11KEXs(String kex) throws Exception { JSch ssh = createRSAIdentity(); Session session = createSession(ssh); session.setConfig("xdh", "com.jcraft.jsch.jce.XDH"); - session.setConfig("kex", "curve448-sha512"); + session.setConfig("kex", kex); doSftp(session, true); - String expected = "kex: algorithm: curve448-sha512.*"; + String expected = String.format(Locale.ROOT, "kex: algorithm: %s.*", kex); checkLogs(expected); } - @Test - public void testBCKEXs() throws Exception { + @ParameterizedTest + @ValueSource(strings = {"mlkem768nistp256-sha256", "mlkem1024nistp384-sha384", "curve448-sha512"}) + public void testBCKEXs(String kex) throws Exception { JSch ssh = createRSAIdentity(); Session session = createSession(ssh); session.setConfig("xdh", "com.jcraft.jsch.bc.XDH"); - session.setConfig("kex", "curve448-sha512"); + session.setConfig("kex", kex); doSftp(session, true); - String expected = "kex: algorithm: curve448-sha512.*"; + String expected = String.format(Locale.ROOT, "kex: algorithm: %s.*", kex); checkLogs(expected); } @ParameterizedTest - @ValueSource(strings = {"curve448-sha512", "diffie-hellman-group17-sha512", - "diffie-hellman-group15-sha512", "diffie-hellman-group18-sha512@ssh.com", - "diffie-hellman-group16-sha512@ssh.com", "diffie-hellman-group16-sha384@ssh.com", - "diffie-hellman-group15-sha384@ssh.com", "diffie-hellman-group15-sha256@ssh.com", - "diffie-hellman-group14-sha256@ssh.com", "diffie-hellman-group14-sha224@ssh.com", - "diffie-hellman-group-exchange-sha512@ssh.com", + @ValueSource(strings = {"mlkem768nistp256-sha256", "mlkem1024nistp384-sha384", "curve448-sha512", + "diffie-hellman-group17-sha512", "diffie-hellman-group15-sha512", + "diffie-hellman-group18-sha512@ssh.com", "diffie-hellman-group16-sha512@ssh.com", + "diffie-hellman-group16-sha384@ssh.com", "diffie-hellman-group15-sha384@ssh.com", + "diffie-hellman-group15-sha256@ssh.com", "diffie-hellman-group14-sha256@ssh.com", + "diffie-hellman-group14-sha224@ssh.com", "diffie-hellman-group-exchange-sha512@ssh.com", "diffie-hellman-group-exchange-sha384@ssh.com", "diffie-hellman-group-exchange-sha224@ssh.com"}) public void testKEXs(String kex) throws Exception { diff --git a/src/test/java/com/jcraft/jsch/Algorithms4IT.java b/src/test/java/com/jcraft/jsch/Algorithms4IT.java index 4d01b5b7..211d2e96 100644 --- a/src/test/java/com/jcraft/jsch/Algorithms4IT.java +++ b/src/test/java/com/jcraft/jsch/Algorithms4IT.java @@ -57,11 +57,9 @@ public class Algorithms4IT { .withFileFromClasspath("ssh_host_ecdsa521_key.pub", "docker/ssh_host_ecdsa521_key.pub") .withFileFromClasspath("ssh_host_ed25519_key", "docker/ssh_host_ed25519_key") .withFileFromClasspath("ssh_host_ed25519_key.pub", "docker/ssh_host_ed25519_key.pub") - .withFileFromClasspath("ssh_host_dsa_key", "docker/ssh_host_dsa_key") - .withFileFromClasspath("ssh_host_dsa_key.pub", "docker/ssh_host_dsa_key.pub") - .withFileFromClasspath("sshd_config", "docker/sshd_config.openssh96") + .withFileFromClasspath("sshd_config", "docker/sshd_config.openssh99") .withFileFromClasspath("authorized_keys", "docker/authorized_keys") - .withFileFromClasspath("Dockerfile", "docker/Dockerfile.openssh96")) + .withFileFromClasspath("Dockerfile", "docker/Dockerfile.openssh99")) .withExposedPorts(22); @BeforeAll @@ -100,7 +98,8 @@ public static void afterAll() { } @ParameterizedTest - @ValueSource(strings = {"sntrup761x25519-sha512@openssh.com"}) + @ValueSource(strings = {"mlkem768x25519-sha256", "sntrup761x25519-sha512", + "sntrup761x25519-sha512@openssh.com"}) public void testBCKEXs(String kex) throws Exception { JSch ssh = createRSAIdentity(); Session session = createSession(ssh); diff --git a/src/test/java/com/jcraft/jsch/StrictKexIT.java b/src/test/java/com/jcraft/jsch/StrictKexIT.java index a16f4974..3b860f25 100644 --- a/src/test/java/com/jcraft/jsch/StrictKexIT.java +++ b/src/test/java/com/jcraft/jsch/StrictKexIT.java @@ -58,11 +58,9 @@ public class StrictKexIT { .withFileFromClasspath("ssh_host_ecdsa521_key.pub", "docker/ssh_host_ecdsa521_key.pub") .withFileFromClasspath("ssh_host_ed25519_key", "docker/ssh_host_ed25519_key") .withFileFromClasspath("ssh_host_ed25519_key.pub", "docker/ssh_host_ed25519_key.pub") - .withFileFromClasspath("ssh_host_dsa_key", "docker/ssh_host_dsa_key") - .withFileFromClasspath("ssh_host_dsa_key.pub", "docker/ssh_host_dsa_key.pub") - .withFileFromClasspath("sshd_config", "docker/sshd_config.openssh96") + .withFileFromClasspath("sshd_config", "docker/sshd_config.openssh99") .withFileFromClasspath("authorized_keys", "docker/authorized_keys") - .withFileFromClasspath("Dockerfile", "docker/Dockerfile.openssh96")) + .withFileFromClasspath("Dockerfile", "docker/Dockerfile.openssh99")) .withExposedPorts(22); @BeforeAll diff --git a/src/test/resources/docker/Dockerfile.asyncssh b/src/test/resources/docker/Dockerfile.asyncssh index 734a0e6c..135e4ae1 100644 --- a/src/test/resources/docker/Dockerfile.asyncssh +++ b/src/test/resources/docker/Dockerfile.asyncssh @@ -1,6 +1,10 @@ -FROM python:3.9 +FROM fedora:41 ARG MAX_PKTSIZE=32768 -RUN pip install 'asyncssh[bcrypt]' && \ +RUN dnf -y update && \ + dnf -y install python3-pip liboqs && \ + dnf -y clean all && \ + pip install 'asyncssh[bcrypt]~=2.18.0' && \ + pip cache purge && \ mkdir /root/.ssh && \ chmod 700 /root/.ssh COPY asyncsshd.py / diff --git a/src/test/resources/docker/Dockerfile.openssh96 b/src/test/resources/docker/Dockerfile.openssh99 similarity index 88% rename from src/test/resources/docker/Dockerfile.openssh96 rename to src/test/resources/docker/Dockerfile.openssh99 index 474c9282..db824a29 100644 --- a/src/test/resources/docker/Dockerfile.openssh96 +++ b/src/test/resources/docker/Dockerfile.openssh99 @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM alpine:edge RUN apk update && \ apk upgrade && \ apk add openssh && \ @@ -15,8 +15,6 @@ COPY ssh_host_ecdsa521_key /etc/ssh/ COPY ssh_host_ecdsa521_key.pub /etc/ssh/ COPY ssh_host_ed25519_key /etc/ssh/ COPY ssh_host_ed25519_key.pub /etc/ssh/ -COPY ssh_host_dsa_key /etc/ssh/ -COPY ssh_host_dsa_key.pub /etc/ssh/ COPY sshd_config /etc/ssh/ COPY authorized_keys /root/.ssh/ RUN chmod 600 /etc/ssh/ssh_*_key /root/.ssh/authorized_keys diff --git a/src/test/resources/docker/asyncsshd.py b/src/test/resources/docker/asyncsshd.py index a76843b1..3d3a13ad 100755 --- a/src/test/resources/docker/asyncsshd.py +++ b/src/test/resources/docker/asyncsshd.py @@ -19,7 +19,8 @@ async def start_server(max_pktsize: int): '/etc/ssh/ssh_host_ecdsa521_key', '/etc/ssh/ssh_host_ed448_key', '/etc/ssh/ssh_host_ed25519_key', '/etc/ssh/ssh_host_rsa_key', '/etc/ssh/ssh_host_dsa_key'], - kex_algs=['curve448-sha512', 'curve25519-sha256', 'curve25519-sha256@libssh.org', + kex_algs=['mlkem768nistp256-sha256', 'mlkem1024nistp384-sha384', 'mlkem768x25519-sha256', + 'curve448-sha512', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp521', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp256', 'diffie-hellman-group18-sha512', 'diffie-hellman-group17-sha512', 'diffie-hellman-group16-sha512', 'diffie-hellman-group15-sha512', diff --git a/src/test/resources/docker/sshd_config.openssh96 b/src/test/resources/docker/sshd_config.openssh99 similarity index 67% rename from src/test/resources/docker/sshd_config.openssh96 rename to src/test/resources/docker/sshd_config.openssh99 index 12c4064f..220c9735 100644 --- a/src/test/resources/docker/sshd_config.openssh96 +++ b/src/test/resources/docker/sshd_config.openssh99 @@ -3,7 +3,7 @@ HostbasedAuthentication no PasswordAuthentication no PubkeyAuthentication yes AuthenticationMethods publickey -PubkeyAcceptedAlgorithms ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-dss +PubkeyAcceptedAlgorithms ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa UseDNS no PrintMotd no PermitRootLogin yes @@ -13,9 +13,8 @@ HostKey /etc/ssh/ssh_host_ecdsa384_key HostKey /etc/ssh/ssh_host_ecdsa521_key HostKey /etc/ssh/ssh_host_ed25519_key HostKey /etc/ssh/ssh_host_rsa_key -HostKey /etc/ssh/ssh_host_dsa_key -KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,diffie-hellman-group14-sha256,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1 -HostKeyAlgorithms ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-dss +KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,diffie-hellman-group14-sha256,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1 +HostKeyAlgorithms ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,ecdsa-sha2-nistp256,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-sha1,hmac-sha1-96-etm@openssh.com,hmac-sha1-96,hmac-md5-etm@openssh.com,hmac-md5,hmac-md5-96-etm@openssh.com,hmac-md5-96 LogLevel DEBUG3