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

org.vngx.jsch.util.KnownHosts 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.

There is a newer version: 0.10
Show 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 org.vngx.jsch.UserInfo;
import org.vngx.jsch.Util;
import org.vngx.jsch.constants.MessageConstants;
import org.vngx.jsch.exception.JSchException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Implementation of HostKeyRepository for registering and checking
 * host keys on the local system.  The registered host keys are backed to
 * persistent storage on the file system whenever keys are added or removed.
 *
 * SSH clients typically save a server's host key locally in a file named
 * "$HOME/.ssh/known_hosts" (where $HOME is the user's home directory). This
 * file is, effectively, a personal Certificate Authority -- it is the list of
 * all SSH server host keys that the user has determined are accurate.
 *
 * Each entry in known_hosts is one big line with three or more whitespace
 * separated fields as follows:
 *		One or more server names or IP addresses, joined together by commas.
 *		The type of key (described later).
 *		The public key data itself (encoded to stay within the ASCII range).
 *		Any optional comment data (not present in the above output).
 *
 * @author Atsuhiko Yamanaka
 * @author Michael Laudati
 */
public final class KnownHosts implements HostKeyRepository {

	/** Pool of registered host keys. (TODO Consider using Set) */
	private final List _hostKeyPool = Collections.synchronizedList(new ArrayList());
	/** Local file containing known hosts. */
	private String _knownHostsFile;
	

	/**
	 * Creates a new instance of KnownHosts.
	 */
	public KnownHosts() { }

	/**
	 * Sets the specified hosts file and attempts to load any host keys found
	 * in the specified location.
	 *
	 * @param hostsFile where host keys are stored
	 * @throws JSchException if any errors occur
	 */
	public void setKnownHosts(String hostsFile) throws JSchException {
		try {
			_knownHostsFile = hostsFile;
			//setKnownHosts(new FileInputStream(hostsFile));
			loadKnownHosts(new FileInputStream(hostsFile));
		} catch(FileNotFoundException e) {
			// Ignore error, as client may not have a known hosts file
			// TODO Optionally log missing hosts file?
		}
	}

	/**
	 * Attempts to load known hosts from the specified input stream.
	 *
	 * TODO Shouldn't method be synchronized?
	 *
	 * @param hostsStream containing host keys
	 * @throws JSchException if any errors occur
	 */
	public void loadKnownHosts(InputStream hostsStream) throws JSchException {
		_hostKeyPool.clear();	// Clear any loaded host keys, only load from stream

		BufferedReader reader = new BufferedReader(new InputStreamReader(hostsStream));
		try {
			String line;
			String[] keyEntry;
			KeyType type;
			while( (line = reader.readLine()) != null ) {
				if( line.indexOf('#') > -1 ) {
					continue;	// Invalid line
				}
				keyEntry = line.split("\\s+");	// Split by whitespace
				if( keyEntry == null || keyEntry.length < 3 ) {
					continue;	// Invalid line
				}

				if( KeyType.SSH_DSS.equals(keyEntry[1]) ) {
					type = KeyType.SSH_DSS;
				} else if( KeyType.SSH_RSA.equals(keyEntry[1]) ) {
					type = KeyType.SSH_RSA;
				} else {
					continue;	// Invalid key type
				}

				if( keyEntry[0].startsWith(HashedHostKey.HASH_MAGIC) ) {
					_hostKeyPool.add(new HashedHostKey(keyEntry[0], type, Util.fromBase64(Util.str2byte(keyEntry[2]), 0, keyEntry[2].length())));
				} else {
					_hostKeyPool.add(new HostKey(keyEntry[0], type, Util.fromBase64(Util.str2byte(keyEntry[2]), 0, keyEntry[2].length())));
				}
			}
		} catch(JSchException e) {
			throw e;
		} catch(Exception e) {
			throw new JSchException("Failed to load known hosts", e);
		} finally {
			if( hostsStream != null ) {
				try { hostsStream.close(); } catch(Exception e) { /* Ignore Error */ }
			}
		}
	}

	/*
	 * Returns the known hosts file location as the repository ID for the
	 * HostKeyRepository interface.
	 */
	@Override
	public String getKnownHostsRepositoryID() {
		return _knownHostsFile;
	}

	@Override
	public Check check(String host, byte[] key) {
		if( host == null ) {
			return Check.NOT_INCLUDED;
		}
		KeyType type = HostKey.guessType(key);

		Check result = Check.NOT_INCLUDED;
		synchronized( _hostKeyPool ) {
			for( HostKey hostKey : _hostKeyPool ) {
				if( hostKey.isMatched(host) && hostKey.getType() == type ) {
					if( Arrays.equals(hostKey._key, key) ) {
						return Check.OK;	// If keys match
					} else {
						result = Check.CHANGED;
					}
				}
			}
		}

		if( result == Check.NOT_INCLUDED && host.startsWith("[") && host.indexOf("]:") > 1 ) {
			return check(host.substring(1, host.indexOf("]:")), key);
		}
		return result;
	}

	/*
	 * Adds the specified host key to the repository.  Attempts to save the
	 * updated repository to the known hosts file.
	 */
	@Override
	public void add(HostKey hostkey, UserInfo userinfo) {
		// TODO Add check to ensure same host key doesn't already exist in pool
		// Add host key to pool of registered keys
		_hostKeyPool.add(hostkey);

		String hostsFileName = getKnownHostsRepositoryID();
		if( hostsFileName != null ) {
			boolean foo = true;
			File hostsFile = new File(hostsFileName);
			if( !hostsFile.exists() ) {
				foo = false;
				if( userinfo != null ) {
					foo = userinfo.promptYesNo(String.format(MessageConstants.PROMPT_CREATE_KNOWN_HOSTS, hostsFileName));
					hostsFile = hostsFile.getParentFile();
					if( foo && hostsFile != null && !hostsFile.exists() ) {
						foo = userinfo.promptYesNo(String.format(MessageConstants.PROMPT_CREATE_HOSTS_DIR, hostsFile));
						if( foo ) {
							if( !hostsFile.mkdirs() ) {
								userinfo.showMessage(String.format(MessageConstants.MSG_KNOWN_HOSTS_NOT_CREATED, hostsFile));
								foo = false;
							} else {
								userinfo.showMessage(String.format(MessageConstants.MSG_KNOWN_HOSTS_CREATED, hostsFile));
							}
						}
					}
					if( hostsFile == null ) {
						foo = false;
					}
				}
			}
			if( foo ) {
				try {
					sync(hostsFileName);
				} catch(Exception e) {
					// TODO Error handling?
					System.err.println("sync known_hosts: " + e);
				}
			}
		}
	}

	@Override
	public List getHostKeys() {
		return getHostKeys(null, null);
	}

	@Override
	public List getHostKeys(String host, KeyType type) {
		synchronized( _hostKeyPool ) {
			List matches = new ArrayList();
			for( HostKey hk : _hostKeyPool ) {
				if( hk.getType() == KeyType.UNKNOWN ) {
					continue;
				} else if( host == null || (hk.isMatched(host) && (type == null || hk.getType() == type)) ) {
					matches.add(hk);
				}
			}
			return matches;
		}
	}

	@Override
	public void remove(String host, KeyType type) {
		remove(host, type, null);
	}

	@Override
	public void remove(String host, KeyType type, byte[] key) {
		boolean sync = false;
		synchronized( _hostKeyPool ) {
			for( HostKey hk : _hostKeyPool ) {
				if( host == null || (hk.isMatched(host)
						&& (type == null || (hk.getType() == type
						&& (key == null || Arrays.equals(key, hk._key))))) ) {
					if( hk.getHost().equalsIgnoreCase(host) ||  hk instanceof HashedHostKey ) {
						_hostKeyPool.remove(hk);
					} else {
						hk.removeHost(host);
					}
					sync = true;
				}
			}
		}
		if( sync ) {
			try {
				sync();	// Save to known hosts file
			} catch(Exception e) {
				// TODO Error handling?
			}
		}
	}

	/**
	 * Syncs the host keys currently stored in the repository with the known
	 * hosts file if it's not null.
	 *
	 * @throws IOException
	 */
	void sync() throws IOException {
		sync(_knownHostsFile);
	}

	/**
	 * Syncs the host keys currently stored in the repository with the specified
	 * known hosts file if it's not null.
	 *
	 * @param knownHostsFile
	 * @throws IOException
	 */
	void sync(String knownHostsFile) throws IOException {
		if( knownHostsFile == null ) {
			return;
		}
		BufferedWriter writer = new BufferedWriter(new FileWriter(knownHostsFile));
		try {
			synchronized( _hostKeyPool ) {
				for( HostKey hk : _hostKeyPool ) {
					if( hk.getType() == KeyType.UNKNOWN ) {
						continue;
					}
					writer.append(hk.getHost());
					writer.append(' ').append(hk.getType().toString());
					writer.append(' ').append(hk.getKey()).append('\n');
				}
			}
		} finally {
			writer.flush();
			writer.close();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy