KeyPair.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.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class KeyPair {
/** DEFERRED should not be be used. */
public static final int DEFERRED = -1;
public static final int ERROR = 0;
public static final int DSA = 1;
public static final int RSA = 2;
public static final int ECDSA = 3;
public static final int UNKNOWN = 4;
public static final int ED25519 = 5;
public static final int ED448 = 6;
static final int VENDOR_OPENSSH = 0;
static final int VENDOR_FSECURE = 1;
static final int VENDOR_PUTTY = 2;
static final int VENDOR_PKCS8 = 3;
static final int VENDOR_OPENSSH_V1 = 4;
static final int VENDOR_PUTTY_V3 = 5;
int vendor = VENDOR_OPENSSH;
private static final byte[] AUTH_MAGIC = Util.str2byte("openssh-key-v1\0");
private static final byte[] cr = Util.str2byte("\n");
public static KeyPair genKeyPair(JSch jsch, int type) throws JSchException {
return genKeyPair(jsch, type, 1024);
}
public static KeyPair genKeyPair(JSch jsch, int type, int key_size) throws JSchException {
KeyPair kpair = null;
if (type == DSA) {
kpair = new KeyPairDSA(jsch.instLogger);
} else if (type == RSA) {
kpair = new KeyPairRSA(jsch.instLogger);
} else if (type == ECDSA) {
kpair = new KeyPairECDSA(jsch.instLogger);
} else if (type == ED25519) {
kpair = new KeyPairEd25519(jsch.instLogger);
} else if (type == ED448) {
kpair = new KeyPairEd448(jsch.instLogger);
}
if (kpair != null) {
kpair.generate(key_size);
}
return kpair;
}
abstract void generate(int key_size) throws JSchException;
abstract byte[] getBegin();
abstract byte[] getEnd();
public abstract int getKeySize();
public abstract byte[] getSignature(byte[] data);
public abstract byte[] getSignature(byte[] data, String alg);
public abstract Signature getVerifier();
public abstract Signature getVerifier(String alg);
public abstract byte[] forSSHAgent() throws JSchException;
public String getPublicKeyComment() {
return publicKeyComment;
}
public void setPublicKeyComment(String publicKeyComment) {
this.publicKeyComment = publicKeyComment;
}
protected String publicKeyComment = "no comment";
JSch.InstanceLogger instLogger;
protected Cipher cipher;
private KDF kdf;
private HASH sha1;
private HASH hash;
private Random random;
private byte[] passphrase;
KeyPair(JSch.InstanceLogger instLogger) {
this.instLogger = instLogger;
}
static byte[][] header =
{Util.str2byte("Proc-Type: 4,ENCRYPTED"), Util.str2byte("DEK-Info: DES-EDE3-CBC,")};
abstract byte[] getPrivateKey();
/**
* Writes the plain private key to the given output stream.
*
* @param out output stream
* @see #writePrivateKey(OutputStream out, byte[] passphrase)
*/
public void writePrivateKey(OutputStream out) {
this.writePrivateKey(out, null);
}
/**
* Writes the cyphered private key to the given output stream.
*
* @param out output stream
* @param passphrase a passphrase to encrypt the private key
*/
public void writePrivateKey(OutputStream out, byte[] passphrase) {
if (passphrase == null)
passphrase = this.passphrase;
byte[] plain = getPrivateKey();
byte[][] _iv = new byte[1][];
byte[] encoded = encrypt(plain, _iv, passphrase);
if (encoded != plain)
Util.bzero(plain);
byte[] iv = _iv[0];
byte[] prv = Util.toBase64(encoded, 0, encoded.length, true);
try {
out.write(getBegin());
out.write(cr);
if (passphrase != null) {
out.write(header[0]);
out.write(cr);
out.write(header[1]);
for (int i = 0; i < iv.length; i++) {
out.write(b2a((byte) ((iv[i] >>> 4) & 0x0f)));
out.write(b2a((byte) (iv[i] & 0x0f)));
}
out.write(cr);
out.write(cr);
}
int i = 0;
while (i < prv.length) {
if (i + 64 < prv.length) {
out.write(prv, i, 64);
out.write(cr);
i += 64;
continue;
}
out.write(prv, i, prv.length - i);
out.write(cr);
break;
}
out.write(getEnd());
out.write(cr);
// out.close();
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to write private key", e);
}
}
}
private static byte[] space = Util.str2byte(" ");
abstract byte[] getKeyTypeName();
public abstract int getKeyType();
/**
* Wrapper to provide the String representation of {@code #getKeyTypeName()}.
*
* @return the standard SSH key type string
*/
public String getKeyTypeString() {
return Util.byte2str(getKeyTypeName());
}
/**
* Returns the blob of the public key.
*
* @return blob of the public key
*/
public byte[] getPublicKeyBlob() {
// TODO JSchException should be thrown
// if(publickeyblob == null)
// throw new JSchException("public-key blob is not available");
return publickeyblob;
}
/**
* Writes the public key with the specified comment to the output stream.
*
* @param out output stream
* @param comment comment
*/
public void writePublicKey(OutputStream out, String comment) {
byte[] pubblob = getPublicKeyBlob();
byte[] pub = Util.toBase64(pubblob, 0, pubblob.length, true);
try {
out.write(getKeyTypeName());
out.write(space);
out.write(pub, 0, pub.length);
out.write(space);
out.write(Util.str2byte(comment));
out.write(cr);
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to write public key", e);
}
}
}
/**
* Writes the public key with the specified comment to the file.
*
* @param name file name
* @param comment comment
* @see #writePublicKey(OutputStream out, String comment)
*/
public void writePublicKey(String name, String comment)
throws FileNotFoundException, IOException {
try (OutputStream fos = new FileOutputStream(name)) {
writePublicKey(fos, comment);
}
}
/**
* Writes the public key with the specified comment to the output stream in the format defined in
* http://www.ietf.org/rfc/rfc4716.txt
*
* @param out output stream
* @param comment comment
*/
public void writeSECSHPublicKey(OutputStream out, String comment) {
byte[] pubblob = getPublicKeyBlob();
byte[] pub = Util.toBase64(pubblob, 0, pubblob.length, true);
try {
out.write(Util.str2byte("---- BEGIN SSH2 PUBLIC KEY ----"));
out.write(cr);
out.write(Util.str2byte("Comment: \"" + comment + "\""));
out.write(cr);
int index = 0;
while (index < pub.length) {
int len = 70;
if ((pub.length - index) < len)
len = pub.length - index;
out.write(pub, index, len);
out.write(cr);
index += len;
}
out.write(Util.str2byte("---- END SSH2 PUBLIC KEY ----"));
out.write(cr);
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to write public key", e);
}
}
}
/**
* Writes the public key with the specified comment to the output stream in the format defined in
* http://www.ietf.org/rfc/rfc4716.txt
*
* @param name file name
* @param comment comment
* @see #writeSECSHPublicKey(OutputStream out, String comment)
*/
public void writeSECSHPublicKey(String name, String comment)
throws FileNotFoundException, IOException {
try (OutputStream fos = new FileOutputStream(name)) {
writeSECSHPublicKey(fos, comment);
}
}
/**
* Writes the plain private key to the file.
*
* @param name file name
* @see #writePrivateKey(String name, byte[] passphrase)
*/
public void writePrivateKey(String name) throws FileNotFoundException, IOException {
this.writePrivateKey(name, null);
}
/**
* Writes the cyphered private key to the file.
*
* @param name file name
* @param passphrase a passphrase to encrypt the private key
* @see #writePrivateKey(OutputStream out, byte[] passphrase)
*/
public void writePrivateKey(String name, byte[] passphrase)
throws FileNotFoundException, IOException {
try (OutputStream fos = new FileOutputStream(name)) {
writePrivateKey(fos, passphrase);
}
}
/**
* Returns the finger-print of the public key.
*
* @return finger print
*/
public String getFingerPrint() {
if (hash == null)
hash = genHash();
byte[] kblob = getPublicKeyBlob();
if (kblob == null)
return null;
return Util.getFingerPrint(hash, kblob, false, true);
}
private byte[] encrypt(byte[] plain, byte[][] _iv, byte[] passphrase) {
if (passphrase == null)
return plain;
if (cipher == null)
cipher = genCipher();
byte[] iv = _iv[0] = new byte[cipher.getIVSize()];
if (random == null)
random = genRandom();
random.fill(iv, 0, iv.length);
byte[] key = genKey(passphrase, iv);
byte[] encoded = plain;
// PKCS#5Padding
{
// int bsize=cipher.getBlockSize();
int bsize = cipher.getIVSize();
byte[] foo = new byte[(encoded.length / bsize + 1) * bsize];
System.arraycopy(encoded, 0, foo, 0, encoded.length);
int padding = bsize - encoded.length % bsize;
for (int i = foo.length - 1; (foo.length - padding) <= i; i--) {
foo[i] = (byte) padding;
}
encoded = foo;
}
try {
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
cipher.update(encoded, 0, encoded.length, encoded, 0);
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to encrypt key", e);
}
}
Util.bzero(key);
return encoded;
}
abstract boolean parse(byte[] data);
private byte[] decrypt(byte[] data, byte[] passphrase, byte[] iv) {
try {
byte[] key = genKey(passphrase, iv);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
Util.bzero(key);
byte[] plain = new byte[data.length];
cipher.update(data, 0, data.length, plain, 0);
return plain;
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to decrypt key", e);
}
}
return null;
}
int writeSEQUENCE(byte[] buf, int index, int len) {
buf[index++] = 0x30;
index = writeLength(buf, index, len);
return index;
}
int writeINTEGER(byte[] buf, int index, byte[] data) {
buf[index++] = 0x02;
index = writeLength(buf, index, data.length);
System.arraycopy(data, 0, buf, index, data.length);
index += data.length;
return index;
}
int writeOCTETSTRING(byte[] buf, int index, byte[] data) {
buf[index++] = 0x04;
index = writeLength(buf, index, data.length);
System.arraycopy(data, 0, buf, index, data.length);
index += data.length;
return index;
}
int writeDATA(byte[] buf, byte n, int index, byte[] data) {
buf[index++] = n;
index = writeLength(buf, index, data.length);
System.arraycopy(data, 0, buf, index, data.length);
index += data.length;
return index;
}
int countLength(int len) {
int i = 1;
if (len <= 0x7f)
return i;
while (len > 0) {
len >>>= 8;
i++;
}
return i;
}
int writeLength(byte[] data, int index, int len) {
int i = countLength(len) - 1;
if (i == 0) {
data[index++] = (byte) len;
return index;
}
data[index++] = (byte) (0x80 | i);
int j = index + i;
while (i > 0) {
data[index + i - 1] = (byte) (len & 0xff);
len >>>= 8;
i--;
}
return j;
}
private Random genRandom() {
if (random == null) {
try {
Class<? extends Random> c =
Class.forName(JSch.getConfig("random")).asSubclass(Random.class);
random = c.getDeclaredConstructor().newInstance();
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to create random", e);
}
}
}
return random;
}
private HASH genHash() {
try {
Class<? extends HASH> c = Class.forName(JSch.getConfig("md5")).asSubclass(HASH.class);
hash = c.getDeclaredConstructor().newInstance();
hash.init();
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to create hash", e);
}
}
return hash;
}
private Cipher genCipher() {
try {
Class<? extends Cipher> c =
Class.forName(JSch.getConfig("3des-cbc")).asSubclass(Cipher.class);
cipher = c.getDeclaredConstructor().newInstance();
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to create cipher", e);
}
}
return cipher;
}
/*
* hash is MD5 h(0) <- hash(passphrase, iv); h(n) <- hash(h(n-1), passphrase, iv); key <-
* (h(0),...,h(n))[0,..,key.length];
*/
synchronized byte[] genKey(byte[] passphrase, byte[] iv) {
if (cipher == null)
cipher = genCipher();
if (hash == null)
hash = genHash();
byte[] key = new byte[cipher.getBlockSize()];
int hsize = hash.getBlockSize();
byte[] hn = new byte[key.length / hsize * hsize + (key.length % hsize == 0 ? 0 : hsize)];
try {
byte[] tmp = null;
if (vendor == VENDOR_OPENSSH) {
for (int index = 0; index + hsize <= hn.length;) {
if (tmp != null) {
hash.update(tmp, 0, tmp.length);
}
hash.update(passphrase, 0, passphrase.length);
hash.update(iv, 0, iv.length > 8 ? 8 : iv.length);
tmp = hash.digest();
System.arraycopy(tmp, 0, hn, index, tmp.length);
index += tmp.length;
}
System.arraycopy(hn, 0, key, 0, key.length);
} else if (vendor == VENDOR_OPENSSH_V1) {
tmp = kdf.getKey(passphrase, cipher.getBlockSize() + cipher.getIVSize());
System.arraycopy(tmp, 0, key, 0, key.length);
System.arraycopy(tmp, key.length, iv, 0, iv.length);
Util.bzero(tmp);
} else if (vendor == VENDOR_FSECURE) {
for (int index = 0; index + hsize <= hn.length;) {
if (tmp != null) {
hash.update(tmp, 0, tmp.length);
}
hash.update(passphrase, 0, passphrase.length);
tmp = hash.digest();
System.arraycopy(tmp, 0, hn, index, tmp.length);
index += tmp.length;
}
System.arraycopy(hn, 0, key, 0, key.length);
} else if (vendor == VENDOR_PUTTY) {
byte[] i = new byte[4];
sha1.update(i, 0, i.length);
sha1.update(passphrase, 0, passphrase.length);
tmp = sha1.digest();
System.arraycopy(tmp, 0, key, 0, tmp.length);
Util.bzero(tmp);
i[3] = (byte) 1;
sha1.update(i, 0, i.length);
sha1.update(passphrase, 0, passphrase.length);
tmp = sha1.digest();
System.arraycopy(tmp, 0, key, tmp.length, key.length - tmp.length);
Util.bzero(tmp);
} else if (vendor == VENDOR_PUTTY_V3) {
tmp = kdf.getKey(passphrase, cipher.getBlockSize() + cipher.getIVSize() + 32);
System.arraycopy(tmp, 0, key, 0, key.length);
System.arraycopy(tmp, key.length, iv, 0, iv.length);
Util.bzero(tmp);
}
} catch (Exception e) {
if (instLogger.getLogger().isEnabled(Logger.ERROR)) {
instLogger.getLogger().log(Logger.ERROR, "failed to generate key from passphrase", e);
}
}
return key;
}
/**
* @deprecated use #writePrivateKey(OutputStream out, byte[] passphrase)
*/
@Deprecated
public void setPassphrase(String passphrase) {
if (passphrase == null || passphrase.length() == 0) {
setPassphrase((byte[]) null);
} else {
setPassphrase(Util.str2byte(passphrase));
}
}
/**
* @deprecated use #writePrivateKey(String name, byte[] passphrase)
*/
@Deprecated
public void setPassphrase(byte[] passphrase) {
if (passphrase != null && passphrase.length == 0)
passphrase = null;
this.passphrase = passphrase;
}
protected boolean encrypted = false;
protected byte[] data = null;
private byte[] iv = null;
private byte[] publickeyblob = null;
public boolean isEncrypted() {
return encrypted;
}
public boolean decrypt(String _passphrase) {
if (_passphrase == null || _passphrase.length() == 0) {
return !encrypted;
}
return decrypt(Util.str2byte(_passphrase));
}
public boolean decrypt(byte[] _passphrase) {
if (!encrypted) {
return true;
}
if (_passphrase == null) {
return !encrypted;
}
byte[] bar = new byte[_passphrase.length];
System.arraycopy(_passphrase, 0, bar, 0, bar.length);
_passphrase = bar;
byte[] foo = null;
try {
foo = decrypt(data, _passphrase, iv);
if (parse(foo)) {
encrypted = false;
Util.bzero(data);
}
} finally {
Util.bzero(_passphrase);
Util.bzero(foo);
}
return !encrypted;
}
public static KeyPair load(JSch jsch, String prvkey) throws JSchException {
String pubkey = prvkey + ".pub";
if (!new File(pubkey).exists()) {
pubkey = null;
}
return load(jsch.instLogger, prvkey, pubkey);
}
public static KeyPair load(JSch jsch, String prvfile, String pubfile) throws JSchException {
return load(jsch.instLogger, prvfile, pubfile);
}
static KeyPair load(JSch.InstanceLogger instLogger, String prvfile, String pubfile)
throws JSchException {
byte[] prvkey = null;
byte[] pubkey = null;
try {
prvkey = Util.fromFile(prvfile);
} catch (IOException e) {
throw new JSchException(e.toString(), e);
}
String _pubfile = pubfile;
if (pubfile == null) {
_pubfile = prvfile + ".pub";
}
try {
pubkey = Util.fromFile(_pubfile);
} catch (IOException e) {
if (pubfile != null) {
throw new JSchException(e.toString(), e);
}
}
try {
return load(instLogger, prvkey, pubkey);
} finally {
Util.bzero(prvkey);
}
}
public static KeyPair load(JSch jsch, byte[] prvkey, byte[] pubkey) throws JSchException {
return load(jsch.instLogger, prvkey, pubkey);
}
static KeyPair load(JSch.InstanceLogger instLogger, byte[] prvkey, byte[] pubkey)
throws JSchException {
byte[] iv = new byte[8]; // 8
boolean encrypted = true;
byte[] data = null;
byte[] publickeyblob = null;
int type = ERROR;
int vendor = VENDOR_OPENSSH;
String publicKeyComment = "";
Cipher cipher = null;
// prvkey from "ssh-add" command on the remote.
if (pubkey == null && prvkey != null
&& (prvkey.length > 11 && prvkey[0] == 0 && prvkey[1] == 0 && prvkey[2] == 0 &&
// length of key type string
(prvkey[3] == 7 || prvkey[3] == 9 || prvkey[3] == 11 || prvkey[3] == 19))) {
Buffer buf = new Buffer(prvkey);
buf.skip(prvkey.length); // for using Buffer#available()
String _type = Util.byte2str(buf.getString()); // ssh-rsa or ssh-dss
buf.rewind();
KeyPair kpair = null;
if (_type.equals("ssh-rsa")) {
kpair = KeyPairRSA.fromSSHAgent(instLogger, buf);
} else if (_type.equals("ssh-dss")) {
kpair = KeyPairDSA.fromSSHAgent(instLogger, buf);
} else if (_type.equals("ecdsa-sha2-nistp256") || _type.equals("ecdsa-sha2-nistp384")
|| _type.equals("ecdsa-sha2-nistp521")) {
kpair = KeyPairECDSA.fromSSHAgent(instLogger, buf);
} else if (_type.equals("ssh-ed25519")) {
kpair = KeyPairEd25519.fromSSHAgent(instLogger, buf);
} else if (_type.equals("ssh-ed448")) {
kpair = KeyPairEd448.fromSSHAgent(instLogger, buf);
} else {
throw new JSchException("privatekey: invalid key " + _type);
}
return kpair;
}
try {
byte[] buf = prvkey;
if (buf != null) {
KeyPair ppk = loadPPK(instLogger, buf);
if (ppk != null)
return ppk;
}
int len = (buf != null ? buf.length : 0);
int i = 0;
// skip garbage lines.
while (i < len) {
if (buf[i] == '-' && i + 4 < len && buf[i + 1] == '-' && buf[i + 2] == '-'
&& buf[i + 3] == '-' && buf[i + 4] == '-') {
break;
}
i++;
}
while (i < len) {
if (buf[i] == 'B' && i + 3 < len && buf[i + 1] == 'E' && buf[i + 2] == 'G'
&& buf[i + 3] == 'I') {
i += 6;
if (i + 2 >= len)
throw new JSchException("invalid privatekey");
if (buf[i] == 'D' && buf[i + 1] == 'S' && buf[i + 2] == 'A') {
type = DSA;
} else if (buf[i] == 'R' && buf[i + 1] == 'S' && buf[i + 2] == 'A') {
type = RSA;
} else if (buf[i] == 'E' && buf[i + 1] == 'C') {
type = ECDSA;
} else if (buf[i] == 'S' && buf[i + 1] == 'S' && buf[i + 2] == 'H') { // FSecure
type = UNKNOWN;
vendor = VENDOR_FSECURE;
} else if (i + 6 < len && buf[i] == 'P' && buf[i + 1] == 'R' && buf[i + 2] == 'I'
&& buf[i + 3] == 'V' && buf[i + 4] == 'A' && buf[i + 5] == 'T' && buf[i + 6] == 'E') {
type = UNKNOWN;
vendor = VENDOR_PKCS8;
encrypted = false;
i += 3;
} else if (i + 8 < len && buf[i] == 'E' && buf[i + 1] == 'N' && buf[i + 2] == 'C'
&& buf[i + 3] == 'R' && buf[i + 4] == 'Y' && buf[i + 5] == 'P' && buf[i + 6] == 'T'
&& buf[i + 7] == 'E' && buf[i + 8] == 'D') {
type = UNKNOWN;
vendor = VENDOR_PKCS8;
i += 5;
} else if (isOpenSSHPrivateKey(buf, i, len)) {
type = UNKNOWN;
vendor = VENDOR_OPENSSH_V1;
} else {
throw new JSchException("invalid privatekey");
}
i += 3;
continue;
}
if (buf[i] == 'A' && i + 7 < len && buf[i + 1] == 'E' && buf[i + 2] == 'S'
&& buf[i + 3] == '-' && buf[i + 4] == '2' && buf[i + 5] == '5' && buf[i + 6] == '6'
&& buf[i + 7] == '-') {
i += 8;
if (Session.checkCipher(JSch.getConfig("aes256-cbc"))) {
Class<? extends Cipher> c =
Class.forName(JSch.getConfig("aes256-cbc")).asSubclass(Cipher.class);
cipher = c.getDeclaredConstructor().newInstance();
// key=new byte[cipher.getBlockSize()];
iv = new byte[cipher.getIVSize()];
} else {
throw new JSchException("privatekey: aes256-cbc is not available");
}
continue;
}
if (buf[i] == 'A' && i + 7 < len && buf[i + 1] == 'E' && buf[i + 2] == 'S'
&& buf[i + 3] == '-' && buf[i + 4] == '1' && buf[i + 5] == '9' && buf[i + 6] == '2'
&& buf[i + 7] == '-') {
i += 8;
if (Session.checkCipher(JSch.getConfig("aes192-cbc"))) {
Class<? extends Cipher> c =
Class.forName(JSch.getConfig("aes192-cbc")).asSubclass(Cipher.class);
cipher = c.getDeclaredConstructor().newInstance();
// key=new byte[cipher.getBlockSize()];
iv = new byte[cipher.getIVSize()];
} else {
throw new JSchException("privatekey: aes192-cbc is not available");
}
continue;
}
if (buf[i] == 'A' && i + 7 < len && buf[i + 1] == 'E' && buf[i + 2] == 'S'
&& buf[i + 3] == '-' && buf[i + 4] == '1' && buf[i + 5] == '2' && buf[i + 6] == '8'
&& buf[i + 7] == '-') {
i += 8;
if (Session.checkCipher(JSch.getConfig("aes128-cbc"))) {
Class<? extends Cipher> c =
Class.forName(JSch.getConfig("aes128-cbc")).asSubclass(Cipher.class);
cipher = c.getDeclaredConstructor().newInstance();
// key=new byte[cipher.getBlockSize()];
iv = new byte[cipher.getIVSize()];
} else {
throw new JSchException("privatekey: aes128-cbc is not available");
}
continue;
}
if (buf[i] == 'C' && i + 3 < len && buf[i + 1] == 'B' && buf[i + 2] == 'C'
&& buf[i + 3] == ',') {
i += 4;
for (int ii = 0; ii < iv.length; ii++) {
iv[ii] = (byte) (((a2b(buf[i++]) << 4) & 0xf0) + (a2b(buf[i++]) & 0xf));
}
continue;
}
if (buf[i] == '\r' && i + 1 < buf.length && buf[i + 1] == '\n') {
i++;
continue;
}
if (buf[i] == '\n' && i + 1 < buf.length) {
if (buf[i + 1] == '\n') {
i += 2;
break;
}
if (buf[i + 1] == '\r' && i + 2 < buf.length && buf[i + 2] == '\n') {
i += 3;
break;
}
boolean inheader = false;
for (int j = i + 1; j < buf.length; j++) {
if (buf[j] == '\n')
break;
// if(buf[j]=='\r') break;
if (buf[j] == ':') {
inheader = true;
break;
}
}
if (!inheader) {
i++;
if (vendor != VENDOR_PKCS8)
encrypted = false; // no passphrase
break;
}
}
i++;
}
if (buf != null) {
if (type == ERROR) {
throw new JSchException("invalid privatekey");
}
int start = i;
while (i < len) {
if (buf[i] == '-') {
break;
}
i++;
}
if ((len - i) == 0 || (i - start) == 0) {
throw new JSchException("invalid privatekey");
}
// The content of 'buf' will be changed, so it should be copied.
byte[] tmp = new byte[i - start];
System.arraycopy(buf, start, tmp, 0, tmp.length);
byte[] _buf = tmp;
start = 0;
i = 0;
int _len = _buf.length;
while (i < _len) {
if (_buf[i] == '\n') {
boolean xd = (i > 0 && _buf[i - 1] == '\r');
// ignore \n (or \r\n)
System.arraycopy(_buf, i + 1, _buf, i - (xd ? 1 : 0), _len - (i + 1));
if (xd) {
_len--;
i--;
}
_len--;
continue;
}
if (_buf[i] == '-') {
break;
}
i++;
}
if (i - start > 0)
data = Util.fromBase64(_buf, start, i - start);
Util.bzero(_buf);
}
if (vendor == VENDOR_OPENSSH_V1) {
return loadOpenSSHKeyv1(instLogger, data);
} else if (data != null && data.length > 4 && // FSecure
data[0] == (byte) 0x3f && data[1] == (byte) 0x6f && data[2] == (byte) 0xf9
&& data[3] == (byte) 0xeb) {
Buffer _buf = new Buffer(data);
_buf.getInt(); // 0x3f6ff9be
_buf.getInt();
byte[] _type = _buf.getString();
String _cipher = Util.byte2str(_buf.getString());
if (_cipher.equals("3des-cbc")) {
_buf.getInt();
byte[] foo = new byte[data.length - _buf.getOffSet()];
_buf.getByte(foo);
data = foo;
encrypted = true;
throw new JSchException(
"cipher " + _cipher + " is not supported for this privatekey format");
} else if (_cipher.equals("none")) {
_buf.getInt();
_buf.getInt();
encrypted = false;
byte[] foo = new byte[data.length - _buf.getOffSet()];
_buf.getByte(foo);
data = foo;
} else {
throw new JSchException(
"cipher " + _cipher + " is not supported for this privatekey format");
}
}
if (pubkey != null) {
try {
buf = pubkey;
len = buf.length;
if (buf.length > 4 && // FSecure's public key
buf[0] == '-' && buf[1] == '-' && buf[2] == '-' && buf[3] == '-') {
boolean valid = true;
i = 0;
do {
i++;
} while (buf.length > i && buf[i] != '\n');
if (buf.length <= i) {
valid = false;
}
while (valid) {
if (buf[i] == '\n') {
boolean inheader = false;
for (int j = i + 1; j < buf.length; j++) {
if (buf[j] == '\n')
break;
if (buf[j] == ':') {
inheader = true;
break;
}
}
if (!inheader) {
i++;
break;
}
}
i++;
}
if (buf.length <= i) {
valid = false;
}
int start = i;
while (valid && i < len) {
if (buf[i] == '\n') {
System.arraycopy(buf, i + 1, buf, i, len - i - 1);
len--;
continue;
}
if (buf[i] == '-') {
break;
}
i++;
}
if (valid) {
publickeyblob = Util.fromBase64(buf, start, i - start);
if (prvkey == null || type == UNKNOWN) {
if (publickeyblob[8] == 'd') {
type = DSA;
} else if (publickeyblob[8] == 'r') {
type = RSA;
}
}
}
} else {
if (buf[0] == 's' && buf[1] == 's' && buf[2] == 'h' && buf[3] == '-') {
if (prvkey == null && buf.length > 7) {
if (buf[4] == 'd') {
type = DSA;
} else if (buf[4] == 'r') {
type = RSA;
} else if (buf[4] == 'e' && buf[6] == '2') {
type = ED25519;
} else if (buf[4] == 'e' && buf[6] == '4') {
type = ED448;
}
}
i = 0;
while (i < len) {
if (buf[i] == ' ')
break;
i++;
}
i++;
if (i < len) {
int start = i;
while (i < len) {
if (buf[i] == ' ')
break;
i++;
}
publickeyblob = Util.fromBase64(buf, start, i - start);
}
if (i++ < len) {
int start = i;
while (i < len) {
if (buf[i] == '\n')
break;
i++;
}
if (i > 0 && buf[i - 1] == '\r')
i--;
if (start < i) {
publicKeyComment = Util.byte2str(buf, start, i - start);
}
}
} else if (buf[0] == 'e' && buf[1] == 'c' && buf[2] == 'd' && buf[3] == 's') {
if (prvkey == null && buf.length > 7) {
type = ECDSA;
}
i = 0;
while (i < len) {
if (buf[i] == ' ')
break;
i++;
}
i++;
if (i < len) {
int start = i;
while (i < len) {
if (buf[i] == ' ')
break;
i++;
}
publickeyblob = Util.fromBase64(buf, start, i - start);
}
if (i++ < len) {
int start = i;
while (i < len) {
if (buf[i] == '\n')
break;
i++;
}
if (i > 0 && buf[i - 1] == '\r')
i--;
if (start < i) {
publicKeyComment = Util.byte2str(buf, start, i - start);
}
}
}
}
} catch (Exception ee) {
if (instLogger.getLogger().isEnabled(Logger.WARN)) {
instLogger.getLogger().log(Logger.WARN, "failed to parse public key", ee);
}
}
}
KeyPair kpair = null;
if (type == DSA) {
kpair = new KeyPairDSA(instLogger);
} else if (type == RSA) {
kpair = new KeyPairRSA(instLogger);
} else if (type == ECDSA) {
kpair = new KeyPairECDSA(instLogger, pubkey);
} else if (type == ED25519) {
kpair = new KeyPairEd25519(instLogger, pubkey, null);
} else if (type == ED448) {
kpair = new KeyPairEd448(instLogger, pubkey, null);
} else if (vendor == VENDOR_PKCS8) {
kpair = new KeyPairPKCS8(instLogger);
}
if (kpair != null) {
kpair.encrypted = encrypted;
kpair.publickeyblob = publickeyblob;
kpair.vendor = vendor;
kpair.publicKeyComment = publicKeyComment;
kpair.cipher = cipher;
if (encrypted) {
kpair.encrypted = true;
kpair.iv = iv;
kpair.data = data;
} else {
if (kpair.parse(data)) {
kpair.encrypted = false;
} else {
throw new JSchException("invalid privatekey");
}
}
}
return kpair;
} catch (Exception | NoClassDefFoundError e) {
Util.bzero(data);
if (e instanceof JSchException)
throw (JSchException) e;
throw new JSchException(e.toString(), e);
}
}
static KeyPair loadOpenSSHKeyv1(JSch.InstanceLogger instLogger, byte[] data)
throws JSchException {
if (data == null) {
throw new JSchException("invalid privatekey");
}
Buffer buffer = new Buffer(data);
byte[] magic = new byte[AUTH_MAGIC.length];
buffer.getByte(magic);
if (!Util.arraysequals(AUTH_MAGIC, magic)) {
throw new JSchException("Invalid openssh v1 format.");
}
String cipherName = Util.byte2str(buffer.getString());
String kdfName = Util.byte2str(buffer.getString()); // string kdfname
byte[] kdfOptions = buffer.getString(); // string kdfoptions
int nrKeys = buffer.getInt(); // int number of keys N; Should be 1
if (nrKeys != 1) {
throw new JSchException("We don't support having more than 1 key in the file (yet).");
}
byte[] publickeyblob = buffer.getString();
KeyPair kpair = parsePubkeyBlob(instLogger, publickeyblob, null);
kpair.encrypted = !"none".equals(cipherName);
kpair.publickeyblob = publickeyblob;
kpair.vendor = VENDOR_OPENSSH_V1;
kpair.publicKeyComment = "";
kpair.data = buffer.getString();
try {
if (!kpair.encrypted) {
if (!kpair.parse(kpair.data)) {
throw new JSchException("invalid privatekey");
} else {
Util.bzero(kpair.data);
}
} else {
if (Session.checkCipher(JSch.getConfig(cipherName))) {
try {
Class<? extends Cipher> c =
Class.forName(JSch.getConfig(cipherName)).asSubclass(Cipher.class);
kpair.cipher = c.getDeclaredConstructor().newInstance();
kpair.iv = new byte[kpair.cipher.getIVSize()];
} catch (Exception | NoClassDefFoundError e) {
throw new JSchException("cipher " + cipherName + " is not available", e);
}
} else {
throw new JSchException("cipher " + cipherName + " is not available");
}
try {
Buffer kdfOpts = new Buffer(kdfOptions);
byte[] salt = kdfOpts.getString();
int rounds = kdfOpts.getInt();
Class<? extends BCrypt> c =
Class.forName(JSch.getConfig(kdfName)).asSubclass(BCrypt.class);
BCrypt bcrypt = c.getDeclaredConstructor().newInstance();
bcrypt.init(salt, rounds);
kpair.kdf = bcrypt;
} catch (Exception | NoClassDefFoundError e) {
throw new JSchException("kdf " + kdfName + " is not available", e);
}
}
return kpair;
} catch (Exception e) {
Util.bzero(kpair.data);
throw e;
}
}
private static boolean isOpenSSHPrivateKey(byte[] buf, int i, int len) {
String ident = "OPENSSH PRIVATE KEY-----";
return i + ident.length() < len
&& ident.equals(Util.byte2str(Arrays.copyOfRange(buf, i, i + ident.length())));
}
private static byte a2b(byte c) {
if ('0' <= c && c <= '9')
return (byte) (c - '0');
return (byte) (c - 'a' + 10);
}
private static byte b2a(byte c) {
if (0 <= c && c <= 9)
return (byte) (c + '0');
return (byte) (c - 10 + 'A');
}
public void dispose() {
Util.bzero(passphrase);
}
@SuppressWarnings("deprecation")
@Override
public void finalize() {
dispose();
}
static KeyPair loadPPK(JSch.InstanceLogger instLogger, byte[] buf) throws JSchException {
byte[] pubkey = null;
byte[] prvkey = null;
byte[] _prvkey = null;
int lines = 0;
Buffer buffer = new Buffer(buf);
Map<String, String> v = new HashMap<>();
while (true) {
if (!parseHeader(buffer, v))
break;
}
int ppkVersion;
String typ = v.get("PuTTY-User-Key-File-2");
if (typ == null) {
typ = v.get("PuTTY-User-Key-File-3");
if (typ == null) {
return null;
} else {
ppkVersion = VENDOR_PUTTY_V3;
}
} else {
ppkVersion = VENDOR_PUTTY;
}
try {
lines = Integer.parseInt(v.get("Public-Lines"));
pubkey = parseLines(buffer, lines);
while (true) {
if (!parseHeader(buffer, v))
break;
}
lines = Integer.parseInt(v.get("Private-Lines"));
_prvkey = parseLines(buffer, lines);
while (true) {
if (!parseHeader(buffer, v))
break;
}
prvkey = Util.fromBase64(_prvkey, 0, _prvkey.length);
pubkey = Util.fromBase64(pubkey, 0, pubkey.length);
KeyPair kpair = parsePubkeyBlob(instLogger, pubkey, typ);
kpair.encrypted = !v.get("Encryption").equals("none");
kpair.publickeyblob = pubkey;
kpair.vendor = ppkVersion;
kpair.publicKeyComment = v.get("Comment");
if (kpair.encrypted) {
if (Session.checkCipher(JSch.getConfig("aes256-cbc"))) {
try {
Class<? extends Cipher> c =
Class.forName(JSch.getConfig("aes256-cbc")).asSubclass(Cipher.class);
kpair.cipher = c.getDeclaredConstructor().newInstance();
kpair.iv = new byte[kpair.cipher.getIVSize()];
} catch (Exception | NoClassDefFoundError e) {
throw new JSchException("The cipher 'aes256-cbc' is required, but it is not available.",
e);
}
} else {
throw new JSchException("The cipher 'aes256-cbc' is required, but it is not available.");
}
if (ppkVersion == VENDOR_PUTTY) {
try {
Class<? extends HASH> c = Class.forName(JSch.getConfig("sha-1")).asSubclass(HASH.class);
HASH sha1 = c.getDeclaredConstructor().newInstance();
sha1.init();
kpair.sha1 = sha1;
} catch (Exception | NoClassDefFoundError e) {
throw new JSchException("'sha-1' is required, but it is not available.", e);
}
} else {
String argonTypeStr = v.get("Key-Derivation");
String saltStr = v.get("Argon2-Salt");
if (argonTypeStr == null || saltStr == null
|| (saltStr != null && saltStr.length() % 2 != 0)) {
throw new JSchException("Invalid argon2 params.");
}
int argonType;
switch (argonTypeStr) {
case "Argon2d":
argonType = Argon2.ARGON2D;
break;
case "Argon2i":
argonType = Argon2.ARGON2I;
break;
case "Argon2id":
argonType = Argon2.ARGON2ID;
break;
default:
throw new JSchException("Invalid argon2 params.");
}
try {
int memory = Integer.parseInt(v.get("Argon2-Memory"));
int passes = Integer.parseInt(v.get("Argon2-Passes"));
int parallelism = Integer.parseInt(v.get("Argon2-Parallelism"));
byte[] salt = new byte[saltStr.length() / 2];
for (int i = 0; i < salt.length; i++) {
int j = i * 2;
salt[i] = (byte) Integer.parseInt(saltStr.substring(j, j + 2), 16);
}
Class<? extends Argon2> c =
Class.forName(JSch.getConfig("argon2")).asSubclass(Argon2.class);
Argon2 argon2 = c.getDeclaredConstructor().newInstance();
argon2.init(salt, passes, argonType, new byte[0], new byte[0], memory, parallelism,
Argon2.V13);
kpair.kdf = argon2;
} catch (NumberFormatException e) {
throw new JSchException("Invalid argon2 params.", e);
} catch (Exception | NoClassDefFoundError e) {
throw new JSchException("'argon2' is required, but it is not available.", e);
}
}
kpair.data = prvkey;
} else {
kpair.data = prvkey;
if (!kpair.parse(prvkey)) {
throw new JSchException("invalid privatekey");
} else {
Util.bzero(prvkey);
}
}
return kpair;
} catch (Exception e) {
Util.bzero(prvkey);
throw e;
} finally {
Util.bzero(_prvkey);
}
}
private static KeyPair parsePubkeyBlob(JSch.InstanceLogger instLogger, byte[] pubkeyblob,
String typ) throws JSchException {
Buffer _buf = new Buffer(pubkeyblob);
_buf.skip(pubkeyblob.length);
String pubkeyType = Util.byte2str(_buf.getString());
if (typ == null || typ.equals("")) {
typ = pubkeyType;
} else if (!typ.equals(pubkeyType)) {
throw new JSchException(
"pubkeyblob type [" + pubkeyType + "] does not match expected type [" + typ + "]");
}
if (typ.equals("ssh-rsa")) {
byte[] pub_array = new byte[_buf.getInt()];
_buf.getByte(pub_array);
byte[] n_array = new byte[_buf.getInt()];
_buf.getByte(n_array);
return new KeyPairRSA(instLogger, n_array, pub_array, null);
} else if (typ.equals("ssh-dss")) {
byte[] p_array = new byte[_buf.getInt()];
_buf.getByte(p_array);
byte[] q_array = new byte[_buf.getInt()];
_buf.getByte(q_array);
byte[] g_array = new byte[_buf.getInt()];
_buf.getByte(g_array);
byte[] y_array = new byte[_buf.getInt()];
_buf.getByte(y_array);
return new KeyPairDSA(instLogger, p_array, q_array, g_array, y_array, null);
} else if (typ.equals("ecdsa-sha2-nistp256") || typ.equals("ecdsa-sha2-nistp384")
|| typ.equals("ecdsa-sha2-nistp521")) {
byte[] name = _buf.getString(); // nistpXXX
int len = _buf.getInt();
int x04 = _buf.getByte(); // in case of x04 it is uncompressed
// https://tools.ietf.org/html/rfc5480#page-7
byte[] r_array = new byte[(len - 1) / 2];
byte[] s_array = new byte[(len - 1) / 2];
_buf.getByte(r_array);
_buf.getByte(s_array);
return new KeyPairECDSA(instLogger, name, r_array, s_array, null);
} else if (typ.equals("ssh-ed25519") || typ.equals("ssh-ed448")) {
byte[] pub_array = new byte[_buf.getInt()];
_buf.getByte(pub_array);
if (typ.equals("ssh-ed25519")) {
return new KeyPairEd25519(instLogger, pub_array, null);
} else {
return new KeyPairEd448(instLogger, pub_array, null);
}
} else {
throw new JSchException("key type " + typ + " is not supported");
}
}
private static byte[] parseLines(Buffer buffer, int lines) {
byte[] buf = buffer.buffer;
int index = buffer.index;
byte[] data = null;
int i = index;
while (lines-- > 0) {
while (buf.length > i) {
byte c = buf[i++];
if (c == '\r' || c == '\n') {
int len = i - index - 1;
if (data == null) {
data = new byte[len];
System.arraycopy(buf, index, data, 0, len);
} else if (len > 0) {
byte[] tmp = new byte[data.length + len];
System.arraycopy(data, 0, tmp, 0, data.length);
System.arraycopy(buf, index, tmp, data.length, len);
Util.bzero(data); // clear
data = tmp;
}
break;
}
}
if (i < buf.length && buf[i] == '\n')
i++;
index = i;
}
if (data != null)
buffer.index = index;
return data;
}
private static boolean parseHeader(Buffer buffer, Map<String, String> v) {
byte[] buf = buffer.buffer;
int index = buffer.index;
String key = null;
String value = null;
for (int i = index; i < buf.length; i++) {
if (buf[i] == '\r' || buf[i] == '\n') {
if (i + 1 < buf.length && buf[i + 1] == '\n') {
i++;
}
break;
}
if (buf[i] == ':') {
key = Util.byte2str(buf, index, i - index);
i++;
if (i < buf.length && buf[i] == ' ') {
i++;
}
index = i;
break;
}
}
if (key == null)
return false;
for (int i = index; i < buf.length; i++) {
if (buf[i] == '\r' || buf[i] == '\n') {
value = Util.byte2str(buf, index, i - index);
i++;
if (i < buf.length && buf[i] == '\n') {
i++;
}
index = i;
break;
}
}
if (value != null) {
v.put(key, value);
buffer.index = index;
}
return (key != null && value != null);
}
void copy(KeyPair kpair) {
this.publickeyblob = kpair.publickeyblob;
this.vendor = kpair.vendor;
this.publicKeyComment = kpair.publicKeyComment;
this.cipher = kpair.cipher;
}
static class ASN1Exception extends Exception {
private static final long serialVersionUID = -1L;
}
static class ASN1 {
byte[] buf;
int start;
int length;
ASN1(byte[] buf) throws ASN1Exception {
this(buf, 0, buf.length);
}
ASN1(byte[] buf, int start, int length) throws ASN1Exception {
this.buf = buf;
this.start = start;
this.length = length;
if (start + length > buf.length)
throw new ASN1Exception();
}
int getType() {
return buf[start] & 0xff;
}
boolean isSEQUENCE() {
return getType() == (0x30 & 0xff);
}
boolean isINTEGER() {
return getType() == (0x02 & 0xff);
}
boolean isOBJECT() {
return getType() == (0x06 & 0xff);
}
boolean isOCTETSTRING() {
return getType() == (0x04 & 0xff);
}
boolean isNULL() {
return getType() == (0x05 & 0xff);
}
boolean isBITSTRING() {
return getType() == (0x03 & 0xff);
}
boolean isCONTEXTPRIMITIVE(int tag) {
if ((tag & ~0xff) != 0 || (tag & 0x60) != 0) {
throw new IllegalArgumentException();
}
return getType() == ((tag | 0x80) & 0xff);
}
boolean isCONTEXTCONSTRUCTED(int tag) {
if ((tag & ~0xff) != 0 || (tag & 0x40) != 0) {
throw new IllegalArgumentException();
}
return getType() == ((tag | 0xa0) & 0xff);
}
private int getLength(int[] indexp) {
int index = indexp[0];
int length = buf[index++] & 0xff;
if ((length & 0x80) != 0) {
int foo = length & 0x7f;
length = 0;
while (foo-- > 0) {
length = (length << 8) + (buf[index++] & 0xff);
}
}
indexp[0] = index;
return length;
}
byte[] getContent() {
int[] indexp = new int[1];
indexp[0] = start + 1;
int length = getLength(indexp);
int index = indexp[0];
byte[] tmp = new byte[length];
System.arraycopy(buf, index, tmp, 0, tmp.length);
return tmp;
}
ASN1[] getContents() throws ASN1Exception {
int typ = buf[start];
int[] indexp = new int[1];
indexp[0] = start + 1;
int length = getLength(indexp);
if (typ == 0x05) {
return new ASN1[0];
}
int index = indexp[0];
List<ASN1> values = new ArrayList<>();
while (length > 0) {
index++;
length--;
int tmp = index;
indexp[0] = index;
int l = getLength(indexp);
index = indexp[0];
length -= (index - tmp);
values.add(new ASN1(buf, tmp - 1, 1 + (index - tmp) + l));
index += l;
length -= l;
}
ASN1[] result = new ASN1[values.size()];
values.toArray(result);
return result;
}
}
}