[SSHD-757] Converted most of the key-pair identity loaders to return an Iterable<KeyPair> instead of single instance
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/3efd1edf Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/3efd1edf Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/3efd1edf Branch: refs/heads/master Commit: 3efd1edf63fbb406524bd9d0cfa563f3b2305789 Parents: 2f92fe5 Author: Lyor Goldstein <[email protected]> Authored: Tue Nov 20 11:50:58 2018 +0200 Committer: Lyor Goldstein <[email protected]> Committed: Thu Nov 22 07:05:16 2018 +0200 ---------------------------------------------------------------------- CHANGES.md | 3 + README.md | 2 +- .../java/org/apache/sshd/cli/CliSupport.java | 2 +- .../apache/sshd/cli/client/ScpCommandMain.java | 2 +- .../sshd/cli/client/SshClientCliSupport.java | 20 ++--- .../sshd/cli/server/SshServerCliSupport.java | 26 +++++-- .../apache/sshd/cli/client/ChannelExecMain.java | 2 +- .../apache/sshd/cli/server/SshFsMounter.java | 2 +- .../config/keys/ClientIdentitiesWatcher.java | 6 +- .../config/keys/ClientIdentityFileWatcher.java | 43 +++++------ .../config/keys/ClientIdentityLoader.java | 6 +- .../config/keys/ClientIdentityProvider.java | 13 ++-- .../config/keys/LazyClientIdentityIterator.java | 20 +++-- .../keys/LazyClientKeyIdentityProvider.java | 16 +++- .../sshd/common/config/keys/IdentityUtils.java | 23 ++++-- .../AbstractResourceKeyPairProvider.java | 54 +++++++------- .../common/keyprovider/FileKeyPairProvider.java | 4 +- .../common/keyprovider/KeyIdentityProvider.java | 18 +++++ .../common/util/security/SecurityUtils.java | 11 +-- .../AbstractGeneratorHostKeyProvider.java | 77 ++++++++++++-------- .../SimpleGeneratorHostKeyProvider.java | 8 +- .../BuiltinClientIdentitiesWatcherTest.java | 6 +- .../keys/ClientIdentityFileWatcherTest.java | 14 ++-- .../pem/PKCS8PEMResourceKeyPairParserTest.java | 11 ++- .../AbstractGeneratorHostKeyProviderTest.java | 2 +- .../keys/LazyClientIdentityIteratorTest.java | 7 +- .../hosts/HostConfigEntryResolverTest.java | 8 +- .../sshd/common/auth/AuthenticationTest.java | 5 +- 28 files changed, 253 insertions(+), 158 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/CHANGES.md ---------------------------------------------------------------------- diff --git a/CHANGES.md b/CHANGES.md index db1e874..0c59a22 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -68,6 +68,9 @@ accept also an `AttributeRepository` connection context argument (propagated fro * Removed API(s) that used string file paths to create `FileInputStream`-s - using only `java.nio.file.Path`-s +* Converted most of the key-pair identity loaders (e.g., `ClientIdentityLoader`, `ClientIdentityProvider`, etc.) +to return an `Iterable<KeyPair>` instead of single `KeyPair` instance. + ## Behavioral changes and enhancements * [SSHD-757](https://issues.apache.org/jira/browse/SSHD-757) - Added hooks and some initial code to allow (limited) usage http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 811911d..6c8a1cb 100644 --- a/README.md +++ b/README.md @@ -1936,7 +1936,7 @@ This code relies on the [jpgpgj](https://github.com/justinludwig/jpgpj) support In order to be able to read `authorized_keys` files that may contain _OpenPGP_ keys references, one needs to register the relevant `PublicKeyEntryDataResolver`-s. This is done by calling `PGPPublicKeyEntryDataResolver#registerDefaultKeyEntryDataResolvers` -once during the _main_ code setup. This will enable the code to read authorized keys entries having the format +once during the _main_ code setup. This will enable the code to safely read authorized keys entries having the format specified in the [OpenSSH PGP configuration](https://www.red-bean.com/~nemo/openssh-gpg/): ``` http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java ---------------------------------------------------------------------- diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java index 1ba94d2..8e59456 100644 --- a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java +++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java @@ -48,7 +48,7 @@ public abstract class CliSupport { } public static boolean showError(PrintStream stderr, String message) { - stderr.println(message); + stderr.append("ERROR: ").println(message); return true; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java ---------------------------------------------------------------------- diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java index 3421cca..88910c2 100644 --- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java +++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/ScpCommandMain.java @@ -149,7 +149,7 @@ public class ScpCommandMain extends SshClientCliSupport { Class<?> clazz = cl.loadClass(className); return ScpClientCreator.class.cast(clazz.newInstance()); } catch (Exception e) { - stderr.append("Failed (").append(e.getClass().getSimpleName()).append(')') + stderr.append("WARNING: Failed (").append(e.getClass().getSimpleName()).append(')') .append(" to instantiate ").append(className) .append(": ").println(e.getMessage()); stderr.flush(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java ---------------------------------------------------------------------- diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java index 4741e72..6917c94 100644 --- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java +++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SshClientCliSupport.java @@ -397,7 +397,8 @@ public abstract class SshClientCliSupport extends CliSupport { answers[i] = stdin.readLine(); } } catch (IOException e) { - stderr.append(e.getClass().getSimpleName()).append(" while read prompts: ").println(e.getMessage()); + stderr.append("WARNING: ").append(e.getClass().getSimpleName()) + .append(" while read prompts: ").println(e.getMessage()); } return answers; } @@ -408,7 +409,8 @@ public abstract class SshClientCliSupport extends CliSupport { try { return stdin.readLine(); } catch (IOException e) { - stderr.append(e.getClass().getSimpleName()).append(" while read password: ").println(e.getMessage()); + stderr.append("WARNING: ").append(e.getClass().getSimpleName()) + .append(" while read password: ").println(e.getMessage()); return null; } } @@ -438,11 +440,11 @@ public abstract class SshClientCliSupport extends CliSupport { } ((KnownHostsServerKeyVerifier) current).setModifiedServerKeyAcceptor((clientSession, remoteAddress, entry, expected, actual) -> { - stderr.append("Mismatched keys presented by ").append(Objects.toString(remoteAddress)) + stderr.append("WARNING: Mismatched keys presented by ").append(Objects.toString(remoteAddress)) .append(" for entry=").println(entry); - stderr.append('\t').append("Expected=").append(KeyUtils.getKeyType(expected)) + stderr.append(" ").append("Expected=").append(KeyUtils.getKeyType(expected)) .append('-').println(KeyUtils.getFingerPrint(expected)); - stderr.append('\t').append("Actual=").append(KeyUtils.getKeyType(actual)) + stderr.append(" ").append("Actual=").append(KeyUtils.getKeyType(actual)) .append('-').println(KeyUtils.getFingerPrint(actual)); stderr.flush(); // just making sure @@ -538,7 +540,7 @@ public abstract class SshClientCliSupport extends CliSupport { Collection<String> unsupported = result.getUnsupportedFactories(); if (GenericUtils.size(unsupported) > 0) { - stderr.append("Ignored unsupported compressions: ").println(GenericUtils.join(unsupported, ',')); + stderr.append("WARNING: Ignored unsupported compressions: ").println(GenericUtils.join(unsupported, ',')); } return new ArrayList<>(available); @@ -566,7 +568,7 @@ public abstract class SshClientCliSupport extends CliSupport { Collection<String> unsupported = result.getUnsupportedFactories(); if (GenericUtils.size(unsupported) > 0) { - stderr.append("Ignored unsupported MACs: ").println(GenericUtils.join(unsupported, ',')); + stderr.append("WARNING: Ignored unsupported MACs: ").println(GenericUtils.join(unsupported, ',')); } return new ArrayList<>(available); @@ -589,13 +591,13 @@ public abstract class SshClientCliSupport extends CliSupport { BuiltinCiphers.ParseResult result = BuiltinCiphers.parseCiphersList(argVal); Collection<? extends NamedFactory<Cipher>> available = result.getParsedFactories(); if (GenericUtils.isEmpty(available)) { - showError(stderr, "No known ciphers in " + argVal); + showError(stderr, "WARNING: No known ciphers in " + argVal); return null; } Collection<String> unsupported = result.getUnsupportedFactories(); if (GenericUtils.size(unsupported) > 0) { - stderr.append("Ignored unsupported ciphers: ").println(GenericUtils.join(unsupported, ',')); + stderr.append("WARNING: Ignored unsupported ciphers: ").println(GenericUtils.join(unsupported, ',')); } return new ArrayList<>(available); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java ---------------------------------------------------------------------- diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java index 2521ea1..a5357f5 100644 --- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java +++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerCliSupport.java @@ -117,19 +117,33 @@ public abstract class SshServerCliSupport extends CliSupport { for (String keyFilePath : keyFiles) { Path path = Paths.get(keyFilePath); PathResource location = new PathResource(path); + Iterable<KeyPair> ids; try (InputStream inputStream = location.openInputStream()) { - KeyPair kp = SecurityUtils.loadKeyPairIdentity(null, location, inputStream, null); - pairs.add(kp); + ids = SecurityUtils.loadKeyPairIdentities(null, location, inputStream, null); } catch (Exception e) { - stderr.append("Failed (").append(e.getClass().getSimpleName()).append(')') + stderr.append("ERROR: Failed (").append(e.getClass().getSimpleName()).append(')') .append(" to load host key file=").append(keyFilePath) .append(": ").println(e.getMessage()); stderr.flush(); throw e; } + + if (ids == null) { + stderr.append("WARNING: No keys loaded from ").println(keyFilePath); + continue; + } + + for (KeyPair kp : ids) { + if (kp == null) { + stderr.append("WARNING: empty key found in ").println(keyFilePath); + continue; // debug breakpoint + } + pairs.add(kp); + } } - return new MappedKeyPairProvider(pairs); + return new MappedKeyPairProvider( + ValidateUtils.checkNotNullAndNotEmpty(pairs, "No key pairs loaded for provided key files")); } } @@ -157,7 +171,7 @@ public abstract class SshServerCliSupport extends CliSupport { SubsystemFactory factory = SubsystemFactory.class.cast(clazz.newInstance()); subsystems.add(factory); } catch (Exception e) { - stderr.append("Failed (").append(e.getClass().getSimpleName()).append(')') + stderr.append("ERROR: Failed (").append(e.getClass().getSimpleName()).append(')') .append(" to instantiate subsystem=").append(fqcn) .append(": ").println(e.getMessage()); stderr.flush(); @@ -208,7 +222,7 @@ public abstract class SshServerCliSupport extends CliSupport { Object instance = clazz.newInstance(); return ShellFactory.class.cast(instance); } catch (Exception e) { - stderr.append("Failed (").append(e.getClass().getSimpleName()).append(')') + stderr.append("ERROR: Failed (").append(e.getClass().getSimpleName()).append(')') .append(" to instantiate shell factory=").append(factory) .append(": ").println(e.getMessage()); stderr.flush(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java ---------------------------------------------------------------------- diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java index 99d0a7d..8afcd42 100644 --- a/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java +++ b/sshd-cli/src/test/java/org/apache/sshd/cli/client/ChannelExecMain.java @@ -59,7 +59,7 @@ public class ChannelExecMain extends BaseTestSupport { stdout.append('\t').println(l); } } catch (Exception e) { - stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage()); + stderr.append("WARNING: ").append(e.getClass().getSimpleName()).append(": ").println(e.getMessage()); } stdout.append("Execute ").append(command).print(" again [y]/n "); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java ---------------------------------------------------------------------- diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java index 0fbc926..6c17416 100644 --- a/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java +++ b/sshd-cli/src/test/java/org/apache/sshd/cli/server/SshFsMounter.java @@ -126,7 +126,7 @@ public final class SshFsMounter extends SshServerCliSupport { callback.onExit(0); } catch (Exception e) { log.error("run(" + username + ")[" + command + "] " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); - stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage()); + stderr.append("ERROR: ").append(e.getClass().getSimpleName()).append(": ").println(e.getMessage()); callback.onExit(-1, e.toString()); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java index d3e567c..dbf9c4a 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentitiesWatcher.java @@ -75,12 +75,12 @@ public class ClientIdentitiesWatcher extends AbstractKeyPairProvider implements } protected Iterable<KeyPair> loadKeys(SessionContext session, Predicate<? super KeyPair> filter) { - return ClientIdentityProvider.lazyKeysLoader(providers, p -> doGetKeyPair(session, p), filter); + return ClientIdentityProvider.lazyKeysLoader(providers, p -> doGetKeyPairs(session, p), filter); } - protected KeyPair doGetKeyPair(SessionContext session, ClientIdentityProvider p) { + protected Iterable<KeyPair> doGetKeyPairs(SessionContext session, ClientIdentityProvider p) { try { - KeyPair kp = p.getClientIdentity(session); + Iterable<KeyPair> kp = p.getClientIdentities(session); if (kp == null) { if (log.isDebugEnabled()) { log.debug("loadKeys({}) no key loaded", p); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/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 56f946d..eabfab8 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 @@ -45,7 +45,7 @@ import org.apache.sshd.common.util.io.resource.PathResource; public class ClientIdentityFileWatcher extends ModifiableFileWatcher implements ClientIdentityProvider, ClientIdentityLoaderHolder, FilePasswordProviderHolder { - private final AtomicReference<KeyPair> identityHolder = new AtomicReference<>(null); + private final AtomicReference<Iterable<KeyPair>> identitiesHolder = new AtomicReference<>(null); private final ClientIdentityLoaderHolder loaderHolder; private final FilePasswordProviderHolder providerHolder; private final boolean strict; @@ -89,30 +89,27 @@ public class ClientIdentityFileWatcher } @Override - public KeyPair getClientIdentity(SessionContext session) throws IOException, GeneralSecurityException { + public Iterable<KeyPair> getClientIdentities(SessionContext session) + throws IOException, GeneralSecurityException { if (!checkReloadRequired()) { - return identityHolder.get(); + return identitiesHolder.get(); } - KeyPair kp = identityHolder.getAndSet(null); // start fresh + Iterable<KeyPair> kp = identitiesHolder.getAndSet(null); // start fresh Path path = getPath(); if (!exists()) { - return identityHolder.get(); + return identitiesHolder.get(); } - KeyPair id = reloadClientIdentity(session, path); - if (!KeyUtils.compareKeyPairs(kp, id)) { - if (log.isDebugEnabled()) { - log.debug("getClientIdentity({}) identity {}", path, (kp == null) ? "loaded" : "re-loaded"); - } - } + Iterable<KeyPair> id = reloadClientIdentities(session, path); updateReloadAttributes(); - identityHolder.set(id); - return identityHolder.get(); + identitiesHolder.set(id); + return identitiesHolder.get(); } - protected KeyPair reloadClientIdentity(SessionContext session, Path path) throws IOException, GeneralSecurityException { + protected Iterable<KeyPair> reloadClientIdentities(SessionContext session, Path path) + throws IOException, GeneralSecurityException { if (isStrict()) { Map.Entry<String, Object> violation = KeyUtils.validateStrictKeyFilePermissions(path, IoUtils.EMPTY_LINK_OPTIONS); @@ -127,18 +124,22 @@ public class ClientIdentityFileWatcher PathResource location = new PathResource(path); ClientIdentityLoader idLoader = Objects.requireNonNull(getClientIdentityLoader(), "No client identity loader"); if (idLoader.isValidLocation(location)) { - KeyPair kp = idLoader.loadClientIdentity(session, location, getFilePasswordProvider()); + Iterable<KeyPair> ids = idLoader.loadClientIdentities(session, location, getFilePasswordProvider()); if (log.isTraceEnabled()) { - PublicKey key = (kp == null) ? null : kp.getPublic(); - if (key != null) { - log.trace("reloadClientIdentity({}) loaded {}-{}", - location, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); + if (ids == null) { + log.trace("reloadClientIdentity({}) no keys loaded", location); } else { - log.trace("reloadClientIdentity({}) no key loaded", location); + for (KeyPair kp : ids) { + PublicKey key = (kp == null) ? null : kp.getPublic(); + if (key != null) { + log.trace("reloadClientIdentity({}) loaded {}-{}", + location, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); + } + } } } - return kp; + return ids; } if (log.isDebugEnabled()) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/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 ceb914e..ae30f40 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 @@ -58,13 +58,13 @@ public interface ClientIdentityLoader { } @Override - public KeyPair loadClientIdentity( + public Iterable<KeyPair> loadClientIdentities( SessionContext session, NamedResource location, FilePasswordProvider provider) throws IOException, GeneralSecurityException { Path path = toPath(location); PathResource resource = new PathResource(path); try (InputStream inputStream = resource.openInputStream()) { - return SecurityUtils.loadKeyPairIdentity(session, resource, inputStream, provider); + return SecurityUtils.loadKeyPairIdentities(session, resource, inputStream, provider); } } @@ -105,7 +105,7 @@ public interface ClientIdentityLoader { * @throws GeneralSecurityException If failed to convert the contents into * a valid identity */ - KeyPair loadClientIdentity( + Iterable<KeyPair> loadClientIdentities( SessionContext session, NamedResource location, FilePasswordProvider provider) throws IOException, GeneralSecurityException; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java index 8c52d4f..9d0f989 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/ClientIdentityProvider.java @@ -40,24 +40,25 @@ public interface ClientIdentityProvider { * * @param session The {@link SessionContext} for invoking this load command - may * be {@code null} if not invoked within a session context (e.g., offline tool). - * @return The client identity - may be {@code null} if no currently + * @return The client identities - may be {@code null}/empty if no currently * available identity from this provider. <B>Note:</B> the provider * may return a <U>different</U> value every time this method is called * - e.g., if it is (re-)loading contents from a file. * @throws IOException If failed to load the identity * @throws GeneralSecurityException If failed to parse the identity */ - KeyPair getClientIdentity(SessionContext session) throws IOException, GeneralSecurityException; + Iterable<KeyPair> getClientIdentities(SessionContext session) + throws IOException, GeneralSecurityException; /** * Wraps a {@link KeyPair} into a {@link ClientIdentityProvider} that - * simply returns this value as it {@link #getClientIdentity()}. + * simply returns this value as it {@link #getClientIdentities(SessionContext)}. * * @param kp The {@link KeyPair} instance (including {@code null}) * @return The wrapping provider */ static ClientIdentityProvider of(KeyPair kp) { - return session -> kp; + return session -> Collections.singletonList(kp); } /** @@ -80,7 +81,7 @@ public interface ClientIdentityProvider { */ static Iterable<KeyPair> lazyKeysLoader( Iterable<? extends ClientIdentityProvider> providers, - Function<? super ClientIdentityProvider, ? extends KeyPair> kpExtractor, + Function<? super ClientIdentityProvider, ? extends Iterable<? extends KeyPair>> kpExtractor, Predicate<? super KeyPair> filter) { Objects.requireNonNull(kpExtractor, "No key pair extractor provided"); if (providers == null) { @@ -116,7 +117,7 @@ public interface ClientIdentityProvider { */ static Iterator<KeyPair> lazyKeysIterator( Iterator<? extends ClientIdentityProvider> providers, - Function<? super ClientIdentityProvider, ? extends KeyPair> kpExtractor, + Function<? super ClientIdentityProvider, ? extends Iterable<? extends KeyPair>> kpExtractor, Predicate<? super KeyPair> filter) { Objects.requireNonNull(kpExtractor, "No key pair extractor provided"); return (providers == null) http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientIdentityIterator.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientIdentityIterator.java b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientIdentityIterator.java index b8b04ae..a3e5a2e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientIdentityIterator.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/config/keys/LazyClientIdentityIterator.java @@ -26,6 +26,8 @@ import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; +import org.apache.sshd.common.keyprovider.KeyIdentityProvider; + /** * Wraps several {@link ClientIdentityProvider} into a {@link KeyPair} * {@link Iterator} that invokes each provider "lazily" - i.e., @@ -36,10 +38,11 @@ import java.util.function.Predicate; */ public class LazyClientIdentityIterator implements Iterator<KeyPair> { protected boolean finished; + protected Iterator<? extends KeyPair> currentIdentities; protected KeyPair currentPair; private final Iterator<? extends ClientIdentityProvider> providers; - private final Function<? super ClientIdentityProvider, ? extends KeyPair> kpExtractor; + private final Function<? super ClientIdentityProvider, ? extends Iterable<? extends KeyPair>> kpExtractor; private final Predicate<? super KeyPair> filter; /** @@ -52,7 +55,7 @@ public class LazyClientIdentityIterator implements Iterator<KeyPair> { */ public LazyClientIdentityIterator( Iterator<? extends ClientIdentityProvider> providers, - Function<? super ClientIdentityProvider, ? extends KeyPair> kpExtractor, + Function<? super ClientIdentityProvider, ? extends Iterable<? extends KeyPair>> kpExtractor, Predicate<? super KeyPair> filter) { this.providers = providers; this.kpExtractor = Objects.requireNonNull(kpExtractor, "No key pair extractor provided"); @@ -63,7 +66,7 @@ public class LazyClientIdentityIterator implements Iterator<KeyPair> { return providers; } - public Function<? super ClientIdentityProvider, ? extends KeyPair> getIdentityExtractor() { + public Function<? super ClientIdentityProvider, ? extends Iterable<? extends KeyPair>> getIdentitiesExtractor() { return kpExtractor; } @@ -83,7 +86,12 @@ public class LazyClientIdentityIterator implements Iterator<KeyPair> { return false; } - Function<? super ClientIdentityProvider, ? extends KeyPair> x = getIdentityExtractor(); + currentPair = KeyIdentityProvider.exhaustCurrentIdentities(currentIdentities); + if (currentPair != null) { + return true; + } + + Function<? super ClientIdentityProvider, ? extends Iterable<? extends KeyPair>> x = getIdentitiesExtractor(); Predicate<? super KeyPair> f = getFilter(); while (provs.hasNext()) { ClientIdentityProvider p = provs.next(); @@ -91,7 +99,9 @@ public class LazyClientIdentityIterator implements Iterator<KeyPair> { continue; } - currentPair = x.apply(p); + Iterable<? extends KeyPair> ids = x.apply(p); + currentIdentities = (ids == null) ? null : ids.iterator(); + currentPair = KeyIdentityProvider.exhaustCurrentIdentities(currentIdentities); if (currentPair == null) { continue; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/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 index 05596be..cd7a8e0 100644 --- 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 @@ -75,6 +75,7 @@ public class LazyClientKeyIdentityProvider implements KeyIdentityProvider, Clien } @Override + @SuppressWarnings("checkstyle:anoninnerlength") public Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException { Collection<? extends NamedResource> locs = getLocations(); @@ -84,6 +85,7 @@ public class LazyClientKeyIdentityProvider implements KeyIdentityProvider, Clien return () -> new Iterator<KeyPair>() { private final Iterator<? extends NamedResource> iter = locs.iterator(); + private Iterator<KeyPair> currentIdentities; private KeyPair currentPair; private boolean finished; @@ -93,15 +95,23 @@ public class LazyClientKeyIdentityProvider implements KeyIdentityProvider, Clien return false; } + currentPair = KeyIdentityProvider.exhaustCurrentIdentities(currentIdentities); + if (currentPair != null) { + return true; + } + while (iter.hasNext()) { NamedResource l = iter.next(); + Iterable<KeyPair> ids; try { - currentPair = loadClientIdentity(session, l); + ids = loadClientIdentities(session, l); } catch (IOException | GeneralSecurityException e) { throw new RuntimeException("Failed (" + e.getClass().getSimpleName() + ")" + " to load key from " + l.getName() + ": " + e.getMessage(), e); } + currentIdentities = (ids == null) ? null : ids.iterator(); + currentPair = KeyIdentityProvider.exhaustCurrentIdentities(currentIdentities); if (currentPair != null) { return true; } @@ -132,7 +142,7 @@ public class LazyClientKeyIdentityProvider implements KeyIdentityProvider, Clien }; } - protected KeyPair loadClientIdentity(SessionContext session, NamedResource location) + protected Iterable<KeyPair> loadClientIdentities(SessionContext session, NamedResource location) throws IOException, GeneralSecurityException { ClientIdentityLoader loader = getClientIdentityLoader(); boolean ignoreInvalid = isIgnoreNonExisting(); @@ -152,6 +162,6 @@ public class LazyClientKeyIdentityProvider implements KeyIdentityProvider, Clien throw e; } - return loader.loadClientIdentity(session, location, getFilePasswordProvider()); + return loader.loadClientIdentities(session, location, getFilePasswordProvider()); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java index 0212806..82f8454 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/IdentityUtils.java @@ -28,6 +28,7 @@ import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Collections; import java.util.Map; +import java.util.NavigableMap; import java.util.TreeMap; import org.apache.sshd.common.keyprovider.KeyPairProvider; @@ -133,29 +134,37 @@ public final class IdentityUtils { * to {@code FilePasswordProvider#getPassword} is the path of the * file whose key is to be loaded * @param options The {@link OpenOption}s to use when reading the key data - * @return A {@link Map} of the identities where key=identity type (case + * @return A {@link NavigableMap} of the identities where key=identity type (case * <U>insensitive</U>), value=the {@link KeyPair} of the identity * @throws IOException If failed to access the file system * @throws GeneralSecurityException If failed to load the keys * @see SecurityUtils#loadKeyPairIdentity(String, InputStream, FilePasswordProvider) */ - public static Map<String, KeyPair> loadIdentities( + public static NavigableMap<String, KeyPair> loadIdentities( SessionContext session, Map<String, ? extends Path> paths, FilePasswordProvider provider, OpenOption... options) throws IOException, GeneralSecurityException { if (GenericUtils.isEmpty(paths)) { - return Collections.emptyMap(); + return Collections.emptyNavigableMap(); } - Map<String, KeyPair> ids = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + NavigableMap<String, KeyPair> ids = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); // Cannot use forEach because the potential for IOExceptions being thrown for (Map.Entry<String, ? extends Path> pe : paths.entrySet()) { String type = pe.getKey(); Path path = pe.getValue(); - PathResource location = new PathResource(path); + PathResource location = new PathResource(path, options); + Iterable<KeyPair> pairs; try (InputStream inputStream = location.openInputStream()) { - KeyPair kp = SecurityUtils.loadKeyPairIdentity(session, location, inputStream, provider); + pairs = SecurityUtils.loadKeyPairIdentities(session, location, inputStream, provider); + } + + if (pairs == null) { + continue; + } + + for (KeyPair kp : pairs) { KeyPair prev = ids.put(type, kp); - ValidateUtils.checkTrue(prev == null, "Multiple keys for type=%s", type); + ValidateUtils.checkTrue(prev == null, "Multiple keys for type=%s due to %s", type, path); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java index fd33ec2..ff78317 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/AbstractResourceKeyPairProvider.java @@ -24,7 +24,6 @@ import java.io.InputStream; import java.io.StreamCorruptedException; import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.PublicKey; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -36,7 +35,6 @@ import java.util.TreeSet; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; @@ -55,7 +53,7 @@ public abstract class AbstractResourceKeyPairProvider<R> extends AbstractKeyPair * practice to have 2 key files that differ from one another only in their * case... */ - private final Map<String, KeyPair> cacheMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final Map<String, Iterable<KeyPair>> cacheMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); protected AbstractResourceKeyPairProvider() { super(); @@ -126,60 +124,55 @@ public abstract class AbstractResourceKeyPairProvider<R> extends AbstractKeyPair return IoResource.forResource(resource); } - protected KeyPair doLoadKey(SessionContext session, R resource) + protected Iterable<KeyPair> doLoadKeys(SessionContext session, R resource) throws IOException, GeneralSecurityException { IoResource<?> ioResource = ValidateUtils.checkNotNull(getIoResource(session, resource), "No I/O resource available for %s", resource); String resourceKey = ValidateUtils.checkNotNullAndNotEmpty(ioResource.getName(), "No resource string value for %s", resource); - KeyPair kp; + Iterable<KeyPair> ids; synchronized (cacheMap) { // check if lucky enough to have already loaded this file - kp = cacheMap.get(resourceKey); + ids = cacheMap.get(resourceKey); } - if (kp != null) { + if (ids != null) { if (log.isTraceEnabled()) { - PublicKey key = kp.getPublic(); - log.trace("doLoadKey({}) use cached key {}-{}", - resourceKey, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); + log.trace("doLoadKeys({}) using cached identifiers", resourceKey); } - return kp; + return ids; } - kp = doLoadKey(session, ioResource, resource, getPasswordFinder()); - if (kp != null) { + ids = doLoadKeys(session, ioResource, resource, getPasswordFinder()); + if (ids != null) { boolean reusedKey; synchronized (cacheMap) { // if somebody else beat us to it, use the cached key - just in case file contents changed reusedKey = cacheMap.containsKey(resourceKey); if (reusedKey) { - kp = cacheMap.get(resourceKey); + ids = cacheMap.get(resourceKey); } else { - cacheMap.put(resourceKey, kp); + cacheMap.put(resourceKey, ids); } } if (log.isDebugEnabled()) { - PublicKey key = kp.getPublic(); - log.debug("doLoadKey({}) {} {}-{}", - resourceKey, reusedKey ? "re-loaded" : "loaded", - KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); + log.debug("doLoadKeys({}) {}", resourceKey, reusedKey ? "re-loaded" : "loaded"); } } else { if (log.isDebugEnabled()) { - log.debug("doLoadKey({}) no key loaded", resourceKey); + log.debug("doLoadKeys({}) no key loaded", resourceKey); } } - return kp; + return ids; } - protected KeyPair doLoadKey( + protected Iterable<KeyPair> doLoadKeys( SessionContext session, NamedResource resourceKey, R resource, FilePasswordProvider provider) throws IOException, GeneralSecurityException { try (InputStream inputStream = openKeyPairResource(session, resourceKey, resource)) { - return doLoadKey(session, resourceKey, inputStream, provider); + return doLoadKeys(session, resourceKey, inputStream, provider); } } @@ -193,15 +186,16 @@ public abstract class AbstractResourceKeyPairProvider<R> extends AbstractKeyPair throw new StreamCorruptedException("Cannot open resource data for " + resource); } - protected KeyPair doLoadKey( + protected Iterable<KeyPair> doLoadKeys( SessionContext session, NamedResource resourceKey, InputStream inputStream, FilePasswordProvider provider) throws IOException, GeneralSecurityException { - return SecurityUtils.loadKeyPairIdentity(session, resourceKey, inputStream, provider); + return SecurityUtils.loadKeyPairIdentities(session, resourceKey, inputStream, provider); } protected class KeyPairIterator implements Iterator<KeyPair> { protected final SessionContext session; private final Iterator<? extends R> iterator; + private Iterator<KeyPair> currentIdentities; private KeyPair nextKeyPair; private boolean nextKeyPairSet; @@ -233,11 +227,19 @@ public abstract class AbstractResourceKeyPairProvider<R> extends AbstractKeyPair @SuppressWarnings("synthetic-access") private boolean setNextObject() { + nextKeyPair = KeyIdentityProvider.exhaustCurrentIdentities(currentIdentities); + if (nextKeyPair != null) { + nextKeyPairSet = true; + return true; + } + boolean debugEnabled = log.isDebugEnabled(); while (iterator.hasNext()) { R r = iterator.next(); try { - nextKeyPair = doLoadKey(session, r); + Iterable<KeyPair> ids = doLoadKeys(session, r); + currentIdentities = (ids == null) ? null : ids.iterator(); + nextKeyPair = KeyIdentityProvider.exhaustCurrentIdentities(currentIdentities); } catch (Throwable e) { log.warn("Failed (" + e.getClass().getSimpleName() + ")" + " to load key resource=" + r + ": " + e.getMessage()); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java index 613e0cf..acb1767 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileKeyPairProvider.java @@ -81,8 +81,8 @@ public class FileKeyPairProvider extends AbstractResourceKeyPairProvider<Path> { } @Override - protected KeyPair doLoadKey(SessionContext session, Path resource) + protected Iterable<KeyPair> doLoadKeys(SessionContext session, Path resource) throws IOException, GeneralSecurityException { - return super.doLoadKey(session, (resource == null) ? null : resource.toAbsolutePath()); + return super.doLoadKeys(session, (resource == null) ? null : resource.toAbsolutePath()); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/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 6ce5996..dafbbaf 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 @@ -186,4 +186,22 @@ public interface KeyIdentityProvider { static KeyIdentityProvider wrapKeyPairs(Iterable<KeyPair> pairs) { return (pairs == null) ? EMPTY_KEYS_PROVIDER : session -> pairs; } + + /** + * Attempts to find the first non-{@code null} {@link KeyPair} + * + * @param ids The {@link Iterator} - ignored if {@code null} or no next element available + * @return The first non-{@code null} key pair found in the iterator - {@code null} if + * all elements exhausted without such an entry + */ + static KeyPair exhaustCurrentIdentities(Iterator<? extends KeyPair> ids) { + while ((ids != null) && ids.hasNext()) { + KeyPair kp = ids.next(); + if (kp != null) { + return kp; + } + } + + return null; + } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java index cac845e..622a55c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -35,7 +35,6 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateFactory; -import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -477,11 +476,11 @@ public final class SecurityUtils { * @param inputStream The {@link InputStream} for the <U>private</U> key * @param provider A {@link FilePasswordProvider} - may be {@code null} * if the loaded key is <U>guaranteed</U> not to be encrypted - * @return The loaded {@link KeyPair} + * @return The loaded {@link KeyPair}-s - or {@code null} if none loaded * @throws IOException If failed to read/parse the input stream * @throws GeneralSecurityException If failed to generate the keys */ - public static KeyPair loadKeyPairIdentity( + public static Iterable<KeyPair> loadKeyPairIdentities( SessionContext session, NamedResource resourceKey, InputStream inputStream, FilePasswordProvider provider) throws IOException, GeneralSecurityException { KeyPairResourceParser parser = getKeyPairResourceParser(); @@ -495,11 +494,7 @@ public final class SecurityUtils { return null; } - if (numLoaded != 1) { - throw new InvalidKeySpecException("Multiple private key pairs N/A: " + resourceKey); - } - - return GenericUtils.head(ids); + return ids; } /* -------------------------------------------------------------------- */ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java index 1ed3a6c..bce84d6 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java @@ -30,6 +30,7 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PublicKey; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -43,6 +44,7 @@ import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider; import org.apache.sshd.common.keyprovider.KeySizeIndicator; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.io.resource.PathResource; import org.apache.sshd.common.util.security.SecurityUtils; @@ -61,7 +63,7 @@ public abstract class AbstractGeneratorHostKeyProvider public static final String DEFAULT_ALGORITHM = KeyUtils.RSA_ALGORITHM; public static final boolean DEFAULT_ALLOWED_TO_OVERWRITE = true; - private final AtomicReference<KeyPair> keyPairHolder = new AtomicReference<>(); + private final AtomicReference<Iterable<KeyPair>> keyPairHolder = new AtomicReference<>(); private Path path; private String algorithm = DEFAULT_ALGORITHM; @@ -116,29 +118,27 @@ public abstract class AbstractGeneratorHostKeyProvider } public void clearLoadedKeys() { - KeyPair kp; + Iterable<KeyPair> ids; synchronized (keyPairHolder) { - kp = keyPairHolder.getAndSet(null); + ids = keyPairHolder.getAndSet(null); } - if ((kp != null) & log.isDebugEnabled()) { - PublicKey key = kp.getPublic(); - log.debug("clearLoadedKeys({}) removed key={}-{}", - getPath(), KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); + if ((ids != null) & log.isDebugEnabled()) { + log.debug("clearLoadedKeys({}) removed keys", getPath()); } } @Override // co-variant return public synchronized List<KeyPair> loadKeys(SessionContext session) { Path keyPath = getPath(); - KeyPair kp; + Iterable<KeyPair> ids; synchronized (keyPairHolder) { - kp = keyPairHolder.get(); - if (kp == null) { + ids = keyPairHolder.get(); + if (ids == null) { try { - kp = resolveKeyPair(session, keyPath); - if (kp != null) { - keyPairHolder.set(kp); + ids = resolveKeyPairs(session, keyPath); + if (ids != null) { + keyPairHolder.set(ids); } } catch (Throwable t) { log.warn("loadKeys({}) Failed ({}) to resolve: {}", @@ -150,21 +150,32 @@ public abstract class AbstractGeneratorHostKeyProvider } } - if (kp == null) { - return Collections.emptyList(); - } else { - return Collections.singletonList(kp); + List<KeyPair> pairs = Collections.emptyList(); + if (ids instanceof List<?>) { + pairs = (List<KeyPair>) ids; + } else if (ids != null) { + pairs = new ArrayList<>(); + for (KeyPair kp : ids) { + if (kp == null) { + continue; + } + + pairs.add(kp); + } } + + return pairs; } - protected KeyPair resolveKeyPair(SessionContext session, Path keyPath) throws IOException, GeneralSecurityException { + protected Iterable<KeyPair> resolveKeyPairs(SessionContext session, Path keyPath) + throws IOException, GeneralSecurityException { String alg = getAlgorithm(); - KeyPair kp; if (keyPath != null) { try { - kp = loadFromFile(session, alg, keyPath); + Iterable<KeyPair> ids = loadFromFile(session, alg, keyPath); + KeyPair kp = GenericUtils.head(ids); if (kp != null) { - return kp; + return ids; } } catch (Throwable e) { log.warn("resolveKeyPair({}) Failed ({}) to load: {}", @@ -176,6 +187,7 @@ public abstract class AbstractGeneratorHostKeyProvider } // either no file specified or no key in file + KeyPair kp = null; try { kp = generateKeyPair(alg); if (kp == null) { @@ -185,11 +197,11 @@ public abstract class AbstractGeneratorHostKeyProvider if (log.isDebugEnabled()) { PublicKey key = kp.getPublic(); log.debug("resolveKeyPair({}) generated {} key={}-{}", - keyPath, alg, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); + keyPath, alg, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); } } catch (Throwable e) { log.warn("resolveKeyPair({})[{}] Failed ({}) to generate {} key-pair: {}", - keyPath, alg, e.getClass().getSimpleName(), alg, e.getMessage()); + keyPath, alg, e.getClass().getSimpleName(), alg, e.getMessage()); if (log.isDebugEnabled()) { log.debug("resolveKeyPair(" + keyPath + ")[" + alg + "] key-pair generation failure details", e); } @@ -209,20 +221,23 @@ public abstract class AbstractGeneratorHostKeyProvider } } - return kp; + return Collections.singletonList(kp); } - protected KeyPair loadFromFile(SessionContext session, String alg, Path keyPath) throws IOException, GeneralSecurityException { + protected Iterable<KeyPair> loadFromFile(SessionContext session, String alg, Path keyPath) + throws IOException, GeneralSecurityException { LinkOption[] options = IoUtils.getLinkOptions(true); if ((!Files.exists(keyPath, options)) || (!Files.isRegularFile(keyPath, options))) { return null; } - KeyPair kp = readKeyPair(session, keyPath, IoUtils.EMPTY_OPEN_OPTIONS); + Iterable<KeyPair> ids = readKeyPairs(session, keyPath, IoUtils.EMPTY_OPEN_OPTIONS); + KeyPair kp = GenericUtils.head(ids); if (kp == null) { return null; } + // Assume all keys are of same type PublicKey key = kp.getPublic(); String keyAlgorithm = key.getAlgorithm(); if (BuiltinIdentities.Constants.ECDSA.equalsIgnoreCase(keyAlgorithm)) { @@ -236,7 +251,7 @@ public abstract class AbstractGeneratorHostKeyProvider log.debug("resolveKeyPair({}) loaded key={}-{}", keyPath, KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key)); } - return kp; + return ids; } // Not same algorithm - start again @@ -248,17 +263,17 @@ public abstract class AbstractGeneratorHostKeyProvider return null; } - protected KeyPair readKeyPair(SessionContext session, Path keyPath, OpenOption... options) + protected Iterable<KeyPair> readKeyPairs(SessionContext session, Path keyPath, OpenOption... options) throws IOException, GeneralSecurityException { PathResource location = new PathResource(keyPath, options); try (InputStream inputStream = location.openInputStream()) { - return doReadKeyPair(session, location, inputStream); + return doReadKeyPairs(session, location, inputStream); } } - protected KeyPair doReadKeyPair(SessionContext session, NamedResource resourceKey, InputStream inputStream) + protected Iterable<KeyPair> doReadKeyPairs(SessionContext session, NamedResource resourceKey, InputStream inputStream) throws IOException, GeneralSecurityException { - return SecurityUtils.loadKeyPairIdentity(session, resourceKey, inputStream, null); + return SecurityUtils.loadKeyPairIdentities(session, resourceKey, inputStream, null); } protected void writeKeyPair(KeyPair kp, Path keyPath, OpenOption... options) http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java index c0ccd16..95c6bb0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/SimpleGeneratorHostKeyProvider.java @@ -27,6 +27,7 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.spec.InvalidKeySpecException; +import java.util.Collections; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.session.SessionContext; @@ -46,15 +47,18 @@ public class SimpleGeneratorHostKeyProvider extends AbstractGeneratorHostKeyProv } @Override - protected KeyPair doReadKeyPair(SessionContext session, NamedResource resourceKey, InputStream inputStream) + protected Iterable<KeyPair> doReadKeyPairs(SessionContext session, NamedResource resourceKey, InputStream inputStream) throws IOException, GeneralSecurityException { + KeyPair kp; try (ObjectInputStream r = new ObjectInputStream(inputStream)) { try { - return (KeyPair) r.readObject(); + kp = (KeyPair) r.readObject(); } catch (ClassNotFoundException e) { throw new InvalidKeySpecException("Missing classes: " + e.getMessage(), e); } } + + return Collections.singletonList(kp); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java index 64573c4..6e6541f 100644 --- a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java @@ -30,6 +30,7 @@ import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.EnumMap; import java.util.Map; @@ -80,12 +81,13 @@ public class BuiltinClientIdentitiesWatcherTest extends JUnitTestSupport { ClientIdentityLoader loader = new ClientIdentityLoader() { @Override - public KeyPair loadClientIdentity( + public Iterable<KeyPair> loadClientIdentities( SessionContext session, NamedResource location, FilePasswordProvider provider) throws IOException, GeneralSecurityException { BuiltinIdentities id = findIdentity(location); assertNotNull("Invalid location: " + location, id); - return idsMap.get(id); + KeyPair kp = idsMap.get(id); + return (kp == null) ? null : Collections.singletonList(kp); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java index 2cf3828..4205205 100644 --- a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.util.Collections; import java.util.Date; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -35,6 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.util.test.CommonTestSupportUtils; import org.apache.sshd.util.test.JUnitTestSupport; @@ -61,11 +63,11 @@ public class ClientIdentityFileWatcherTest extends JUnitTestSupport { KeyPair identity = CommonTestSupportUtils.getFirstKeyPair(createTestHostKeyProvider()); ClientIdentityLoader loader = new ClientIdentityLoader() { @Override - public KeyPair loadClientIdentity( + public Iterable<KeyPair> loadClientIdentities( SessionContext session, NamedResource location, FilePasswordProvider provider) throws IOException, GeneralSecurityException { assertTrue("Invalid location: " + location, isValidLocation(location)); - return identity; + return Collections.singletonList(identity); } @Override @@ -82,10 +84,11 @@ public class ClientIdentityFileWatcherTest extends JUnitTestSupport { AtomicInteger reloadCount = new AtomicInteger(0); ClientIdentityProvider idProvider = new ClientIdentityFileWatcher(idFile, loader, FilePasswordProvider.EMPTY, false) { @Override - protected KeyPair reloadClientIdentity(SessionContext session, Path path) throws IOException, GeneralSecurityException { + protected Iterable<KeyPair> reloadClientIdentities(SessionContext session, Path path) + throws IOException, GeneralSecurityException { assertEquals("Mismatched client identity path", idFile, path); reloadCount.incrementAndGet(); - return super.reloadClientIdentity(session, path); + return super.reloadClientIdentities(session, path); } }; Files.deleteIfExists(idFile); @@ -118,7 +121,8 @@ public class ClientIdentityFileWatcherTest extends JUnitTestSupport { private static void testIdentityReload( String phase, Number reloadCount, ClientIdentityProvider provider, KeyPair expectedIdentity, int expectedCount) throws Exception { - KeyPair actualIdentity = provider.getClientIdentity(null); + Iterable<KeyPair> ids = provider.getClientIdentities(null); + KeyPair actualIdentity = GenericUtils.head(ids); assertSame(phase + ": mismatched identity", expectedIdentity, actualIdentity); assertEquals(phase + ": mismatched re-load count", expectedCount, reloadCount.intValue()); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java index 445de2e..afa3738 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParserTest.java @@ -33,6 +33,7 @@ import org.apache.commons.ssl.PEMItem; import org.apache.commons.ssl.PEMUtil; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; import org.apache.sshd.util.test.JUnitTestSupport; @@ -93,10 +94,12 @@ public class PKCS8PEMResourceKeyPairParserTest extends JUnitTestSupport { os.close(); try (ByteArrayInputStream bais = new ByteArrayInputStream(os.toByteArray())) { - KeyPair kp2 = SecurityUtils.loadKeyPairIdentity(null, NamedResource.ofName(getCurrentTestName()), bais, null); - - assertEquals("Mismatched public key", kp.getPublic(), kp2.getPublic()); - assertEquals("Mismatched private key", prv1, kp2.getPrivate()); + Iterable<KeyPair> ids = SecurityUtils.loadKeyPairIdentities( + null, NamedResource.ofName(getCurrentTestName()), bais, null); + KeyPair kp2 = GenericUtils.head(ids); + assertNotNull("No identity loaded", kp2); + assertKeyEquals("Mismatched public key", kp.getPublic(), kp2.getPublic()); + assertKeyEquals("Mismatched private key", prv1, kp2.getPrivate()); } } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java index 7cd690c..29a77c7 100644 --- a/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProviderTest.java @@ -69,7 +69,7 @@ public class AbstractGeneratorHostKeyProviderTest extends JUnitTestSupport { } @Override - protected KeyPair doReadKeyPair( + protected Iterable<KeyPair> doReadKeyPairs( SessionContext session, NamedResource resourceKey, InputStream inputStream) throws IOException, GeneralSecurityException { return null; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java b/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java index 32f2712..8e072c1 100644 --- a/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java +++ b/sshd-common/src/test/java/rg/apache/sshd/client/config/keys/LazyClientIdentityIteratorTest.java @@ -23,6 +23,7 @@ import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -57,7 +58,7 @@ public class LazyClientIdentityIteratorTest extends JUnitTestSupport { Iterable<KeyPair> ids = ClientIdentityProvider.lazyKeysLoader( providers, p -> { try { - return p.getClientIdentity(null); + return p.getClientIdentities(null); } catch (Exception e) { throw new RuntimeException("Unexpected " + e.getClass().getSimpleName() + ": " + e.getMessage(), e); } @@ -94,9 +95,9 @@ public class LazyClientIdentityIteratorTest extends JUnitTestSupport { } @Override - public KeyPair getClientIdentity(SessionContext session) { + public Iterable<KeyPair> getClientIdentities(SessionContext session) { loadCount++; - return getKeyPair(); + return Collections.singletonList(getKeyPair()); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/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 8ede710..2fd8584 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 @@ -156,11 +156,11 @@ public class HostConfigEntryResolverTest extends BaseTestSupport { } @Override - public KeyPair loadClientIdentity( + public Iterable<KeyPair> loadClientIdentities( SessionContext session, NamedResource location, FilePasswordProvider provider) throws IOException, GeneralSecurityException { if (isValidLocation(location)) { - return identity; + return Collections.singletonList(identity); } throw new FileNotFoundException("Unknown location: " + location); @@ -223,12 +223,12 @@ public class HostConfigEntryResolverTest extends BaseTestSupport { } @Override - public KeyPair loadClientIdentity( + public Iterable<KeyPair> loadClientIdentities( SessionContext session, NamedResource location, FilePasswordProvider provider) throws IOException, GeneralSecurityException { if (isValidLocation(location)) { specificIdentityLoadCount.incrementAndGet(); - return specificIdentity; + return Collections.singletonList(specificIdentity); } throw new FileNotFoundException("Unknown location: " + location); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3efd1edf/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java index 34c418b..a2f3374 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java @@ -924,11 +924,12 @@ public class AuthenticationTest extends BaseTestSupport { URL location = getClass().getResource(keyLocation); assertNotNull("Missing key file " + keyLocation, location); - KeyPair kp; URLResource resourceKey = new URLResource(location); + Iterable<KeyPair> ids; try (InputStream keyData = resourceKey.openInputStream()) { - kp = SecurityUtils.loadKeyPairIdentity(session, resourceKey, keyData, passwordProvider); + ids = SecurityUtils.loadKeyPairIdentities(session, resourceKey, keyData, passwordProvider); } + KeyPair kp = GenericUtils.head(ids); assertNotNull("No identity loaded from " + resourceKey, kp); return Collections.singletonList(kp); }
