All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vngx.jsch.util.KeyPair Maven / Gradle / Ivy

Go to download

**vngx-jsch** (beta) is an updated version of the popular JSch SSH library written in pure Java. It has been updated to Java 6 with all the latest language features and improved code clarity.

The newest version!
/*
 * Copyright (c) 2002-2010 Atsuhiko Yamanaka, JCraft,Inc.  All rights reserved.
 * Copyright (c) 2010-2011 Michael Laudati, N1 Concepts LLC.
 *
 * 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 org.vngx.jsch.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.vngx.jsch.Buffer;
import org.vngx.jsch.Util;
import org.vngx.jsch.algorithm.AlgorithmManager;
import org.vngx.jsch.algorithm.Algorithms;
import org.vngx.jsch.algorithm.Random;
import org.vngx.jsch.cipher.Cipher;
import org.vngx.jsch.cipher.CipherManager;
import org.vngx.jsch.exception.JSchException;
import org.vngx.jsch.hash.Hash;
import org.vngx.jsch.hash.HashManager;

/**
 * Abstract implementation of a key pair.
 *
 * @author Atsuhiko Yamanaka
 * @author Michael Laudati
 */
public abstract class KeyPair {

	/** Constant to indicate SSH vendor is OpenSSH. */
	static final int VENDOR_OPENSSH = 0;
	/** Constant to indicate SSH vendor is FSecure. */
	static final int VENDOR_FSECURE = 1;
	
	/** Constant byte[] representation of new line character. */
	private static final byte[] CR = Util.str2byte("\n");
	/** Constant byte[] representation of space character. */
	private static final byte[] SPACE = Util.str2byte(" ");
	/** Constant array of bytes for headers. */
	private static final byte[][] HEADER = {
		Util.str2byte("Proc-Type: 4,ENCRYPTED"),
		Util.str2byte("DEK-Info: DES-EDE3-CBC,")
	};


	public static KeyPair genKeyPair(KeyType type) throws JSchException {
		return genKeyPair(type, 1024);
	}

	public static KeyPair genKeyPair(KeyType type, int keySize) throws JSchException {
		KeyPair keyPair;
		switch( type ) {
			case SSH_DSS: keyPair = new KeyPairDSA(); break;
			case SSH_RSA: keyPair = new KeyPairRSA(); break;
			default: throw new JSchException("Unsupported KeyType: "+type);
		}
		keyPair.generate(keySize);	// Generate keys for the specified size
		return keyPair;				// and return key pair
	}


	/** Instance of 3DES-CBC Cipher for creating keys. */
	private final Cipher _cipher;
	/** Instance of MD5 HASH used for creating fingerprints. */
	private final Hash _hash;
	/** Instance of Random used for generating keys. */
	private final Random _random;
	
	/** Vendor to generate key pair for. */
	int _vendor = VENDOR_OPENSSH;

	byte[] _passphrase;
	boolean _encrypted = false;
	byte[] _data;
	byte[] _iv;
	byte[] _publicKeyBlob;


	/**
	 * Creates a new instance of KeyPair and generates the cipher,
	 * hash and random instances for creating keys.
	 *
	 * @throws IllegalStateException if hash, random or cipher instances fail
	 */
	public KeyPair() {
		try {
			// Generate HASH, Random and Cipher instances
			_hash = HashManager.getManager().createHash(Hash.HASH_MD5);
			_random = AlgorithmManager.getManager().createAlgorithm(Algorithms.RANDOM);
			_cipher = CipherManager.getManager().createCipher(Cipher.CIPHER_3DES_CBC);
		} catch(Exception e) {
			throw new IllegalStateException("Failed to create KeyPair: "+e, e);
		}
	}


	abstract void generate(int keySize) throws JSchException;

	abstract byte[] getBegin();

	abstract byte[] getEnd();

	abstract int getKeySize();

	abstract byte[] getPrivateKey();

	abstract byte[] getKeyTypeName();

	public abstract KeyType getKeyType();

	abstract boolean parse(byte[] data);

	/**
	 * Writes the private key to the specified output stream.
	 *
	 * @param out to write private key to
	 * @throws IOException if an IO error occurs
	 * @throws Exception if any other errors occur
	 */
	public void writePrivateKey(OutputStream out) throws IOException, Exception {
		byte[] plain = getPrivateKey();
		byte[][] ivArray = new byte[1][];
		byte[] encoded = encrypt(plain, ivArray);
		if( encoded != plain ) {
			Util.bzero(plain);
		}
		byte[] iv = ivArray[0];
		byte[] prv = Util.toBase64(encoded, 0, encoded.length);

		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(DataUtil.b2a((byte) ((iv[i] >>> 4) & 0x0f)));
				out.write(DataUtil.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);
	}
	
	public byte[] getPublicKeyBlob() {
		return _publicKeyBlob;
	}

	public void writePublicKey(OutputStream out, String comment) throws IOException {
		byte[] pubblob = getPublicKeyBlob();
		byte[] pub = Util.toBase64(pubblob, 0, pubblob.length);
		out.write(getKeyTypeName());
		out.write(SPACE);
		out.write(pub, 0, pub.length);
		out.write(SPACE);
		out.write(Util.str2byte(comment));
		out.write(CR);
	}

	public void writePublicKey(String name, String comment) throws FileNotFoundException, IOException {
		FileOutputStream fos = null;
		try {
			writePublicKey(fos = new FileOutputStream(name), comment);
		} finally {
			if( fos != null ) {
				try { fos.close(); } catch(Exception e) { /* Ignore error. */ }
			}
		}
	}

	public void writeSECSHPublicKey(OutputStream out, String comment) throws IOException {
		byte[] pubblob = getPublicKeyBlob();
		byte[] pub = Util.toBase64(pubblob, 0, pubblob.length);
		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);
	}

	public void writeSECSHPublicKey(String name, String comment) throws FileNotFoundException, IOException {
		FileOutputStream fos = null;
		try {
			writeSECSHPublicKey(fos = new FileOutputStream(name), comment);
		} finally {
			if( fos != null ) {
				try { fos.close(); } catch(Exception e) { /* Ignore error. */ }
			}
		}
	}

	public void writePrivateKey(String name) throws FileNotFoundException, IOException, Exception {
		FileOutputStream fos = null;
		try {
			writePrivateKey(fos = new FileOutputStream(name));
		} finally {
			if( fos != null ) {
				try { fos.close(); } catch(Exception e) { /* Ignore error. */ }
			}
		}
	}

	/**
	 * Returns a MD5 hash fingerprint for the public key.
	 *
	 * @return MD5 hash fingerprint of public key
	 * @throws JSchException if any errors occur
	 */
	public String getFingerPrint() throws JSchException {
		byte[] kblob = getPublicKeyBlob();
		if( kblob == null ) {
			return null;
		}
		return getKeySize() + " " + Util.getFingerPrint(_hash, kblob);
	}

	private byte[] encrypt(byte[] plain, byte[][] ivReturn) throws Exception {
		if( _passphrase == null ) {
			return plain;
		}
		byte[] encoded = plain;
		byte[] iv = ivReturn[0] = new byte[_cipher.getIVSize()];
		_random.fill(iv, 0, iv.length);

		{	// PKCS#5Padding
			byte[] foo = new byte[(encoded.length / iv.length + 1) * iv.length];
			System.arraycopy(encoded, 0, foo, 0, encoded.length);
			int padding = iv.length - encoded.length % iv.length;
			for( int i = foo.length - 1; (foo.length - padding) <= i; i-- ) {
				foo[i] = (byte) padding;
			}
			encoded = foo;
		}

		byte[] key = null;
		try {
			_cipher.init(Cipher.ENCRYPT_MODE, key = genKey(_passphrase, iv), iv);
			_cipher.update(encoded, 0, encoded.length, encoded, 0);
		} catch(Exception e) {
			// TODO Error handling?
		} finally {
			Util.bzero(key);	// Always zero out key
		}

		return encoded;
	}

	private byte[] decrypt(byte[] data, byte[] passphrase, byte[] iv) throws Exception {
		byte[] key = null;
		try {
			_cipher.init(Cipher.DECRYPT_MODE, key = genKey(passphrase, iv), iv);
		} finally {
			Util.bzero(key);	// Always zero out key
		}
		byte[] plain = new byte[data.length];
		_cipher.update(data, 0, data.length, plain, 0);
		return plain;
	}

	/*
	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) throws Exception {
		byte[] key = new byte[_cipher.getBlockSize()];
		int hsize = _hash.getBlockSize();
		byte[] hn = new byte[key.length / hsize * hsize + (key.length % hsize == 0 ? 0 : hsize)];
		byte[] tmp = null;
		switch( _vendor ) {
			case VENDOR_OPENSSH:
			case 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);
					if( _vendor == VENDOR_OPENSSH ) {
						_hash.update(iv, 0, iv.length);
					}
					tmp = _hash.digest();
					System.arraycopy(tmp, 0, hn, index, tmp.length);
					index += tmp.length;
				}
				System.arraycopy(hn, 0, key, 0, key.length);
				return key;

			default:	// No support for other vendor types
				throw new JSchException("Unsupported vendor type: "+_vendor);
		}
	}

	public void setPassphrase(String passphrase) {
		if( passphrase == null || passphrase.length() == 0 ) {
			setPassphrase((byte[]) null);
		} else {
			setPassphrase(Util.str2byte(passphrase));
		}
	}

	public void setPassphrase(byte[] passphrase) {
		if( passphrase != null && passphrase.length == 0 ) {
			passphrase = null;
		}
		_passphrase = passphrase;
	}

	public boolean isEncrypted() {
		return _encrypted;
	}

	public boolean decrypt(String passphrase) throws Exception {
		if( passphrase == null || passphrase.length() == 0 ) {
			return !_encrypted;
		}
		return decrypt(Util.str2byte(passphrase));
	}

	public boolean decrypt(byte[] passphrase) throws Exception {
		if( !_encrypted ) {
			return true;
		} else if( passphrase == null ) {
			return !_encrypted;
		}
		byte[] bar = new byte[passphrase.length];
		System.arraycopy(passphrase, 0, bar, 0, bar.length);
		passphrase = bar;	// Why make a copy of passphrase?
		try {
			if( parse(decrypt(_data, passphrase, _iv)) ) {
				_encrypted = false;
			}
		} finally {
			Util.bzero(passphrase);
		}
		return !_encrypted;
	}

	public static KeyPair load(String prvkey) throws JSchException {
		String pubkey = prvkey + ".pub";
		if( !new File(pubkey).exists() ) {
			pubkey = null;
		}
		return load(prvkey, pubkey);
	}

	public static KeyPair load(String prvkey, String pubkey) throws JSchException {
		byte[] iv = new byte[8];       // 8
		boolean encrypted = true;
		byte[] buf, data, publickeyblob = null;
		KeyType type = null;
		int vendor = VENDOR_OPENSSH;

		FileChannel fc = null;
		try {
			fc = new FileInputStream(prvkey).getChannel();
			int i = 0, len = (int) fc.size();
			fc.read(ByteBuffer.wrap(buf = new byte[len]));
			fc.close();

			while( i < len ) {
				if( buf[i] == 'B' && buf[i + 1] == 'E' && buf[i + 2] == 'G' && buf[i + 3] == 'I' ) {
					i += 6;
					if( buf[i] == 'D' && buf[i + 1] == 'S' && buf[i + 2] == 'A' ) {
						type = KeyType.SSH_DSS;
					} else if( buf[i] == 'R' && buf[i + 1] == 'S' && buf[i + 2] == 'A' ) {
						type = KeyType.SSH_RSA;
					} else if( buf[i] == 'S' && buf[i + 1] == 'S' && buf[i + 2] == 'H' ) { // FSecure
						type = KeyType.UNKNOWN;
						vendor = VENDOR_FSECURE;
					} else {
						throw new JSchException("invalid privatekey: " + prvkey);
					}
					i += 3;
					continue;
				}
				if( buf[i] == 'C' && buf[i + 1] == 'B' && buf[i + 2] == 'C' && buf[i + 3] == ',' ) {
					i += 4;
					for( int ii = 0; ii < iv.length; ii++ ) {
						iv[ii] = (byte) (((DataUtil.a2b(buf[i++]) << 4) & 0xf0) + (DataUtil.a2b(buf[i++]) & 0xf));
					}
					continue;
				}
				if( buf[i] == 0x0d
						&& i + 1 < buf.length && buf[i + 1] == 0x0a ) {
					i++;
					continue;
				}
				if( buf[i] == 0x0a && i + 1 < buf.length ) {
					if( buf[i + 1] == 0x0a ) {
						i += 2;
						break;
					}
					if( buf[i + 1] == 0x0d
							&& i + 2 < buf.length && buf[i + 2] == 0x0a ) {
						i += 3;
						break;
					}
					boolean inheader = false;
					for( int j = i + 1; j < buf.length; j++ ) {
						if( buf[j] == 0x0a ) {
							break;
						}
						//if(buf[j]==0x0d) break;
						if( buf[j] == ':' ) {
							inheader = true;
							break;
						}
					}
					if( !inheader ) {
						i++;
						encrypted = false;    // no passphrase
						break;
					}
				}
				i++;
			}

			if( type == null ) {
				throw new JSchException("invalid privatekey: " + prvkey);
			}

			int start = i;
			while( i < len ) {
				if( buf[i] == 0x0a ) {
					boolean xd = (buf[i - 1] == 0x0d);
					System.arraycopy(buf, i + 1,
							buf,
							i - (xd ? 1 : 0),
							len - i - 1 - (xd ? 1 : 0));
					if( xd ) {
						len--;
					}
					len--;
					continue;
				}
				if( buf[i] == '-' ) {
					break;
				}
				i++;
			}
			data = Util.fromBase64(buf, start, i - start);

			if( 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();
				@SuppressWarnings("unused")
				byte[] _type = _buf.getString();
				byte[] _cipher = _buf.getString();
				String cipher = Util.byte2str(_cipher);
				if( cipher.equals("3des-cbc") ) {
					_buf.getInt();
					byte[] foo = new byte[data.length - _buf.getOffSet()];
					_buf.getBytes(foo);
					data = foo;
					encrypted = true;
					throw new JSchException("unknown privatekey format: " + prvkey);
				} else if( cipher.equals("none") ) {
					_buf.getInt();
					_buf.getInt();
					encrypted = false;
					byte[] foo = new byte[data.length - _buf.getOffSet()];
					_buf.getBytes(foo);
					data = foo;
				}
			}

			if( pubkey != null ) {
				try {
					fc = new FileInputStream(pubkey).getChannel();
					len = (int) fc.size();
					fc.read(ByteBuffer.wrap(buf = new byte[len]));
					fc.close();

					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] != 0x0a );
						if( buf.length <= i ) {
							valid = false;
						}

						while( valid ) {
							if( buf[i] == 0x0a ) {
								boolean inheader = false;
								for( int j = i + 1; j < buf.length; j++ ) {
									if( buf[j] == 0x0a ) {
										break;
									}
									if( buf[j] == ':' ) {
										inheader = true;
										break;
									}
								}
								if( !inheader ) {
									i++;
									break;
								}
							}
							i++;
						}
						if( buf.length <= i ) {
							valid = false;
						}

						start = i;
						while( valid && i < len ) {
							if( buf[i] == 0x0a ) {
								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( type == KeyType.UNKNOWN ) {
								if( publickeyblob[8] == 'd' ) {
									type = KeyType.SSH_DSS;
								} else if( publickeyblob[8] == 'r' ) {
									type = KeyType.SSH_RSA;
								}
							}
						}
					} else {
						if( buf[0] == 's' && buf[1] == 's' && buf[2] == 'h' && buf[3] == '-' ) {
							i = 0;
							while( i < len ) {
								if( buf[i] == ' ' ) {
									break;
								}
								i++;
							}
							i++;
							if( i < len ) {
								start = i;
								while( i < len ) {
									if( buf[i] == ' ' ) {
										break;
									}
									i++;
								}
								publickeyblob = Util.fromBase64(buf, start, i - start);
							}
						}
					}
				} catch(Exception ee) {
					// TODO Error handling???
				}
			}
		} catch(JSchException e) {
			throw e;
		} catch(Exception e) {
			throw new JSchException("Failed to load KeyPair: "+e, e);
		}

		KeyPair keyPair = null;
		switch( type ) {
			case SSH_DSS: keyPair = new KeyPairDSA(); break;
			case SSH_RSA: keyPair = new KeyPairRSA(); break;
			default: throw new JSchException("Unsupported key type: "+type);
		}
		keyPair._encrypted = encrypted;
		keyPair._publicKeyBlob = publickeyblob;
		keyPair._vendor = vendor;

		if( encrypted ) {
			keyPair._iv = iv;
			keyPair._data = data;
		} else if( !keyPair.parse(data) ) {
			throw new JSchException("Invalid private key: " + prvkey);
		}
		return keyPair;
	}

	public void dispose() {
		Util.bzero(_passphrase);
	}

	@Override
	protected void finalize() throws Throwable {
		dispose();
		super.finalize();
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy