diff options
Diffstat (limited to 'org.eclipse.jgit.ssh.apache')
7 files changed, 238 insertions, 37 deletions
diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties index 5bc0867674..defcbdcfc1 100644 --- a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties +++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties @@ -8,6 +8,7 @@ configInvalidProxyJump=Ssh config, host ''{0}'': Cannot parse ProxyJump ''{1}'' configNoKnownAlgorithms=Ssh config ''{0}'' ''{1}'' resulted in empty list (none known, or all known removed); using default. configProxyJumpNotSsh=Non-ssh URI in ProxyJump ssh config configProxyJumpWithPath=ProxyJump ssh config: jump host specification must not have a path +configUnknownAlgorithm=Ssh config {0}: ignoring unknown algorithm ''{1}'' in {2} {3} ftpCloseFailed=Closing the SFTP channel failed gssapiFailure=GSS-API error for mechanism OID {0} gssapiInitFailure=GSS-API initialization failure for mechanism {0} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java index 7656fe8d08..066cec38ba 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2019 Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -29,17 +29,26 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import org.apache.sshd.client.ClientBuilder; import org.apache.sshd.client.ClientFactoryManager; import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.client.session.ClientSessionImpl; import org.apache.sshd.common.AttributeRepository; import org.apache.sshd.common.FactoryManager; +import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.PropertyResolver; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.io.IoSession; import org.apache.sshd.common.io.IoWriteFuture; +import org.apache.sshd.common.kex.BuiltinDHFactories; +import org.apache.sshd.common.kex.DHFactory; import org.apache.sshd.common.kex.KexProposalOption; +import org.apache.sshd.common.kex.KeyExchangeFactory; +import org.apache.sshd.common.kex.extension.KexExtensionHandler; +import org.apache.sshd.common.kex.extension.KexExtensions; +import org.apache.sshd.common.signature.BuiltinSignatures; +import org.apache.sshd.common.kex.extension.KexExtensionHandler.AvailabilityPhase; import org.apache.sshd.common.util.Readable; import org.apache.sshd.common.util.buffer.Buffer; import org.eclipse.jgit.errors.InvalidPatternException; @@ -70,6 +79,8 @@ public class JGitClientSession extends ClientSessionImpl { */ private static final int DEFAULT_MAX_IDENTIFICATION_SIZE = 64 * 1024; + private static final AttributeKey<Boolean> INITIAL_KEX_DONE = new AttributeKey<>(); + private HostConfigEntry hostConfig; private CredentialsProvider credentialsProvider; @@ -219,6 +230,32 @@ public class JGitClientSession extends ClientSessionImpl { return result; } + Set<String> getAllAvailableSignatureAlgorithms() { + Set<String> allAvailable = new HashSet<>(); + BuiltinSignatures.VALUES.forEach(s -> allAvailable.add(s.getName())); + BuiltinSignatures.getRegisteredExtensions() + .forEach(s -> allAvailable.add(s.getName())); + return allAvailable; + } + + private void setNewFactories(Collection<String> defaultFactories, + Collection<String> finalFactories) { + // If new factory names were added make sure we actually have factories + // for them all. + // + // But add new ones at the end: we don't want to change the order for + // pubkey auth, and any new ones added here were not included in the + // default set for some reason, such as being deprecated or weak. + // + // The order for KEX is determined by the order in the proposal string, + // but the order in pubkey auth is determined by the order in the + // factory list (possibly overridden via ssh config + // PubkeyAcceptedAlgorithms; see JGitPublicKeyAuthentication). + Set<String> resultSet = new LinkedHashSet<>(defaultFactories); + resultSet.addAll(finalFactories); + setSignatureFactoriesNames(resultSet); + } + @Override protected String resolveAvailableSignaturesProposal( FactoryManager manager) { @@ -229,16 +266,17 @@ public class JGitClientSession extends ClientSessionImpl { .getProperty(SshConstants.HOST_KEY_ALGORITHMS); if (!StringUtils.isEmptyOrNull(algorithms)) { List<String> result = modifyAlgorithmList(defaultSignatures, - algorithms, SshConstants.HOST_KEY_ALGORITHMS); + getAllAvailableSignatureAlgorithms(), algorithms, + SshConstants.HOST_KEY_ALGORITHMS); if (!result.isEmpty()) { if (log.isDebugEnabled()) { log.debug(SshConstants.HOST_KEY_ALGORITHMS + ' ' + result); } + setNewFactories(defaultSignatures, result); return String.join(",", result); //$NON-NLS-1$ } log.warn(format(SshdText.get().configNoKnownAlgorithms, - SshConstants.HOST_KEY_ALGORITHMS, - algorithms)); + SshConstants.HOST_KEY_ALGORITHMS, algorithms)); } // No HostKeyAlgorithms; using default -- change order to put existing // keys first. @@ -261,6 +299,10 @@ public class JGitClientSession extends ClientSessionImpl { if (log.isDebugEnabled()) { log.debug(SshConstants.HOST_KEY_ALGORITHMS + ' ' + reordered); } + // Make sure we actually have factories for them all. + if (reordered.size() > defaultSignatures.size()) { + setNewFactories(defaultSignatures, reordered); + } return String.join(",", reordered); //$NON-NLS-1$ } if (log.isDebugEnabled()) { @@ -270,15 +312,87 @@ public class JGitClientSession extends ClientSessionImpl { return String.join(",", defaultSignatures); //$NON-NLS-1$ } + private List<String> determineKexProposal() { + List<KeyExchangeFactory> kexFactories = getKeyExchangeFactories(); + List<String> defaultKexMethods = NamedResource + .getNameList(kexFactories); + HostConfigEntry config = resolveAttribute( + JGitSshClient.HOST_CONFIG_ENTRY); + String algorithms = config.getProperty(SshConstants.KEX_ALGORITHMS); + if (!StringUtils.isEmptyOrNull(algorithms)) { + Set<String> allAvailable = new HashSet<>(); + BuiltinDHFactories.VALUES + .forEach(s -> allAvailable.add(s.getName())); + BuiltinDHFactories.getRegisteredExtensions() + .forEach(s -> allAvailable.add(s.getName())); + List<String> result = modifyAlgorithmList(defaultKexMethods, + allAvailable, algorithms, SshConstants.KEX_ALGORITHMS); + if (!result.isEmpty()) { + // If new ones were added, update the installed factories + Set<String> configuredKexMethods = new HashSet<>( + defaultKexMethods); + List<KeyExchangeFactory> newKexFactories = new ArrayList<>(); + result.forEach(name -> { + if (!configuredKexMethods.contains(name)) { + DHFactory factory = BuiltinDHFactories + .resolveFactory(name); + if (factory == null) { + // Should not occur here + if (log.isDebugEnabled()) { + log.debug( + "determineKexProposal({}) unknown KEX algorithm {} ignored", //$NON-NLS-1$ + this, name); + } + } else { + newKexFactories + .add(ClientBuilder.DH2KEX.apply(factory)); + } + } + }); + if (!newKexFactories.isEmpty()) { + newKexFactories.addAll(kexFactories); + setKeyExchangeFactories(newKexFactories); + } + return result; + } + log.warn(format(SshdText.get().configNoKnownAlgorithms, + SshConstants.KEX_ALGORITHMS, algorithms)); + } + return defaultKexMethods; + } + + @Override + protected String resolveSessionKexProposal(String hostKeyTypes) + throws IOException { + String kexMethods = String.join(",", determineKexProposal()); //$NON-NLS-1$ + Boolean isRekey = getAttribute(INITIAL_KEX_DONE); + if (isRekey == null || !isRekey.booleanValue()) { + // First time + KexExtensionHandler extHandler = getKexExtensionHandler(); + if (extHandler != null && extHandler.isKexExtensionsAvailable(this, + AvailabilityPhase.PROPOSAL)) { + if (kexMethods.isEmpty()) { + kexMethods = KexExtensions.CLIENT_KEX_EXTENSION; + } else { + kexMethods += ',' + KexExtensions.CLIENT_KEX_EXTENSION; + } + } + setAttribute(INITIAL_KEX_DONE, Boolean.TRUE); + } + if (log.isDebugEnabled()) { + log.debug(SshConstants.KEX_ALGORITHMS + ' ' + kexMethods); + } + return kexMethods; + } + /** * Modifies a given algorithm list according to a list from the ssh config, - * including remove ('-') and reordering ('^') operators. Addition ('+') is - * not handled since we have no way of adding dynamically implementations, - * and the defaultList is supposed to contain all known implementations - * already. + * including add ('+'), remove ('-') and reordering ('^') operators. * * @param defaultList * to modify + * @param allAvailable + * all available values * @param fromConfig * telling how to modify the {@code defaultList}, must not be * {@code null} or empty @@ -288,22 +402,22 @@ public class JGitClientSession extends ClientSessionImpl { * set */ public List<String> modifyAlgorithmList(List<String> defaultList, - String fromConfig, String overrideKey) { + Set<String> allAvailable, String fromConfig, String overrideKey) { Set<String> defaults = new LinkedHashSet<>(); defaults.addAll(defaultList); switch (fromConfig.charAt(0)) { case '+': - // Additions make not much sense -- it's either in - // defaultList already, or we have no implementation for - // it. No point in proposing it. - return defaultList; + List<String> newSignatures = filteredList(allAvailable, overrideKey, + fromConfig.substring(1)); + defaults.addAll(newSignatures); + return new ArrayList<>(defaults); case '-': // This takes wildcard patterns! removeFromList(defaults, overrideKey, fromConfig.substring(1)); return new ArrayList<>(defaults); case '^': // Specified entries go to the front of the default list - List<String> allSignatures = filteredList(defaults, + List<String> allSignatures = filteredList(allAvailable, overrideKey, fromConfig.substring(1)); Set<String> atFront = new HashSet<>(allSignatures); for (String sig : defaults) { @@ -315,7 +429,7 @@ public class JGitClientSession extends ClientSessionImpl { default: // Default is overridden -- only accept the ones for which we do // have an implementation. - return filteredList(defaults, fromConfig); + return filteredList(allAvailable, overrideKey, fromConfig); } } @@ -342,11 +456,15 @@ public class JGitClientSession extends ClientSessionImpl { } } - private List<String> filteredList(Set<String> known, String values) { + private List<String> filteredList(Set<String> known, String key, + String values) { List<String> newNames = new ArrayList<>(); for (String newValue : values.split("\\s*,\\s*")) { //$NON-NLS-1$ if (known.contains(newValue)) { newNames.add(newValue); + } else { + log.warn(format(SshdText.get().configUnknownAlgorithm, this, + newValue, key, values)); } } return newNames; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java new file mode 100644 index 0000000000..0e3e24dcff --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.transport.sshd; + +import java.io.IOException; + +import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey; +import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory; +import org.apache.sshd.client.session.ClientSession; + +/** + * A customized authentication factory for public key user authentication. + */ +public class JGitPublicKeyAuthFactory extends UserAuthPublicKeyFactory { + + /** The singleton {@link JGitPublicKeyAuthFactory}. */ + public static final JGitPublicKeyAuthFactory FACTORY = new JGitPublicKeyAuthFactory(); + + private JGitPublicKeyAuthFactory() { + super(); + } + + @Override + public UserAuthPublicKey createUserAuth(ClientSession session) + throws IOException { + return new JGitPublicKeyAuthentication(getSignatureFactories()); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java new file mode 100644 index 0000000000..08da18f5aa --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.internal.transport.sshd; + +import static java.text.MessageFormat.format; +import static org.eclipse.jgit.transport.SshConstants.PUBKEY_ACCEPTED_ALGORITHMS; + +import java.util.List; + +import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey; +import org.apache.sshd.client.config.hosts.HostConfigEntry; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.signature.Signature; +import org.eclipse.jgit.util.StringUtils; + +/** + * Custom {@link UserAuthPublicKey} implementation for handling SSH config + * PubkeyAcceptedAlgorithms. + */ +public class JGitPublicKeyAuthentication extends UserAuthPublicKey { + + JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) { + super(factories); + } + + @Override + public void init(ClientSession rawSession, String service) + throws Exception { + if (!(rawSession instanceof JGitClientSession)) { + throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$ + + rawSession.getClass().getCanonicalName()); + } + JGitClientSession session = ((JGitClientSession) rawSession); + HostConfigEntry hostConfig = session.getHostConfigEntry(); + // Set signature algorithms for public key authentication + String pubkeyAlgos = hostConfig.getProperty(PUBKEY_ACCEPTED_ALGORITHMS); + if (!StringUtils.isEmptyOrNull(pubkeyAlgos)) { + List<String> signatures = session.getSignatureFactoriesNames(); + signatures = session.modifyAlgorithmList(signatures, + session.getAllAvailableSignatureAlgorithms(), pubkeyAlgos, + PUBKEY_ACCEPTED_ALGORITHMS); + if (!signatures.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug(PUBKEY_ACCEPTED_ALGORITHMS + ' ' + signatures); + } + setSignatureFactoriesNames(signatures); + } else { + log.warn(format(SshdText.get().configNoKnownAlgorithms, + PUBKEY_ACCEPTED_ALGORITHMS, pubkeyAlgos)); + } + } + // If we don't set signature factories here, the default ones from the + // session will be used. + super.init(session, service); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java index 071e1979d3..ae12c2028d 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others + * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -267,24 +267,6 @@ public class JGitSshClient extends SshClient { session.setUsername(username); session.setConnectAddress(address); session.setHostConfigEntry(hostConfig); - // Set signature algorithms for public key authentication - String pubkeyAlgos = hostConfig - .getProperty(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS); - if (!StringUtils.isEmptyOrNull(pubkeyAlgos)) { - List<String> signatures = getSignatureFactoriesNames(); - signatures = session.modifyAlgorithmList(signatures, pubkeyAlgos, - SshConstants.PUBKEY_ACCEPTED_ALGORITHMS); - if (!signatures.isEmpty()) { - if (log.isDebugEnabled()) { - log.debug(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS + ' ' - + signatures); - } - session.setSignatureFactoriesNames(signatures); - } else { - log.warn(format(SshdText.get().configNoKnownAlgorithms, - SshConstants.PUBKEY_ACCEPTED_ALGORITHMS, pubkeyAlgos)); - } - } if (session.getCredentialsProvider() == null) { session.setCredentialsProvider(getCredentialsProvider()); } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java index 73c2288ccc..c0f5719629 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java @@ -28,6 +28,7 @@ public final class SshdText extends TranslationBundle { /***/ public String configNoKnownAlgorithms; /***/ public String configProxyJumpNotSsh; /***/ public String configProxyJumpWithPath; + /***/ public String configUnknownAlgorithm; /***/ public String ftpCloseFailed; /***/ public String gssapiFailure; /***/ public String gssapiInitFailure; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java index d52e24adfb..cad959c904 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java @@ -32,7 +32,6 @@ import org.apache.sshd.client.ClientBuilder; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.auth.UserAuthFactory; import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory; -import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory; import org.apache.sshd.client.config.hosts.HostConfigEntryResolver; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.SshException; @@ -49,6 +48,7 @@ import org.eclipse.jgit.internal.transport.sshd.AuthenticationCanceledException; import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider; import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory; import org.eclipse.jgit.internal.transport.sshd.JGitPasswordAuthFactory; +import org.eclipse.jgit.internal.transport.sshd.JGitPublicKeyAuthFactory; import org.eclipse.jgit.internal.transport.sshd.JGitServerKeyVerifier; import org.eclipse.jgit.internal.transport.sshd.JGitSshClient; import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig; @@ -577,7 +577,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable { // Password auth doesn't have this problem. return Collections.unmodifiableList( Arrays.asList(GssApiWithMicAuthFactory.INSTANCE, - UserAuthPublicKeyFactory.INSTANCE, + JGitPublicKeyAuthFactory.FACTORY, JGitPasswordAuthFactory.INSTANCE, UserAuthKeyboardInteractiveFactory.INSTANCE)); } |