You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/11/19 12:50:17 UTC
[2/3] mina-sshd git commit: [SSHD-865] Use lazy-loading for keys
specified in the HostConfigEntry
[SSHD-865] Use lazy-loading for keys specified in the HostConfigEntry
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/98a708a5
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/98a708a5
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/98a708a5
Branch: refs/heads/master
Commit: 98a708a555cd5ba123161c8cbaa627cb855dff65
Parents: e849cc5
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 18 17:32:30 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Mon Nov 19 14:50:10 2018 +0200
----------------------------------------------------------------------
CHANGES.md | 3 +
.../config/keys/ClientIdentityFileWatcher.java | 13 +-
.../config/keys/ClientIdentityLoader.java | 22 +++
.../config/keys/ClientIdentityLoaderHolder.java | 34 ++++
.../keys/ClientIdentityLoaderManager.java | 29 ++++
.../keys/LazyClientKeyIdentityProvider.java | 157 +++++++++++++++++++
.../config/keys/FilePasswordProviderHolder.java | 35 +++++
.../keys/FilePasswordProviderManager.java | 9 +-
.../common/keyprovider/KeyIdentityProvider.java | 19 ++-
.../KeyIdentityProviderResolutionTest.java | 90 +++++++++++
.../sshd/client/ClientFactoryManager.java | 10 +-
.../java/org/apache/sshd/client/SshClient.java | 112 +++++--------
.../hosts/HostConfigEntryResolverTest.java | 10 +-
13 files changed, 438 insertions(+), 105 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index 3ca0d3d..9cbb99f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -60,6 +60,9 @@ accept also an `AttributeRepository` connection context argument (propagated fro
* `SshClient` and `ClientSession` use a `KeyIdentityProvider` instead of a full blown `KeyPairProvider`.
`KeyPairProvider` is used only in the context of an `SshServer` and/or `ServerSession`.
+* `SshClient#loadClientIdentities` has been renamed to `preloadClientIdentities` + it returns a
+`KeyIdentityProvider` instead of a collection of strings representing paths.
+
## Behavioral changes and enhancements
* [SSHD-849](https://issues.apache.org/jira/browse/SSHD-849) - Data forwarding code makes sure all
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
index ca81e47..5832911 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcher.java
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.FilePasswordProviderHolder;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
@@ -43,7 +44,9 @@ import org.apache.sshd.common.util.io.resource.PathResource;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class ClientIdentityFileWatcher extends ModifiableFileWatcher implements ClientIdentityProvider {
+public class ClientIdentityFileWatcher
+ extends ModifiableFileWatcher
+ implements ClientIdentityProvider, ClientIdentityLoaderHolder, FilePasswordProviderHolder {
private final AtomicReference<KeyPair> identityHolder = new AtomicReference<>(null);
private final Supplier<? extends ClientIdentityLoader> loaderHolder;
private final Supplier<? extends FilePasswordProvider> providerHolder;
@@ -73,15 +76,17 @@ public class ClientIdentityFileWatcher extends ModifiableFileWatcher implements
this.strict = strict;
}
- public final boolean isStrict() {
+ public boolean isStrict() {
return strict;
}
- public final ClientIdentityLoader getClientIdentityLoader() {
+ @Override
+ public ClientIdentityLoader getClientIdentityLoader() {
return loaderHolder.get();
}
- public final FilePasswordProvider getFilePasswordProvider() {
+ @Override
+ public FilePasswordProvider getFilePasswordProvider() {
return providerHolder.get();
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
index 1d941ac..ceb914e 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoader.java
@@ -26,11 +26,14 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
+import java.util.Collection;
import java.util.Objects;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.io.resource.PathResource;
@@ -105,4 +108,23 @@ public interface ClientIdentityLoader {
KeyPair loadClientIdentity(
SessionContext session, NamedResource location, FilePasswordProvider provider)
throws IOException, GeneralSecurityException;
+
+ /**
+ * Uses the provided {@link ClientIdentityLoader} to <U>lazy</U> load the keys locations
+ *
+ * @param loader The loader instance to use
+ * @param locations The locations to load - ignored if {@code null}/empty
+ * @param passwordProvider The {@link FilePasswordProvider} to use if any
+ * encrypted keys found
+ * @param ignoreNonExisting Whether to ignore non existing locations as indicated
+ * by {@link #isValidLocation(NamedResource)}
+ * @return The {@link KeyIdentityProvider} wrapper
+ */
+ static KeyIdentityProvider asKeyIdentityProvider(
+ ClientIdentityLoader loader, Collection<? extends NamedResource> locations,
+ FilePasswordProvider passwordProvider, boolean ignoreNonExisting) {
+ return GenericUtils.isEmpty(locations)
+ ? KeyIdentityProvider.EMPTY_KEYS_PROVIDER
+ : new LazyClientKeyIdentityProvider(loader, locations, passwordProvider, ignoreNonExisting);
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderHolder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderHolder.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderHolder.java
new file mode 100644
index 0000000..ecd0f0e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderHolder.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface ClientIdentityLoaderHolder {
+ /**
+ * @return The {@link ClientIdentityLoader} to use in order to load client
+ * key pair identities - never {@code null}
+ */
+ ClientIdentityLoader getClientIdentityLoader();
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderManager.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderManager.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderManager.java
new file mode 100644
index 0000000..9aa2e55
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityLoaderManager.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ClientIdentityLoaderManager extends ClientIdentityLoaderHolder {
+ void setClientIdentityLoader(ClientIdentityLoader loader);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientKeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientKeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientKeyIdentityProvider.java
new file mode 100644
index 0000000..05596be
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientKeyIdentityProvider.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.FilePasswordProviderHolder;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyClientKeyIdentityProvider implements KeyIdentityProvider, ClientIdentityLoaderHolder, FilePasswordProviderHolder {
+ private final ClientIdentityLoader clientIdentityLoader;
+ private final Collection<? extends NamedResource> locations;
+ private final FilePasswordProvider passwordProvider;
+ private final boolean ignoreNonExisting;
+
+ public LazyClientKeyIdentityProvider(
+ ClientIdentityLoader loader, Collection<? extends NamedResource> locations,
+ FilePasswordProvider passwordProvider, boolean ignoreNonExisting) {
+ this.clientIdentityLoader = Objects.requireNonNull(loader, "No client identity loader provided");
+ this.locations = locations;
+ this.passwordProvider = passwordProvider;
+ this.ignoreNonExisting = ignoreNonExisting;
+ }
+
+ @Override
+ public ClientIdentityLoader getClientIdentityLoader() {
+ return clientIdentityLoader;
+ }
+
+ public Collection<? extends NamedResource> getLocations() {
+ return locations;
+ }
+
+ @Override
+ public FilePasswordProvider getFilePasswordProvider() {
+ return passwordProvider;
+ }
+
+ public boolean isIgnoreNonExisting() {
+ return ignoreNonExisting;
+ }
+
+ @Override
+ public Iterable<KeyPair> loadKeys(SessionContext session)
+ throws IOException, GeneralSecurityException {
+ Collection<? extends NamedResource> locs = getLocations();
+ if (GenericUtils.isEmpty(locs)) {
+ return Collections.emptyList();
+ }
+
+ return () -> new Iterator<KeyPair>() {
+ private final Iterator<? extends NamedResource> iter = locs.iterator();
+ private KeyPair currentPair;
+ private boolean finished;
+
+ @Override
+ public boolean hasNext() {
+ if (finished) {
+ return false;
+ }
+
+ while (iter.hasNext()) {
+ NamedResource l = iter.next();
+ try {
+ currentPair = loadClientIdentity(session, l);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException("Failed (" + e.getClass().getSimpleName() + ")"
+ + " to load key from " + l.getName() + ": " + e.getMessage(), e);
+ }
+
+ if (currentPair != null) {
+ return true;
+ }
+ }
+
+ finished = true;
+ return false;
+ }
+
+ @Override
+ public KeyPair next() {
+ if (finished) {
+ throw new NoSuchElementException("All identities have been exhausted");
+ }
+ if (currentPair == null) {
+ throw new IllegalStateException("'next()' called without asking 'hasNext()'");
+ }
+
+ KeyPair kp = currentPair;
+ currentPair = null;
+ return kp;
+ }
+
+ @Override
+ public String toString() {
+ return Iterator.class.getSimpleName() + "[" + LazyClientKeyIdentityProvider.class.getSimpleName() + "]";
+ }
+ };
+ }
+
+ protected KeyPair loadClientIdentity(SessionContext session, NamedResource location)
+ throws IOException, GeneralSecurityException {
+ ClientIdentityLoader loader = getClientIdentityLoader();
+ boolean ignoreInvalid = isIgnoreNonExisting();
+ try {
+ if (!loader.isValidLocation(location)) {
+ if (ignoreInvalid) {
+ return null;
+ }
+
+ throw new FileNotFoundException("Invalid identity location: " + location.getName());
+ }
+ } catch (IOException e) {
+ if (ignoreInvalid) {
+ return null;
+ }
+
+ throw e;
+ }
+
+ return loader.loadClientIdentity(session, location, getFilePasswordProvider());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderHolder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderHolder.java
new file mode 100644
index 0000000..a872fac
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderHolder.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface FilePasswordProviderHolder {
+ /**
+ * @return The {@link FilePasswordProvider} to use if need to load encrypted
+ * identities keys - never {@code null}
+ * @see FilePasswordProvider#EMPTY
+ */
+ FilePasswordProvider getFilePasswordProvider();
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderManager.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderManager.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderManager.java
index aae151c..e38443a 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderManager.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProviderManager.java
@@ -24,13 +24,6 @@ package org.apache.sshd.common.config.keys;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public interface FilePasswordProviderManager {
- /**
- * @return The {@link FilePasswordProvider} to use if need to load encrypted
- * identities keys - never {@code null}
- * @see FilePasswordProvider#EMPTY
- */
- FilePasswordProvider getFilePasswordProvider();
-
+public interface FilePasswordProviderManager extends FilePasswordProviderHolder {
void setFilePasswordProvider(FilePasswordProvider provider);
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
index 3298f20..6ce5996 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
@@ -62,12 +62,20 @@ public interface KeyIdentityProvider {
Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException;
/**
+ * @param provider The {@link KeyIdentityProvider} instance to verify
+ * @return {@code true} if instance is {@code null} or the {@link #EMPTY_KEYS_PROVIDER}
+ */
+ static boolean isEmpty(KeyIdentityProvider provider) {
+ return (provider == null) || GenericUtils.isSameReference(provider, EMPTY_KEYS_PROVIDER);
+ }
+
+ /**
* <P>Creates a "unified" {@link KeyIdentityProvider} out of 2 possible ones
* as follows:</P></BR>
* <UL>
* <LI>If both are {@code null} then return {@code null}.</LI>
- * <LI>If either one is {@code null} then use the non-{@code null} one.</LI>
- * <LI>If both are the same instance then use it.</U>
+ * <LI>If either one is {@code null}/{@link #EMPTY_KEYS_PROVIDER empty} then use the non-{@code null} one.</LI>
+ * <LI>If both are the same instance then use the instance.</U>
* <LI>Otherwise, returns a wrapper that groups both providers.</LI>
* </UL>
*
@@ -78,9 +86,10 @@ public interface KeyIdentityProvider {
*/
static KeyIdentityProvider resolveKeyIdentityProvider(
KeyIdentityProvider identities, KeyIdentityProvider keys) {
- if ((keys == null) || (identities == keys)) {
- return identities;
- } else if (identities == null) {
+ if (isEmpty(keys) || GenericUtils.isSameReference(identities, keys)) {
+ // Prefer EMPTY over null
+ return (identities == null) ? keys : identities;
+ } else if (isEmpty(identities)) {
return keys;
} else {
return multiProvider(identities, keys);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyIdentityProviderResolutionTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyIdentityProviderResolutionTest.java b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyIdentityProviderResolutionTest.java
new file mode 100644
index 0000000..15ebb5d
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/KeyIdentityProviderResolutionTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.keyprovider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+import org.mockito.Mockito;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class KeyIdentityProviderResolutionTest extends JUnitTestSupport {
+ private final KeyIdentityProvider p1;
+ private final KeyIdentityProvider p2;
+ private final KeyIdentityProvider expected;
+
+ public KeyIdentityProviderResolutionTest(
+ KeyIdentityProvider p1, KeyIdentityProvider p2, KeyIdentityProvider expected) {
+ this.p1 = p1;
+ this.p2 = p2;
+ this.expected = expected;
+ }
+
+ @Parameters(name = "p1={0}, p2={1}, expected={2}")
+ public static List<Object[]> parameters() {
+ return new ArrayList<Object[]>() {
+ // Not serializing it
+ private static final long serialVersionUID = 1L;
+
+ {
+ add(new Object[]{null, null, null});
+ add(new Object[]{null, KeyIdentityProvider.EMPTY_KEYS_PROVIDER, KeyIdentityProvider.EMPTY_KEYS_PROVIDER});
+ add(new Object[]{KeyIdentityProvider.EMPTY_KEYS_PROVIDER, null, KeyIdentityProvider.EMPTY_KEYS_PROVIDER});
+ add(new Object[]{KeyIdentityProvider.EMPTY_KEYS_PROVIDER, KeyIdentityProvider.EMPTY_KEYS_PROVIDER, KeyIdentityProvider.EMPTY_KEYS_PROVIDER});
+
+ KeyIdentityProvider p = createKeyIdentityProvider("MOCK");
+ add(new Object[]{null, p, p});
+ add(new Object[]{KeyIdentityProvider.EMPTY_KEYS_PROVIDER, p, p});
+ add(new Object[]{p, null, p});
+ add(new Object[]{p, KeyIdentityProvider.EMPTY_KEYS_PROVIDER, p});
+ }
+
+ private KeyIdentityProvider createKeyIdentityProvider(String name) {
+ KeyIdentityProvider p = Mockito.mock(KeyIdentityProvider.class);
+ Mockito.when(p.toString()).thenReturn(name);
+ return p;
+ }
+ };
+ }
+
+ @Test
+ public void testResolveKeyIdentityProvider() {
+ assertSame(expected, KeyIdentityProvider.resolveKeyIdentityProvider(p1, p2));
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 35b42b1..368ebc2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -20,6 +20,7 @@ package org.apache.sshd.client;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.client.config.keys.ClientIdentityLoaderManager;
import org.apache.sshd.client.session.ClientProxyConnectorHolder;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.config.keys.FilePasswordProviderManager;
@@ -34,6 +35,7 @@ public interface ClientFactoryManager
extends FactoryManager,
ClientProxyConnectorHolder,
FilePasswordProviderManager,
+ ClientIdentityLoaderManager,
ClientAuthenticationManager {
/**
@@ -84,12 +86,4 @@ public interface ClientFactoryManager
HostConfigEntryResolver getHostConfigEntryResolver();
void setHostConfigEntryResolver(HostConfigEntryResolver resolver);
-
- /**
- * @return The {@link ClientIdentityLoader} to use in order to load client
- * key pair identities - never {@code null}
- */
- ClientIdentityLoader getClientIdentityLoader();
-
- void setClientIdentityLoader(ClientIdentityLoader loader);
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 796d781..1dc6859 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -18,9 +18,7 @@
*/
package org.apache.sshd.client;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.StreamCorruptedException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
@@ -501,7 +499,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
log.debug("connect({}@{}:{}) no overrides", username, host, port);
}
- return doConnect(username, targetAddress, context, localAddress, Collections.emptyList(), true);
+ return doConnect(username, targetAddress, context, localAddress, KeyIdentityProvider.EMPTY_KEYS_PROVIDER, true);
} else {
if (log.isDebugEnabled()) {
log.debug("connect({}@{}:{}) effective: {}", username, host, port, entry);
@@ -513,7 +511,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
if (log.isDebugEnabled()) {
log.debug("connect({}@{}) not an InetSocketAddress: {}", username, targetAddress, targetAddress.getClass().getName());
}
- return doConnect(username, targetAddress, context, localAddress, Collections.emptyList(), true);
+ return doConnect(username, targetAddress, context, localAddress, KeyIdentityProvider.EMPTY_KEYS_PROVIDER, true);
}
}
@@ -530,60 +528,27 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
Collection<PathResource> idFiles = GenericUtils.isEmpty(hostIds)
? Collections.emptyList()
: hostIds.stream()
- .map(l -> Paths.get(l))
+ .map(Paths::get)
.map(PathResource::new)
.collect(Collectors.toCollection(() -> new ArrayList<>(hostIds.size())));
- Collection<KeyPair> keys = loadClientIdentities(idFiles);
+ KeyIdentityProvider keys = preloadClientIdentities(idFiles);
return doConnect(hostConfig.getUsername(), new InetSocketAddress(host, port),
context, localAddress, keys, !hostConfig.isIdentitiesOnly());
}
- protected List<KeyPair> loadClientIdentities(Collection<? extends NamedResource> locations) throws IOException {
- if (GenericUtils.isEmpty(locations)) {
- return Collections.emptyList();
- }
-
- List<KeyPair> ids = new ArrayList<>(locations.size());
- boolean ignoreNonExisting = this.getBooleanProperty(IGNORE_INVALID_IDENTITIES, DEFAULT_IGNORE_INVALID_IDENTITIES);
- ClientIdentityLoader loader = Objects.requireNonNull(getClientIdentityLoader(), "No ClientIdentityLoader");
- FilePasswordProvider provider = getFilePasswordProvider();
- boolean debugEnabled = log.isDebugEnabled();
- for (NamedResource l : locations) {
- if (!loader.isValidLocation(l)) {
- if (ignoreNonExisting) {
- if (debugEnabled) {
- log.debug("loadClientIdentities - skip non-existing identity location: {}", l);
- }
- continue;
- }
-
- throw new FileNotFoundException("Invalid identity location: " + l);
- }
-
- try {
- KeyPair kp = loader.loadClientIdentity(null /* TODO use lazy-load here as well */, l, provider);
- if (kp == null) {
- throw new IOException("No identity loaded from " + l);
- }
-
- if (debugEnabled) {
- log.debug("loadClientIdentities({}) type={}, fingerprint={}",
- l, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
- }
-
- ids.add(kp);
- } catch (GeneralSecurityException e) {
- throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ") to load identity from " + l + ": " + e.getMessage());
- }
- }
-
- return ids;
+ protected KeyIdentityProvider preloadClientIdentities(Collection<? extends NamedResource> locations) throws IOException {
+ return GenericUtils.isEmpty(locations)
+ ? KeyIdentityProvider.EMPTY_KEYS_PROVIDER
+ : ClientIdentityLoader.asKeyIdentityProvider(
+ Objects.requireNonNull(getClientIdentityLoader(), "No ClientIdentityLoader"),
+ locations, getFilePasswordProvider(),
+ this.getBooleanProperty(IGNORE_INVALID_IDENTITIES, DEFAULT_IGNORE_INVALID_IDENTITIES));
}
protected ConnectFuture doConnect(
String username, SocketAddress targetAddress,
AttributeRepository context, SocketAddress localAddress,
- Iterable<? extends KeyPair> identities, boolean useDefaultIdentities)
+ KeyIdentityProvider identities, boolean useDefaultIdentities)
throws IOException {
if (connector == null) {
throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
@@ -600,7 +565,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
protected SshFutureListener<IoConnectFuture> createConnectCompletionListener(
ConnectFuture connectFuture, String username, SocketAddress address,
- Iterable<? extends KeyPair> identities, boolean useDefaultIdentities) {
+ KeyIdentityProvider identities, boolean useDefaultIdentities) {
return new SshFutureListener<IoConnectFuture>() {
@Override
@SuppressWarnings("synthetic-access")
@@ -643,53 +608,46 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
protected void onConnectOperationComplete(
IoSession ioSession, ConnectFuture connectFuture, String username,
- SocketAddress address, Iterable<? extends KeyPair> identities, boolean useDefaultIdentities) {
+ SocketAddress address, KeyIdentityProvider identities, boolean useDefaultIdentities) {
AbstractClientSession session = (AbstractClientSession) AbstractSession.getSession(ioSession);
session.setUsername(username);
session.setConnectAddress(address);
if (useDefaultIdentities) {
- setupDefaultSessionIdentities(session);
- }
-
- if (identities != null) {
- boolean traceEnabled = log.isTraceEnabled();
- for (KeyPair kp : identities) {
- if (traceEnabled) {
- log.trace("onConnectOperationComplete({}) add identity type={}, fingerprint={}",
- session, KeyUtils.getKeyType(kp), KeyUtils.getFingerPrint(kp.getPublic()));
- }
- session.addPublicKeyIdentity(kp);
- }
+ setupDefaultSessionIdentities(session, identities);
+ } else {
+ session.setKeyIdentityProvider((identities == null) ? KeyIdentityProvider.EMPTY_KEYS_PROVIDER : identities);
}
connectFuture.setSession(session);
}
- protected void setupDefaultSessionIdentities(ClientSession session) {
+ protected void setupDefaultSessionIdentities(ClientSession session, KeyIdentityProvider extraIdentities) {
+ boolean debugEnabled = log.isDebugEnabled();
// check if session listener intervened
KeyIdentityProvider kpSession = session.getKeyIdentityProvider();
KeyIdentityProvider kpClient = getKeyIdentityProvider();
- boolean debugEnabled = log.isDebugEnabled();
- if (kpSession == null) {
- session.setKeyIdentityProvider(kpClient);
- } else {
- if (kpSession != kpClient) {
- if (debugEnabled) {
- log.debug("setupDefaultSessionIdentities({}) key identity provider override", session);
- }
+ if (GenericUtils.isSameReference(kpSession, kpClient)) {
+ if (debugEnabled) {
+ log.debug("setupDefaultSessionIdentities({}) key identity provider override in session listener", session);
+ }
+ }
+
+ // Prefer the extra identities to come first since they were probably indicate by the host-config entry
+ KeyIdentityProvider kpEffective =
+ KeyIdentityProvider.resolveKeyIdentityProvider(extraIdentities, kpSession);
+ if (!GenericUtils.isSameReference(kpSession, kpEffective)) {
+ if (debugEnabled) {
+ log.debug("setupDefaultSessionIdentities({}) key identity provider enhanced", session);
}
+ session.setKeyIdentityProvider(kpEffective);
}
PasswordIdentityProvider passSession = session.getPasswordIdentityProvider();
PasswordIdentityProvider passClient = getPasswordIdentityProvider();
- if (passSession == null) {
- session.setPasswordIdentityProvider(passClient);
- } else {
- if (passSession != passClient) {
- if (debugEnabled) {
- log.debug("setupDefaultSessionIdentities({}) password provider override", session);
- }
+ if (!GenericUtils.isSameReference(passSession, passClient)) {
+ if (debugEnabled) {
+ log.debug("setupDefaultSessionIdentities({}) password provider override", session);
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/98a708a5/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
index b76e46c..8ede710 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolverTest.java
@@ -32,6 +32,7 @@ import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.sshd.client.ClientFactoryManager;
import org.apache.sshd.client.SshClient;
@@ -214,6 +215,7 @@ public class HostConfigEntryResolverTest extends BaseTestSupport {
entry.addIdentity(clientIdentity);
entry.setIdentitiesOnly(true);
+ AtomicInteger specificIdentityLoadCount = new AtomicInteger(0);
client.setClientIdentityLoader(new ClientIdentityLoader() {
@Override
public boolean isValidLocation(NamedResource location) throws IOException {
@@ -225,6 +227,7 @@ public class HostConfigEntryResolverTest extends BaseTestSupport {
SessionContext session, NamedResource location, FilePasswordProvider provider)
throws IOException, GeneralSecurityException {
if (isValidLocation(location)) {
+ specificIdentityLoadCount.incrementAndGet();
return specificIdentity;
}
@@ -243,12 +246,13 @@ public class HostConfigEntryResolverTest extends BaseTestSupport {
client.setKeyIdentityProvider(provider);
client.start();
- try (ClientSession session = client.connect(entry).verify(7L, TimeUnit.SECONDS).getSession()) {
- assertSame("Unexpected session key pairs provider", provider, session.getKeyIdentityProvider());
+ try (ClientSession session = client.connect(entry)
+ .verify(7L, TimeUnit.SECONDS)
+ .getSession()) {
session.auth().verify(5L, TimeUnit.SECONDS);
assertFalse("Unexpected default client identity attempted", defaultClientIdentityAttempted.get());
assertNull("Default client identity auto-added", session.removePublicKeyIdentity(defaultIdentity));
- assertNotNull("Entry identity not automatically added", session.removePublicKeyIdentity(specificIdentity));
+ assertEquals("Entry identity not used", 1, specificIdentityLoadCount.get());
assertEffectiveRemoteAddress(session, entry);
} finally {
client.stop();