Browse Source

Merge branch 'stable-5.11'

* stable-5.11:
  Refactor CommitCommand to improve readability
  CommitCommand: fix formatting
  CommitCommand: remove unncessary comment
  Ensure post-commit hook is called after index lock was released
  sshd: try all configured signature algorithms for a key
  sshd: modernize ssh config file parsing
  sshd: implement ssh config PubkeyAcceptedAlgorithms

Change-Id: Ic3235ffd84c9d7537a1fe5ff4f216578e6e26724
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v5.12.0.202105051250-m2
Matthias Sohn 3 years ago
parent
commit
beecca02bb
16 changed files with 582 additions and 170 deletions
  1. 1
    0
      org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
  2. 2
    0
      org.eclipse.jgit.ssh.apache.test/build.properties
  3. 121
    25
      org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
  4. 1
    2
      org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
  5. 70
    37
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
  6. 35
    0
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java
  7. 133
    0
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
  8. 18
    0
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
  9. 1
    2
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
  10. 3
    3
      org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
  11. 30
    0
      org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
  12. 1
    0
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  13. 106
    79
      org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
  14. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  15. 51
    22
      org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
  16. 8
    0
      org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java

+ 1
- 0
org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF View File

@@ -14,6 +14,7 @@ Import-Package: org.apache.sshd.client.config.hosts;version="[2.6.0,2.7.0)",
org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)",
org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)",
org.apache.sshd.common.session;version="[2.6.0,2.7.0)",
org.apache.sshd.common.signature;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.net;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)",
org.apache.sshd.core;version="[2.6.0,2.7.0)",

+ 2
- 0
org.eclipse.jgit.ssh.apache.test/build.properties View File

@@ -3,3 +3,5 @@ output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.properties
additional.bundles = org.apache.log4j,\
org.slf4j.binding.log4j12

+ 121
- 25
org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java View File

@@ -47,7 +47,9 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.junit.ssh.SshTestBase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.RemoteSession;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.junit.Test;
import org.junit.experimental.theories.Theories;
@@ -232,64 +234,89 @@ public class ApacheSshTest extends SshTestBase {
}

/**
* Creates a simple proxy server. Accepts only publickey authentication from
* the given user with the given key, allows all forwardings. Adds the
* proxy's host key to {@link #knownHosts}.
* Creates a simple SSH server without git setup.
*
* @param user
* to accept
* @param userKey
* public key of that user at this server
* @param report
* single-element array to report back the forwarded address.
* @return the started server
* @return the {@link SshServer}, not yet started
* @throws Exception
*/
private SshServer createProxy(String user, File userKey,
SshdSocketAddress[] report) throws Exception {
SshServer proxy = SshServer.setUpDefaultServer();
private SshServer createServer(String user, File userKey) throws Exception {
SshServer srv = SshServer.setUpDefaultServer();
// Give the server its own host key
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair proxyHostKey = generator.generateKeyPair();
proxy.setKeyPairProvider(
srv.setKeyPairProvider(
session -> Collections.singletonList(proxyHostKey));
// Allow (only) publickey authentication
proxy.setUserAuthFactories(Collections.singletonList(
srv.setUserAuthFactories(Collections.singletonList(
ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY));
// Install the user's public key
PublicKey userProxyKey = AuthorizedKeyEntry
.readAuthorizedKeys(userKey.toPath()).get(0)
.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
proxy.setPublickeyAuthenticator(
srv.setPublickeyAuthenticator(
(userName, publicKey, session) -> user.equals(userName)
&& KeyUtils.compareKeys(userProxyKey, publicKey));
// Allow forwarding
proxy.setForwardingFilter(new StaticDecisionForwardingFilter(true) {
return srv;
}

@Override
protected boolean checkAcceptance(String request, Session session,
SshdSocketAddress target) {
report[0] = target;
return super.checkAcceptance(request, session, target);
}
});
proxy.start();
/**
* Writes the server's host key to our knownhosts file.
*
* @param srv to register
* @throws Exception
*/
private void registerServer(SshServer srv) throws Exception {
// Add the proxy's host key to knownhosts
try (BufferedWriter writer = Files.newBufferedWriter(
knownHosts.toPath(), StandardCharsets.US_ASCII,
StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
writer.append('\n');
KnownHostHashValue.appendHostPattern(writer, "localhost",
proxy.getPort());
srv.getPort());
writer.append(',');
KnownHostHashValue.appendHostPattern(writer, "127.0.0.1",
proxy.getPort());
srv.getPort());
writer.append(' ');
PublicKeyEntry.appendPublicKeyEntry(writer,
proxyHostKey.getPublic());
srv.getKeyPairProvider().loadKeys(null).iterator().next().getPublic());
writer.append('\n');
}
}

/**
* Creates a simple proxy server. Accepts only publickey authentication from
* the given user with the given key, allows all forwardings. Adds the
* proxy's host key to {@link #knownHosts}.
*
* @param user
* to accept
* @param userKey
* public key of that user at this server
* @param report
* single-element array to report back the forwarded address.
* @return the started server
* @throws Exception
*/
private SshServer createProxy(String user, File userKey,
SshdSocketAddress[] report) throws Exception {
SshServer proxy = createServer(user, userKey);
// Allow forwarding
proxy.setForwardingFilter(new StaticDecisionForwardingFilter(true) {

@Override
protected boolean checkAcceptance(String request, Session session,
SshdSocketAddress target) {
report[0] = target;
return super.checkAcceptance(request, session, target);
}
});
proxy.start();
registerServer(proxy);
return proxy;
}

@@ -606,4 +633,73 @@ public class ApacheSshTest extends SshTestBase {
}
}
}

/**
* Tests that one can log in to an old server that doesn't handle
* rsa-sha2-512 if one puts ssh-rsa first in the client's list of public key
* signature algorithms.
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">bug
* 572056</a>
* @throws Exception
* on failure
*/
@Test
public void testConnectAuthSshRsaPubkeyAcceptedAlgorithms()
throws Exception {
try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
oldServer.setSignatureFactoriesNames("ssh-rsa");
oldServer.start();
registerServer(oldServer);
installConfig("Host server", //
"HostName localhost", //
"Port " + oldServer.getPort(), //
"User " + TEST_USER, //
"IdentityFile " + privateKey1.getAbsolutePath(), //
"PubkeyAcceptedAlgorithms ^ssh-rsa");
RemoteSession session = getSessionFactory().getSession(
new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
10000);
assertNotNull(session);
session.disconnect();
}
}

/**
* Tests that one can log in to an old server that knows only the ssh-rsa
* signature algorithm. The client has by default the list of signature
* algorithms for RSA as "rsa-sha2-512,rsa-sha2-256,ssh-rsa". It should try
* all three with the single key configured, and finally succeed.
* <p>
* The re-ordering mechanism (see
* {@link #testConnectAuthSshRsaPubkeyAcceptedAlgorithms()}) is still
* important; servers may impose a penalty (back-off delay) for subsequent
* attempts with signature algorithms unknown to the server. So a user
* connecting to such a server and noticing delays may still want to put
* ssh-rsa first in the list for that host.
* </p>
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">bug
* 572056</a>
* @throws Exception
* on failure
*/
@Test
public void testConnectAuthSshRsa() throws Exception {
try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
oldServer.setSignatureFactoriesNames("ssh-rsa");
oldServer.start();
registerServer(oldServer);
installConfig("Host server", //
"HostName localhost", //
"Port " + oldServer.getPort(), //
"User " + TEST_USER, //
"IdentityFile " + privateKey1.getAbsolutePath());
RemoteSession session = getSessionFactory().getSession(
new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
10000);
assertNotNull(session);
session.disconnect();
}
}
}

+ 1
- 2
org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties View File

@@ -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

+ 70
- 37
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java View File

@@ -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$

+ 35
- 0
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java View File

@@ -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());
}
}

+ 133
- 0
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java View File

@@ -0,0 +1,133 @@
/*
* 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.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.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 List<String> algorithms = new LinkedList<>();

JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
super(factories);
}

@Override
protected boolean sendAuthDataRequest(ClientSession session, String service)
throws Exception {
if (current == null) {
algorithms.clear();
}
String currentAlgorithm = null;
if (current != null && !algorithms.isEmpty()) {
currentAlgorithm = algorithms.remove(0);
}
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);
}
return false;
}
current = keys.next();
algorithms.clear();
} catch (Error e) { // Copied from superclass
warn("sendAuthDataRequest({})[{}] failed ({}) to get next key: {}", //$NON-NLS-1$
session, service, e.getClass().getSimpleName(),
e.getMessage(), e);
throw new RuntimeSshException(e);
}
}
PublicKey key;
try {
key = current.getPublicKey();
} catch (Error e) { // Copied from superclass
warn("sendAuthDataRequest({})[{}] failed ({}) to retrieve public key: {}", //$NON-NLS-1$
session, service, e.getClass().getSimpleName(),
e.getMessage(), e);
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) {
// Select the factories by name and in order
existingFactories.forEach(f -> {
if (aliases.contains(f.getName())) {
algorithms.add(f.getName());
}
});
}
currentAlgorithm = algorithms.isEmpty() ? keyType
: algorithms.remove(0);
}
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));
}

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 void releaseKeys() throws IOException {
algorithms.clear();
current = null;
super.releaseKeys();
}
}

+ 18
- 0
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java View File

@@ -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());
}

+ 1
- 2
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java View File

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

+ 3
- 3
org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java View File

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

+ 30
- 0
org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java View File

@@ -467,4 +467,34 @@ public class OpenSshConfigTest extends RepositoryTestCase {
new File(new File(home, ".ssh"), localhost + "_id_dsa"),
h.getIdentityFile());
}

@Test
public void testPubKeyAcceptedAlgorithms() throws Exception {
config("Host=orcz\n\tPubkeyAcceptedAlgorithms ^ssh-rsa");
Host h = osc.lookup("orcz");
Config c = h.getConfig();
assertEquals("^ssh-rsa",
c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
assertEquals("^ssh-rsa", c.getValue("PubkeyAcceptedKeyTypes"));
}

@Test
public void testPubKeyAcceptedKeyTypes() throws Exception {
config("Host=orcz\n\tPubkeyAcceptedKeyTypes ^ssh-rsa");
Host h = osc.lookup("orcz");
Config c = h.getConfig();
assertEquals("^ssh-rsa",
c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
assertEquals("^ssh-rsa", c.getValue("PubkeyAcceptedKeyTypes"));
}

@Test
public void testEolComments() throws Exception {
config("#Comment\nHost=orcz #Comment\n\tPubkeyAcceptedAlgorithms ^ssh-rsa # Comment\n#Comment");
Host h = osc.lookup("orcz");
assertNotNull(h);
Config c = h.getConfig();
assertEquals("^ssh-rsa",
c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
}
}

+ 1
- 0
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties View File

@@ -530,6 +530,7 @@ peeledRefIsRequired=Peeled ref is required.
peerDidNotSupplyACompleteObjectGraph=peer did not supply a complete object graph
personIdentEmailNonNull=E-mail address of PersonIdent must not be null.
personIdentNameNonNull=Name of PersonIdent must not be null.
postCommitHookFailed=Execution of post-commit hook failed: {0}.
prefixRemote=remote:
problemWithResolvingPushRefSpecsLocally=Problem with resolving push ref specs locally: {0}
progressMonUploading=Uploading {0}

+ 106
- 79
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java View File

@@ -20,6 +20,7 @@ import java.util.LinkedList;
import java.util.List;

import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -36,6 +37,8 @@ import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.hooks.CommitMsgHook;
import org.eclipse.jgit.hooks.Hooks;
@@ -67,6 +70,8 @@ import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.util.ChangeIdUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A class used to execute a {@code Commit} command. It has setters for all
@@ -78,6 +83,9 @@ import org.eclipse.jgit.util.ChangeIdUtil;
* >Git documentation about Commit</a>
*/
public class CommitCommand extends GitCommand<RevCommit> {
private static final Logger log = LoggerFactory
.getLogger(CommitCommand.class);

private PersonIdent author;

private PersonIdent committer;
@@ -173,8 +181,7 @@ public class CommitCommand extends GitCommand<RevCommit> {

if (all && !repo.isBare()) {
try (Git git = new Git(repo)) {
git.add()
.addFilepattern(".") //$NON-NLS-1$
git.add().addFilepattern(".") //$NON-NLS-1$
.setUpdate(true).call();
} catch (NoFilepatternException e) {
// should really not happen
@@ -212,7 +219,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
.setCommitMessage(message).call();
}

// lock the index
RevCommit revCommit;
DirCache index = repo.lockDirCache();
try (ObjectInserter odi = repo.newObjectInserter()) {
if (!only.isEmpty())
@@ -226,100 +233,37 @@ public class CommitCommand extends GitCommand<RevCommit> {
if (insertChangeId)
insertChangeId(indexTreeId);

// Check for empty commits
if (headId != null && !allowEmpty.booleanValue()) {
RevCommit headCommit = rw.parseCommit(headId);
headCommit.getTree();
if (indexTreeId.equals(headCommit.getTree())) {
throw new EmptyCommitException(
JGitText.get().emptyCommit);
}
}
checkIfEmpty(rw, headId, indexTreeId);

// Create a Commit object, populate it and write it
CommitBuilder commit = new CommitBuilder();
commit.setCommitter(committer);
commit.setAuthor(author);
commit.setMessage(message);

commit.setParentIds(parents);
commit.setTreeId(indexTreeId);

if (signCommit.booleanValue()) {
if (gpgSigner == null) {
throw new ServiceUnavailableException(
JGitText.get().signingServiceUnavailable);
}
if (gpgSigner instanceof GpgObjectSigner) {
((GpgObjectSigner) gpgSigner).signObject(commit,
signingKey, committer, credentialsProvider,
gpgConfig);
} else {
if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
throw new UnsupportedSigningFormatException(JGitText
.get().onlyOpenPgpSupportedForSigning);
}
gpgSigner.sign(commit, signingKey, committer,
credentialsProvider);
}
sign(commit);
}

ObjectId commitId = odi.insert(commit);
odi.flush();
revCommit = rw.parseCommit(commitId);

RevCommit revCommit = rw.parseCommit(commitId);
RefUpdate ru = repo.updateRef(Constants.HEAD);
ru.setNewObjectId(commitId);
if (!useDefaultReflogMessage) {
ru.setRefLogMessage(reflogComment, false);
} else {
String prefix = amend ? "commit (amend): " //$NON-NLS-1$
: parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
: "commit: "; //$NON-NLS-1$
ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
false);
}
if (headId != null)
ru.setExpectedOldObjectId(headId);
else
ru.setExpectedOldObjectId(ObjectId.zeroId());
Result rc = ru.forceUpdate();
switch (rc) {
case NEW:
case FORCED:
case FAST_FORWARD: {
setCallable(false);
if (state == RepositoryState.MERGING_RESOLVED
|| isMergeDuringRebase(state)) {
// Commit was successful. Now delete the files
// used for merge commits
repo.writeMergeCommitMsg(null);
repo.writeMergeHeads(null);
} else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
repo.writeMergeCommitMsg(null);
repo.writeCherryPickHead(null);
} else if (state == RepositoryState.REVERTING_RESOLVED) {
repo.writeMergeCommitMsg(null);
repo.writeRevertHead(null);
}
Hooks.postCommit(repo,
hookOutRedirect.get(PostCommitHook.NAME),
hookErrRedirect.get(PostCommitHook.NAME)).call();

return revCommit;
}
case REJECTED:
case LOCK_FAILURE:
throw new ConcurrentRefUpdateException(
JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
default:
throw new JGitInternalException(MessageFormat.format(
JGitText.get().updatingRefFailed, Constants.HEAD,
commitId.toString(), rc));
}
updateRef(state, headId, revCommit, commitId);
} finally {
index.unlock();
}
try {
Hooks.postCommit(repo, hookOutRedirect.get(PostCommitHook.NAME),
hookErrRedirect.get(PostCommitHook.NAME)).call();
} catch (Exception e) {
log.error(MessageFormat.format(
JGitText.get().postCommitHookFailed, e.getMessage()),
e);
}
return revCommit;
} catch (UnmergedPathException e) {
throw new UnmergedPathsException(e);
} catch (IOException e) {
@@ -328,6 +272,89 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
}

private void checkIfEmpty(RevWalk rw, ObjectId headId, ObjectId indexTreeId)
throws EmptyCommitException, MissingObjectException,
IncorrectObjectTypeException, IOException {
if (headId != null && !allowEmpty.booleanValue()) {
RevCommit headCommit = rw.parseCommit(headId);
headCommit.getTree();
if (indexTreeId.equals(headCommit.getTree())) {
throw new EmptyCommitException(JGitText.get().emptyCommit);
}
}
}

private void sign(CommitBuilder commit) throws ServiceUnavailableException,
CanceledException, UnsupportedSigningFormatException {
if (gpgSigner == null) {
throw new ServiceUnavailableException(
JGitText.get().signingServiceUnavailable);
}
if (gpgSigner instanceof GpgObjectSigner) {
((GpgObjectSigner) gpgSigner).signObject(commit,
signingKey, committer, credentialsProvider,
gpgConfig);
} else {
if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
throw new UnsupportedSigningFormatException(JGitText
.get().onlyOpenPgpSupportedForSigning);
}
gpgSigner.sign(commit, signingKey, committer,
credentialsProvider);
}
}

private void updateRef(RepositoryState state, ObjectId headId,
RevCommit revCommit, ObjectId commitId)
throws ConcurrentRefUpdateException, IOException {
RefUpdate ru = repo.updateRef(Constants.HEAD);
ru.setNewObjectId(commitId);
if (!useDefaultReflogMessage) {
ru.setRefLogMessage(reflogComment, false);
} else {
String prefix = amend ? "commit (amend): " //$NON-NLS-1$
: parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
: "commit: "; //$NON-NLS-1$
ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
false);
}
if (headId != null) {
ru.setExpectedOldObjectId(headId);
} else {
ru.setExpectedOldObjectId(ObjectId.zeroId());
}
Result rc = ru.forceUpdate();
switch (rc) {
case NEW:
case FORCED:
case FAST_FORWARD: {
setCallable(false);
if (state == RepositoryState.MERGING_RESOLVED
|| isMergeDuringRebase(state)) {
// Commit was successful. Now delete the files
// used for merge commits
repo.writeMergeCommitMsg(null);
repo.writeMergeHeads(null);
} else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
repo.writeMergeCommitMsg(null);
repo.writeCherryPickHead(null);
} else if (state == RepositoryState.REVERTING_RESOLVED) {
repo.writeMergeCommitMsg(null);
repo.writeRevertHead(null);
}
break;
}
case REJECTED:
case LOCK_FAILURE:
throw new ConcurrentRefUpdateException(
JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
default:
throw new JGitInternalException(MessageFormat.format(
JGitText.get().updatingRefFailed, Constants.HEAD,
commitId.toString(), rc));
}
}

private void insertChangeId(ObjectId treeId) {
ObjectId firstParentId = null;
if (!parents.isEmpty())

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java View File

@@ -558,6 +558,7 @@ public class JGitText extends TranslationBundle {
/***/ public String peerDidNotSupplyACompleteObjectGraph;
/***/ public String personIdentEmailNonNull;
/***/ public String personIdentNameNonNull;
/***/ public String postCommitHookFailed;
/***/ public String prefixRemote;
/***/ public String problemWithResolvingPushRefSpecsLocally;
/***/ public String progressMonUploading;

+ 51
- 22
org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java View File

@@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -224,8 +223,17 @@ public class OpenSshConfigFile implements SshConfigStore {
entries.put(DEFAULT_NAME, defaults);

while ((line = reader.readLine()) != null) {
// OpenSsh ignores trailing comments on a line. Anything after the
// first # on a line is trimmed away (yes, even if the hash is
// inside quotes).
//
// See https://github.com/openssh/openssh-portable/commit/2bcbf679
int i = line.indexOf('#');
if (i >= 0) {
line = line.substring(0, i);
}
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
if (line.isEmpty()) {
continue;
}
String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
@@ -484,12 +492,30 @@ public class OpenSshConfigFile implements SshConfigStore {
LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
}

/**
* OpenSSH has renamed some config keys. This maps old names to new
* names.
*/
private static final Map<String, String> ALIASES = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER);

static {
// See https://github.com/openssh/openssh-portable/commit/ee9c0da80
ALIASES.put("PubkeyAcceptedKeyTypes", //$NON-NLS-1$
SshConstants.PUBKEY_ACCEPTED_ALGORITHMS);
}

private Map<String, String> options;

private Map<String, List<String>> multiOptions;

private Map<String, List<String>> listOptions;

private static String toKey(String key) {
String k = ALIASES.get(key);
return k != null ? k : key;
}

/**
* Retrieves the value of a single-valued key, or the first if the key
* has multiple values. Keys are case-insensitive, so
@@ -501,15 +527,15 @@ public class OpenSshConfigFile implements SshConfigStore {
*/
@Override
public String getValue(String key) {
String result = options != null ? options.get(key) : null;
String k = toKey(key);
String result = options != null ? options.get(k) : null;
if (result == null) {
// Let's be lenient and return at least the first value from
// a list-valued or multi-valued key.
List<String> values = listOptions != null ? listOptions.get(key)
List<String> values = listOptions != null ? listOptions.get(k)
: null;
if (values == null) {
values = multiOptions != null ? multiOptions.get(key)
: null;
values = multiOptions != null ? multiOptions.get(k) : null;
}
if (values != null && !values.isEmpty()) {
result = values.get(0);
@@ -529,10 +555,11 @@ public class OpenSshConfigFile implements SshConfigStore {
*/
@Override
public List<String> getValues(String key) {
List<String> values = listOptions != null ? listOptions.get(key)
String k = toKey(key);
List<String> values = listOptions != null ? listOptions.get(k)
: null;
if (values == null) {
values = multiOptions != null ? multiOptions.get(key) : null;
values = multiOptions != null ? multiOptions.get(k) : null;
}
if (values == null || values.isEmpty()) {
return new ArrayList<>();
@@ -551,34 +578,35 @@ public class OpenSshConfigFile implements SshConfigStore {
* to set or add
*/
public void setValue(String key, String value) {
String k = toKey(key);
if (value == null) {
if (multiOptions != null) {
multiOptions.remove(key);
multiOptions.remove(k);
}
if (listOptions != null) {
listOptions.remove(key);
listOptions.remove(k);
}
if (options != null) {
options.remove(key);
options.remove(k);
}
return;
}
if (MULTI_KEYS.contains(key)) {
if (MULTI_KEYS.contains(k)) {
if (multiOptions == null) {
multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
List<String> values = multiOptions.get(key);
List<String> values = multiOptions.get(k);
if (values == null) {
values = new ArrayList<>(4);
multiOptions.put(key, values);
multiOptions.put(k, values);
}
values.add(value);
} else {
if (options == null) {
options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
if (!options.containsKey(key)) {
options.put(key, value);
if (!options.containsKey(k)) {
options.put(k, value);
}
}
}
@@ -595,20 +623,21 @@ public class OpenSshConfigFile implements SshConfigStore {
if (values.isEmpty()) {
return;
}
String k = toKey(key);
// Check multi-valued keys first; because of the replacement
// strategy, they must take precedence over list-valued keys
// which always follow the "first occurrence wins" strategy.
//
// Note that SendEnv is a multi-valued list-valued key. (It's
// rather immaterial for JGit, though.)
if (MULTI_KEYS.contains(key)) {
if (MULTI_KEYS.contains(k)) {
if (multiOptions == null) {
multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
List<String> items = multiOptions.get(key);
List<String> items = multiOptions.get(k);
if (items == null) {
items = new ArrayList<>(values);
multiOptions.put(key, items);
multiOptions.put(k, items);
} else {
items.addAll(values);
}
@@ -616,8 +645,8 @@ public class OpenSshConfigFile implements SshConfigStore {
if (listOptions == null) {
listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
if (!listOptions.containsKey(key)) {
listOptions.put(key, values);
if (!listOptions.containsKey(k)) {
listOptions.put(k, values);
}
}
}
@@ -630,7 +659,7 @@ public class OpenSshConfigFile implements SshConfigStore {
* @return {@code true} if the key is a list-valued key.
*/
public static boolean isListKey(String key) {
return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
return LIST_KEYS.contains(toKey(key));
}

void merge(HostEntry entry) {

+ 8
- 0
org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java View File

@@ -114,6 +114,14 @@ public final class SshConstants {
/** Key in an ssh config file. */
public static final String PREFERRED_AUTHENTICATIONS = "PreferredAuthentications";

/**
* Key in an ssh config file; defines signature algorithms for public key
* authentication as a comma-separated list.
*
* @since 5.11
*/
public static final String PUBKEY_ACCEPTED_ALGORITHMS = "PubkeyAcceptedAlgorithms";

/** Key in an ssh config file. */
public static final String PROXY_COMMAND = "ProxyCommand";


Loading…
Cancel
Save