diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java
index 61114c63..2080a122 100644
--- a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java
+++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java
@@ -15,7 +15,12 @@
*/
package net.schmizz.sshj.transport.verification;
-import net.schmizz.sshj.common.*;
+import net.schmizz.sshj.common.Base64;
+import net.schmizz.sshj.common.Buffer;
+import net.schmizz.sshj.common.IOUtils;
+import net.schmizz.sshj.common.KeyType;
+import net.schmizz.sshj.common.SSHException;
+import net.schmizz.sshj.common.SecurityUtils;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.MAC;
import org.slf4j.Logger;
@@ -41,348 +46,361 @@ import java.util.List;
* @see Hashed hostnames spec
*/
public class OpenSSHKnownHosts
- implements HostKeyVerifier {
+ implements HostKeyVerifier {
- protected final Logger log = LoggerFactory.getLogger(getClass());
+ protected final Logger log = LoggerFactory.getLogger(getClass());
- protected final File khFile;
- protected final List entries = new ArrayList();
+ protected final File khFile;
+ protected final List entries = new ArrayList();
- public OpenSSHKnownHosts(File khFile) throws IOException {
- this.khFile = khFile;
- if (khFile.exists()) {
- final BufferedReader br = new BufferedReader(new FileReader(khFile));
- try {
- // Read in the file, storing each line as an entry
- String line;
- while ((line = br.readLine()) != null)
- try {
- HostEntry entry = EntryFactory.parseEntry(line);
- if (entry != null) {
- entries.add(entry);
- }
- } catch (SSHException ignore) {
- log.debug("Bad line ({}): {} ", ignore.toString(), line);
- }
- } finally {
- IOUtils.closeQuietly(br);
- }
- }
- }
+ public OpenSSHKnownHosts(File khFile)
+ throws IOException {
+ this.khFile = khFile;
+ if (khFile.exists()) {
+ final BufferedReader br = new BufferedReader(new FileReader(khFile));
+ try {
+ // Read in the file, storing each line as an entry
+ String line;
+ while ((line = br.readLine()) != null)
+ try {
+ HostEntry entry = EntryFactory.parseEntry(line);
+ if (entry != null) {
+ entries.add(entry);
+ }
+ } catch (SSHException ignore) {
+ log.debug("Bad line ({}): {} ", ignore.toString(), line);
+ }
+ } finally {
+ IOUtils.closeQuietly(br);
+ }
+ }
+ }
- public File getFile() {
- return khFile;
- }
+ public File getFile() {
+ return khFile;
+ }
- @Override
- public boolean verify(final String hostname, final int port, final PublicKey key) {
- final KeyType type = KeyType.fromKey(key);
- if (type == KeyType.UNKNOWN)
- return false;
+ @Override
+ public boolean verify(final String hostname, final int port, final PublicKey key) {
+ final KeyType type = KeyType.fromKey(key);
+ if (type == KeyType.UNKNOWN)
+ return false;
- final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
+ final String adjustedHostname = (port != 22) ? "[" + hostname + "]:" + port : hostname;
- for (HostEntry e : entries)
- try {
- if (e.appliesTo(type, adjustedHostname))
- return e.verify(key) || hostKeyChangedAction(e, adjustedHostname, key);
- } catch (IOException ioe) {
- log.error("Error with {}: {}", e, ioe);
- return false;
- }
- return hostKeyUnverifiableAction(adjustedHostname, key);
- }
+ for (HostEntry e : entries)
+ try {
+ if (e.appliesTo(type, adjustedHostname))
+ return e.verify(key) || hostKeyChangedAction(e, adjustedHostname, key);
+ } catch (IOException ioe) {
+ log.error("Error with {}: {}", e, ioe);
+ return false;
+ }
+ return hostKeyUnverifiableAction(adjustedHostname, key);
+ }
- protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
- return false;
- }
+ protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
+ return false;
+ }
- protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
- log.warn("Host key for `{}` has changed!", hostname);
- return false;
- }
+ protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
+ log.warn("Host key for `{}` has changed!", hostname);
+ return false;
+ }
- public List entries() {
- return entries;
- }
+ public List entries() {
+ return entries;
+ }
- private static final String LS = System.getProperty("line.separator");
+ private static final String LS = System.getProperty("line.separator");
- public void write()
- throws IOException {
- final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(khFile));
- try {
- for (HostEntry entry : entries)
- bos.write((entry.getLine() + LS).getBytes(IOUtils.UTF8));
- } finally {
- bos.close();
- }
- }
+ public void write()
+ throws IOException {
+ final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(khFile));
+ try {
+ for (HostEntry entry : entries)
+ bos.write((entry.getLine() + LS).getBytes(IOUtils.UTF8));
+ } finally {
+ bos.close();
+ }
+ }
- public static File detectSSHDir() {
- final File sshDir = new File(System.getProperty("user.home"), ".ssh");
- return sshDir.exists() ? sshDir : null;
- }
+ public static File detectSSHDir() {
+ final File sshDir = new File(System.getProperty("user.home"), ".ssh");
+ return sshDir.exists() ? sshDir : null;
+ }
- /**
- * Each line in these files contains the following fields: markers
- * (optional), hostnames, bits, exponent, modulus, comment. The fields are
- * separated by spaces.
- *
- * The marker is optional, but if it is present then it must be one of
- * ``@cert-authority'', to indicate that the line contains a certification
- * authority (CA) key, or ``@revoked'', to indicate that the key contained
- * on the line is revoked and must not ever be accepted. Only one marker
- * should be used on a key line.
- *
- * Hostnames is a comma-separated list of patterns (`*' and `?' act as
- * wildcards); each pattern in turn is matched against the canonical host
- * name (when authenticating a client) or against the user-supplied name
- * (when authenticating a server). A pattern may also be preceded by `!' to
- * indicate negation: if the host name matches a negated pattern, it is not
- * accepted (by that line) even if it matched another pattern on the line.
- * A hostname or address may optionally be enclosed within `[' and `]'
- * brackets then followed by `:' and a non-standard port number.
- *
- * Alternately, hostnames may be stored in a hashed form which hides host
- * names and addresses should the file's contents be disclosed. Hashed
- * hostnames start with a `|' character. Only one hashed hostname may
- * appear on a single line and none of the above negation or wildcard
- * operators may be applied.
- *
- * Bits, exponent, and modulus are taken directly from the RSA host key;
- * they can be obtained, for example, from /etc/ssh/ssh_host_key.pub. The
- * optional comment field continues to the end of the line, and is not used.
- *
- * Lines starting with `#' and empty lines are ignored as comments.
- */
- public static class EntryFactory {
+ /**
+ * Each line in these files contains the following fields: markers
+ * (optional), hostnames, bits, exponent, modulus, comment. The fields are
+ * separated by spaces.
+ *
+ * The marker is optional, but if it is present then it must be one of
+ * ``@cert-authority'', to indicate that the line contains a certification
+ * authority (CA) key, or ``@revoked'', to indicate that the key contained
+ * on the line is revoked and must not ever be accepted. Only one marker
+ * should be used on a key line.
+ *
+ * Hostnames is a comma-separated list of patterns (`*' and `?' act as
+ * wildcards); each pattern in turn is matched against the canonical host
+ * name (when authenticating a client) or against the user-supplied name
+ * (when authenticating a server). A pattern may also be preceded by `!' to
+ * indicate negation: if the host name matches a negated pattern, it is not
+ * accepted (by that line) even if it matched another pattern on the line.
+ * A hostname or address may optionally be enclosed within `[' and `]'
+ * brackets then followed by `:' and a non-standard port number.
+ *
+ * Alternately, hostnames may be stored in a hashed form which hides host
+ * names and addresses should the file's contents be disclosed. Hashed
+ * hostnames start with a `|' character. Only one hashed hostname may
+ * appear on a single line and none of the above negation or wildcard
+ * operators may be applied.
+ *
+ * Bits, exponent, and modulus are taken directly from the RSA host key;
+ * they can be obtained, for example, from /etc/ssh/ssh_host_key.pub. The
+ * optional comment field continues to the end of the line, and is not used.
+ *
+ * Lines starting with `#' and empty lines are ignored as comments.
+ */
+ public static class EntryFactory {
- public static HostEntry parseEntry(String line) throws IOException {
- if (isComment(line)) {
- return new CommentEntry(line);
- }
+ public static HostEntry parseEntry(String line)
+ throws IOException {
+ if (isComment(line)) {
+ return new CommentEntry(line);
+ }
- String[] split = line.split(" ");
- int i = 0;
- Marker marker = getMarker(split[i]);
- if (marker != null) {
- i++;
- }
+ String[] split = line.split(" ");
+ int i = 0;
+ Marker marker = getMarker(split[i]);
+ if (marker != null) {
+ i++;
+ }
- String hostnames = split[i++];
- String sType = split[i++];
- KeyType type = KeyType.fromString(sType);
- PublicKey key;
+ String hostnames = split[i++];
+ String sType = split[i++];
+ KeyType type = KeyType.fromString(sType);
+ PublicKey key;
- if (isType(type)) {
- String sKey = split[i++];
- key = getKey(sKey);
- } else if (isBits(sType)) {
- type = KeyType.RSA;
- int bits = Integer.valueOf(sType);
- BigInteger e = new BigInteger(split[i++]);
- BigInteger n = new BigInteger(split[i++]);
- try {
- final KeyFactory keyFactory = SecurityUtils.getKeyFactory("RSA");
- key = keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
- } catch (Exception ex) {
- logger.error("Error reading entry {}, could not create key", line, ex);
- return null;
- }
- } else {
- logger.error("Error reading entry {}, could not determine type", line);
- return null;
- }
+ if (isType(type)) {
+ String sKey = split[i++];
+ key = getKey(sKey);
+ } else if (isBits(sType)) {
+ type = KeyType.RSA;
+ int bits = Integer.valueOf(sType);
+ BigInteger e = new BigInteger(split[i++]);
+ BigInteger n = new BigInteger(split[i++]);
+ try {
+ final KeyFactory keyFactory = SecurityUtils.getKeyFactory("RSA");
+ key = keyFactory.generatePublic(new RSAPublicKeySpec(n, e));
+ } catch (Exception ex) {
+ logger.error("Error reading entry {}, could not create key", line, ex);
+ return null;
+ }
+ } else {
+ logger.error("Error reading entry {}, could not determine type", line);
+ return null;
+ }
- if (isHashed(hostnames)) {
- return new HashedEntry(marker, hostnames, type, key);
- } else {
- return new SimpleEntry(marker, hostnames, type, key);
- }
- }
+ if (isHashed(hostnames)) {
+ return new HashedEntry(marker, hostnames, type, key);
+ } else {
+ return new SimpleEntry(marker, hostnames, type, key);
+ }
+ }
- private static PublicKey getKey(String sKey) throws IOException {
- return new Buffer.PlainBuffer(Base64.decode(sKey)).readPublicKey();
- }
+ private static PublicKey getKey(String sKey)
+ throws IOException {
+ return new Buffer.PlainBuffer(Base64.decode(sKey)).readPublicKey();
+ }
- private static boolean isBits(String type) {
- try {
- Integer.parseInt(type);
- return true;
- } catch (NumberFormatException e) {
- return false;
- }
- }
+ private static boolean isBits(String type) {
+ try {
+ Integer.parseInt(type);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
- private static boolean isType(KeyType type) {
- return type != KeyType.UNKNOWN;
- }
+ private static boolean isType(KeyType type) {
+ return type != KeyType.UNKNOWN;
+ }
- private static boolean isComment(String line) {
- return line.isEmpty() || line.startsWith("#");
- }
+ private static boolean isComment(String line) {
+ return line.isEmpty() || line.startsWith("#");
+ }
- public static Marker getMarker(String line) {
- if (line.equals("@cert-authority")) return Marker.CA_CERT;
- if (line.equals("@revoked")) return Marker.REVOKED;
- return null;
- }
+ public static Marker getMarker(String line) {
+ if (line.equals("@cert-authority")) return Marker.CA_CERT;
+ if (line.equals("@revoked")) return Marker.REVOKED;
+ return null;
+ }
- public static boolean isHashed(String line) {
- return line.startsWith("|1|");
- }
+ public static boolean isHashed(String line) {
+ return line.startsWith("|1|");
+ }
- }
+ }
- public interface HostEntry {
- boolean appliesTo(KeyType type, String host) throws IOException;
- boolean verify(PublicKey key) throws IOException;
- String getLine();
- }
+ public interface HostEntry {
+ boolean appliesTo(KeyType type, String host)
+ throws IOException;
- public static class CommentEntry implements HostEntry {
- private final String comment;
+ boolean verify(PublicKey key)
+ throws IOException;
- public CommentEntry(String comment) {
- this.comment = comment;
- }
+ String getLine();
+ }
- @Override
- public boolean appliesTo(KeyType type, String host) {
- return false;
- }
+ public static class CommentEntry implements HostEntry {
+ private final String comment;
- @Override
- public boolean verify(PublicKey key) {
- return false;
- }
+ public CommentEntry(String comment) {
+ this.comment = comment;
+ }
- @Override
- public String getLine() {
- return comment;
- }
- }
+ @Override
+ public boolean appliesTo(KeyType type, String host) {
+ return false;
+ }
- public static abstract class AbstractEntry implements HostEntry {
+ @Override
+ public boolean verify(PublicKey key) {
+ return false;
+ }
- protected final OpenSSHKnownHosts.Marker marker;
- protected final KeyType type;
- protected PublicKey key;
+ @Override
+ public String getLine() {
+ return comment;
+ }
+ }
- public AbstractEntry(Marker marker, KeyType type, PublicKey key) {
- this.marker = marker;
- this.type = type;
- this.key = key;
- }
+ public static abstract class AbstractEntry implements HostEntry {
- @Override
- public boolean verify(PublicKey key) throws IOException {
- return key.equals(this.key) && marker != Marker.REVOKED;
- }
+ protected final OpenSSHKnownHosts.Marker marker;
+ protected final KeyType type;
+ protected PublicKey key;
- public String getLine() {
- final StringBuilder line = new StringBuilder();
+ public AbstractEntry(Marker marker, KeyType type, PublicKey key) {
+ this.marker = marker;
+ this.type = type;
+ this.key = key;
+ }
- if (marker != null) line.append(marker.getMarkerString()).append(" ");
+ @Override
+ public boolean verify(PublicKey key)
+ throws IOException {
+ return key.equals(this.key) && marker != Marker.REVOKED;
+ }
- line.append(getHostPart());
- line.append(" ").append(type.toString());
- line.append(" ").append(getKeyString());
- return line.toString();
- }
+ public String getLine() {
+ final StringBuilder line = new StringBuilder();
- private String getKeyString() {
- final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(key);
- return Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
- }
+ if (marker != null) line.append(marker.getMarkerString()).append(" ");
- protected abstract String getHostPart();
- }
+ line.append(getHostPart());
+ line.append(" ").append(type.toString());
+ line.append(" ").append(getKeyString());
+ return line.toString();
+ }
- public static class SimpleEntry extends AbstractEntry {
- private List hosts;
- private String hostnames;
+ private String getKeyString() {
+ final Buffer.PlainBuffer buf = new Buffer.PlainBuffer().putPublicKey(key);
+ return Base64.encodeBytes(buf.array(), buf.rpos(), buf.available());
+ }
- public SimpleEntry(Marker marker, String hostnames, KeyType type, PublicKey key) {
- super(marker, type, key);
- this.hostnames = hostnames;
- hosts = Arrays.asList(hostnames.split(","));
- }
+ protected abstract String getHostPart();
+ }
- @Override
- protected String getHostPart() {
- return hostnames;
- }
+ public static class SimpleEntry extends AbstractEntry {
+ private List hosts;
+ private String hostnames;
- @Override
- public boolean appliesTo(KeyType type, String host) throws IOException {
- return type == this.type && hostnames.contains(host);
- }
- }
+ public SimpleEntry(Marker marker, String hostnames, KeyType type, PublicKey key) {
+ super(marker, type, key);
+ this.hostnames = hostnames;
+ hosts = Arrays.asList(hostnames.split(","));
+ }
- public static class HashedEntry extends AbstractEntry {
- private final MAC sha1 = new HMACSHA1();
+ @Override
+ protected String getHostPart() {
+ return hostnames;
+ }
- private String salt;
- private byte[] saltyBytes;
+ @Override
+ public boolean appliesTo(KeyType type, String host)
+ throws IOException {
+ return type == this.type && hostnames.contains(host);
+ }
+ }
- private final String hashedHost;
+ public static class HashedEntry extends AbstractEntry {
+ private final MAC sha1 = new HMACSHA1();
- public HashedEntry(Marker marker, String hash, KeyType type, PublicKey key) throws SSHException {
- super(marker, type, key);
- this.hashedHost = hash;
- {
- final String[] hostParts = hashedHost.split("\\|");
- if (hostParts.length != 4)
- throw new SSHException("Unrecognized format for hashed hostname");
- salt = hostParts[2];
- }
- }
+ private String salt;
+ private byte[] saltyBytes;
- @Override
- public boolean appliesTo(KeyType type, String host) throws IOException {
- return this.type == type && hashedHost.equals(hashHost(host));
- }
+ private final String hashedHost;
- private String hashHost(String host) throws IOException {
- sha1.init(getSaltyBytes());
- return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
- }
+ public HashedEntry(Marker marker, String hash, KeyType type, PublicKey key)
+ throws SSHException {
+ super(marker, type, key);
+ this.hashedHost = hash;
+ {
+ final String[] hostParts = hashedHost.split("\\|");
+ if (hostParts.length != 4)
+ throw new SSHException("Unrecognized format for hashed hostname");
+ salt = hostParts[2];
+ }
+ }
- private byte[] getSaltyBytes() throws IOException {
- if (saltyBytes == null) {
- saltyBytes = Base64.decode(salt);
- }
- return saltyBytes;
- }
+ @Override
+ public boolean appliesTo(KeyType type, String host)
+ throws IOException {
+ return this.type == type && hashedHost.equals(hashHost(host));
+ }
+
+ private String hashHost(String host)
+ throws IOException {
+ sha1.init(getSaltyBytes());
+ return "|1|" + salt + "|" + Base64.encodeBytes(sha1.doFinal(host.getBytes(IOUtils.UTF8)));
+ }
+
+ private byte[] getSaltyBytes()
+ throws IOException {
+ if (saltyBytes == null) {
+ saltyBytes = Base64.decode(salt);
+ }
+ return saltyBytes;
+ }
- @Override
- public String getLine() {
- return null;
- }
+ @Override
+ public String getLine() {
+ return null;
+ }
- @Override
- protected String getHostPart() {
- return hashedHost;
- }
- }
+ @Override
+ protected String getHostPart() {
+ return hashedHost;
+ }
+ }
- public enum Marker {
- CA_CERT("@cert-authority"), REVOKED("@revoked");
+ public enum Marker {
+ CA_CERT("@cert-authority"), REVOKED("@revoked");
- private final String sMarker;
+ private final String sMarker;
- Marker(String sMarker) {
- this.sMarker = sMarker;
- }
+ Marker(String sMarker) {
+ this.sMarker = sMarker;
+ }
- public String getMarkerString() {
- return sMarker;
- }
- }
+ public String getMarkerString() {
+ return sMarker;
+ }
+ }
- private static final Logger logger = LoggerFactory.getLogger(OpenSSHKnownHosts.class);
+ private static final Logger logger = LoggerFactory.getLogger(OpenSSHKnownHosts.class);
}
\ No newline at end of file