Skip to content

Commit

Permalink
Added support for wildcard host entries in known_hosts (Fixes #331)
Browse files Browse the repository at this point in the history
  • Loading branch information
hierynomus committed May 22, 2017
1 parent 9d4f8fc commit 7b535a8
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.sshj.transport.verification;

import net.schmizz.sshj.common.Base64;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.transport.mac.HMACSHA1;
import net.schmizz.sshj.transport.mac.MAC;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class KnownHostMatchers {

public static HostMatcher createMatcher(String hostEntry) throws SSHException {
if (hostEntry.contains(",")) {
return new AnyHostMatcher(hostEntry);
}
if (hostEntry.startsWith("!")) {
return new NegateHostMatcher(hostEntry);
}
if (hostEntry.startsWith("|1|")) {
return new HashedHostMatcher(hostEntry);
}
if (hostEntry.contains("*") || hostEntry.contains("?")) {
return new WildcardHostMatcher(hostEntry);
}

return new EquiHostMatcher(hostEntry);
}

public interface HostMatcher {
boolean match(String hostname) throws IOException;
}

private static class EquiHostMatcher implements HostMatcher {
private String host;

public EquiHostMatcher(String host) {
this.host = host;
}

@Override
public boolean match(String hostname) {
return host.equals(hostname);
}
}

private static class HashedHostMatcher implements HostMatcher {
private final MAC sha1 = new HMACSHA1();
private final String hash;
private final String salt;
private byte[] saltyBytes;

HashedHostMatcher(String hash) throws SSHException {
this.hash = hash;
final String[] hostParts = hash.split("\\|");
if (hostParts.length != 4) {
throw new SSHException("Unrecognized format for hashed hostname");
}
salt = hostParts[2];
}

@Override
public boolean match(String hostname) throws IOException {
return hash.equals(hashHost(hostname));
}

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;
}


}

private static class AnyHostMatcher implements HostMatcher {
private final List<HostMatcher> matchers;

AnyHostMatcher(String hostEntry) throws SSHException {
matchers = new ArrayList<HostMatcher>();
for (String subEntry : hostEntry.split(",")) {
matchers.add(KnownHostMatchers.createMatcher(subEntry));
}
}

@Override
public boolean match(String hostname) throws IOException {
for (HostMatcher matcher : matchers) {
if (matcher.match(hostname)) {
return true;
}
}
return false;
}
}

private static class NegateHostMatcher implements HostMatcher {
private final HostMatcher matcher;

NegateHostMatcher(String hostEntry) throws SSHException {
this.matcher = createMatcher(hostEntry.substring(1));
}

@Override
public boolean match(String hostname) throws IOException {
return !matcher.match(hostname);
}
}

private static class WildcardHostMatcher implements HostMatcher {
private final Pattern pattern;

public WildcardHostMatcher(String hostEntry) {
this.pattern = Pattern.compile(hostEntry.replace(".", "\\.").replace("*", ".*").replace("?", "."));
}

@Override
public boolean match(String hostname) throws IOException {
return pattern.matcher(hostname).matches();
}

@Override
public String toString() {
return "WildcardHostMatcher[" + pattern + ']';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
}
if (response.equalsIgnoreCase(YES)) {
try {
entries().add(new SimpleEntry(null, hostname, KeyType.fromKey(key), key));
entries().add(new HostEntry(null, hostname, KeyType.fromKey(key), key));
write();
console.printf("Warning: Permanently added '%s' (%s) to the list of known hosts.\n", hostname, type);
} catch (IOException e) {
Expand All @@ -60,7 +60,7 @@ protected boolean hostKeyUnverifiableAction(String hostname, PublicKey key) {
}

@Override
protected boolean hostKeyChangedAction(HostEntry entry, String hostname, PublicKey key) {
protected boolean hostKeyChangedAction(KnownHostEntry entry, String hostname, PublicKey key) {
final KeyType type = KeyType.fromKey(key);
final String fp = SecurityUtils.getFingerprint(key);
final String path = getFile().getAbsolutePath();
Expand Down
Loading

0 comments on commit 7b535a8

Please sign in to comment.