Browse Source

sshd: simplify OpenSshServerKeyVerifier

Reduce the dependency on the ClientSession in preparation to
remove it altogether. Remove the internal helper, re-implement
the needed bits. We have not implemented any configuration
possibility in JGit for creating hashed host names in known hosts
files, so we don't need the sshd code that theoretically would
enable this.

Change-Id: I295f5106b60e1cc3a9d085b0cb7ff747daae88be
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
stable-5.5
Thomas Wolf 5 years ago committed by Matthias Sohn
parent
commit
4e8d5d4c63
  1. 295
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java

295
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyVerifier.java

@ -47,7 +47,6 @@ import static java.text.MessageFormat.format;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
@ -67,17 +66,19 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.config.hosts.KnownHostEntry; import org.apache.sshd.client.config.hosts.KnownHostEntry;
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier; import org.apache.sshd.client.config.hosts.KnownHostHashValue;
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair; import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.util.io.ModifiableFileWatcher; import org.apache.sshd.common.util.io.ModifiableFileWatcher;
@ -166,10 +167,6 @@ public class OpenSshServerKeyVerifier
private final List<HostKeyFile> defaultFiles = new ArrayList<>(); private final List<HostKeyFile> defaultFiles = new ArrayList<>();
private enum ModifiedKeyHandling {
DENY, ALLOW, ALLOW_AND_STORE
}
/** /**
* Creates a new {@link OpenSshServerKeyVerifier}. * Creates a new {@link OpenSshServerKeyVerifier}.
* *
@ -215,10 +212,9 @@ public class OpenSshServerKeyVerifier
public List<PublicKey> lookup(ClientSession session, public List<PublicKey> lookup(ClientSession session,
SocketAddress remote) { SocketAddress remote) {
List<HostKeyFile> filesToUse = getFilesToUse(session); List<HostKeyFile> filesToUse = getFilesToUse(session);
HostKeyHelper helper = new HostKeyHelper();
List<PublicKey> result = new ArrayList<>(); List<PublicKey> result = new ArrayList<>();
Collection<SshdSocketAddress> candidates = helper Collection<SshdSocketAddress> candidates = getCandidates(
.resolveHostNetworkIdentities(session, remote); session.getConnectAddress(), remote);
for (HostKeyFile file : filesToUse) { for (HostKeyFile file : filesToUse) {
for (HostEntryPair current : file.get()) { for (HostEntryPair current : file.get()) {
KnownHostEntry entry = current.getHostEntry(); KnownHostEntry entry = current.getHostEntry();
@ -237,19 +233,18 @@ public class OpenSshServerKeyVerifier
public boolean verifyServerKey(ClientSession clientSession, public boolean verifyServerKey(ClientSession clientSession,
SocketAddress remoteAddress, PublicKey serverKey) { SocketAddress remoteAddress, PublicKey serverKey) {
List<HostKeyFile> filesToUse = getFilesToUse(clientSession); List<HostKeyFile> filesToUse = getFilesToUse(clientSession);
AskUser ask = new AskUser(); AskUser ask = new AskUser(clientSession);
HostEntryPair[] modified = { null }; HostEntryPair[] modified = { null };
Path path = null; Path path = null;
HostKeyHelper helper = new HostKeyHelper(); Collection<SshdSocketAddress> candidates = getCandidates(
clientSession.getConnectAddress(), remoteAddress);
for (HostKeyFile file : filesToUse) { for (HostKeyFile file : filesToUse) {
try { try {
if (find(clientSession, remoteAddress, serverKey, file.get(), if (find(candidates, serverKey, file.get(), modified)) {
modified, helper)) {
return true; return true;
} }
} catch (RevokedKeyException e) { } catch (RevokedKeyException e) {
ask.revokedKey(clientSession, remoteAddress, serverKey, ask.revokedKey(remoteAddress, serverKey, file.getPath());
file.getPath());
return false; return false;
} }
if (path == null && modified[0] != null) { if (path == null && modified[0] != null) {
@ -260,20 +255,19 @@ public class OpenSshServerKeyVerifier
} }
if (modified[0] != null) { if (modified[0] != null) {
// We found an entry, but with a different key // We found an entry, but with a different key
ModifiedKeyHandling toDo = ask.acceptModifiedServerKey( AskUser.ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
clientSession, remoteAddress, modified[0].getServerKey(), remoteAddress, modified[0].getServerKey(),
serverKey, path); serverKey, path);
if (toDo == ModifiedKeyHandling.ALLOW_AND_STORE) { if (toDo == AskUser.ModifiedKeyHandling.ALLOW_AND_STORE) {
try { try {
updateModifiedServerKey(clientSession, remoteAddress, updateModifiedServerKey(serverKey, modified[0], path);
serverKey, modified[0], path, helper);
knownHostsFiles.get(path).resetReloadAttributes(); knownHostsFiles.get(path).resetReloadAttributes();
} catch (IOException e) { } catch (IOException e) {
LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate, LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
path)); path));
} }
} }
if (toDo == ModifiedKeyHandling.DENY) { if (toDo == AskUser.ModifiedKeyHandling.DENY) {
return false; return false;
} }
// TODO: OpenSsh disables password and keyboard-interactive // TODO: OpenSsh disables password and keyboard-interactive
@ -281,15 +275,16 @@ public class OpenSshServerKeyVerifier
// are switched off. (Plus a few other things such as X11 forwarding // are switched off. (Plus a few other things such as X11 forwarding
// that are of no interest to a git client.) // that are of no interest to a git client.)
return true; return true;
} else if (ask.acceptUnknownKey(clientSession, remoteAddress, } else if (ask.acceptUnknownKey(remoteAddress, serverKey)) {
serverKey)) {
if (!filesToUse.isEmpty()) { if (!filesToUse.isEmpty()) {
HostKeyFile toUpdate = filesToUse.get(0); HostKeyFile toUpdate = filesToUse.get(0);
path = toUpdate.getPath(); path = toUpdate.getPath();
try { try {
updateKnownHostsFile(clientSession, remoteAddress, if (Files.exists(path) || !askAboutNewFile
serverKey, path, helper); || ask.createNewFile(path)) {
toUpdate.resetReloadAttributes(); updateKnownHostsFile(candidates, serverKey, path);
toUpdate.resetReloadAttributes();
}
} catch (IOException e) { } catch (IOException e) {
LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate, LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
path)); path));
@ -304,12 +299,9 @@ public class OpenSshServerKeyVerifier
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
} }
private boolean find(ClientSession clientSession, private boolean find(Collection<SshdSocketAddress> candidates,
SocketAddress remoteAddress, PublicKey serverKey, PublicKey serverKey, List<HostEntryPair> entries,
List<HostEntryPair> entries, HostEntryPair[] modified, HostEntryPair[] modified) throws RevokedKeyException {
HostKeyHelper helper) throws RevokedKeyException {
Collection<SshdSocketAddress> candidates = helper
.resolveHostNetworkIdentities(clientSession, remoteAddress);
for (HostEntryPair current : entries) { for (HostEntryPair current : entries) {
KnownHostEntry entry = current.getHostEntry(); KnownHostEntry entry = current.getHostEntry();
for (SshdSocketAddress host : candidates) { for (SshdSocketAddress host : candidates) {
@ -355,33 +347,13 @@ public class OpenSshServerKeyVerifier
return userFiles; return userFiles;
} }
private void updateKnownHostsFile(ClientSession clientSession, private void updateKnownHostsFile(Collection<SshdSocketAddress> candidates,
SocketAddress remoteAddress, PublicKey serverKey, Path path, PublicKey serverKey, Path path)
HostKeyHelper updater)
throws IOException { throws IOException {
KnownHostEntry entry = updater.prepareKnownHostEntry(clientSession, String newEntry = createHostKeyLine(candidates, serverKey);
remoteAddress, serverKey); if (newEntry == null) {
if (entry == null) {
return; return;
} }
if (!Files.exists(path)) {
if (askAboutNewFile) {
CredentialsProvider provider = getCredentialsProvider(
clientSession);
if (provider == null) {
// We can't ask, so don't create the file
return;
}
URIish uri = new URIish().setPath(path.toString());
if (!askUser(provider, uri, //
format(SshdText.get().knownHostsUserAskCreationPrompt,
path), //
format(SshdText.get().knownHostsUserAskCreationMsg,
path))) {
return;
}
}
}
LockFile lock = new LockFile(path.toFile()); LockFile lock = new LockFile(path.toFile());
if (lock.lockForAppend()) { if (lock.lockForAppend()) {
try { try {
@ -389,7 +361,7 @@ public class OpenSshServerKeyVerifier
new OutputStreamWriter(lock.getOutputStream(), new OutputStreamWriter(lock.getOutputStream(),
UTF_8))) { UTF_8))) {
writer.newLine(); writer.newLine();
writer.write(entry.getConfigLine()); writer.write(newEntry);
writer.newLine(); writer.newLine();
} }
lock.commit(); lock.commit();
@ -403,15 +375,12 @@ public class OpenSshServerKeyVerifier
} }
} }
private void updateModifiedServerKey(ClientSession clientSession, private void updateModifiedServerKey(PublicKey serverKey,
SocketAddress remoteAddress, PublicKey serverKey, HostEntryPair entry, Path path)
HostEntryPair entry, Path path, HostKeyHelper helper)
throws IOException { throws IOException {
KnownHostEntry hostEntry = entry.getHostEntry(); KnownHostEntry hostEntry = entry.getHostEntry();
String oldLine = hostEntry.getConfigLine(); String oldLine = hostEntry.getConfigLine();
String newLine = helper.prepareModifiedServerKeyLine(clientSession, String newLine = updateHostKeyLine(oldLine, serverKey);
remoteAddress, hostEntry, oldLine, entry.getServerKey(),
serverKey);
if (newLine == null || newLine.isEmpty()) { if (newLine == null || newLine.isEmpty()) {
return; return;
} }
@ -454,61 +423,64 @@ public class OpenSshServerKeyVerifier
} }
} }
private static CredentialsProvider getCredentialsProvider( private static class AskUser {
ClientSession session) {
if (session instanceof JGitClientSession) { public enum ModifiedKeyHandling {
return ((JGitClientSession) session).getCredentialsProvider(); DENY, ALLOW, ALLOW_AND_STORE
} }
return null;
}
private static boolean askUser(CredentialsProvider provider, URIish uri, private enum Check {
String prompt, String... messages) { ASK, DENY, ALLOW;
List<CredentialItem> items = new ArrayList<>(messages.length + 1);
for (String message : messages) {
items.add(new CredentialItem.InformationalMessage(message));
} }
if (prompt != null) {
CredentialItem.YesNoType answer = new CredentialItem.YesNoType( private final JGitClientSession session;
prompt);
items.add(answer); public AskUser(ClientSession clientSession) {
return provider.get(uri, items) && answer.getValue(); session = (clientSession instanceof JGitClientSession)
} else { ? (JGitClientSession) clientSession
return provider.get(uri, items); : null;
} }
}
private static class AskUser { private CredentialsProvider getCredentialsProvider() {
return session == null ? null : session.getCredentialsProvider();
}
private enum Check { private static boolean askUser(CredentialsProvider provider, URIish uri,
ASK, DENY, ALLOW; String prompt, String... messages) {
List<CredentialItem> items = new ArrayList<>(messages.length + 1);
for (String message : messages) {
items.add(new CredentialItem.InformationalMessage(message));
}
if (prompt != null) {
CredentialItem.YesNoType answer = new CredentialItem.YesNoType(
prompt);
items.add(answer);
return provider.get(uri, items) && answer.getValue();
} else {
return provider.get(uri, items);
}
} }
@SuppressWarnings("nls") private Check checkMode(SocketAddress remoteAddress, boolean changed) {
private Check checkMode(ClientSession session,
SocketAddress remoteAddress, boolean changed) {
if (!(remoteAddress instanceof InetSocketAddress)) { if (!(remoteAddress instanceof InetSocketAddress)) {
return Check.DENY; return Check.DENY;
} }
if (session instanceof JGitClientSession) { HostConfigEntry entry = session.getHostConfigEntry();
HostConfigEntry entry = ((JGitClientSession) session) String value = entry
.getHostConfigEntry(); .getProperty(SshConstants.STRICT_HOST_KEY_CHECKING, "ask"); //$NON-NLS-1$
String value = entry.getProperty( switch (value.toLowerCase(Locale.ROOT)) {
SshConstants.STRICT_HOST_KEY_CHECKING, "ask"); case SshConstants.YES:
switch (value.toLowerCase(Locale.ROOT)) { case SshConstants.ON:
case SshConstants.YES: return Check.DENY;
case SshConstants.ON: case SshConstants.NO:
return Check.DENY; case SshConstants.OFF:
case SshConstants.NO: return Check.ALLOW;
case SshConstants.OFF: case "accept-new": //$NON-NLS-1$
return Check.ALLOW; return changed ? Check.DENY : Check.ALLOW;
case "accept-new": default:
return changed ? Check.DENY : Check.ALLOW; break;
default:
break;
}
} }
if (getCredentialsProvider(session) == null) { if (getCredentialsProvider() == null) {
// This is called only for new, unknown hosts. If we have no way // This is called only for new, unknown hosts. If we have no way
// to interact with the user, the fallback mode is to deny the // to interact with the user, the fallback mode is to deny the
// key. // key.
@ -517,15 +489,14 @@ public class OpenSshServerKeyVerifier
return Check.ASK; return Check.ASK;
} }
public void revokedKey(ClientSession clientSession, public void revokedKey(SocketAddress remoteAddress, PublicKey serverKey,
SocketAddress remoteAddress, PublicKey serverKey, Path path) { Path path) {
CredentialsProvider provider = getCredentialsProvider( CredentialsProvider provider = getCredentialsProvider();
clientSession);
if (provider == null) { if (provider == null) {
return; return;
} }
InetSocketAddress remote = (InetSocketAddress) remoteAddress; InetSocketAddress remote = (InetSocketAddress) remoteAddress;
URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(), URIish uri = JGitUserInteraction.toURI(session.getUsername(),
remote); remote);
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256, String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
serverKey); serverKey);
@ -539,14 +510,13 @@ public class OpenSshServerKeyVerifier
md5, sha256); md5, sha256);
} }
public boolean acceptUnknownKey(ClientSession clientSession, public boolean acceptUnknownKey(SocketAddress remoteAddress,
SocketAddress remoteAddress, PublicKey serverKey) { PublicKey serverKey) {
Check check = checkMode(clientSession, remoteAddress, false); Check check = checkMode(remoteAddress, false);
if (check != Check.ASK) { if (check != Check.ASK) {
return check == Check.ALLOW; return check == Check.ALLOW;
} }
CredentialsProvider provider = getCredentialsProvider( CredentialsProvider provider = getCredentialsProvider();
clientSession);
InetSocketAddress remote = (InetSocketAddress) remoteAddress; InetSocketAddress remote = (InetSocketAddress) remoteAddress;
// Ask the user // Ask the user
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256, String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
@ -554,7 +524,7 @@ public class OpenSshServerKeyVerifier
String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey); String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
String keyAlgorithm = serverKey.getAlgorithm(); String keyAlgorithm = serverKey.getAlgorithm();
String remoteHost = remote.getHostString(); String remoteHost = remote.getHostString();
URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(), URIish uri = JGitUserInteraction.toURI(session.getUsername(),
remote); remote);
String prompt = SshdText.get().knownHostsUnknownKeyPrompt; String prompt = SshdText.get().knownHostsUnknownKeyPrompt;
return askUser(provider, uri, prompt, // return askUser(provider, uri, prompt, //
@ -566,10 +536,9 @@ public class OpenSshServerKeyVerifier
} }
public ModifiedKeyHandling acceptModifiedServerKey( public ModifiedKeyHandling acceptModifiedServerKey(
ClientSession clientSession,
SocketAddress remoteAddress, PublicKey expected, SocketAddress remoteAddress, PublicKey expected,
PublicKey actual, Path path) { PublicKey actual, Path path) {
Check check = checkMode(clientSession, remoteAddress, true); Check check = checkMode(remoteAddress, true);
if (check == Check.ALLOW) { if (check == Check.ALLOW) {
// Never auto-store on CHECK.ALLOW // Never auto-store on CHECK.ALLOW
return ModifiedKeyHandling.ALLOW; return ModifiedKeyHandling.ALLOW;
@ -577,7 +546,7 @@ public class OpenSshServerKeyVerifier
InetSocketAddress remote = (InetSocketAddress) remoteAddress; InetSocketAddress remote = (InetSocketAddress) remoteAddress;
String keyAlgorithm = actual.getAlgorithm(); String keyAlgorithm = actual.getAlgorithm();
String remoteHost = remote.getHostString(); String remoteHost = remote.getHostString();
URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(), URIish uri = JGitUserInteraction.toURI(session.getUsername(),
remote); remote);
List<String> messages = new ArrayList<>(); List<String> messages = new ArrayList<>();
String warning = format( String warning = format(
@ -589,8 +558,7 @@ public class OpenSshServerKeyVerifier
KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual)); KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual));
messages.addAll(Arrays.asList(warning.split("\n"))); //$NON-NLS-1$ messages.addAll(Arrays.asList(warning.split("\n"))); //$NON-NLS-1$
CredentialsProvider provider = getCredentialsProvider( CredentialsProvider provider = getCredentialsProvider();
clientSession);
if (check == Check.DENY) { if (check == Check.DENY) {
if (provider != null) { if (provider != null) {
messages.add(format( messages.add(format(
@ -618,6 +586,18 @@ public class OpenSshServerKeyVerifier
return ModifiedKeyHandling.DENY; return ModifiedKeyHandling.DENY;
} }
public boolean createNewFile(Path path) {
CredentialsProvider provider = getCredentialsProvider();
if (provider == null) {
// We can't ask, so don't create the file
return false;
}
URIish uri = new URIish().setPath(path.toString());
return askUser(provider, uri, //
format(SshdText.get().knownHostsUserAskCreationPrompt,
path), //
format(SshdText.get().knownHostsUserAskCreationMsg, path));
}
} }
private static class HostKeyFile extends ModifiableFileWatcher private static class HostKeyFile extends ModifiableFileWatcher
@ -694,50 +674,45 @@ public class OpenSshServerKeyVerifier
} }
} }
// The stuff below is just a hack to avoid having to copy a lot of code from private Collection<SshdSocketAddress> getCandidates(
// KnownHostsServerKeyVerifier SocketAddress connectAddress, SocketAddress remoteAddress) {
Collection<SshdSocketAddress> candidates = new TreeSet<>(
private static class HostKeyHelper extends KnownHostsServerKeyVerifier { SshdSocketAddress.BY_HOST_AND_PORT);
candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress));
public HostKeyHelper() { candidates.add(SshdSocketAddress.toSshdSocketAddress(connectAddress));
// These two arguments will never be used in any way. return candidates;
super((c, r, s) -> false, new File(".").toPath()); //$NON-NLS-1$ }
}
@Override private String createHostKeyLine(Collection<SshdSocketAddress> patterns,
protected KnownHostEntry prepareKnownHostEntry( PublicKey key) throws IOException {
ClientSession clientSession, SocketAddress remoteAddress, StringBuilder result = new StringBuilder();
PublicKey serverKey) throws IOException { for (SshdSocketAddress address : patterns) {
// Make this method accessible if (result.length() > 0) {
try { result.append(',');
return super.prepareKnownHostEntry(clientSession, remoteAddress,
serverKey);
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
} }
KnownHostHashValue.appendHostPattern(result, address.getHostName(),
address.getPort());
} }
result.append(' ');
PublicKeyEntry.appendPublicKeyEntry(result, key);
return result.toString();
}
@Override private String updateHostKeyLine(String line, PublicKey newKey)
protected String prepareModifiedServerKeyLine( throws IOException {
ClientSession clientSession, SocketAddress remoteAddress, // Replaces an existing public key by the new key
KnownHostEntry entry, String curLine, PublicKey expected, int pos = line.indexOf(' ');
PublicKey actual) throws IOException { if (pos > 0 && line.charAt(0) == KnownHostEntry.MARKER_INDICATOR) {
// Make this method accessible // We're at the end of the marker. Skip ahead to the next blank.
try { pos = line.indexOf(' ', pos + 1);
return super.prepareModifiedServerKeyLine(clientSession,
remoteAddress, entry, curLine, expected, actual);
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
} }
if (pos < 0) {
@Override // Don't update if bogus format
protected Collection<SshdSocketAddress> resolveHostNetworkIdentities( return null;
ClientSession clientSession, SocketAddress remoteAddress) {
// Make this method accessible
return super.resolveHostNetworkIdentities(clientSession,
remoteAddress);
} }
StringBuilder result = new StringBuilder(line.substring(0, pos + 1));
PublicKeyEntry.appendPublicKeyEntry(result, newKey);
return result.toString();
} }
} }

Loading…
Cancel
Save