diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2021-05-12 08:59:07 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2021-05-12 08:59:07 +0200 |
commit | 1aa3cf7f416658a4fafd592732ba70f5a1aee19e (patch) | |
tree | 275b1176d04d102832641292a765d1a8f3e1b3a2 /org.eclipse.jgit.ssh.apache | |
parent | b6d4844b5d60f63a46f2b8b42189f70de653b385 (diff) | |
parent | fe3034d5b90a8a1f2592a40a670705fbd7305158 (diff) | |
download | jgit-1aa3cf7f416658a4fafd592732ba70f5a1aee19e.tar.gz jgit-1aa3cf7f416658a4fafd592732ba70f5a1aee19e.zip |
Merge branch 'master' into next
* master: (34 commits)
Remove texts which were added by mistake in 00386272
Fix formatting which was broken in 00386272
LockFile: create OutputStream only when needed
Add a cgit interoperability test for LockFile
Add TemporaryBuffer.toString(int limit)
LockFile: create OutputStream only when needed
Prepare 5.12.0-SNAPSHOT builds
JGit v5.12.0.202105051250-m2
Update jetty to 9.4.40.v20210413
[releng] Update eclipse-jarsigner-plugin to 1.3.1
Implement ours/theirs content conflict resolution
ssh: ensure list is modifiable before using Iterator.remove().
Update orbit to S20210406213021 and add 4.20-staging target
Fix typo in test method name
Allow file mode conflicts in virtual base commit on recursive merge.
sshd: don't lock the known_hosts files on reading
Allow info messages in UsernamePasswordCredentialsProvider
ssh config: do environment variable replacement
sshd: implement server-sig-algs SSH extension (client side)
Upgrade ecj to 3.25.0
...
Change-Id: Ibc39a9c4e431d15b67ab4a307241f47a7f3740a9
Diffstat (limited to 'org.eclipse.jgit.ssh.apache')
10 files changed, 535 insertions, 67 deletions
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF index faa52b26ac..89b5133afc 100644 --- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF @@ -59,6 +59,8 @@ Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)", org.apache.sshd.common.io;version="[2.6.0,2.7.0)", org.apache.sshd.common.kex;version="[2.6.0,2.7.0)", + org.apache.sshd.common.kex.extension;version="[2.6.0,2.7.0)", + org.apache.sshd.common.kex.extension.parser;version="[2.6.0,2.7.0)", org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)", org.apache.sshd.common.mac;version="[2.6.0,2.7.0)", org.apache.sshd.common.random;version="[2.6.0,2.7.0)", 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 f810fd40e4..5bc0867674 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 @@ -5,8 +5,7 @@ configInvalidPath=Invalid path in ssh config key {0}: {1} configInvalidPattern=Invalid pattern in ssh config key {0}: {1} configInvalidPositive=Ssh config entry {0} must be a strictly positive number but is ''{1}'' configInvalidProxyJump=Ssh config, host ''{0}'': Cannot parse ProxyJump ''{1}'' -configNoKnownHostKeyAlgorithms=No implementations for any of the algorithms ''{0}'' given in HostKeyAlgorithms in the ssh config; using the default. -configNoRemainingHostKeyAlgorithms=Ssh config removed all host key algorithms: HostKeyAlgorithms ''{0}'' +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 ftpCloseFailed=Closing the SFTP channel failed @@ -25,7 +24,6 @@ keyEncryptedPrompt=Passphrase keyEncryptedRetry=Encrypted key ''{0}'' could not be decrypted. Enter the passphrase again. keyLoadFailed=Could not load key ''{0}'' knownHostsCouldNotUpdate=Could not update known hosts file {0} -knownHostsFileLockedRead=Could not read known hosts file (locked) {0} knownHostsFileLockedUpdate=Could not update known hosts file (locked) {0} knownHostsFileReadFailed=Failed to read known hosts file {0} knownHostsInvalidLine=Known hosts file {0} contains invalid line {1} @@ -77,6 +75,9 @@ proxySocksPasswordTooLong=Password for proxy {0} must be at most 255 bytes long, proxySocksUnexpectedMessage=Unexpected message received from SOCKS5 proxy {0}; client state {1}: {2} proxySocksUnexpectedVersion=Expected SOCKS version 5, got {0} proxySocksUsernameTooLong=User name for proxy {0} must be at most 255 bytes long, is {1} bytes: {2} +pubkeyAuthWrongCommand=Public key authentication received unknown SSH command {0} from {1} ({2}) +pubkeyAuthWrongKey=Public key authentication received wrong key; sent {0}, got back {1} from {2} ({3}) +pubkeyAuthWrongSignatureAlgorithm=Public key authentication requested signature type {0} but got back {1} from {2} ({3}) serverIdNotReceived=No server identification received within {0} bytes serverIdTooLong=Server identification is longer than 255 characters (including line ending): {0} serverIdWithNul=Server identification contains a NUL character: {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 66713ba632..8183a92b9f 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 @@ -21,6 +21,7 @@ import java.security.PublicKey; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -45,6 +46,7 @@ import org.eclipse.jgit.fnmatch.FileNameMatcher; import org.eclipse.jgit.internal.transport.sshd.proxy.StatefulProxyConnector; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConstants; +import org.eclipse.jgit.util.StringUtils; /** * A {@link org.apache.sshd.client.session.ClientSession ClientSession} that can @@ -201,48 +203,23 @@ public class JGitClientSession extends ClientSessionImpl { @Override protected String resolveAvailableSignaturesProposal( FactoryManager manager) { - Set<String> defaultSignatures = new LinkedHashSet<>(); - defaultSignatures.addAll(getSignatureFactoriesNames()); + List<String> defaultSignatures = getSignatureFactoriesNames(); HostConfigEntry config = resolveAttribute( JGitSshClient.HOST_CONFIG_ENTRY); - String hostKeyAlgorithms = config + String algorithms = config .getProperty(SshConstants.HOST_KEY_ALGORITHMS); - if (hostKeyAlgorithms != null && !hostKeyAlgorithms.isEmpty()) { - char first = hostKeyAlgorithms.charAt(0); - switch (first) { - case '+': - // Additions make not much sense -- it's either in - // defaultSignatures already, or we have no implementation for - // it. No point in proposing it. - return String.join(",", defaultSignatures); //$NON-NLS-1$ - case '-': - // This takes wildcard patterns! - removeFromList(defaultSignatures, - SshConstants.HOST_KEY_ALGORITHMS, - hostKeyAlgorithms.substring(1)); - if (defaultSignatures.isEmpty()) { - // Too bad: user config error. Warn here, and then fail - // later. - log.warn(format( - SshdText.get().configNoRemainingHostKeyAlgorithms, - hostKeyAlgorithms)); - } - return String.join(",", defaultSignatures); //$NON-NLS-1$ - default: - // Default is overridden -- only accept the ones for which we do - // have an implementation. - List<String> newNames = filteredList(defaultSignatures, - hostKeyAlgorithms); - if (newNames.isEmpty()) { - log.warn(format( - SshdText.get().configNoKnownHostKeyAlgorithms, - hostKeyAlgorithms)); - // Use the default instead. - } else { - return String.join(",", newNames); //$NON-NLS-1$ + if (!StringUtils.isEmptyOrNull(algorithms)) { + List<String> result = modifyAlgorithmList(defaultSignatures, + algorithms, SshConstants.HOST_KEY_ALGORITHMS); + if (!result.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug(SshConstants.HOST_KEY_ALGORITHMS + ' ' + result); } - break; + return String.join(",", result); //$NON-NLS-1$ } + log.warn(format(SshdText.get().configNoKnownAlgorithms, + SshConstants.HOST_KEY_ALGORITHMS, + algorithms)); } // No HostKeyAlgorithms; using default -- change order to put existing // keys first. @@ -262,11 +239,67 @@ public class JGitClientSession extends ClientSessionImpl { } } reordered.addAll(defaultSignatures); + if (log.isDebugEnabled()) { + log.debug(SshConstants.HOST_KEY_ALGORITHMS + ' ' + reordered); + } return String.join(",", reordered); //$NON-NLS-1$ } + if (log.isDebugEnabled()) { + log.debug( + SshConstants.HOST_KEY_ALGORITHMS + ' ' + defaultSignatures); + } return String.join(",", defaultSignatures); //$NON-NLS-1$ } + /** + * 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. + * + * @param defaultList + * to modify + * @param fromConfig + * telling how to modify the {@code defaultList}, must not be + * {@code null} or empty + * @param overrideKey + * ssh config key; used for logging + * @return the modified list or {@code null} if {@code overrideKey} is not + * set + */ + public List<String> modifyAlgorithmList(List<String> defaultList, + 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; + 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, + fromConfig.substring(1)); + Set<String> atFront = new HashSet<>(allSignatures); + for (String sig : defaults) { + if (!atFront.contains(sig)) { + allSignatures.add(sig); + } + } + return allSignatures; + default: + // Default is overridden -- only accept the ones for which we do + // have an implementation. + return filteredList(defaults, fromConfig); + } + } + private void removeFromList(Set<String> current, String key, String patterns) { for (String toRemove : patterns.split("\\s*,\\s*")) { //$NON-NLS-1$ diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitKexExtensionHandler.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitKexExtensionHandler.java new file mode 100644 index 0000000000..9446aaa7d6 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitKexExtensionHandler.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 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 java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.sshd.common.AttributeRepository.AttributeKey; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.kex.KexProposalOption; +import org.apache.sshd.common.kex.extension.KexExtensionHandler; +import org.apache.sshd.common.kex.extension.KexExtensions; +import org.apache.sshd.common.kex.extension.parser.ServerSignatureAlgorithms; +import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; +import org.eclipse.jgit.util.StringUtils; + +/** + * Do not use the DefaultClientKexExtensionHandler from sshd; it doesn't work + * properly because of misconceptions. See SSHD-1141. + * + * @see <a href="https://issues.apache.org/jira/browse/SSHD-1141">SSHD-1141</a> + */ +public class JGitKexExtensionHandler extends AbstractLoggingBean + implements KexExtensionHandler { + + /** Singleton instance. */ + public static final JGitKexExtensionHandler INSTANCE = new JGitKexExtensionHandler(); + + /** + * Session {@link AttributeKey} used to store whether the extension + * indicator was already sent. + */ + private static final AttributeKey<Boolean> CLIENT_PROPOSAL_MADE = new AttributeKey<>(); + + /** + * Session {@link AttributeKey} storing the algorithms announced by the + * server as known. + */ + public static final AttributeKey<Set<String>> SERVER_ALGORITHMS = new AttributeKey<>(); + + private JGitKexExtensionHandler() { + // No public instantiation for singleton + } + + @Override + public boolean isKexExtensionsAvailable(Session session, + AvailabilityPhase phase) throws IOException { + return !AvailabilityPhase.PREKEX.equals(phase); + } + + @Override + public void handleKexInitProposal(Session session, boolean initiator, + Map<KexProposalOption, String> proposal) throws IOException { + // If it's the very first time, we may add the marker telling the server + // that we are ready to handle SSH_MSG_EXT_INFO + if (session == null || session.isServerSession() || !initiator) { + return; + } + if (session.getAttribute(CLIENT_PROPOSAL_MADE) != null) { + return; + } + String kexAlgorithms = proposal.get(KexProposalOption.SERVERKEYS); + if (StringUtils.isEmptyOrNull(kexAlgorithms)) { + return; + } + List<String> algorithms = new ArrayList<>(); + // We're a client. We mustn't send the server extension, and we should + // send the client extension only once. + for (String algo : kexAlgorithms.split(",")) { //$NON-NLS-1$ + if (KexExtensions.CLIENT_KEX_EXTENSION.equalsIgnoreCase(algo) + || KexExtensions.SERVER_KEX_EXTENSION + .equalsIgnoreCase(algo)) { + continue; + } + algorithms.add(algo); + } + // Tell the server that we want to receive SSH2_MSG_EXT_INFO + algorithms.add(KexExtensions.CLIENT_KEX_EXTENSION); + if (log.isDebugEnabled()) { + log.debug( + "handleKexInitProposal({}): proposing HostKeyAlgorithms {}", //$NON-NLS-1$ + session, algorithms); + } + proposal.put(KexProposalOption.SERVERKEYS, + String.join(",", algorithms)); //$NON-NLS-1$ + session.setAttribute(CLIENT_PROPOSAL_MADE, Boolean.TRUE); + } + + @Override + public boolean handleKexExtensionRequest(Session session, int index, + int count, String name, byte[] data) throws IOException { + if (ServerSignatureAlgorithms.NAME.equals(name)) { + handleServerSignatureAlgorithms(session, + ServerSignatureAlgorithms.INSTANCE.parseExtension(data)); + } + return true; + } + + /** + * Perform updates after a server-sig-algs extension has been received. + * + * @param session + * the message was received for + * @param serverAlgorithms + * signature algorithm names announced by the server + */ + protected void handleServerSignatureAlgorithms(Session session, + Collection<String> serverAlgorithms) { + if (log.isDebugEnabled()) { + log.debug("handleServerSignatureAlgorithms({}): {}", session, //$NON-NLS-1$ + serverAlgorithms); + } + // Client determines order; server says what it supports. Re-order + // such that supported ones are at the front, in client order, + // followed by unsupported ones, also in client order. + if (serverAlgorithms != null && !serverAlgorithms.isEmpty()) { + List<NamedFactory<Signature>> clientAlgorithms = new ArrayList<>( + session.getSignatureFactories()); + if (log.isDebugEnabled()) { + log.debug( + "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms before: {}", //$NON-NLS-1$ + session, clientAlgorithms); + } + List<NamedFactory<Signature>> unknown = new ArrayList<>(); + Set<String> known = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + known.addAll(serverAlgorithms); + for (Iterator<NamedFactory<Signature>> iter = clientAlgorithms + .iterator(); iter.hasNext();) { + NamedFactory<Signature> algo = iter.next(); + if (!known.contains(algo.getName())) { + unknown.add(algo); + iter.remove(); + } + } + // Re-add the unknown ones at the end. Per RFC 8308, some + // servers may not announce _all_ their supported algorithms, + // and a client may use unknown algorithms. + clientAlgorithms.addAll(unknown); + if (log.isDebugEnabled()) { + log.debug( + "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms after: {}", //$NON-NLS-1$ + session, clientAlgorithms); + } + session.setAttribute(SERVER_ALGORITHMS, known); + session.setSignatureFactories(clientAlgorithms); + } + } +} 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..6755094420 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java @@ -0,0 +1,224 @@ +/* + * 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 java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.text.MessageFormat; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.RuntimeSshException; +import org.apache.sshd.common.SshConstants; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.signature.SignatureFactoriesHolder; +import org.apache.sshd.common.util.buffer.Buffer; + +/** + * Custom {@link UserAuthPublicKey} implementation fixing SSHD-1105: if there + * are several signature algorithms applicable for a public key type, we must + * try them all, in the correct order. + * + * @see <a href="https://issues.apache.org/jira/browse/SSHD-1105">SSHD-1105</a> + * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">Bug + * 572056</a> + */ +public class JGitPublicKeyAuthentication extends UserAuthPublicKey { + + private final Deque<String> currentAlgorithms = new LinkedList<>(); + + private String chosenAlgorithm; + + JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) { + super(factories); + } + + @Override + protected boolean sendAuthDataRequest(ClientSession session, String service) + throws Exception { + if (current == null) { + currentAlgorithms.clear(); + chosenAlgorithm = null; + } + String currentAlgorithm = null; + if (current != null && !currentAlgorithms.isEmpty()) { + currentAlgorithm = currentAlgorithms.poll(); + if (chosenAlgorithm != null) { + Set<String> knownServerAlgorithms = session.getAttribute( + JGitKexExtensionHandler.SERVER_ALGORITHMS); + if (knownServerAlgorithms != null + && knownServerAlgorithms.contains(chosenAlgorithm)) { + // We've tried key 'current' with 'chosenAlgorithm', but it + // failed. However, the server had told us it supported + // 'chosenAlgorithm'. Thus it makes no sense to continue + // with this key and other signature algorithms. Skip to the + // next key, if any. + currentAlgorithm = null; + } + } + } + if (currentAlgorithm == null) { + try { + if (keys == null || !keys.hasNext()) { + if (log.isDebugEnabled()) { + log.debug( + "sendAuthDataRequest({})[{}] no more keys to send", //$NON-NLS-1$ + session, service); + } + current = null; + return false; + } + current = keys.next(); + currentAlgorithms.clear(); + chosenAlgorithm = null; + } catch (Error e) { // Copied from superclass + throw new RuntimeSshException(e); + } + } + PublicKey key; + try { + key = current.getPublicKey(); + } catch (Error e) { // Copied from superclass + throw new RuntimeSshException(e); + } + if (currentAlgorithm == null) { + String keyType = KeyUtils.getKeyType(key); + Set<String> aliases = new HashSet<>( + KeyUtils.getAllEquivalentKeyTypes(keyType)); + aliases.add(keyType); + List<NamedFactory<Signature>> existingFactories; + if (current instanceof SignatureFactoriesHolder) { + existingFactories = ((SignatureFactoriesHolder) current) + .getSignatureFactories(); + } else { + existingFactories = getSignatureFactories(); + } + if (existingFactories != null) { + if (log.isDebugEnabled()) { + log.debug( + "sendAuthDataRequest({})[{}] selecting from PubKeyAcceptedAlgorithms {}", //$NON-NLS-1$ + session, service, + NamedResource.getNames(existingFactories)); + } + // Select the factories by name and in order + existingFactories.forEach(f -> { + if (aliases.contains(f.getName())) { + currentAlgorithms.add(f.getName()); + } + }); + } + currentAlgorithm = currentAlgorithms.isEmpty() ? keyType + : currentAlgorithms.poll(); + } + String name = getName(); + if (log.isDebugEnabled()) { + log.debug( + "sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}", //$NON-NLS-1$ + session, service, name, currentAlgorithm, + KeyUtils.getFingerPrint(key)); + } + + chosenAlgorithm = currentAlgorithm; + Buffer buffer = session + .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST); + buffer.putString(session.getUsername()); + buffer.putString(service); + buffer.putString(name); + buffer.putBoolean(false); + buffer.putString(currentAlgorithm); + buffer.putPublicKey(key); + session.writePacket(buffer); + return true; + } + + @Override + protected boolean processAuthDataRequest(ClientSession session, + String service, Buffer buffer) throws Exception { + String name = getName(); + int cmd = buffer.getUByte(); + if (cmd != SshConstants.SSH_MSG_USERAUTH_PK_OK) { + throw new IllegalStateException(MessageFormat.format( + SshdText.get().pubkeyAuthWrongCommand, + SshConstants.getCommandMessageName(cmd), + session.getConnectAddress(), session.getServerVersion())); + } + PublicKey key; + try { + key = current.getPublicKey(); + } catch (Error e) { // Copied from superclass + throw new RuntimeSshException(e); + } + String rspKeyAlgorithm = buffer.getString(); + PublicKey rspKey = buffer.getPublicKey(); + if (log.isDebugEnabled()) { + log.debug( + "processAuthDataRequest({})[{}][{}] SSH_MSG_USERAUTH_PK_OK type={}, fingerprint={}", //$NON-NLS-1$ + session, service, name, rspKeyAlgorithm, + KeyUtils.getFingerPrint(rspKey)); + } + if (!KeyUtils.compareKeys(rspKey, key)) { + throw new InvalidKeySpecException(MessageFormat.format( + SshdText.get().pubkeyAuthWrongKey, + KeyUtils.getFingerPrint(key), + KeyUtils.getFingerPrint(rspKey), + session.getConnectAddress(), session.getServerVersion())); + } + if (!chosenAlgorithm.equalsIgnoreCase(rspKeyAlgorithm)) { + // 'algo' SHOULD be the same as 'chosenAlgorithm', which is the one + // we sent above. See https://tools.ietf.org/html/rfc4252#page-9 . + // + // However, at least Github (SSH-2.0-babeld-383743ad) servers seem + // to return the key type, not the algorithm name. + // + // So we don't check but just log the inconsistency. We sign using + // 'chosenAlgorithm' in any case, so we don't really care what the + // server says here. + log.warn(MessageFormat.format( + SshdText.get().pubkeyAuthWrongSignatureAlgorithm, + chosenAlgorithm, rspKeyAlgorithm, session.getConnectAddress(), + session.getServerVersion())); + } + String username = session.getUsername(); + Buffer out = session + .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST); + out.putString(username); + out.putString(service); + out.putString(name); + out.putBoolean(true); + out.putString(chosenAlgorithm); + out.putPublicKey(key); + if (log.isDebugEnabled()) { + log.debug( + "processAuthDataRequest({})[{}][{}]: signing with algorithm {}", //$NON-NLS-1$ + session, service, name, chosenAlgorithm); + } + appendSignature(session, service, name, username, chosenAlgorithm, key, + out); + session.writePacket(out); + return true; + } + + @Override + protected void releaseKeys() throws IOException { + currentAlgorithms.clear(); + current = null; + chosenAlgorithm = null; + super.releaseKeys(); + } +} 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 74455dc808..071e1979d3 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 @@ -267,6 +267,24 @@ 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/OpenSshServerKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java index 47e09b75d7..1a530b7743 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java @@ -21,6 +21,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; @@ -561,29 +562,17 @@ public class OpenSshServerKeyDatabase @Override public List<HostEntryPair> get() { Path path = getPath(); - try { - if (checkReloadRequired()) { - if (!Files.exists(path)) { - // Has disappeared. - resetReloadAttributes(); - return Collections.emptyList(); - } - LockFile lock = new LockFile(path.toFile()); - if (lock.lock()) { - try { - entries = reload(getPath()); - } finally { - lock.unlock(); - } - } else { - LOG.warn(format(SshdText.get().knownHostsFileLockedRead, - path)); + synchronized (this) { + try { + if (checkReloadRequired()) { + entries = reload(getPath()); } + } catch (IOException e) { + LOG.warn(format(SshdText.get().knownHostsFileReadFailed, + path)); } - } catch (IOException e) { - LOG.warn(format(SshdText.get().knownHostsFileReadFailed, path)); + return Collections.unmodifiableList(entries); } - return Collections.unmodifiableList(entries); } private List<HostEntryPair> reload(Path path) throws IOException { @@ -616,7 +605,7 @@ public class OpenSshServerKeyDatabase } } return newEntries; - } catch (FileNotFoundException e) { + } catch (FileNotFoundException | NoSuchFileException e) { resetReloadAttributes(); return Collections.emptyList(); } 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 13bb3ebe75..73c2288ccc 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 @@ -25,8 +25,7 @@ public final class SshdText extends TranslationBundle { /***/ public String configInvalidPattern; /***/ public String configInvalidPositive; /***/ public String configInvalidProxyJump; - /***/ public String configNoKnownHostKeyAlgorithms; - /***/ public String configNoRemainingHostKeyAlgorithms; + /***/ public String configNoKnownAlgorithms; /***/ public String configProxyJumpNotSsh; /***/ public String configProxyJumpWithPath; /***/ public String ftpCloseFailed; @@ -45,7 +44,6 @@ public final class SshdText extends TranslationBundle { /***/ public String keyEncryptedRetry; /***/ public String keyLoadFailed; /***/ public String knownHostsCouldNotUpdate; - /***/ public String knownHostsFileLockedRead; /***/ public String knownHostsFileLockedUpdate; /***/ public String knownHostsFileReadFailed; /***/ public String knownHostsInvalidLine; @@ -89,6 +87,9 @@ public final class SshdText extends TranslationBundle { /***/ public String proxySocksUnexpectedMessage; /***/ public String proxySocksUnexpectedVersion; /***/ public String proxySocksUsernameTooLong; + /***/ public String pubkeyAuthWrongCommand; + /***/ public String pubkeyAuthWrongKey; + /***/ public String pubkeyAuthWrongSignatureAlgorithm; /***/ public String serverIdNotReceived; /***/ public String serverIdTooLong; /***/ public String serverIdWithNul; 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 357994d431..2d7e0c7c77 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,10 +32,9 @@ 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.SshException; import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.SshException; import org.apache.sshd.common.compression.BuiltinCompressions; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions; @@ -48,7 +47,9 @@ import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile; 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.JGitKexExtensionHandler; 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; @@ -216,6 +217,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable { new JGitUserInteraction(credentialsProvider)); client.setUserAuthFactories(getUserAuthFactories()); client.setKeyIdentityProvider(defaultKeysProvider); + client.setKexExtensionHandler(JGitKexExtensionHandler.INSTANCE); // JGit-specific things: JGitSshClient jgitClient = (JGitSshClient) client; jgitClient.setKeyCache(getKeyCache()); @@ -577,7 +579,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)); } |