files/files-jsch/src/main/java/com/jcraft/jsch/KeyExchange.java

505 lines
16 KiB
Java

/*
* Copyright (c) 2002-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;
import java.util.Locale;
public abstract class KeyExchange {
static final int PROPOSAL_KEX_ALGS = 0;
static final int PROPOSAL_SERVER_HOST_KEY_ALGS = 1;
static final int PROPOSAL_ENC_ALGS_CTOS = 2;
static final int PROPOSAL_ENC_ALGS_STOC = 3;
static final int PROPOSAL_MAC_ALGS_CTOS = 4;
static final int PROPOSAL_MAC_ALGS_STOC = 5;
static final int PROPOSAL_COMP_ALGS_CTOS = 6;
static final int PROPOSAL_COMP_ALGS_STOC = 7;
static final int PROPOSAL_LANG_CTOS = 8;
static final int PROPOSAL_LANG_STOC = 9;
static final int PROPOSAL_MAX = 10;
static final String[] PROPOSAL_NAMES =
{"KEX algorithms", "host key algorithms", "ciphers c2s", "ciphers s2c", "MACs c2s",
"MACs s2c", "compression c2s", "compression s2c", "languages c2s", "languages s2c"};
// static String kex_algs="diffie-hellman-group-exchange-sha1"+
// ",diffie-hellman-group1-sha1";
// static String kex="diffie-hellman-group-exchange-sha1";
static String kex = "diffie-hellman-group1-sha1";
static String server_host_key = "ssh-rsa,ssh-dss";
static String enc_c2s = "blowfish-cbc";
static String enc_s2c = "blowfish-cbc";
static String mac_c2s = "hmac-md5"; // hmac-md5,hmac-sha1,hmac-ripemd160,
// hmac-sha1-96,hmac-md5-96
static String mac_s2c = "hmac-md5";
// static String comp_c2s="none"; // zlib
// static String comp_s2c="none";
static String lang_c2s = "";
static String lang_s2c = "";
public static final int STATE_END = 0;
protected Session session = null;
protected HASH sha = null;
protected byte[] K = null;
protected byte[] H = null;
protected byte[] K_S = null;
public KeyExchange() {
}
public abstract void init(Session session, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C)
throws Exception;
void doInit(Session session, byte[] V_S, byte[] V_C, byte[] I_S, byte[] I_C) throws Exception {
this.session = session;
init(session, V_S, V_C, I_S, I_C);
}
public abstract boolean next(Buffer buf) throws Exception;
public abstract int getState();
protected final int RSA = 0;
protected final int DSS = 1;
protected final int ECDSA = 2;
protected final int EDDSA = 3;
private int type = 0;
private String key_alg_name = "";
public String getKeyType() {
if (type == DSS)
return "DSA";
if (type == RSA)
return "RSA";
if (type == EDDSA)
return "EDDSA";
return "ECDSA";
}
public String getKeyAlgorithName() {
return key_alg_name;
}
protected static String[] guess(Session session, byte[] I_S, byte[] I_C) throws Exception {
String[] guess = new String[PROPOSAL_MAX];
Buffer sb = new Buffer(I_S);
sb.setOffSet(17);
Buffer cb = new Buffer(I_C);
cb.setOffSet(17);
if (session.getLogger().isEnabled(Logger.INFO)) {
for (int i = 0; i < PROPOSAL_MAX; i++) {
session.getLogger().log(Logger.INFO,
"server proposal: " + PROPOSAL_NAMES[i] + ": " + Util.byte2str(sb.getString()));
}
for (int i = 0; i < PROPOSAL_MAX; i++) {
session.getLogger().log(Logger.INFO,
"client proposal: " + PROPOSAL_NAMES[i] + ": " + Util.byte2str(cb.getString()));
}
sb.setOffSet(17);
cb.setOffSet(17);
}
for (int i = 0; i < PROPOSAL_MAX; i++) {
byte[] sp = sb.getString(); // server proposal
byte[] cp = cb.getString(); // client proposal
int j = 0;
int k = 0;
loop: while (j < cp.length) {
while (j < cp.length && cp[j] != ',')
j++;
if (k == j)
throw new JSchAlgoNegoFailException(i, Util.byte2str(cp), Util.byte2str(sp));
String algorithm = Util.byte2str(cp, k, j - k);
int l = 0;
int m = 0;
while (l < sp.length) {
while (l < sp.length && sp[l] != ',')
l++;
if (m == l)
throw new JSchAlgoNegoFailException(i, Util.byte2str(cp), Util.byte2str(sp));
if (algorithm.equals(Util.byte2str(sp, m, l - m))) {
guess[i] = algorithm;
break loop;
}
l++;
m = l;
}
j++;
k = j;
}
if (j == 0) {
guess[i] = "";
} else if (guess[i] == null) {
throw new JSchAlgoNegoFailException(i, Util.byte2str(cp), Util.byte2str(sp));
}
}
boolean _s2cAEAD = false;
boolean _c2sAEAD = false;
try {
Class<? extends Cipher> _s2cclazz =
Class.forName(session.getConfig(guess[PROPOSAL_ENC_ALGS_STOC])).asSubclass(Cipher.class);
Cipher _s2ccipher = _s2cclazz.getDeclaredConstructor().newInstance();
_s2cAEAD = _s2ccipher.isAEAD();
if (_s2cAEAD) {
guess[PROPOSAL_MAC_ALGS_STOC] = null;
}
Class<? extends Cipher> _c2sclazz =
Class.forName(session.getConfig(guess[PROPOSAL_ENC_ALGS_CTOS])).asSubclass(Cipher.class);
Cipher _c2scipher = _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 (session.getLogger().isEnabled(Logger.INFO)) {
session.getLogger().log(Logger.INFO, "kex: algorithm: " + guess[PROPOSAL_KEX_ALGS]);
session.getLogger().log(Logger.INFO,
"kex: host key algorithm: " + guess[PROPOSAL_SERVER_HOST_KEY_ALGS]);
session.getLogger().log(Logger.INFO,
"kex: server->client" + " cipher: " + guess[PROPOSAL_ENC_ALGS_STOC] + " MAC: "
+ (_s2cAEAD ? ("<implicit>") : (guess[PROPOSAL_MAC_ALGS_STOC])) + " compression: "
+ guess[PROPOSAL_COMP_ALGS_STOC]);
session.getLogger().log(Logger.INFO,
"kex: client->server" + " cipher: " + guess[PROPOSAL_ENC_ALGS_CTOS] + " MAC: "
+ (_c2sAEAD ? ("<implicit>") : (guess[PROPOSAL_MAC_ALGS_CTOS])) + " compression: "
+ guess[PROPOSAL_COMP_ALGS_CTOS]);
}
return guess;
}
public String getFingerPrint() {
HASH hash = null;
try {
String _c = session.getConfig("FingerprintHash").toLowerCase(Locale.ROOT);
Class<? extends HASH> c = Class.forName(session.getConfig(_c)).asSubclass(HASH.class);
hash = c.getDeclaredConstructor().newInstance();
} catch (Exception e) {
if (session.getLogger().isEnabled(Logger.ERROR)) {
session.getLogger().log(Logger.ERROR, "getFingerPrint: " + e.getMessage(), e);
}
}
return Util.getFingerPrint(hash, getHostKey(), true, false);
}
byte[] getK() {
return K;
}
void clearK() {
Util.bzero(K);
K = null;
}
byte[] getH() {
return H;
}
HASH getHash() {
return sha;
}
byte[] getHostKey() {
return K_S;
}
/*
* It seems JCE included in Oracle's Java7u6(and later) has suddenly changed its behavior. The
* secrete generated by KeyAgreement#generateSecret() may start with 0, even if it is a positive
* value. See https://bugs.openjdk.org/browse/JDK-7146728.
*/
protected byte[] normalize(byte[] secret) {
// This should be a timing safe version of the following:
// if (secret.length > 1 && secret[0] == 0 && (secret[1] & 0x80) == 0) {
// byte[] tmp = new byte[secret.length - 1];
// System.arraycopy(secret, 1, tmp, 0, tmp.length);
// Util.bzero(secret);
// return normalize(tmp);
// } else {
// return secret;
// }
int len = secret.length;
if (len < 2) {
return secret;
}
// secret[0] == 0
int a = 0;
int s0 = secret[0] & 0xff;
for (int i = 0; i < 8; i++) {
int j = s0 >>> i;
j &= 0x1;
a |= j;
}
a ^= 0x1;
// (secret[1..n] & 0x80) == 0 && secret[1..n] != 0
int offset = 0;
for (int i = 1; i < len; i++) {
int j = secret[i] & 0x80;
j >>>= 7;
j ^= 0x1;
a &= j;
offset += a;
j = secret[i] & 0x7f;
for (int k = 0; k < 7; k++) {
int l = j >>> k;
l &= 0x1;
l ^= 0x1;
a &= l;
}
}
len -= offset;
// Try to remain timing safe by performing an allocation + copy for leading bytes removed
byte[] foo = new byte[len];
byte[] bar = new byte[offset];
System.arraycopy(secret, 0, bar, 0, offset);
System.arraycopy(secret, offset, foo, 0, len);
Util.bzero(secret);
return foo;
}
protected boolean verify(String alg, byte[] K_S, int index, byte[] sig_of_H) throws Exception {
int i, j;
i = index;
boolean result = false;
if (alg.equals("ssh-rsa")) {
byte[] tmp;
byte[] ee;
byte[] n;
type = RSA;
key_alg_name = alg;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
ee = tmp;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
n = tmp;
SignatureRSA sig = null;
Buffer buf = new Buffer(sig_of_H);
String foo = Util.byte2str(buf.getString());
try {
Class<? extends SignatureRSA> c =
Class.forName(session.getConfig(foo)).asSubclass(SignatureRSA.class);
sig = c.getDeclaredConstructor().newInstance();
sig.init();
} catch (Exception e) {
throw new JSchException(e.toString(), e);
}
sig.setPubKey(ee, n);
sig.update(H);
result = sig.verify(sig_of_H);
if (session.getLogger().isEnabled(Logger.INFO)) {
session.getLogger().log(Logger.INFO, "ssh_rsa_verify: " + foo + " signature " + result);
}
} else if (alg.equals("ssh-dss")) {
byte[] q = null;
byte[] tmp;
byte[] p;
byte[] g;
byte[] f;
type = DSS;
key_alg_name = alg;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
p = tmp;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
q = tmp;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
g = tmp;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
f = tmp;
SignatureDSA sig = null;
try {
Class<? extends SignatureDSA> c =
Class.forName(session.getConfig("signature.dss")).asSubclass(SignatureDSA.class);
sig = c.getDeclaredConstructor().newInstance();
sig.init();
} catch (Exception e) {
throw new JSchException(e.toString(), e);
}
sig.setPubKey(f, p, q, g);
sig.update(H);
result = sig.verify(sig_of_H);
if (session.getLogger().isEnabled(Logger.INFO)) {
session.getLogger().log(Logger.INFO, "ssh_dss_verify: signature " + result);
}
} else if (alg.equals("ecdsa-sha2-nistp256") || alg.equals("ecdsa-sha2-nistp384")
|| alg.equals("ecdsa-sha2-nistp521")) {
byte[] tmp;
byte[] r;
byte[] s;
// RFC 5656,
type = ECDSA;
key_alg_name = alg;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
i++;
tmp = new byte[(j - 1) / 2];
System.arraycopy(K_S, i, tmp, 0, tmp.length);
i += (j - 1) / 2;
r = tmp;
tmp = new byte[(j - 1) / 2];
System.arraycopy(K_S, i, tmp, 0, tmp.length);
i += (j - 1) / 2;
s = tmp;
SignatureECDSA sig = null;
try {
Class<? extends SignatureECDSA> c =
Class.forName(session.getConfig(alg)).asSubclass(SignatureECDSA.class);
sig = c.getDeclaredConstructor().newInstance();
sig.init();
} catch (Exception e) {
throw new JSchException(e.toString(), e);
}
sig.setPubKey(r, s);
sig.update(H);
result = sig.verify(sig_of_H);
if (session.getLogger().isEnabled(Logger.INFO)) {
session.getLogger().log(Logger.INFO, "ssh_ecdsa_verify: " + alg + " signature " + result);
}
} else if (alg.equals("ssh-ed25519") || alg.equals("ssh-ed448")) {
byte[] tmp;
// RFC 8709,
type = EDDSA;
key_alg_name = alg;
j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000)
| ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
tmp = new byte[j];
System.arraycopy(K_S, i, tmp, 0, j);
i += j;
SignatureEdDSA sig = null;
try {
Class<? extends SignatureEdDSA> c =
Class.forName(session.getConfig(alg)).asSubclass(SignatureEdDSA.class);
sig = c.getDeclaredConstructor().newInstance();
sig.init();
} catch (Exception | NoClassDefFoundError e) {
throw new JSchException(e.toString(), e);
}
sig.setPubKey(tmp);
sig.update(H);
result = sig.verify(sig_of_H);
if (session.getLogger().isEnabled(Logger.INFO)) {
session.getLogger().log(Logger.INFO, "ssh_eddsa_verify: " + alg + " signature " + result);
}
} else {
if (session.getLogger().isEnabled(Logger.ERROR)) {
session.getLogger().log(Logger.ERROR, "unknown alg: " + alg);
}
}
return result;
}
protected byte[] encodeAsMPInt(byte[] raw) {
int i = (raw[0] & 0x80) >>> 7;
int len = raw.length + i;
byte[] foo = new byte[len + 4];
// Try to remain timing safe by performing an extra allocation when i == 0
byte[] bar = new byte[i ^ 0x1];
foo[0] = (byte) (len >>> 24);
foo[1] = (byte) (len >>> 16);
foo[2] = (byte) (len >>> 8);
foo[3] = (byte) (len);
System.arraycopy(raw, 0, foo, 4 + i, len - i);
Util.bzero(raw);
return foo;
}
protected byte[] encodeAsString(byte[] raw) {
int len = raw.length;
byte[] foo = new byte[len + 4];
foo[0] = (byte) (len >>> 24);
foo[1] = (byte) (len >>> 16);
foo[2] = (byte) (len >>> 8);
foo[3] = (byte) (len);
System.arraycopy(raw, 0, foo, 4, len);
Util.bzero(raw);
return foo;
}
}