Browse Source
Update target platforms, maven and bazel builds to use sshd 2.2.0. Adapt internal classes to changed sshd interfaces and remove previous work-arounds for asking repeatedly for key passwords and for loading keys lazily; both are now done by sshd. CQ: 19034 CQ: 19035 Bug: 541425 Change-Id: I85e1df6ebb8a94953a912d9b2b8a7b5bdfbd608a Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>stable-5.4
Thomas Wolf
6 years ago
committed by
Matthias Sohn
36 changed files with 271 additions and 885 deletions
@ -1,158 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> |
|
||||||
* and other copyright owners as documented in the project's IP log. |
|
||||||
* |
|
||||||
* This program and the accompanying materials are made available |
|
||||||
* under the terms of the Eclipse Distribution License v1.0 which |
|
||||||
* accompanies this distribution, is reproduced below, and is |
|
||||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
|
||||||
* |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or |
|
||||||
* without modification, are permitted provided that the following |
|
||||||
* conditions are met: |
|
||||||
* |
|
||||||
* - Redistributions of source code must retain the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer. |
|
||||||
* |
|
||||||
* - Redistributions in binary form must reproduce the above |
|
||||||
* copyright notice, this list of conditions and the following |
|
||||||
* disclaimer in the documentation and/or other materials provided |
|
||||||
* with the distribution. |
|
||||||
* |
|
||||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
|
||||||
* names of its contributors may be used to endorse or promote |
|
||||||
* products derived from this software without specific prior |
|
||||||
* written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
|
||||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
|
||||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
||||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
|
||||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
package org.eclipse.jgit.internal.transport.sshd; |
|
||||||
|
|
||||||
import static java.text.MessageFormat.format; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.nio.file.Path; |
|
||||||
import java.security.GeneralSecurityException; |
|
||||||
import java.security.InvalidKeyException; |
|
||||||
import java.security.KeyPair; |
|
||||||
import java.security.NoSuchProviderException; |
|
||||||
import java.security.PrivateKey; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.Iterator; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import javax.security.auth.DestroyFailedException; |
|
||||||
|
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider; |
|
||||||
import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; |
|
||||||
import org.apache.sshd.common.keyprovider.FileKeyPairProvider; |
|
||||||
import org.apache.sshd.common.util.io.IoUtils; |
|
||||||
import org.apache.sshd.common.util.security.SecurityUtils; |
|
||||||
import org.eclipse.jgit.internal.transport.sshd.RepeatingFilePasswordProvider.ResourceDecodeResult; |
|
||||||
|
|
||||||
/** |
|
||||||
* A {@link FileKeyPairProvider} that asks repeatedly for a passphrase for an |
|
||||||
* encrypted private key if the {@link FilePasswordProvider} is a |
|
||||||
* {@link RepeatingFilePasswordProvider}. |
|
||||||
*/ |
|
||||||
public abstract class EncryptedFileKeyPairProvider extends FileKeyPairProvider { |
|
||||||
|
|
||||||
// TODO: remove this class once we're based on sshd > 2.1.0. See upstream
|
|
||||||
// issue SSHD-850 https://issues.apache.org/jira/browse/SSHD-850 and commit
|
|
||||||
// https://github.com/apache/mina-sshd/commit/f19bd2e34
|
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link EncryptedFileKeyPairProvider} for the given |
|
||||||
* {@link Path}s. |
|
||||||
* |
|
||||||
* @param paths |
|
||||||
* to read keys from |
|
||||||
*/ |
|
||||||
public EncryptedFileKeyPairProvider(List<Path> paths) { |
|
||||||
super(paths); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected KeyPair doLoadKey(String resourceKey, InputStream inputStream, |
|
||||||
FilePasswordProvider provider) |
|
||||||
throws IOException, GeneralSecurityException { |
|
||||||
if (!(provider instanceof RepeatingFilePasswordProvider)) { |
|
||||||
return super.doLoadKey(resourceKey, inputStream, provider); |
|
||||||
} |
|
||||||
KeyPairResourceParser parser = SecurityUtils.getKeyPairResourceParser(); |
|
||||||
if (parser == null) { |
|
||||||
// This is an internal configuration error, thus no translation.
|
|
||||||
throw new NoSuchProviderException( |
|
||||||
"No registered key-pair resource parser"); //$NON-NLS-1$
|
|
||||||
} |
|
||||||
RepeatingFilePasswordProvider realProvider = (RepeatingFilePasswordProvider) provider; |
|
||||||
// Read the stream now so that we can process the content several
|
|
||||||
// times.
|
|
||||||
List<String> lines = IoUtils.readAllLines(inputStream); |
|
||||||
Collection<KeyPair> ids = null; |
|
||||||
while (ids == null) { |
|
||||||
try { |
|
||||||
ids = parser.loadKeyPairs(resourceKey, realProvider, lines); |
|
||||||
realProvider.handleDecodeAttemptResult(resourceKey, "", null); //$NON-NLS-1$
|
|
||||||
// No exception; success. Exit the loop even if ids is still
|
|
||||||
// null!
|
|
||||||
break; |
|
||||||
} catch (IOException | GeneralSecurityException |
|
||||||
| RuntimeException e) { |
|
||||||
ResourceDecodeResult loadResult = realProvider |
|
||||||
.handleDecodeAttemptResult(resourceKey, "", e); //$NON-NLS-1$
|
|
||||||
if (loadResult == null |
|
||||||
|| loadResult == ResourceDecodeResult.TERMINATE) { |
|
||||||
throw e; |
|
||||||
} else if (loadResult == ResourceDecodeResult.RETRY) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
// IGNORE doesn't make any sense here, but OK, let's ignore it.
|
|
||||||
// ids == null, so we'll throw an exception below.
|
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (ids == null) { |
|
||||||
// The javadoc on loadKeyPairs says it might return null if no
|
|
||||||
// key pair found. Bad API.
|
|
||||||
throw new InvalidKeyException( |
|
||||||
format(SshdText.get().identityFileNoKey, resourceKey)); |
|
||||||
} |
|
||||||
Iterator<KeyPair> keys = ids.iterator(); |
|
||||||
if (!keys.hasNext()) { |
|
||||||
throw new InvalidKeyException(format( |
|
||||||
SshdText.get().identityFileUnsupportedFormat, resourceKey)); |
|
||||||
} |
|
||||||
KeyPair result = keys.next(); |
|
||||||
if (keys.hasNext()) { |
|
||||||
log.warn(format(SshdText.get().identityFileMultipleKeys, |
|
||||||
resourceKey)); |
|
||||||
keys.forEachRemaining(k -> { |
|
||||||
PrivateKey pk = k.getPrivate(); |
|
||||||
if (pk != null) { |
|
||||||
try { |
|
||||||
pk.destroy(); |
|
||||||
} catch (DestroyFailedException e) { |
|
||||||
// Ignore
|
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
@ -1,83 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> |
|
||||||
* and other copyright owners as documented in the project's IP log. |
|
||||||
* |
|
||||||
* This program and the accompanying materials are made available |
|
||||||
* under the terms of the Eclipse Distribution License v1.0 which |
|
||||||
* accompanies this distribution, is reproduced below, and is |
|
||||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
|
||||||
* |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or |
|
||||||
* without modification, are permitted provided that the following |
|
||||||
* conditions are met: |
|
||||||
* |
|
||||||
* - Redistributions of source code must retain the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer. |
|
||||||
* |
|
||||||
* - Redistributions in binary form must reproduce the above |
|
||||||
* copyright notice, this list of conditions and the following |
|
||||||
* disclaimer in the documentation and/or other materials provided |
|
||||||
* with the distribution. |
|
||||||
* |
|
||||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
|
||||||
* names of its contributors may be used to endorse or promote |
|
||||||
* products derived from this software without specific prior |
|
||||||
* written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
|
||||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
|
||||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
||||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
|
||||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
package org.eclipse.jgit.internal.transport.sshd; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.apache.sshd.client.auth.AbstractUserAuthFactory; |
|
||||||
import org.apache.sshd.client.auth.UserAuth; |
|
||||||
import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory; |
|
||||||
import org.apache.sshd.common.NamedFactory; |
|
||||||
import org.apache.sshd.common.signature.Signature; |
|
||||||
import org.apache.sshd.common.signature.SignatureFactoriesManager; |
|
||||||
|
|
||||||
/** |
|
||||||
* A customized authentication factory for public key user authentication. The |
|
||||||
* default implementation {@link UserAuthPublicKeyFactory} ends up doing some |
|
||||||
* crazy stream "magic" that loads too many keys too early. |
|
||||||
*/ |
|
||||||
public class JGitPublicKeyAuthFactory extends AbstractUserAuthFactory |
|
||||||
implements SignatureFactoriesManager { |
|
||||||
|
|
||||||
/** The singleton {@link JGitPublicKeyAuthFactory}. */ |
|
||||||
public static final JGitPublicKeyAuthFactory INSTANCE = new JGitPublicKeyAuthFactory(); |
|
||||||
|
|
||||||
private JGitPublicKeyAuthFactory() { |
|
||||||
super(UserAuthPublicKeyFactory.NAME); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public UserAuth create() { |
|
||||||
return new JGitPublicKeyAuthentication(getSignatureFactories()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public List<NamedFactory<Signature>> getSignatureFactories() { |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void setSignatureFactories(List<NamedFactory<Signature>> factories) { |
|
||||||
throw new UnsupportedOperationException(); |
|
||||||
} |
|
||||||
} |
|
@ -1,103 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> |
|
||||||
* and other copyright owners as documented in the project's IP log. |
|
||||||
* |
|
||||||
* This program and the accompanying materials are made available |
|
||||||
* under the terms of the Eclipse Distribution License v1.0 which |
|
||||||
* accompanies this distribution, is reproduced below, and is |
|
||||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
|
||||||
* |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or |
|
||||||
* without modification, are permitted provided that the following |
|
||||||
* conditions are met: |
|
||||||
* |
|
||||||
* - Redistributions of source code must retain the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer. |
|
||||||
* |
|
||||||
* - Redistributions in binary form must reproduce the above |
|
||||||
* copyright notice, this list of conditions and the following |
|
||||||
* disclaimer in the documentation and/or other materials provided |
|
||||||
* with the distribution. |
|
||||||
* |
|
||||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
|
||||||
* names of its contributors may be used to endorse or promote |
|
||||||
* products derived from this software without specific prior |
|
||||||
* written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
|
||||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
|
||||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
||||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
|
||||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
package org.eclipse.jgit.internal.transport.sshd; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
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.signature.Signature; |
|
||||||
|
|
||||||
/** |
|
||||||
* A specialized public key authentication handler that uses our own |
|
||||||
* {@link JGitPublicKeyIterator}. The super class creates in |
|
||||||
* {@link #init(ClientSession, String)} a |
|
||||||
* {@link org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator}, which |
|
||||||
* in its constructor does some strange {@link java.util.stream.Stream} "magic" |
|
||||||
* that ends up loading keys prematurely. |
|
||||||
*/ |
|
||||||
public class JGitPublicKeyAuthentication extends UserAuthPublicKey { |
|
||||||
|
|
||||||
private ClientSession clientSession; |
|
||||||
|
|
||||||
private String serviceName; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link JGitPublicKeyAuthentication}. |
|
||||||
* |
|
||||||
* @param factories |
|
||||||
* signature factories to use |
|
||||||
*/ |
|
||||||
public JGitPublicKeyAuthentication( |
|
||||||
List<NamedFactory<Signature>> factories) { |
|
||||||
super(factories); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void init(ClientSession session, String service) throws Exception { |
|
||||||
// Do *not* call super.init(); it'll create a UserAuthPublicKeyIterator
|
|
||||||
// and that's where things then go wrong. Instead, do the whole
|
|
||||||
// initialization directly here.
|
|
||||||
clientSession = session; |
|
||||||
serviceName = service; |
|
||||||
releaseKeys(); |
|
||||||
// Use our own iterator!
|
|
||||||
keys = new JGitPublicKeyIterator(session, this); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public ClientSession getClientSession() { |
|
||||||
return clientSession; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public ClientSession getSession() { |
|
||||||
return clientSession; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String getService() { |
|
||||||
return serviceName; |
|
||||||
} |
|
||||||
} |
|
@ -1,275 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> |
|
||||||
* and other copyright owners as documented in the project's IP log. |
|
||||||
* |
|
||||||
* This program and the accompanying materials are made available |
|
||||||
* under the terms of the Eclipse Distribution License v1.0 which |
|
||||||
* accompanies this distribution, is reproduced below, and is |
|
||||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
|
||||||
* |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or |
|
||||||
* without modification, are permitted provided that the following |
|
||||||
* conditions are met: |
|
||||||
* |
|
||||||
* - Redistributions of source code must retain the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer. |
|
||||||
* |
|
||||||
* - Redistributions in binary form must reproduce the above |
|
||||||
* copyright notice, this list of conditions and the following |
|
||||||
* disclaimer in the documentation and/or other materials provided |
|
||||||
* with the distribution. |
|
||||||
* |
|
||||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
|
||||||
* names of its contributors may be used to endorse or promote |
|
||||||
* products derived from this software without specific prior |
|
||||||
* written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
|
||||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
|
||||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
||||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
|
||||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
package org.eclipse.jgit.internal.transport.sshd; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.nio.channels.Channel; |
|
||||||
import java.security.KeyPair; |
|
||||||
import java.security.PublicKey; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Iterator; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.NoSuchElementException; |
|
||||||
import java.util.concurrent.atomic.AtomicBoolean; |
|
||||||
|
|
||||||
import org.apache.sshd.agent.SshAgent; |
|
||||||
import org.apache.sshd.agent.SshAgentFactory; |
|
||||||
import org.apache.sshd.client.auth.pubkey.AbstractKeyPairIterator; |
|
||||||
import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity; |
|
||||||
import org.apache.sshd.client.auth.pubkey.KeyPairIdentity; |
|
||||||
import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity; |
|
||||||
import org.apache.sshd.client.config.hosts.HostConfigEntry; |
|
||||||
import org.apache.sshd.client.session.ClientSession; |
|
||||||
import org.apache.sshd.common.FactoryManager; |
|
||||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider; |
|
||||||
import org.apache.sshd.common.signature.SignatureFactoriesManager; |
|
||||||
|
|
||||||
/** |
|
||||||
* A new iterator over key pairs that we use instead of the default |
|
||||||
* {@link org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyIterator}, which |
|
||||||
* in its constructor does some strange {@link java.util.stream.Stream} "magic" |
|
||||||
* that ends up loading keys prematurely. This class uses plain |
|
||||||
* {@link Iterator}s instead to avoid that problem. Used in |
|
||||||
* {@link JGitPublicKeyAuthentication}. |
|
||||||
* |
|
||||||
* @see <a href= |
|
||||||
* "https://issues.apache.org/jira/projects/SSHD/issues/SSHD-860">Upstream |
|
||||||
* issue SSHD-860</a> |
|
||||||
*/ |
|
||||||
public class JGitPublicKeyIterator |
|
||||||
extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel { |
|
||||||
|
|
||||||
// Re: the cause for the problem mentioned above has not been determined.
|
|
||||||
// It looks as if either the Apache code inadvertently calls
|
|
||||||
// GenericUtils.isEmpty() on all streams (which would load the first key
|
|
||||||
// of each stream), or the Java stream implementation does some prefetching.
|
|
||||||
// It's not entirely clear. Using Iterators we have more control over
|
|
||||||
// what happens when.
|
|
||||||
|
|
||||||
private final AtomicBoolean open = new AtomicBoolean(true); |
|
||||||
|
|
||||||
private SshAgent agent; |
|
||||||
|
|
||||||
private final List<Iterator<PublicKeyIdentity>> keys = new ArrayList<>(3); |
|
||||||
|
|
||||||
private final Iterator<Iterator<PublicKeyIdentity>> keyIter; |
|
||||||
|
|
||||||
private Iterator<PublicKeyIdentity> current; |
|
||||||
|
|
||||||
private Boolean hasElement; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link JGitPublicKeyIterator}. |
|
||||||
* |
|
||||||
* @param session |
|
||||||
* we're trying to authenticate |
|
||||||
* @param signatureFactories |
|
||||||
* to use |
|
||||||
* @throws Exception |
|
||||||
* if an {@link SshAgentFactory} is configured and getting |
|
||||||
* identities from the agent fails |
|
||||||
*/ |
|
||||||
public JGitPublicKeyIterator(ClientSession session, |
|
||||||
SignatureFactoriesManager signatureFactories) throws Exception { |
|
||||||
super(session); |
|
||||||
boolean useAgent = true; |
|
||||||
if (session instanceof JGitClientSession) { |
|
||||||
HostConfigEntry config = ((JGitClientSession) session) |
|
||||||
.getHostConfigEntry(); |
|
||||||
useAgent = !config.isIdentitiesOnly(); |
|
||||||
} |
|
||||||
if (useAgent) { |
|
||||||
FactoryManager manager = session.getFactoryManager(); |
|
||||||
SshAgentFactory factory = manager == null ? null |
|
||||||
: manager.getAgentFactory(); |
|
||||||
if (factory != null) { |
|
||||||
try { |
|
||||||
agent = factory.createClient(manager); |
|
||||||
keys.add(new AgentIdentityIterator(agent)); |
|
||||||
} catch (IOException e) { |
|
||||||
try { |
|
||||||
closeAgent(); |
|
||||||
} catch (IOException err) { |
|
||||||
e.addSuppressed(err); |
|
||||||
} |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
keys.add( |
|
||||||
new KeyPairIdentityIterator(session.getRegisteredIdentities(), |
|
||||||
session, signatureFactories)); |
|
||||||
keys.add(new KeyPairIdentityIterator(session.getKeyPairProvider(), |
|
||||||
session, signatureFactories)); |
|
||||||
keyIter = keys.iterator(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean isOpen() { |
|
||||||
return open.get(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void close() throws IOException { |
|
||||||
if (open.getAndSet(false)) { |
|
||||||
closeAgent(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean hasNext() { |
|
||||||
if (!isOpen()) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (hasElement != null) { |
|
||||||
return hasElement.booleanValue(); |
|
||||||
} |
|
||||||
while (current == null || !current.hasNext()) { |
|
||||||
if (keyIter.hasNext()) { |
|
||||||
current = keyIter.next(); |
|
||||||
} else { |
|
||||||
current = null; |
|
||||||
hasElement = Boolean.FALSE; |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
hasElement = Boolean.TRUE; |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public PublicKeyIdentity next() { |
|
||||||
if (!isOpen() || hasElement == null && !hasNext() |
|
||||||
|| !hasElement.booleanValue()) { |
|
||||||
throw new NoSuchElementException(); |
|
||||||
} |
|
||||||
hasElement = null; |
|
||||||
PublicKeyIdentity result; |
|
||||||
try { |
|
||||||
result = current.next(); |
|
||||||
} catch (NoSuchElementException e) { |
|
||||||
result = null; |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
private void closeAgent() throws IOException { |
|
||||||
if (agent == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
try { |
|
||||||
agent.close(); |
|
||||||
} finally { |
|
||||||
agent = null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* An {@link Iterator} that maps the data obtained from an agent to |
|
||||||
* {@link PublicKeyIdentity}. |
|
||||||
*/ |
|
||||||
private static class AgentIdentityIterator |
|
||||||
implements Iterator<PublicKeyIdentity> { |
|
||||||
|
|
||||||
private final SshAgent agent; |
|
||||||
|
|
||||||
private final Iterator<? extends Map.Entry<PublicKey, String>> iter; |
|
||||||
|
|
||||||
public AgentIdentityIterator(SshAgent agent) throws IOException { |
|
||||||
this.agent = agent; |
|
||||||
iter = agent == null ? null : agent.getIdentities().iterator(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean hasNext() { |
|
||||||
return iter != null && iter.hasNext(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public PublicKeyIdentity next() { |
|
||||||
if (iter == null) { |
|
||||||
throw new NoSuchElementException(); |
|
||||||
} |
|
||||||
Map.Entry<PublicKey, String> entry = iter.next(); |
|
||||||
return new KeyAgentIdentity(agent, entry.getKey(), |
|
||||||
entry.getValue()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* An {@link Iterator} that maps {@link KeyPair} to |
|
||||||
* {@link PublicKeyIdentity}. |
|
||||||
*/ |
|
||||||
private static class KeyPairIdentityIterator |
|
||||||
implements Iterator<PublicKeyIdentity> { |
|
||||||
|
|
||||||
private final Iterator<KeyPair> keyPairs; |
|
||||||
|
|
||||||
private final ClientSession session; |
|
||||||
|
|
||||||
private final SignatureFactoriesManager signatureFactories; |
|
||||||
|
|
||||||
public KeyPairIdentityIterator(KeyIdentityProvider provider, |
|
||||||
ClientSession session, |
|
||||||
SignatureFactoriesManager signatureFactories) { |
|
||||||
this.session = session; |
|
||||||
this.signatureFactories = signatureFactories; |
|
||||||
keyPairs = provider == null ? null : provider.loadKeys().iterator(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean hasNext() { |
|
||||||
return keyPairs != null && keyPairs.hasNext(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public PublicKeyIdentity next() { |
|
||||||
if (keyPairs == null) { |
|
||||||
throw new NoSuchElementException(); |
|
||||||
} |
|
||||||
KeyPair key = keyPairs.next(); |
|
||||||
return new KeyPairIdentity(signatureFactories, session, key); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue