Skip to content

Commit

Permalink
Add support for [email protected] KEX algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
norrisjeremy committed Dec 22, 2023
1 parent f955209 commit fa68dc9
Show file tree
Hide file tree
Showing 16 changed files with 615 additions and 18 deletions.
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,9 @@
<bundledSignature>jdk-non-portable</bundledSignature>
<bundledSignature>jdk-reflection</bundledSignature>
</bundledSignatures>
<suppressAnnotations>
<suppressAnnotation>com.jcraft.jsch.annotations.SuppressForbiddenApi</suppressAnnotation>
</suppressAnnotations>
</configuration>
</plugin>
</plugins>
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/jcraft/jsch/DH25519SNTRUP761.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 DH25519SNTRUP761 extends DHXECKEM {
public DH25519SNTRUP761() {
kem_name = "sntrup761";
sha_name = "sha-512";
curve_name = "X25519";
encapsulation_len = 1039;
key_len = 32;
}
}
8 changes: 7 additions & 1 deletion src/main/java/com/jcraft/jsch/DHECN.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public boolean next(Buffer _buf) throws Exception {
buf.putString(K_S);
buf.putString(Q_C);
buf.putString(Q_S);
buf.putMPInt(K);
putK(buf);
byte[] foo = new byte[buf.getLength()];
buf.getByte(foo);

Expand All @@ -185,4 +185,10 @@ public boolean next(Buffer _buf) throws Exception {
public int getState() {
return state;
}


@Override
void putK(Buffer b) {
b.putMPInt(K);
}
}
8 changes: 7 additions & 1 deletion src/main/java/com/jcraft/jsch/DHGEX.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public boolean next(Buffer _buf) throws Exception {
buf.putMPInt(g);
buf.putMPInt(e);
buf.putMPInt(f);
buf.putMPInt(K);
putK(buf);

byte[] foo = new byte[buf.getLength()];
buf.getByte(foo);
Expand Down Expand Up @@ -238,6 +238,12 @@ public int getState() {
return state;
}


@Override
void putK(Buffer b) {
b.putMPInt(K);
}

static boolean checkInvalidSize(int size) {
return (size < 1024 || size > 8192 || size % 1024 != 0);
}
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/com/jcraft/jsch/DHGN.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public boolean next(Buffer _buf) throws Exception {
buf.putString(K_S);
buf.putMPInt(e);
buf.putMPInt(f);
buf.putMPInt(K);
putK(buf);
byte[] foo = new byte[buf.getLength()];
buf.getByte(foo);
sha.update(foo, 0, foo.length);
Expand All @@ -182,4 +182,9 @@ public boolean next(Buffer _buf) throws Exception {
public int getState() {
return state;
}

@Override
void putK(Buffer b) {
b.putMPInt(K);
}
}
7 changes: 6 additions & 1 deletion src/main/java/com/jcraft/jsch/DHXEC.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public boolean next(Buffer _buf) throws Exception {
buf.putString(K_S);
buf.putString(Q_C);
buf.putString(Q_S);
buf.putMPInt(K);
putK(buf);
byte[] foo = new byte[buf.getLength()];
buf.getByte(foo);

Expand All @@ -197,4 +197,9 @@ public boolean next(Buffer _buf) throws Exception {
public int getState() {
return state;
}

@Override
void putK(Buffer b) {
b.putMPInt(K);
}
}
223 changes: 223 additions & 0 deletions src/main/java/com/jcraft/jsch/DHXECKEM.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* 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 DHXECKEM extends KeyExchange {

private static final int SSH_MSG_KEX_ECDH_INIT = 30;
private static final int SSH_MSG_KEX_ECDH_REPLY = 31;
private int state;

byte[] Q_C;

byte[] V_S;
byte[] V_C;
byte[] I_S;
byte[] I_C;

byte[] e;

private Buffer buf;
private Packet packet;

private KEM kem;
private XDH xdh;

protected String kem_name;
protected String sha_name;
protected String curve_name;
protected int encapsulation_len;
protected int 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<? extends HASH> 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();
buf.putByte((byte) SSH_MSG_KEX_ECDH_INIT);

try {
Class<? extends KEM> k = Class.forName(session.getConfig(kem_name)).asSubclass(KEM.class);
kem = k.getDeclaredConstructor().newInstance();
kem.init();

Class<? extends XDH> c = Class.forName(session.getConfig("xdh")).asSubclass(XDH.class);
xdh = c.getDeclaredConstructor().newInstance();
xdh.init(curve_name, key_len);

byte[] kem_public_key_C = kem.getPublicKey();
byte[] xec_public_key_C = xdh.getQ();
Q_C = new byte[kem_public_key_C.length + xec_public_key_C.length];
System.arraycopy(kem_public_key_C, 0, Q_C, 0, kem_public_key_C.length);
System.arraycopy(xec_public_key_C, 0, Q_C, kem_public_key_C.length, xec_public_key_C.length);
buf.putString(Q_C);
} catch (Exception | NoClassDefFoundError 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_ECDH_INIT sent");
session.getLogger().log(Logger.INFO, "expecting SSH_MSG_KEX_ECDH_REPLY");
}

state = SSH_MSG_KEX_ECDH_REPLY;
}

@Override
public boolean next(Buffer _buf) throws Exception {
int i, j;
switch (state) {
case SSH_MSG_KEX_ECDH_REPLY:
// The server responds with:
// byte SSH_MSG_KEX_ECDH_REPLY
// string K_S, server's public host key
// string Q_S, server's ephemeral public key octet string
// string the signature on the exchange hash
j = _buf.getInt();
j = _buf.getByte();
j = _buf.getByte();
if (j != SSH_MSG_KEX_ECDH_REPLY) {
if (session.getLogger().isEnabled(Logger.ERROR)) {
session.getLogger().log(Logger.ERROR, "type: must be SSH_MSG_KEX_ECDH_REPLY " + j);
}
return false;
}

K_S = _buf.getString();

byte[] Q_S = _buf.getString();
if (Q_S.length != encapsulation_len + key_len) {
return false;
}

byte[] encapsulation = new byte[encapsulation_len];
byte[] xec_public_key_S = new byte[key_len];
System.arraycopy(Q_S, 0, encapsulation, 0, encapsulation_len);
System.arraycopy(Q_S, encapsulation_len, xec_public_key_S, 0, key_len);

// 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 (!xdh.validate(xec_public_key_S)) {
return false;
}

byte[] tmp = kem.decapsulate(encapsulation);
sha.update(tmp, 0, tmp.length);
tmp = xdh.getSecret(xec_public_key_S);
tmp = normalize(tmp);
sha.update(tmp, 0, tmp.length);
K = sha.digest();

byte[] sig_of_H = _buf.getString();

// The hash H is computed as the HASH hash of 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 Q_C, client's ephemeral public key octet string
// string Q_S, server's ephemeral public key octet string
// string K, shared secret

// draft-josefsson-ntruprime-ssh-02,
// 3. Key Exchange Method: sntrup761x25519-sha512
// ...
// The SSH_MSG_KEX_ECDH_REPLY's signature value is computed as described
// in [RFC5656] with the following changes. Instead of encoding the
// shared secret K as 'mpint', it MUST be encoded as 'string'. The
// shared secret K value MUST be the 64-byte output octet string of the
// SHA-512 hash computed with the input as the 32-byte octet string key
// output from the key encapsulation mechanism of sntrup761 concatenated
// with the 32-byte octet string of X25519(a, X25519(b, 9)) = X25519(b,
// X25519(a, 9)).
buf.reset();
buf.putString(V_C);
buf.putString(V_S);
buf.putString(I_C);
buf.putString(I_S);
buf.putString(K_S);
buf.putString(Q_C);
buf.putString(Q_S);
putK(buf);
byte[] foo = new byte[buf.getLength()];
buf.getByte(foo);

sha.update(foo, 0, foo.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;
}

@Override
void putK(Buffer b) {
b.putString(K);
}
}
5 changes: 4 additions & 1 deletion src/main/java/com/jcraft/jsch/JSch.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public class JSch {
config.put("curve25519-sha256", "com.jcraft.jsch.DH25519");
config.put("[email protected]", "com.jcraft.jsch.DH25519");
config.put("curve448-sha512", "com.jcraft.jsch.DH448");
config.put("[email protected]", "com.jcraft.jsch.DH25519SNTRUP761");

config.put("sntrup761", "com.jcraft.jsch.bc.SNTRUP761");

config.put("dh", "com.jcraft.jsch.jce.DH");
config.put("3des-cbc", "com.jcraft.jsch.jce.TripleDESCBC");
Expand Down Expand Up @@ -239,7 +242,7 @@ public class JSch {
Util.getSystemProperty("jsch.check_ciphers", "[email protected]"));
config.put("CheckMacs", Util.getSystemProperty("jsch.check_macs", ""));
config.put("CheckKexes", Util.getSystemProperty("jsch.check_kexes",
"curve25519-sha256,[email protected],curve448-sha512"));
"[email protected],curve25519-sha256,[email protected],curve448-sha512"));
config.put("CheckSignatures",
Util.getSystemProperty("jsch.check_signatures", "ssh-ed25519,ssh-ed448"));
config.put("FingerprintHash", Util.getSystemProperty("jsch.fingerprint_hash", "sha256"));
Expand Down
Loading

0 comments on commit fa68dc9

Please sign in to comment.