import java.io.BufferedReader;
import java.io.BufferedWriter;
-import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
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.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
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.digest.BuiltinDigests;
import org.apache.sshd.common.util.io.ModifiableFileWatcher;
private final List<HostKeyFile> defaultFiles = new ArrayList<>();
- private enum ModifiedKeyHandling {
- DENY, ALLOW, ALLOW_AND_STORE
- }
-
/**
* Creates a new {@link OpenSshServerKeyVerifier}.
*
public List<PublicKey> lookup(ClientSession session,
SocketAddress remote) {
List<HostKeyFile> filesToUse = getFilesToUse(session);
- HostKeyHelper helper = new HostKeyHelper();
List<PublicKey> result = new ArrayList<>();
- Collection<SshdSocketAddress> candidates = helper
- .resolveHostNetworkIdentities(session, remote);
+ Collection<SshdSocketAddress> candidates = getCandidates(
+ session.getConnectAddress(), remote);
for (HostKeyFile file : filesToUse) {
for (HostEntryPair current : file.get()) {
KnownHostEntry entry = current.getHostEntry();
public boolean verifyServerKey(ClientSession clientSession,
SocketAddress remoteAddress, PublicKey serverKey) {
List<HostKeyFile> filesToUse = getFilesToUse(clientSession);
- AskUser ask = new AskUser();
+ AskUser ask = new AskUser(clientSession);
HostEntryPair[] modified = { null };
Path path = null;
- HostKeyHelper helper = new HostKeyHelper();
+ Collection<SshdSocketAddress> candidates = getCandidates(
+ clientSession.getConnectAddress(), remoteAddress);
for (HostKeyFile file : filesToUse) {
try {
- if (find(clientSession, remoteAddress, serverKey, file.get(),
- modified, helper)) {
+ if (find(candidates, serverKey, file.get(), modified)) {
return true;
}
} catch (RevokedKeyException e) {
- ask.revokedKey(clientSession, remoteAddress, serverKey,
- file.getPath());
+ ask.revokedKey(remoteAddress, serverKey, file.getPath());
return false;
}
if (path == null && modified[0] != null) {
}
if (modified[0] != null) {
// We found an entry, but with a different key
- ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
- clientSession, remoteAddress, modified[0].getServerKey(),
+ AskUser.ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
+ remoteAddress, modified[0].getServerKey(),
serverKey, path);
- if (toDo == ModifiedKeyHandling.ALLOW_AND_STORE) {
+ if (toDo == AskUser.ModifiedKeyHandling.ALLOW_AND_STORE) {
try {
- updateModifiedServerKey(clientSession, remoteAddress,
- serverKey, modified[0], path, helper);
+ updateModifiedServerKey(serverKey, modified[0], path);
knownHostsFiles.get(path).resetReloadAttributes();
} catch (IOException e) {
LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
path));
}
}
- if (toDo == ModifiedKeyHandling.DENY) {
+ if (toDo == AskUser.ModifiedKeyHandling.DENY) {
return false;
}
// TODO: OpenSsh disables password and keyboard-interactive
// are switched off. (Plus a few other things such as X11 forwarding
// that are of no interest to a git client.)
return true;
- } else if (ask.acceptUnknownKey(clientSession, remoteAddress,
- serverKey)) {
+ } else if (ask.acceptUnknownKey(remoteAddress, serverKey)) {
if (!filesToUse.isEmpty()) {
HostKeyFile toUpdate = filesToUse.get(0);
path = toUpdate.getPath();
try {
- updateKnownHostsFile(clientSession, remoteAddress,
- serverKey, path, helper);
- toUpdate.resetReloadAttributes();
+ if (Files.exists(path) || !askAboutNewFile
+ || ask.createNewFile(path)) {
+ updateKnownHostsFile(candidates, serverKey, path);
+ toUpdate.resetReloadAttributes();
+ }
} catch (IOException e) {
LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
path));
private static final long serialVersionUID = 1L;
}
- private boolean find(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey,
- List<HostEntryPair> entries, HostEntryPair[] modified,
- HostKeyHelper helper) throws RevokedKeyException {
- Collection<SshdSocketAddress> candidates = helper
- .resolveHostNetworkIdentities(clientSession, remoteAddress);
+ private boolean find(Collection<SshdSocketAddress> candidates,
+ PublicKey serverKey, List<HostEntryPair> entries,
+ HostEntryPair[] modified) throws RevokedKeyException {
for (HostEntryPair current : entries) {
KnownHostEntry entry = current.getHostEntry();
for (SshdSocketAddress host : candidates) {
return userFiles;
}
- private void updateKnownHostsFile(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey, Path path,
- HostKeyHelper updater)
+ private void updateKnownHostsFile(Collection<SshdSocketAddress> candidates,
+ PublicKey serverKey, Path path)
throws IOException {
- KnownHostEntry entry = updater.prepareKnownHostEntry(clientSession,
- remoteAddress, serverKey);
- if (entry == null) {
+ String newEntry = createHostKeyLine(candidates, serverKey);
+ if (newEntry == null) {
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());
if (lock.lockForAppend()) {
try {
new OutputStreamWriter(lock.getOutputStream(),
UTF_8))) {
writer.newLine();
- writer.write(entry.getConfigLine());
+ writer.write(newEntry);
writer.newLine();
}
lock.commit();
}
}
- private void updateModifiedServerKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey,
- HostEntryPair entry, Path path, HostKeyHelper helper)
+ private void updateModifiedServerKey(PublicKey serverKey,
+ HostEntryPair entry, Path path)
throws IOException {
KnownHostEntry hostEntry = entry.getHostEntry();
String oldLine = hostEntry.getConfigLine();
- String newLine = helper.prepareModifiedServerKeyLine(clientSession,
- remoteAddress, hostEntry, oldLine, entry.getServerKey(),
- serverKey);
+ String newLine = updateHostKeyLine(oldLine, serverKey);
if (newLine == null || newLine.isEmpty()) {
return;
}
}
}
- private static CredentialsProvider getCredentialsProvider(
- ClientSession session) {
- if (session instanceof JGitClientSession) {
- return ((JGitClientSession) session).getCredentialsProvider();
+ private static class AskUser {
+
+ public enum ModifiedKeyHandling {
+ DENY, ALLOW, ALLOW_AND_STORE
}
- return null;
- }
- private static boolean askUser(CredentialsProvider provider, URIish uri,
- String prompt, String... messages) {
- List<CredentialItem> items = new ArrayList<>(messages.length + 1);
- for (String message : messages) {
- items.add(new CredentialItem.InformationalMessage(message));
+ private enum Check {
+ ASK, DENY, ALLOW;
}
- 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);
+
+ private final JGitClientSession session;
+
+ public AskUser(ClientSession clientSession) {
+ session = (clientSession instanceof JGitClientSession)
+ ? (JGitClientSession) clientSession
+ : null;
}
- }
- private static class AskUser {
+ private CredentialsProvider getCredentialsProvider() {
+ return session == null ? null : session.getCredentialsProvider();
+ }
- private enum Check {
- ASK, DENY, ALLOW;
+ private static boolean askUser(CredentialsProvider provider, URIish uri,
+ 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(ClientSession session,
- SocketAddress remoteAddress, boolean changed) {
+ private Check checkMode(SocketAddress remoteAddress, boolean changed) {
if (!(remoteAddress instanceof InetSocketAddress)) {
return Check.DENY;
}
- if (session instanceof JGitClientSession) {
- HostConfigEntry entry = ((JGitClientSession) session)
- .getHostConfigEntry();
- String value = entry.getProperty(
- SshConstants.STRICT_HOST_KEY_CHECKING, "ask");
- switch (value.toLowerCase(Locale.ROOT)) {
- case SshConstants.YES:
- case SshConstants.ON:
- return Check.DENY;
- case SshConstants.NO:
- case SshConstants.OFF:
- return Check.ALLOW;
- case "accept-new":
- return changed ? Check.DENY : Check.ALLOW;
- default:
- break;
- }
+ HostConfigEntry entry = session.getHostConfigEntry();
+ String value = entry
+ .getProperty(SshConstants.STRICT_HOST_KEY_CHECKING, "ask"); //$NON-NLS-1$
+ switch (value.toLowerCase(Locale.ROOT)) {
+ case SshConstants.YES:
+ case SshConstants.ON:
+ return Check.DENY;
+ case SshConstants.NO:
+ case SshConstants.OFF:
+ return Check.ALLOW;
+ case "accept-new": //$NON-NLS-1$
+ return changed ? Check.DENY : Check.ALLOW;
+ default:
+ break;
}
- if (getCredentialsProvider(session) == null) {
+ if (getCredentialsProvider() == null) {
// 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
// key.
return Check.ASK;
}
- public void revokedKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey, Path path) {
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
+ public void revokedKey(SocketAddress remoteAddress, PublicKey serverKey,
+ Path path) {
+ CredentialsProvider provider = getCredentialsProvider();
if (provider == null) {
return;
}
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
- URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(session.getUsername(),
remote);
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
serverKey);
md5, sha256);
}
- public boolean acceptUnknownKey(ClientSession clientSession,
- SocketAddress remoteAddress, PublicKey serverKey) {
- Check check = checkMode(clientSession, remoteAddress, false);
+ public boolean acceptUnknownKey(SocketAddress remoteAddress,
+ PublicKey serverKey) {
+ Check check = checkMode(remoteAddress, false);
if (check != Check.ASK) {
return check == Check.ALLOW;
}
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
+ CredentialsProvider provider = getCredentialsProvider();
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
// Ask the user
String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
String keyAlgorithm = serverKey.getAlgorithm();
String remoteHost = remote.getHostString();
- URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(session.getUsername(),
remote);
String prompt = SshdText.get().knownHostsUnknownKeyPrompt;
return askUser(provider, uri, prompt, //
}
public ModifiedKeyHandling acceptModifiedServerKey(
- ClientSession clientSession,
SocketAddress remoteAddress, PublicKey expected,
PublicKey actual, Path path) {
- Check check = checkMode(clientSession, remoteAddress, true);
+ Check check = checkMode(remoteAddress, true);
if (check == Check.ALLOW) {
// Never auto-store on CHECK.ALLOW
return ModifiedKeyHandling.ALLOW;
InetSocketAddress remote = (InetSocketAddress) remoteAddress;
String keyAlgorithm = actual.getAlgorithm();
String remoteHost = remote.getHostString();
- URIish uri = JGitUserInteraction.toURI(clientSession.getUsername(),
+ URIish uri = JGitUserInteraction.toURI(session.getUsername(),
remote);
List<String> messages = new ArrayList<>();
String warning = format(
KeyUtils.getFingerPrint(BuiltinDigests.sha256, actual));
messages.addAll(Arrays.asList(warning.split("\n"))); //$NON-NLS-1$
- CredentialsProvider provider = getCredentialsProvider(
- clientSession);
+ CredentialsProvider provider = getCredentialsProvider();
if (check == Check.DENY) {
if (provider != null) {
messages.add(format(
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
}
}
- // The stuff below is just a hack to avoid having to copy a lot of code from
- // KnownHostsServerKeyVerifier
-
- private static class HostKeyHelper extends KnownHostsServerKeyVerifier {
-
- public HostKeyHelper() {
- // These two arguments will never be used in any way.
- super((c, r, s) -> false, new File(".").toPath()); //$NON-NLS-1$
- }
+ private Collection<SshdSocketAddress> getCandidates(
+ SocketAddress connectAddress, SocketAddress remoteAddress) {
+ Collection<SshdSocketAddress> candidates = new TreeSet<>(
+ SshdSocketAddress.BY_HOST_AND_PORT);
+ candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress));
+ candidates.add(SshdSocketAddress.toSshdSocketAddress(connectAddress));
+ return candidates;
+ }
- @Override
- protected KnownHostEntry prepareKnownHostEntry(
- ClientSession clientSession, SocketAddress remoteAddress,
- PublicKey serverKey) throws IOException {
- // Make this method accessible
- try {
- return super.prepareKnownHostEntry(clientSession, remoteAddress,
- serverKey);
- } catch (Exception e) {
- throw new IOException(e.getMessage(), e);
+ private String createHostKeyLine(Collection<SshdSocketAddress> patterns,
+ PublicKey key) throws IOException {
+ StringBuilder result = new StringBuilder();
+ for (SshdSocketAddress address : patterns) {
+ if (result.length() > 0) {
+ result.append(',');
}
+ KnownHostHashValue.appendHostPattern(result, address.getHostName(),
+ address.getPort());
}
+ result.append(' ');
+ PublicKeyEntry.appendPublicKeyEntry(result, key);
+ return result.toString();
+ }
- @Override
- protected String prepareModifiedServerKeyLine(
- ClientSession clientSession, SocketAddress remoteAddress,
- KnownHostEntry entry, String curLine, PublicKey expected,
- PublicKey actual) throws IOException {
- // Make this method accessible
- try {
- return super.prepareModifiedServerKeyLine(clientSession,
- remoteAddress, entry, curLine, expected, actual);
- } catch (Exception e) {
- throw new IOException(e.getMessage(), e);
- }
+ private String updateHostKeyLine(String line, PublicKey newKey)
+ throws IOException {
+ // Replaces an existing public key by the new key
+ int pos = line.indexOf(' ');
+ if (pos > 0 && line.charAt(0) == KnownHostEntry.MARKER_INDICATOR) {
+ // We're at the end of the marker. Skip ahead to the next blank.
+ pos = line.indexOf(' ', pos + 1);
}
-
- @Override
- protected Collection<SshdSocketAddress> resolveHostNetworkIdentities(
- ClientSession clientSession, SocketAddress remoteAddress) {
- // Make this method accessible
- return super.resolveHostNetworkIdentities(clientSession,
- remoteAddress);
+ if (pos < 0) {
+ // Don't update if bogus format
+ return null;
}
+ StringBuilder result = new StringBuilder(line.substring(0, pos + 1));
+ PublicKeyEntry.appendPublicKeyEntry(result, newKey);
+ return result.toString();
}
}