From a45f0ac74cf66d760d46c933ad1c720a7184de33 Mon Sep 17 00:00:00 2001 From: Jeremy Norris Date: Mon, 6 Sep 2021 21:43:31 -0500 Subject: [PATCH] Add support for various algorithms with older Java releases by using Bouncy Castle: - ssh-ed25519 - ssh-ed448 - curve25519-sha256 - curve25519-sha256@libssh.org - curve448-sha512 - chacha20-poly1305@openssh.com --- Readme.md | 8 + pom.xml | 6 + src/main/java/com/jcraft/jsch/DHXEC.java | 2 +- src/main/java/com/jcraft/jsch/JSch.java | 26 +++- .../java/com/jcraft/jsch/JavaVersion.java | 8 + .../java/com/jcraft/jsch/KeyExchange.java | 31 ++-- .../java/com/jcraft/jsch/KeyPairEdDSA.java | 6 +- src/main/java/com/jcraft/jsch/Session.java | 15 +- .../com/jcraft/jsch/bc/ChaCha20Poly1305.java | 138 ++++++++++++++++++ .../com/jcraft/jsch/bc/KeyPairGenEdDSA.java | 64 ++++++++ .../com/jcraft/jsch/bc/SignatureEd25519.java | 47 ++++++ .../com/jcraft/jsch/bc/SignatureEd448.java | 47 ++++++ .../com/jcraft/jsch/bc/SignatureEdDSA.java | 114 +++++++++++++++ src/main/java/com/jcraft/jsch/bc/XDH.java | 94 ++++++++++++ .../com/jcraft/jsch/jce/ChaCha20Poly1305.java | 1 - .../com/jcraft/jsch/jce/KeyPairGenXEC.java | 1 - .../java9/com/jcraft/jsch/JavaVersion.java | 8 + src/main/java9/module-info.java | 1 + .../java/com/jcraft/jsch/Algorithms2IT.java | 31 +++- .../java/com/jcraft/jsch/AlgorithmsIT.java | 55 ++++++- 20 files changed, 668 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/jcraft/jsch/JavaVersion.java create mode 100644 src/main/java/com/jcraft/jsch/bc/ChaCha20Poly1305.java create mode 100644 src/main/java/com/jcraft/jsch/bc/KeyPairGenEdDSA.java create mode 100644 src/main/java/com/jcraft/jsch/bc/SignatureEd25519.java create mode 100644 src/main/java/com/jcraft/jsch/bc/SignatureEd448.java create mode 100644 src/main/java/com/jcraft/jsch/bc/SignatureEdDSA.java create mode 100644 src/main/java/com/jcraft/jsch/bc/XDH.java create mode 100644 src/main/java9/com/jcraft/jsch/JavaVersion.java diff --git a/Readme.md b/Readme.md index abc6269a..39035536 100644 --- a/Readme.md +++ b/Readme.md @@ -64,6 +64,7 @@ As I explained in a [blog post](http://www.matez.de/index.php/2020/06/22/the-fut * This library is a Multi-Release-jar, which means that you can only use certain features when a more recent Java version is used. * In order to use ssh-ed25519 & ssh-ed448, you must use at least Java 15. * In order to use curve25519-sha256, curve448-sha512 & chacha20-poly1305@openssh.com, you must use at least Java 11. + * As of the [0.1.66](https://github.com/mwiede/jsch/releases/tag/jsch-0.1.66) release, these algorithms can now be used with older Java releases if [Bouncy Castle](https://www.bouncycastle.org/) (bcprov-jdk15on) is added to the classpath. ## Changes since fork: * [0.1.66](https://github.com/mwiede/jsch/releases/tag/jsch-0.1.66) @@ -99,6 +100,13 @@ As I explained in a [blog post](http://www.matez.de/index.php/2020/06/22/the-fut * See `examples/JSchWithAgentProxy.java` for simple example * ssh-agent support requires either [Java 16's JEP 380](https://openjdk.java.net/jeps/380) or the addition of [junixsocket](https://github.com/kohlschutter/junixsocket) to classpath * Pageant support is untested & requires the addition of [JNA](https://github.com/java-native-access/jna) to classpath + * Added support for the following algorithms with older Java releases by using [Bouncy Castle](https://www.bouncycastle.org/): + * ssh-ed25519 + * ssh-ed448 + * curve25519-sha256 + * curve25519-sha256@libssh.org + * curve448-sha512 + * chacha20-poly1305@openssh.com * [0.1.65](https://github.com/mwiede/jsch/releases/tag/jsch-0.1.65) * Added system properties to allow manipulation of various crypto algorithms used by default * Integrated JZlib, allowing use of zlib@openssh.com & zlib compressions without the need to provide the JZlib jar-file diff --git a/pom.xml b/pom.xml index 19812b80..4f197228 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,12 @@ 5.9.0 + + org.bouncycastle + bcprov-jdk15on + 1.69 + true + com.kohlschutter.junixsocket junixsocket-common diff --git a/src/main/java/com/jcraft/jsch/DHXEC.java b/src/main/java/com/jcraft/jsch/DHXEC.java index 4bbeeeac..b1100207 100644 --- a/src/main/java/com/jcraft/jsch/DHXEC.java +++ b/src/main/java/com/jcraft/jsch/DHXEC.java @@ -89,7 +89,7 @@ public void init(Session session, Q_C = xdh.getQ(); buf.putString(Q_C); } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ throw new JSchException(e.toString(), e); } diff --git a/src/main/java/com/jcraft/jsch/JSch.java b/src/main/java/com/jcraft/jsch/JSch.java index 6a83e70d..e6f6569e 100644 --- a/src/main/java/com/jcraft/jsch/JSch.java +++ b/src/main/java/com/jcraft/jsch/JSch.java @@ -106,13 +106,9 @@ public class JSch{ config.put("ecdh-sha2-nistp", "com.jcraft.jsch.jce.ECDHN"); - config.put("ssh-ed25519", "com.jcraft.jsch.jce.SignatureEd25519"); - config.put("ssh-ed448", "com.jcraft.jsch.jce.SignatureEd448"); - 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("xdh", "com.jcraft.jsch.jce.XDH"); config.put("dh", "com.jcraft.jsch.jce.DH"); config.put("3des-cbc", "com.jcraft.jsch.jce.TripleDESCBC"); @@ -156,12 +152,10 @@ public class JSch{ config.put("keypairgen.dsa", "com.jcraft.jsch.jce.KeyPairGenDSA"); config.put("keypairgen.rsa", "com.jcraft.jsch.jce.KeyPairGenRSA"); config.put("keypairgen.ecdsa", "com.jcraft.jsch.jce.KeyPairGenECDSA"); - config.put("keypairgen.eddsa", "com.jcraft.jsch.jce.KeyPairGenEdDSA"); config.put("random", "com.jcraft.jsch.jce.Random"); config.put("none", "com.jcraft.jsch.CipherNone"); - config.put("chacha20-poly1305@openssh.com", "com.jcraft.jsch.jce.ChaCha20Poly1305"); config.put("aes128-gcm@openssh.com", "com.jcraft.jsch.jce.AES128GCM"); config.put("aes256-gcm@openssh.com", "com.jcraft.jsch.jce.AES256GCM"); @@ -189,6 +183,26 @@ public class JSch{ config.put("pbkdf", "com.jcraft.jsch.jce.PBKDF"); + if(JavaVersion.getVersion()>=11){ + config.put("chacha20-poly1305@openssh.com", "com.jcraft.jsch.jce.ChaCha20Poly1305"); + config.put("xdh", "com.jcraft.jsch.jce.XDH"); + } + else{ + config.put("chacha20-poly1305@openssh.com", "com.jcraft.jsch.bc.ChaCha20Poly1305"); + config.put("xdh", "com.jcraft.jsch.bc.XDH"); + } + + if(JavaVersion.getVersion()>=15){ + config.put("keypairgen.eddsa", "com.jcraft.jsch.jce.KeyPairGenEdDSA"); + config.put("ssh-ed25519", "com.jcraft.jsch.jce.SignatureEd25519"); + config.put("ssh-ed448", "com.jcraft.jsch.jce.SignatureEd448"); + } + else{ + config.put("keypairgen.eddsa", "com.jcraft.jsch.bc.KeyPairGenEdDSA"); + config.put("ssh-ed25519", "com.jcraft.jsch.bc.SignatureEd25519"); + config.put("ssh-ed448", "com.jcraft.jsch.bc.SignatureEd448"); + } + config.put("StrictHostKeyChecking", "ask"); config.put("HashKnownHosts", "no"); diff --git a/src/main/java/com/jcraft/jsch/JavaVersion.java b/src/main/java/com/jcraft/jsch/JavaVersion.java new file mode 100644 index 00000000..b6184b14 --- /dev/null +++ b/src/main/java/com/jcraft/jsch/JavaVersion.java @@ -0,0 +1,8 @@ +package com.jcraft.jsch; + +final class JavaVersion { + + static int getVersion() { + return 8; + } +} diff --git a/src/main/java/com/jcraft/jsch/KeyExchange.java b/src/main/java/com/jcraft/jsch/KeyExchange.java index cd2e86f9..cb611909 100644 --- a/src/main/java/com/jcraft/jsch/KeyExchange.java +++ b/src/main/java/com/jcraft/jsch/KeyExchange.java @@ -143,18 +143,25 @@ else if(guess[i]==null){ } } - Class _s2cclazz=Class.forName(session.getConfig(guess[PROPOSAL_ENC_ALGS_STOC])); - Cipher _s2ccipher=(Cipher)(_s2cclazz.getDeclaredConstructor().newInstance()); - boolean _s2cAEAD=_s2ccipher.isAEAD(); - if(_s2cAEAD){ - guess[PROPOSAL_MAC_ALGS_STOC]=null; - } + boolean _s2cAEAD=false; + boolean _c2sAEAD=false; + try{ + Class _s2cclazz=Class.forName(session.getConfig(guess[PROPOSAL_ENC_ALGS_STOC])); + Cipher _s2ccipher=(Cipher)(_s2cclazz.getDeclaredConstructor().newInstance()); + _s2cAEAD=_s2ccipher.isAEAD(); + if(_s2cAEAD){ + guess[PROPOSAL_MAC_ALGS_STOC]=null; + } - Class _c2sclazz=Class.forName(session.getConfig(guess[PROPOSAL_ENC_ALGS_CTOS])); - Cipher _c2scipher=(Cipher)(_c2sclazz.getDeclaredConstructor().newInstance()); - boolean _c2sAEAD=_c2scipher.isAEAD(); - if(_c2sAEAD){ - guess[PROPOSAL_MAC_ALGS_CTOS]=null; + Class _c2sclazz=Class.forName(session.getConfig(guess[PROPOSAL_ENC_ALGS_CTOS])); + Cipher _c2scipher=(Cipher)(_c2sclazz.getDeclaredConstructor().newInstance()); + _c2sAEAD=_c2scipher.isAEAD(); + if(_c2sAEAD){ + guess[PROPOSAL_MAC_ALGS_CTOS]=null; + } + } + catch(Exception | NoClassDefFoundError e){ + throw new JSchException(e.toString(), e); } if(JSch.getLogger().isEnabled(Logger.INFO)){ @@ -361,7 +368,7 @@ else if(alg.equals("ssh-ed25519") || sig=(SignatureEdDSA)(c.getDeclaredConstructor().newInstance()); sig.init(); } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ System.err.println(e); } diff --git a/src/main/java/com/jcraft/jsch/KeyPairEdDSA.java b/src/main/java/com/jcraft/jsch/KeyPairEdDSA.java index 585a5be5..92d6a43f 100644 --- a/src/main/java/com/jcraft/jsch/KeyPairEdDSA.java +++ b/src/main/java/com/jcraft/jsch/KeyPairEdDSA.java @@ -57,7 +57,7 @@ void generate(int key_size) throws JSchException{ keypairgen=null; } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ //System.err.println("KeyPairEdDSA: "+e); throw new JSchException(e.toString(), e); } @@ -134,7 +134,7 @@ public byte[] getSignature(byte[] data, String alg){ tmp[1] = sig; return Buffer.fromBytes(tmp).buffer; } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ } return null; } @@ -160,7 +160,7 @@ public Signature getVerifier(String alg){ eddsa.setPubKey(pub_array); return eddsa; } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ } return null; } diff --git a/src/main/java/com/jcraft/jsch/Session.java b/src/main/java/com/jcraft/jsch/Session.java index 69e1bf34..80d807a7 100644 --- a/src/main/java/com/jcraft/jsch/Session.java +++ b/src/main/java/com/jcraft/jsch/Session.java @@ -610,7 +610,7 @@ private KeyExchange receive_kexinit(Buffer buf) throws Exception { Class c=Class.forName(getConfig(guess[KeyExchange.PROPOSAL_KEX_ALGS])); kex=(KeyExchange)(c.getDeclaredConstructor().newInstance()); } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ throw new JSchException(e.toString(), e); } @@ -1528,7 +1528,7 @@ private void updateKeys(KeyExchange kex) throws Exception{ method=guess[KeyExchange.PROPOSAL_COMP_ALGS_STOC]; initInflater(method); } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ if(e instanceof JSchException) throw e; throw new JSchException(e.toString(), e); @@ -2574,9 +2574,6 @@ private void initDeflater(String method) throws JSchException{ catch(Exception ee){ } deflater.init(Compression.DEFLATER, level); } - catch(NoClassDefFoundError ee){ - throw new JSchException(ee.toString(), ee); - } catch(Exception ee){ throw new JSchException(ee.toString(), ee); //System.err.println(foo+" isn't accessible."); @@ -2855,7 +2852,7 @@ static boolean checkCipher(String cipher){ new byte[_c.getIVSize()]); return true; } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ return false; } } @@ -2904,7 +2901,7 @@ static boolean checkMac(String mac){ _c.init(new byte[_c.getBlockSize()]); return true; } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ return false; } } @@ -2947,7 +2944,7 @@ static boolean checkKex(Session s, String kex){ _c.init(s ,null, null, null, null); return true; } - catch(Exception e){ return false; } + catch(Exception | NoClassDefFoundError e){ return false; } } private String[] checkSignatures(String sigs){ @@ -2967,7 +2964,7 @@ private String[] checkSignatures(String sigs){ final Signature sig=(Signature)(c.getDeclaredConstructor().newInstance()); sig.init(); } - catch(Exception e){ + catch(Exception | NoClassDefFoundError e){ result.addElement(_sigs[i]); } } diff --git a/src/main/java/com/jcraft/jsch/bc/ChaCha20Poly1305.java b/src/main/java/com/jcraft/jsch/bc/ChaCha20Poly1305.java new file mode 100644 index 00000000..8ab84dc9 --- /dev/null +++ b/src/main/java/com/jcraft/jsch/bc/ChaCha20Poly1305.java @@ -0,0 +1,138 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2008-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.Cipher; +import com.jcraft.jsch.openjax.Poly1305; +import java.nio.ByteBuffer; +import javax.crypto.AEADBadTagException; +import org.bouncycastle.crypto.engines.ChaChaEngine; +import org.bouncycastle.crypto.params.*; + +public class ChaCha20Poly1305 implements Cipher{ + //Actually the block size, not IV size + private static final int ivsize=8; + //Actually the key size, not block size + private static final int bsize=64; + private static final int tagsize=16; + private ChaChaEngine header_cipher; + private ChaChaEngine main_cipher; + private KeyParameter K_1_spec; + private KeyParameter K_2_spec; + private int mode; + private Poly1305 poly1305; + @Override + public int getIVSize(){return ivsize;} + @Override + public int getBlockSize(){return bsize;} + @Override + public int getTagSize(){return tagsize;} + @Override + public void init(int mode, byte[] key, byte[] iv) throws Exception{ + byte[] tmp; + if(key.length>bsize){ + tmp=new byte[bsize]; + System.arraycopy(key, 0, tmp, 0, tmp.length); + key=tmp; + } + byte[] K_1=new byte[bsize/2]; + byte[] K_2=new byte[bsize/2]; + System.arraycopy(key, bsize/2, K_1, 0, bsize/2); + System.arraycopy(key, 0, K_2, 0, bsize/2); + this.mode=mode; + try{ + K_1_spec=new KeyParameter(K_1); + K_2_spec=new KeyParameter(K_2); + header_cipher=new ChaChaEngine(); + main_cipher=new ChaChaEngine(); + } + catch(Exception e){ + header_cipher=null; + main_cipher=null; + K_1_spec=null; + K_2_spec=null; + throw e; + } + } + @Override + public void update(int foo) throws Exception{ + ByteBuffer nonce=ByteBuffer.allocate(8); + nonce.putLong(0, foo); + header_cipher.init(this.mode==ENCRYPT_MODE, new ParametersWithIV(K_1_spec, nonce.array())); + main_cipher.init(this.mode==ENCRYPT_MODE, new ParametersWithIV(K_2_spec, nonce.array())); + // Trying to reinit the cipher again with same nonce results in InvalidKeyException + // So just read entire first 64-byte block, which should increment global counter from 0->1 + byte[] poly_key = new byte[32]; + byte[] discard = new byte[32]; + main_cipher.processBytes(poly_key, 0, 32, poly_key, 0); + main_cipher.processBytes(discard, 0, 32, discard, 0); + poly1305 = new Poly1305(poly_key); + } + @Override + public void update(byte[] foo, int s1, int len, byte[] bar, int s2) throws Exception{ + header_cipher.processBytes(foo, s1, len, bar, s2); + } + @Override + public void updateAAD(byte[] foo, int s1, int len) throws Exception{ + } + @Override + public void doFinal(byte[] foo, int s1, int len, byte[] bar, int s2) throws Exception{ + if(this.mode==DECRYPT_MODE){ + byte[] actual_tag = new byte[tagsize]; + System.arraycopy(foo, len, actual_tag, 0, tagsize); + byte[] expected_tag = new byte[tagsize]; + poly1305.update(foo, s1, len).finish(expected_tag, 0); + if(!arraysequals(actual_tag, expected_tag)){ + throw new AEADBadTagException("Tag mismatch"); + } + } + + main_cipher.processBytes(foo, s1+4, len-4, bar, s2+4); + + if(this.mode==ENCRYPT_MODE){ + poly1305.update(bar, s2, len).finish(bar, len); + } + } + @Override + public boolean isCBC(){return false; } + @Override + public boolean isAEAD(){return true; } + @Override + public boolean isChaCha20(){return true; } + + private static boolean arraysequals(byte[] a, byte[] b){ + if(a.length!=b.length) return false; + int res=0; + for(int i=0; iclient cipher: %s.*", cipher); + String expectedC2S = String.format("kex: client->server cipher: %s.*", cipher); + checkLogs(expectedS2C); + checkLogs(expectedC2S); + } + + @ParameterizedTest + @CsvSource( + value = { + "chacha20-poly1305@openssh.com,none", + "chacha20-poly1305@openssh.com,zlib@openssh.com" + }) + public void testBCCiphers(String cipher, String compression) throws Exception { + JSch ssh = createRSAIdentity(); + Session session = createSession(ssh); + session.setConfig("chacha20-poly1305@openssh.com", "com.jcraft.jsch.bc.ChaCha20Poly1305"); session.setConfig("cipher.s2c", cipher); session.setConfig("cipher.c2s", cipher); session.setConfig("compression.s2c", compression);