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/22 05:05:26 UTC

[3/7] mina-sshd git commit: [SSHD-757] Added initial code to allow using OpenPGP public keys for public key authentication via 'authorized_keys' file(s)

[SSHD-757] Added initial code to allow using OpenPGP public keys for public key authentication via 'authorized_keys' file(s)


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/193c9326
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/193c9326
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/193c9326

Branch: refs/heads/master
Commit: 193c93269bc3775eee5e33f9040f1e433a3f148e
Parents: 3efd1ed
Author: Lyor Goldstein <lg...@apache.org>
Authored: Tue Nov 20 16:18:16 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Thu Nov 22 07:05:16 2018 +0200

----------------------------------------------------------------------
 CHANGES.md                                      |   4 +
 README.md                                       |  26 +-
 .../common/config/keys/AuthorizedKeyEntry.java  |  27 +-
 .../sshd/common/config/keys/PublicKeyEntry.java |  53 +++-
 .../config/keys/PublicKeyEntryResolver.java     |   9 +-
 .../FileWatcherKeyPairResourceLoader.java       | 109 ++++++++
 .../common/keyprovider/KeyTypeIndicator.java    |  23 ++
 .../common/util/io/ModifiableFileWatcher.java   |  10 +
 .../auth/pubkey/PublickeyAuthenticator.java     |   7 +-
 .../keys/AuthorizedKeysAuthenticator.java       |   9 +-
 .../config/keys/AuthorizedKeyEntryTest.java     |   4 +-
 .../keys/AuthorizedKeysAuthenticatorTest.java   |   2 +-
 .../DefaultAuthorizedKeysAuthenticatorTest.java |   4 +-
 .../openpgp/PGPAuthorizedEntriesTracker.java    | 270 +++++++++++++++++++
 .../keys/loader/openpgp/PGPKeyFileWatcher.java  |  51 ++++
 .../keys/loader/openpgp/PGPKeyLoader.java       |  79 ++++++
 .../openpgp/PGPKeyPairResourceParser.java       | 240 +----------------
 .../loader/openpgp/PGPPrivateKeyExtractor.java  | 152 +++++++++++
 .../openpgp/PGPPublicKeyEntryDataResolver.java  |  20 +-
 .../openpgp/PGPPublicKeyEntryDecoder.java       |  87 ------
 .../loader/openpgp/PGPPublicKeyExtractor.java   | 172 ++++++++++++
 .../loader/openpgp/PGPPublicKeyFileWatcher.java |  91 +++++++
 .../config/keys/loader/openpgp/PGPUtils.java    |  39 +++
 .../openpgp/PGPUtilsKeyFingerprintTest.java     |  48 ++++
 24 files changed, 1170 insertions(+), 366 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index 0c59a22..caffb47 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -71,6 +71,10 @@ accept also an `AttributeRepository` connection context argument (propagated fro
 * Converted most of the key-pair identity loaders (e.g., `ClientIdentityLoader`, `ClientIdentityProvider`, etc.)
 to return an `Iterable<KeyPair>` instead of single `KeyPair` instance.
 
+* Code that converts authorized keys entries into `PublicKey`-s has been renamed to `resolvePublicKeyEntries`
+and moved to `PublicKeyEntry` class.
+    * Note that the parameters **order** has also been modified
+
 ## 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/193c9326/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 6c8a1cb..529308c 100644
--- a/README.md
+++ b/README.md
@@ -1944,7 +1944,31 @@ specified in the [OpenSSH PGP configuration](https://www.red-bean.com/~nemo/open
     pgp-sign-rsa 87C36E60187451050A4F26B134824FC95C781A18
 ```
 
-Where the key data following the key type specification is the fingerprint value of the referenced key.
+Where the key data following the key type specification is the fingerprint value of the referenced key. In order to
+use a "mixed mode" file (i.e., one that has both SSH and _OpenPGP_ keys) one needs to replace the default `AuthorizedKeysAuthenticator`
+instance with one that is derived from it and overrides the `createDelegateAuthenticator` method in a manner similar
+as shown below:
+
+```java
+public class MyAuthorizedKeysAuthenticatorWithBothPGPAndSsh extends AuthorizedKeysAuthenticator {
+    ... constructor(s) ...
+
+    @Override
+    protected PublickeyAuthenticator createDelegateAuthenticator(
+            String username, ServerSession session, Path path,
+            Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
+                throws IOException, GeneralSecurityException {
+        PGPAuthorizedEntriesTracker tracker = ... obtain an instance ...
+        // Note: need to catch the PGPException and transform it into either an IOException or a GeneralSecurityException
+        Collection<PublicKey> keys = tracker.resolveAuthorizedEntries(session, entries, fallbackResolver);
+        if (GenericUtils.isEmpty(keys)) {
+            return RejectAllPublickeyAuthenticator.INSTANCE;
+        } else {
+            return new KeySetPublickeyAuthenticator(id, keys);
+        }
+    }
+}
+```
 
 ## Useful extra components in _sshd-contrib_
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
index 2e60968..3492f33 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java
@@ -34,7 +34,6 @@ import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -104,7 +103,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
                 sb.append(key);
                 // TODO figure out a way to remember which options where quoted
                 // TODO figure out a way to remember which options had no value
-                if (!Boolean.TRUE.toString().equals(value)) {
+                if (!"true".equals(value)) {
                     sb.append('=').append(value);
                 }
                 index++;
@@ -144,24 +143,6 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
                 + (GenericUtils.isEmpty(kc) ? "" : " " + kc);
     }
 
-    public static List<PublicKey> resolveAuthorizedKeys(
-            PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries)
-                throws IOException, GeneralSecurityException {
-        if (GenericUtils.isEmpty(entries)) {
-            return Collections.emptyList();
-        }
-
-        List<PublicKey> keys = new ArrayList<>(entries.size());
-        for (AuthorizedKeyEntry e : entries) {
-            PublicKey k = e.resolvePublicKey(fallbackResolver);
-            if (k != null) {
-                keys.add(k);
-            }
-        }
-
-        return keys;
-    }
-
     /**
      * Reads read the contents of an {@code authorized_keys} file
      *
@@ -296,7 +277,11 @@ public class AuthorizedKeyEntry extends PublicKeyEntry {
         }
 
         String keyType = line.substring(0, startPos);
-        PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
+        Object decoder = PublicKeyEntry.getKeyDataEntryResolver(keyType);
+        if (decoder == null) {
+            decoder = KeyUtils.getPublicKeyEntryDecoder(keyType);
+        }
+
         AuthorizedKeyEntry entry;
         // assume this is due to the fact that it starts with login options
         if (decoder == null) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
index 49a5e41..ee64e5b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
@@ -27,8 +27,11 @@ import java.nio.file.Path;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.NavigableMap;
 import java.util.Objects;
 import java.util.TreeMap;
@@ -194,6 +197,36 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator {
     }
 
     /**
+     * @param entries The entries to convert - ignored if {@code null}/empty
+     * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if
+     * none of the built-in ones can be used. If {@code null} and no built-in
+     * resolver can be used then an {@link InvalidKeySpecException} is thrown.
+     * @return The {@link List} of all {@link PublicKey}-s that have been resolved
+     * @throws IOException If failed to decode the key data
+     * @throws GeneralSecurityException If failed to generate the {@link PublicKey}
+     * from the decoded data
+     * @see #resolvePublicKey(PublicKeyEntryResolver)
+     */
+    public static List<PublicKey> resolvePublicKeyEntries(
+            Collection<? extends PublicKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
+                throws IOException, GeneralSecurityException {
+        int numEntries = GenericUtils.size(entries);
+        if (numEntries <= 0) {
+            return Collections.emptyList();
+        }
+
+        List<PublicKey> keys = new ArrayList<>(numEntries);
+        for (PublicKeyEntry e : entries) {
+            PublicKey k = e.resolvePublicKey(fallbackResolver);
+            if (k != null) {
+                keys.add(k);
+            }
+        }
+
+        return keys;
+    }
+
+    /**
      * Registers a specialized decoder for the public key entry data bytes instead of the
      * {@link PublicKeyEntryDataResolver#DEFAULT default} one.
      *
@@ -216,6 +249,20 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator {
      * - e.g., &quot;ssh-rsa&quot;, &quot;pgp-sign-dss&quot;, etc.
      * @return The registered resolver instance - {@code null} if none was registered
      */
+    public static PublicKeyEntryDataResolver getKeyDataEntryResolver(String keyType) {
+        keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+
+        synchronized (KEY_DATA_RESOLVERS) {
+            return KEY_DATA_RESOLVERS.get(keyType);
+        }
+    }
+
+    /**
+     * @param keyType The key-type value (case <U>insensitive</U>) that may have been
+     * previously {@link #registerKeyDataDecoder(String, PublicKeyEntryDataResolver) registered}
+     * - e.g., &quot;ssh-rsa&quot;, &quot;pgp-sign-dss&quot;, etc.
+     * @return The un-registered resolver instance - {@code null} if none was registered
+     */
     public static PublicKeyEntryDataResolver unregisterKeyDataEntryResolver(String keyType) {
         keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
 
@@ -234,11 +281,7 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator {
     public static PublicKeyEntryDataResolver resolveKeyDataEntryResolver(String keyType) {
         keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
 
-        PublicKeyEntryDataResolver resolver;
-        synchronized (KEY_DATA_RESOLVERS) {
-            resolver = KEY_DATA_RESOLVERS.get(keyType);
-        }
-
+        PublicKeyEntryDataResolver resolver = getKeyDataEntryResolver(keyType);
         if (resolver != null) {
             return resolver; // debug breakpoint
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
index e5eb0ed..3d3cc45 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
@@ -34,7 +34,8 @@ public interface PublicKeyEntryResolver {
      */
     PublicKeyEntryResolver IGNORING = new PublicKeyEntryResolver() {
         @Override
-        public PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+        public PublicKey resolve(String keyType, byte[] keyData)
+                throws IOException, GeneralSecurityException {
             return null;
         }
 
@@ -49,7 +50,8 @@ public interface PublicKeyEntryResolver {
      */
     PublicKeyEntryResolver FAILING = new PublicKeyEntryResolver() {
         @Override
-        public PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+        public PublicKey resolve(String keyType, byte[] keyData)
+                throws IOException, GeneralSecurityException {
             throw new InvalidKeySpecException("Failing resolver on key type=" + keyType);
         }
 
@@ -66,5 +68,6 @@ public interface PublicKeyEntryResolver {
      * @throws IOException If failed to parse the key data
      * @throws GeneralSecurityException If failed to generate the key
      */
-    PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException;
+    PublicKey resolve(String keyType, byte[] keyData)
+        throws IOException, GeneralSecurityException;
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java
new file mode 100644
index 0000000..b476d13
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java
@@ -0,0 +1,109 @@
+/*
+ * 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.loader;
+
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+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.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+import org.apache.sshd.common.util.io.resource.PathResource;
+
+/**
+ * Tracks a file containing {@link KeyPair}-s an re-loads it whenever a change
+ * has been sensed in the monitored file (if it exists)
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FileWatcherKeyPairResourceLoader extends ModifiableFileWatcher implements KeyPairResourceLoader {
+    protected final AtomicReference<Collection<KeyPair>> keysHolder = new AtomicReference<>(Collections.emptyList());
+    private KeyPairResourceLoader delegateLoader;
+
+    public FileWatcherKeyPairResourceLoader(Path file, KeyPairResourceLoader delegateLoader) {
+        this(file, delegateLoader, IoUtils.getLinkOptions(true));
+    }
+
+    public FileWatcherKeyPairResourceLoader(
+            Path file, KeyPairResourceLoader delegateLoader, LinkOption... options) {
+        super(file, options);
+        this.delegateLoader = Objects.requireNonNull(delegateLoader, "No delegate loader provided");
+    }
+
+    public KeyPairResourceLoader getKeyPairResourceLoader() {
+        return delegateLoader;
+    }
+
+    public void setKeyPairResourceLoader(KeyPairResourceLoader loader) {
+        this.delegateLoader = Objects.requireNonNull(loader, "No delegate loader provided");
+    }
+
+    @Override
+    public Collection<KeyPair> loadKeyPairs(
+            SessionContext session, NamedResource resourceKey,
+            FilePasswordProvider passwordProvider, List<String> lines)
+                throws IOException, GeneralSecurityException {
+
+        Collection<KeyPair> ids = keysHolder.get();
+        if (GenericUtils.isEmpty(ids) || checkReloadRequired()) {
+            keysHolder.set(Collections.emptyList());    // mark stale
+
+            if (!exists()) {
+                return keysHolder.get();
+            }
+
+            Path path = getPath();
+            ids = reloadKeyPairs(session, new PathResource(path), passwordProvider, lines);
+            int numKeys = GenericUtils.size(ids);
+            if (log.isDebugEnabled()) {
+                log.debug("loadKeyPairs({})[{}] reloaded {} keys from {}",
+                        session, resourceKey, numKeys, path);
+            }
+
+            if (numKeys > 0) {
+                keysHolder.set(ids);
+                updateReloadAttributes();
+            }
+        }
+
+        return ids;
+    }
+
+    protected Collection<KeyPair> reloadKeyPairs(
+            SessionContext session, NamedResource resourceKey,
+            FilePasswordProvider passwordProvider, List<String> lines)
+                throws IOException, GeneralSecurityException {
+        KeyPairResourceLoader loader =
+            ValidateUtils.checkNotNull(getKeyPairResourceLoader(), "No resource loader for %s", resourceKey.getName());
+        return loader.loadKeyPairs(session, resourceKey, passwordProvider, lines);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
index 1830447..3a6b16b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java
@@ -19,6 +19,15 @@
 
 package org.apache.sshd.common.keyprovider;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.util.GenericUtils;
+
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
@@ -28,4 +37,18 @@ public interface KeyTypeIndicator {
      * @return The <U>SSH</U> key type name - e.g., &quot;ssh-rsa&quot;, &quot;sshd-dss&quot; etc.
      */
     String getKeyType();
+
+    /**
+     * @param <I> The {@link KeyTypeIndicator}
+     * @param indicators The indicators to group
+     * @return A {@link NavigableMap} where key=the case <U>insensitive</U> {@link #getKeyType() key type},
+     * value = the {@link List} of all indicators having the same key type
+     */
+    static <I extends KeyTypeIndicator> NavigableMap<String, List<I>> groupByKeyType(Collection<? extends I> indicators) {
+        return GenericUtils.isEmpty(indicators)
+             ? Collections.emptyNavigableMap()
+             : indicators.stream()
+                 .collect(Collectors.groupingBy(
+                     KeyTypeIndicator::getKeyType, () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Collectors.toList()));
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
index 0d8009d..72f6674 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
@@ -22,6 +22,7 @@ package org.apache.sshd.common.util.io;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
@@ -38,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.resource.PathResource;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**
@@ -180,6 +182,14 @@ public class ModifiableFileWatcher extends AbstractLoggingBean {
         resetReloadAttributes();
     }
 
+    public PathResource toPathResource() {
+        return toPathResource(IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+
+    public PathResource toPathResource(OpenOption... options) {
+        return new PathResource(getPath(), options);
+    }
+
     @Override
     public String toString() {
         return Objects.toString(getPath());

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
index b6a8ab6..592e215 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
@@ -24,6 +24,7 @@ import java.security.PublicKey;
 import java.util.Collection;
 
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.server.auth.AsyncAuthException;
@@ -51,17 +52,17 @@ public interface PublickeyAuthenticator {
 
     /**
      * @param id Some kind of mnemonic identifier for the authenticator - used also in {@link #toString()}
-     * @param fallbackResolver The public key resolver to use if none of the default registered ones works
      * @param entries The entries to parse - ignored if {@code null}/empty
+     * @param fallbackResolver The public key resolver to use if none of the default registered ones works
      * @return A wrapper with all the parsed keys
      * @throws IOException If failed to parse the keys data
      * @throws GeneralSecurityException If failed to generate the relevant keys from the parsed data
      */
     static PublickeyAuthenticator fromAuthorizedEntries(
-            Object id, PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries)
+            Object id, Collection<? extends AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
                 throws IOException, GeneralSecurityException {
         Collection<PublicKey> keys =
-            AuthorizedKeyEntry.resolveAuthorizedKeys(fallbackResolver, entries);
+            PublicKeyEntry.resolvePublicKeyEntries(entries, fallbackResolver);
         if (GenericUtils.isEmpty(keys)) {
             return RejectAllPublickeyAuthenticator.INSTANCE;
         } else {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
index 3e9ca3a..d2e918d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
@@ -70,7 +70,7 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement
         new AtomicReference<>(RejectAllPublickeyAuthenticator.INSTANCE);
 
     public AuthorizedKeysAuthenticator(Path file) {
-        this(file, IoUtils.EMPTY_LINK_OPTIONS);
+        this(file, IoUtils.getLinkOptions(false));
     }
 
     public AuthorizedKeysAuthenticator(Path file, LinkOption... options) {
@@ -128,7 +128,7 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement
                 Collection<AuthorizedKeyEntry> entries = reloadAuthorizedKeys(path, username, session);
                 if (GenericUtils.size(entries) > 0) {
                     PublickeyAuthenticator authDelegate =
-                        createDelegateAuthenticator(path, entries, getFallbackPublicKeyEntryResolver());
+                        createDelegateAuthenticator(username, session, path, entries, getFallbackPublicKeyEntryResolver());
                     delegateHolder.set(authDelegate);
                 }
             } else {
@@ -140,9 +140,10 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement
     }
 
     protected PublickeyAuthenticator createDelegateAuthenticator(
-            Path path, Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
+            String username, ServerSession session, Path path,
+            Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
                 throws IOException, GeneralSecurityException {
-        return PublickeyAuthenticator.fromAuthorizedEntries(path, fallbackResolver, entries);
+        return PublickeyAuthenticator.fromAuthorizedEntries(path, entries, fallbackResolver);
     }
 
     protected PublicKeyEntryResolver getFallbackPublicKeyEntryResolver() {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
index 99f5e76..d626572 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java
@@ -127,10 +127,10 @@ public class AuthorizedKeyEntryTest extends AuthorizedKeysTestSupport {
     private PublickeyAuthenticator testAuthorizedKeysAuth(Collection<AuthorizedKeyEntry> entries)
             throws IOException, GeneralSecurityException {
         Collection<PublicKey> keySet =
-            AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries);
+            PublicKeyEntry.resolvePublicKeyEntries(entries, PublicKeyEntryResolver.FAILING);
         PublickeyAuthenticator auth =
             PublickeyAuthenticator.fromAuthorizedEntries(
-                getCurrentTestName(), PublicKeyEntryResolver.FAILING, entries);
+                getCurrentTestName(), entries, PublicKeyEntryResolver.FAILING);
         for (PublicKey key : keySet) {
             assertTrue("Failed to authenticate with key=" + key.getAlgorithm(), auth.authenticate(getCurrentTestName(), key, null));
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
index fc5facc..cdd2122 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
@@ -89,7 +89,7 @@ public class AuthorizedKeysAuthenticatorTest extends AuthorizedKeysTestSupport {
             List<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(file);
             assertEquals("Mismatched number of loaded entries", keyLines.size(), entries.size());
 
-            List<PublicKey> keySet = AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries);
+            List<PublicKey> keySet = PublicKeyEntry.resolvePublicKeyEntries(entries, PublicKeyEntryResolver.FAILING);
             assertEquals("Mismatched number of loaded keys", entries.size(), keySet.size());
 
             reloadCount.set(0);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
index 5d6b22a..d93abe7 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
@@ -25,6 +25,7 @@ import java.util.Collection;
 
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.AuthorizedKeysTestSupport;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
@@ -47,7 +48,8 @@ public class DefaultAuthorizedKeysAuthenticatorTest extends AuthorizedKeysTestSu
         writeDefaultSupportedKeys(file);
 
         Collection<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(file);
-        Collection<PublicKey> keySet = AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries);
+        Collection<PublicKey> keySet =
+            PublicKeyEntry.resolvePublicKeyEntries(entries, PublicKeyEntryResolver.FAILING);
         PublickeyAuthenticator auth = new DefaultAuthorizedKeysAuthenticator(file, false);
         String thisUser = OsUtils.getCurrentUser();
         for (String username : new String[]{null, "", thisUser, getClass().getName() + "#" + getCurrentTestName()}) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java
new file mode 100644
index 0000000..e8d9715
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java
@@ -0,0 +1,270 @@
+/*
+ * 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.loader.openpgp;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.KeySpec;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.FilePasswordProviderManager;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.resource.PathResource;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.bouncycastle.openpgp.PGPException;
+import org.c02e.jpgpj.Key;
+import org.c02e.jpgpj.Subkey;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PGPAuthorizedEntriesTracker
+        extends AbstractLoggingBean
+        implements PGPPublicKeyExtractor, FilePasswordProviderManager, PublicKeyEntryResolver {
+    private FilePasswordProvider filePasswordProvider;
+    private final List<PGPPublicKeyFileWatcher> keyFiles;
+
+    public PGPAuthorizedEntriesTracker() {
+        this(Collections.emptyList());
+    }
+
+    public PGPAuthorizedEntriesTracker(Path path) {
+        this(path, null);
+    }
+
+    public PGPAuthorizedEntriesTracker(Path path, FilePasswordProvider passwordProvider) {
+        this(Collections.singletonList(Objects.requireNonNull(path, "No path provided")), passwordProvider);
+    }
+
+    public PGPAuthorizedEntriesTracker(Collection<? extends Path> keys) {
+        this(keys, null);
+    }
+
+    public PGPAuthorizedEntriesTracker(Collection<? extends Path> keys, FilePasswordProvider passwordProvider) {
+        this.keyFiles = GenericUtils.isEmpty(keys)
+            ? new ArrayList<>()
+            : keys.stream()
+                .map(k -> new PGPPublicKeyFileWatcher(k))
+                .collect(Collectors.toCollection(() -> new ArrayList<>(keys.size())));
+    }
+
+    @Override
+    public FilePasswordProvider getFilePasswordProvider() {
+        return filePasswordProvider;
+    }
+
+    @Override
+    public void setFilePasswordProvider(FilePasswordProvider filePasswordProvider) {
+        this.filePasswordProvider = filePasswordProvider;
+    }
+
+    public List<PGPPublicKeyFileWatcher> getWatchedFiles() {
+        return keyFiles;
+    }
+
+    @Override
+    public PublicKey resolve(String keyType, byte[] keyData)
+            throws IOException, GeneralSecurityException {
+        if (!PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType)) {
+            return null;
+        }
+
+        String fingerprint = PGPPublicKeyEntryDataResolver.encodeKeyFingerprint(keyData);
+        if (GenericUtils.isEmpty(fingerprint)) {
+            return null;
+        }
+
+        Collection<PublicKey> keys;
+        try {
+            keys = loadMatchingKeyFingerprints(null, Collections.singletonList(fingerprint));
+        } catch (PGPException e) {
+            throw new InvalidKeyException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to load key type=" + keyType + " with fingerprint=" + fingerprint
+                    + ": " + e.getMessage(), e);
+        }
+
+        int numKeys = GenericUtils.size(keys);
+        if (numKeys > 1) {
+            throw new StreamCorruptedException("Multiple matches (" + numKeys + ")"
+                + " for " + keyType + " fingerprint=" + fingerprint);
+        }
+
+        return GenericUtils.head(keys);
+    }
+
+    public void addWatchedFile(Path p) {
+        Objects.requireNonNull(p, "No file provided");
+        List<PGPPublicKeyFileWatcher> files = getWatchedFiles();
+        files.add(new PGPPublicKeyFileWatcher(p));
+    }
+
+    public List<PublicKey> resolveAuthorizedEntries(
+            SessionContext session, Collection<? extends PublicKeyEntry> entries, PublicKeyEntryResolver fallbackResolver)
+                throws IOException, GeneralSecurityException, PGPException {
+        Map<String, ? extends Collection<PublicKeyEntry>> typesMap = KeyTypeIndicator.groupByKeyType(entries);
+        if (GenericUtils.isEmpty(typesMap)) {
+            return Collections.emptyList();
+        }
+
+        List<PublicKey> keys = new ArrayList<>(entries.size());
+        for (Map.Entry<String, ? extends Collection<PublicKeyEntry>> te : typesMap.entrySet()) {
+            String keyType = te.getKey();
+            Collection<PublicKeyEntry> keyEntries = te.getValue();
+            Collection<PublicKey> subKeys = PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType)
+                ? loadMatchingAuthorizedEntries(session, keyEntries)
+                : PublicKeyEntry.resolvePublicKeyEntries(keyEntries, fallbackResolver);
+            if (GenericUtils.isEmpty(subKeys)) {
+                continue;
+            }
+
+            keys.addAll(subKeys);
+        }
+
+        return keys;
+    }
+
+    public List<PublicKey> loadMatchingAuthorizedEntries(
+            SessionContext session, Collection<? extends PublicKeyEntry> entries)
+                throws IOException, GeneralSecurityException, PGPException {
+        int numEntries = GenericUtils.size(entries);
+        if (numEntries <= 0) {
+            return Collections.emptyList();
+        }
+
+        Collection<String> fingerprints = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        for (PublicKeyEntry pke : entries) {
+            String keyType = pke.getKeyType();
+            if (GenericUtils.isEmpty(keyType)
+                    || (!PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType))) {
+                continue;
+            }
+
+            String fp = PGPPublicKeyEntryDataResolver.DEFAULT.encodeEntryKeyData(pke.getKeyData());
+            if (GenericUtils.isEmpty(fp)) {
+                continue;
+            }
+
+            if (!fingerprints.add(fp)) {
+                //noinspection UnnecessaryContinue
+                continue;   // debug breakpoint
+            }
+        }
+
+        return loadMatchingKeyFingerprints(session, fingerprints);
+    }
+
+    public List<PublicKey> loadMatchingKeyFingerprints(
+            SessionContext session, Collection<String> fingerprints)
+                throws IOException, GeneralSecurityException, PGPException {
+        int numEntries = GenericUtils.size(fingerprints);
+        if (numEntries <= 0) {
+            return Collections.emptyList();
+        }
+
+        Collection<PGPPublicKeyFileWatcher> files = getWatchedFiles();
+        int numFiles = GenericUtils.size(files);
+        if (numFiles <= 0) {
+            return Collections.emptyList();
+        }
+
+        List<PublicKey> keys = new ArrayList<>(Math.min(numEntries, numFiles));
+        FilePasswordProvider provider = getFilePasswordProvider();
+        boolean debugEnabled = log.isDebugEnabled();
+        for (PGPPublicKeyFileWatcher f : files) {
+            PathResource resourceKey = f.toPathResource();
+            Key container = f.loadPublicKey(session, resourceKey, provider);
+            Map<String, Subkey> fpMap = PGPUtils.mapSubKeysByFingerprint(container);
+            int numSubKeys = GenericUtils.size(fpMap);
+            Collection<Subkey> matches = (numSubKeys <= 0)
+                ? Collections.emptyList()
+                : fpMap.entrySet()
+                    .stream()
+                    .filter(e -> fingerprints.contains(e.getKey()))
+                    .map(Map.Entry::getValue)
+                    .collect(Collectors.toCollection(() -> new ArrayList<>(numSubKeys)));
+            int numMatches = GenericUtils.size(matches);
+            if (debugEnabled) {
+                log.debug("loadMatchingKeyFingerprints({}) found {}/{} matches in {}",
+                    session, numMatches, numEntries, resourceKey);
+            }
+            if (numMatches <= 0) {
+                continue;   // debug breakpoint
+            }
+
+            for (Subkey sk : matches) {
+                PublicKey pk;
+                try {
+                    pk = extractPublicKey(resourceKey, sk);
+                    if (pk == null) {
+                        continue;   // debug breakpoint
+                    }
+                } catch (IOException | GeneralSecurityException | RuntimeException e) {
+                    log.error("loadMatchingKeyFingerprints({}) failed ({}) to convert {} from {} to public key: {}",
+                        session, e.getClass().getSimpleName(), sk, resourceKey, e.getMessage());
+                    if (debugEnabled) {
+                        log.debug("loadMatchingKeyFingerprints(" + session + ")[" + resourceKey + "][" + sk + "] conversion failure details", e);
+                    }
+                    throw e;
+                }
+
+                if (debugEnabled) {
+                    log.debug("loadMatchingKeyFingerprints({}) loaded key={}, fingerprint={}, hash={} from {}",
+                        session, KeyUtils.getKeyType(pk), sk.getFingerprint(), KeyUtils.getFingerPrint(pk), resourceKey);
+                }
+                keys.add(pk);
+            }
+        }
+
+        return keys;
+    }
+
+    @Override
+    public <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec)
+            throws GeneralSecurityException {
+        KeyFactory factory = getKeyFactory(algorithm);
+        PublicKey pubKey = factory.generatePublic(keySpec);
+        return keyType.cast(pubKey);
+    }
+
+    protected KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException {
+        return SecurityUtils.getKeyFactory(algorithm);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java
new file mode 100644
index 0000000..d2567fb
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java
@@ -0,0 +1,51 @@
+/*
+ * 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.loader.openpgp;
+
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+
+import org.apache.sshd.common.config.keys.loader.FileWatcherKeyPairResourceLoader;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * Tracks the contents of a PGP key file - uses the default {@link PGPKeyPairResourceParser#INSTANCE instance}
+ * unless otherwise specified.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PGPKeyFileWatcher extends FileWatcherKeyPairResourceLoader {
+    public PGPKeyFileWatcher(Path file) {
+        this(file, IoUtils.getLinkOptions(true));
+    }
+
+    public PGPKeyFileWatcher(Path file, LinkOption... options) {
+        this(file, PGPKeyPairResourceParser.INSTANCE, options);
+    }
+
+    public PGPKeyFileWatcher(Path file, KeyPairResourceLoader delegateLoader) {
+        this(file, delegateLoader, IoUtils.getLinkOptions(true));
+    }
+
+    public PGPKeyFileWatcher(Path file, KeyPairResourceLoader delegateLoader, LinkOption... options) {
+        super(file, delegateLoader, options);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java
new file mode 100644
index 0000000..2d9feb6
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java
@@ -0,0 +1,79 @@
+/*
+ * 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.loader.openpgp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+
+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.resource.IoResource;
+import org.apache.sshd.common.util.io.resource.PathResource;
+import org.apache.sshd.common.util.io.resource.URLResource;
+import org.bouncycastle.openpgp.PGPException;
+import org.c02e.jpgpj.Key;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PGPKeyLoader {
+    default Key loadPGPKey(
+            SessionContext session, URL url, FilePasswordProvider passwordProvider)
+                throws IOException, GeneralSecurityException, PGPException {
+        return loadPGPKey(session, new URLResource(url), passwordProvider);
+    }
+
+    default Key loadPGPKey(
+            SessionContext session, Path path, FilePasswordProvider passwordProvider, OpenOption... options)
+                throws IOException, GeneralSecurityException, PGPException {
+        return loadPGPKey(session, new PathResource(path, options), passwordProvider);
+    }
+
+    default Key loadPGPKey(
+            SessionContext session, IoResource<?> resourceKey, FilePasswordProvider passwordProvider)
+                throws IOException, GeneralSecurityException, PGPException {
+        try (InputStream input = resourceKey.openInputStream()) {
+            return loadPGPKey(session, resourceKey, passwordProvider, input);
+        }
+    }
+
+    default Key loadPGPKey(
+            SessionContext session, NamedResource resourceKey, FilePasswordProvider passwordProvider, InputStream input)
+                throws IOException, GeneralSecurityException, PGPException {
+        return loadPGPKey(input, (passwordProvider == null) ? null : passwordProvider.getPassword(session, resourceKey, 0));
+    }
+
+    static Key loadPGPKey(InputStream input, String password) throws IOException, PGPException {
+        boolean withPassword = GenericUtils.isNotEmpty(password);
+        Key key = withPassword ? new Key(input, password) : new Key(input);
+        if (!withPassword) {
+            key.setNoPassphrase(true);
+        }
+        return key;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
index e6ae158..740edc3 100644
--- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java
@@ -22,72 +22,39 @@ package org.apache.sshd.common.config.keys.loader.openpgp;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StreamCorruptedException;
-import java.math.BigInteger;
 import java.net.ProtocolException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.ECPrivateKeySpec;
-import java.security.spec.ECPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
-import java.security.spec.RSAPrivateCrtKeySpec;
-import java.security.spec.RSAPublicKeySpec;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 
 import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.cipher.ECCurves;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.bcpg.BCPGKey;
-import org.bouncycastle.bcpg.DSAPublicBCPGKey;
-import org.bouncycastle.bcpg.DSASecretBCPGKey;
-import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
-import org.bouncycastle.bcpg.ECPublicBCPGKey;
-import org.bouncycastle.bcpg.ECSecretBCPGKey;
-import org.bouncycastle.bcpg.EdDSAPublicBCPGKey;
-import org.bouncycastle.bcpg.EdSecretBCPGKey;
-import org.bouncycastle.bcpg.PublicKeyPacket;
-import org.bouncycastle.bcpg.RSAPublicBCPGKey;
-import org.bouncycastle.bcpg.RSASecretBCPGKey;
 import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPrivateKey;
-import org.bouncycastle.openpgp.PGPPublicKey;
 import org.c02e.jpgpj.Key;
 import org.c02e.jpgpj.Subkey;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class PGPKeyPairResourceParser extends AbstractKeyPairResourceParser {
+public class PGPKeyPairResourceParser
+        extends AbstractKeyPairResourceParser
+        implements PGPKeyLoader,
+        PGPPublicKeyExtractor,
+        PGPPrivateKeyExtractor {
     public static final String BEGIN_MARKER = "BEGIN PGP PRIVATE KEY BLOCK";
     public static final List<String> BEGINNERS =
         Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
@@ -142,13 +109,7 @@ public class PGPKeyPairResourceParser extends AbstractKeyPairResourceParser {
                     stream.reset();
                 }
 
-                Key key = new Key(stream, password);
-                if (GenericUtils.isEmpty(password)) {
-                    key.setNoPassphrase(true);
-                } else {
-                    key.setPassphrase(password);
-                }
-
+                Key key = PGPKeyLoader.loadPGPKey(stream, password);
                 keys = extractKeyPairs(resourceKey, key.getSubkeys());
             } catch (IOException | GeneralSecurityException | PGPException | RuntimeException e) {
                 ResourceDecodeResult result = (passwordProvider != null)
@@ -250,197 +211,16 @@ public class PGPKeyPairResourceParser extends AbstractKeyPairResourceParser {
         return kpList;
     }
 
-    public PublicKey extractPublicKey(NamedResource resourceKey, Subkey sk) throws IOException, GeneralSecurityException {
-        if (sk == null) {
-            return null;
-        }
-
-        PGPPublicKey pgpKey = Objects.requireNonNull(sk.getPublicKey(), "Missing sub-key public key");
-        PublicKeyPacket pgpPacket = Objects.requireNonNull(pgpKey.getPublicKeyPacket(), "Missing public key packet");
-        BCPGKey bcKey = Objects.requireNonNull(pgpPacket.getKey(), "Missing BC key");
-        if (bcKey instanceof RSAPublicBCPGKey) {
-            return extractRSAPublicKey(resourceKey, (RSAPublicBCPGKey) bcKey);
-        } else if (bcKey instanceof ECPublicBCPGKey) {
-            return extractECPublicKey(resourceKey, (ECPublicBCPGKey) bcKey);
-        } else if (bcKey instanceof DSAPublicBCPGKey) {
-            return extractDSSPublicKey(resourceKey, (DSAPublicBCPGKey) bcKey);
-        } else {
-            throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName());
-        }
-    }
-
-    public RSAPublicKey extractRSAPublicKey(NamedResource resourceKey, RSAPublicBCPGKey bcKey) throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        BigInteger e = bcKey.getPublicExponent();
-        BigInteger n = bcKey.getModulus();
-        return generatePublicKey(KeyUtils.RSA_ALGORITHM, RSAPublicKey.class, new RSAPublicKeySpec(n, e));
-    }
-
-    public PublicKey extractECPublicKey(NamedResource resourceKey, ECPublicBCPGKey bcKey) throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        } else if (bcKey instanceof EdDSAPublicBCPGKey) {
-            return extractEdDSAPublicKey(resourceKey, (EdDSAPublicBCPGKey) bcKey);
-        } else if (bcKey instanceof ECDSAPublicBCPGKey) {
-            return extractECDSAPublicKey(resourceKey, (ECDSAPublicBCPGKey) bcKey);
-        } else {
-            throw new NoSuchAlgorithmException("Unsupported EC public key type: " + bcKey.getClass().getSimpleName());
-        }
-    }
-
-    public ECPublicKey extractECDSAPublicKey(NamedResource resourceKey, ECDSAPublicBCPGKey bcKey) throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        ASN1ObjectIdentifier asnId = bcKey.getCurveOID();
-        String oid = asnId.getId();
-        ECCurves curve = ECCurves.fromOID(oid);
-        if (curve == null) {
-            throw new InvalidKeySpecException("Not an EC curve OID: " + oid);
-        }
-
-        if (!SecurityUtils.isECCSupported()) {
-            throw new NoSuchProviderException("ECC not supported");
-        }
-
-        BigInteger encPoint = bcKey.getEncodedPoint();
-        byte[] octets = encPoint.toByteArray();
-        ECPoint w;
-        try {
-            w = ECCurves.octetStringToEcPoint(octets);
-            if (w == null) {
-                throw new InvalidKeySpecException("No ECPoint generated for curve=" + curve.getName()
-                        + " from octets=" + BufferUtils.toHex(':', octets));
-            }
-        } catch (RuntimeException e) {
-            throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")"
-                    + " to generate ECPoint for curve=" + curve.getName()
-                    + " from octets=" + BufferUtils.toHex(':', octets)
-                    + ": " + e.getMessage());
-        }
-
-        ECParameterSpec paramSpec = curve.getParameters();
-        return generatePublicKey(KeyUtils.EC_ALGORITHM, ECPublicKey.class, new ECPublicKeySpec(w, paramSpec));
-    }
-
-    public PublicKey extractEdDSAPublicKey(NamedResource resourceKey, EdDSAPublicBCPGKey bcKey)  throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        if (!SecurityUtils.isEDDSACurveSupported()) {
-            throw new NoSuchProviderException("EdDSA not supported");
-        }
-
-        throw new NoSuchAlgorithmException("Unsupported EdDSA public key type: " + bcKey.getClass().getSimpleName());
-    }
-
-    public DSAPublicKey extractDSSPublicKey(NamedResource resourceKey, DSAPublicBCPGKey bcKey) throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        BigInteger p = bcKey.getP();
-        BigInteger q = bcKey.getQ();
-        BigInteger g = bcKey.getG();
-        BigInteger y = bcKey.getY();
-        return generatePublicKey(KeyUtils.DSS_ALGORITHM, DSAPublicKey.class, new DSAPublicKeySpec(y, p, q, g));
-    }
-
-    protected <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec)
+    @Override
+    public <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec)
                 throws GeneralSecurityException {
         KeyFactory factory = getKeyFactory(algorithm);
         PublicKey pubKey = factory.generatePublic(keySpec);
         return keyType.cast(pubKey);
     }
 
-    public PrivateKey extractPrivateKey(NamedResource resourceKey, Subkey sk, PublicKey pubKey)
-            throws IOException, GeneralSecurityException, PGPException {
-        if (sk == null) {
-            return null;
-        }
-
-        PGPPrivateKey pgpKey = Objects.requireNonNull(sk.getPrivateKey(), "Missing sub-key private key");
-        BCPGKey bcKey = Objects.requireNonNull(pgpKey.getPrivateKeyDataPacket(), "Missing BC key");
-        if (bcKey instanceof RSASecretBCPGKey) {
-            return extractRSAPrivateKey(resourceKey, (RSAPublicKey) pubKey, (RSASecretBCPGKey) bcKey);
-        } else if (bcKey instanceof ECSecretBCPGKey) {
-            return extractECDSAPrivateKey(resourceKey, (ECPublicKey) pubKey, (ECSecretBCPGKey) bcKey);
-        } else if (bcKey instanceof EdSecretBCPGKey) {
-            return extractEdDSAPrivateKey(resourceKey, pubKey, (EdSecretBCPGKey) bcKey);
-        } else if (bcKey instanceof DSASecretBCPGKey) {
-            return extractDSSPrivateKey(resourceKey, (DSAPublicKey) pubKey, (DSASecretBCPGKey) bcKey);
-        } else {
-            throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName());
-        }
-    }
-
-    public ECPrivateKey extractECDSAPrivateKey(NamedResource resourceKey, ECPublicKey pubKey, ECSecretBCPGKey bcKey)
-            throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        if (!SecurityUtils.isECCSupported()) {
-            throw new NoSuchProviderException("ECC not supported");
-        }
-
-        ECParameterSpec params = pubKey.getParams();
-        BigInteger x = bcKey.getX();
-        return generatePrivateKey(KeyUtils.EC_ALGORITHM, ECPrivateKey.class, new ECPrivateKeySpec(x, params));
-    }
-
-    public PrivateKey extractEdDSAPrivateKey(NamedResource resourceKey, PublicKey pubKey, EdSecretBCPGKey bcKey)
-            throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        if (!SecurityUtils.isEDDSACurveSupported()) {
-            throw new NoSuchProviderException("EdDSA not supported");
-        }
-
-        throw new NoSuchAlgorithmException("Unsupported EdDSA private key type: " + bcKey.getClass().getSimpleName());
-    }
-
-    public RSAPrivateKey extractRSAPrivateKey(NamedResource resourceKey, RSAPublicKey pubKey, RSASecretBCPGKey bcKey)
-            throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        return generatePrivateKey(KeyUtils.RSA_ALGORITHM, RSAPrivateKey.class,
-                new RSAPrivateCrtKeySpec(
-                        bcKey.getModulus(),
-                        pubKey.getPublicExponent(),
-                        bcKey.getPrivateExponent(),
-                        bcKey.getPrimeP(),
-                        bcKey.getPrimeQ(),
-                        bcKey.getPrimeExponentP(),
-                        bcKey.getPrimeExponentQ(),
-                        bcKey.getCrtCoefficient()));
-    }
-
-    public DSAPrivateKey extractDSSPrivateKey(NamedResource resourceKey, DSAPublicKey pubKey, DSASecretBCPGKey bcKey)
-            throws GeneralSecurityException {
-        if (bcKey == null) {
-            return null;
-        }
-
-        DSAParams params = pubKey.getParams();
-        if (params == null) {
-            throw new InvalidKeyException("Missing parameters in public key");
-        }
-
-        return generatePrivateKey(KeyUtils.DSS_ALGORITHM, DSAPrivateKey.class,
-            new DSAPrivateKeySpec(bcKey.getX(), params.getP(), params.getQ(), params.getG()));
-    }
-
-    protected <K extends PrivateKey> K generatePrivateKey(String algorithm, Class<K> keyType, KeySpec keySpec)
+    @Override
+    public <K extends PrivateKey> K generatePrivateKey(String algorithm, Class<K> keyType, KeySpec keySpec)
             throws GeneralSecurityException {
         KeyFactory factory = getKeyFactory(algorithm);
         PrivateKey prvKey = factory.generatePrivate(keySpec);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java
new file mode 100644
index 0000000..e797071
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java
@@ -0,0 +1,152 @@
+/*
+ * 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.loader.openpgp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.KeySpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.util.Objects;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSASecretBCPGKey;
+import org.bouncycastle.bcpg.ECSecretBCPGKey;
+import org.bouncycastle.bcpg.EdSecretBCPGKey;
+import org.bouncycastle.bcpg.RSASecretBCPGKey;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.c02e.jpgpj.Subkey;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface PGPPrivateKeyExtractor {
+    default PrivateKey extractPrivateKey(
+            NamedResource resourceKey, Subkey sk, PublicKey pubKey)
+                throws IOException, GeneralSecurityException, PGPException {
+        if (sk == null) {
+            return null;
+        }
+
+        PGPPrivateKey pgpKey = Objects.requireNonNull(sk.getPrivateKey(), "Missing sub-key private key");
+        BCPGKey bcKey = Objects.requireNonNull(pgpKey.getPrivateKeyDataPacket(), "Missing BC key");
+        if (bcKey instanceof RSASecretBCPGKey) {
+            return extractRSAPrivateKey(resourceKey, (RSAPublicKey) pubKey, (RSASecretBCPGKey) bcKey);
+        } else if (bcKey instanceof ECSecretBCPGKey) {
+            return extractECDSAPrivateKey(resourceKey, (ECPublicKey) pubKey, (ECSecretBCPGKey) bcKey);
+        } else if (bcKey instanceof EdSecretBCPGKey) {
+            return extractEdDSAPrivateKey(resourceKey, pubKey, (EdSecretBCPGKey) bcKey);
+        } else if (bcKey instanceof DSASecretBCPGKey) {
+            return extractDSSPrivateKey(resourceKey, (DSAPublicKey) pubKey, (DSASecretBCPGKey) bcKey);
+        } else {
+            throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName());
+        }
+    }
+
+    default ECPrivateKey extractECDSAPrivateKey(
+            NamedResource resourceKey, ECPublicKey pubKey, ECSecretBCPGKey bcKey)
+                throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        ECParameterSpec params = pubKey.getParams();
+        BigInteger x = bcKey.getX();
+        return generatePrivateKey(KeyUtils.EC_ALGORITHM, ECPrivateKey.class, new ECPrivateKeySpec(x, params));
+    }
+
+    default PrivateKey extractEdDSAPrivateKey(
+            NamedResource resourceKey, PublicKey pubKey, EdSecretBCPGKey bcKey)
+                throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            throw new NoSuchProviderException("EdDSA not supported");
+        }
+
+        throw new NoSuchAlgorithmException("Unsupported EdDSA private key type: " + bcKey.getClass().getSimpleName());
+    }
+
+    default RSAPrivateKey extractRSAPrivateKey(
+            NamedResource resourceKey, RSAPublicKey pubKey, RSASecretBCPGKey bcKey)
+                throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        return generatePrivateKey(KeyUtils.RSA_ALGORITHM, RSAPrivateKey.class,
+                new RSAPrivateCrtKeySpec(
+                        bcKey.getModulus(),
+                        pubKey.getPublicExponent(),
+                        bcKey.getPrivateExponent(),
+                        bcKey.getPrimeP(),
+                        bcKey.getPrimeQ(),
+                        bcKey.getPrimeExponentP(),
+                        bcKey.getPrimeExponentQ(),
+                        bcKey.getCrtCoefficient()));
+    }
+
+    default DSAPrivateKey extractDSSPrivateKey(
+            NamedResource resourceKey, DSAPublicKey pubKey, DSASecretBCPGKey bcKey)
+                throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        DSAParams params = pubKey.getParams();
+        if (params == null) {
+            throw new InvalidKeyException("Missing parameters in public key");
+        }
+
+        return generatePrivateKey(KeyUtils.DSS_ALGORITHM, DSAPrivateKey.class,
+            new DSAPrivateKeySpec(bcKey.getX(), params.getP(), params.getQ(), params.getG()));
+    }
+
+    <K extends PrivateKey> K generatePrivateKey(String algorithm, Class<K> keyType, KeySpec keySpec)
+            throws GeneralSecurityException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java
index c32103e..49dd912 100644
--- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java
@@ -22,7 +22,6 @@ package org.apache.sshd.common.config.keys.loader.openpgp;
 import java.util.Collections;
 import java.util.NavigableSet;
 
-import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.config.keys.PublicKeyEntryDataResolver;
 import org.apache.sshd.common.util.GenericUtils;
@@ -54,17 +53,25 @@ public class PGPPublicKeyEntryDataResolver implements PublicKeyEntryDataResolver
 
     @Override
     public byte[] decodeEntryKeyData(String encData) {
+        return decodeKeyFingerprint(encData);
+    }
+
+    @Override
+    public String encodeEntryKeyData(byte[] keyData) {
+        return encodeKeyFingerprint(keyData);
+    }
+
+    public static byte[] decodeKeyFingerprint(String encData) {
         if (GenericUtils.isEmpty(encData)) {
-            return GenericUtils.EMPTY_BYTE_ARRAY;
+            return GenericUtils.EMPTY_BYTE_ARRAY;   // debug breakpoint
         }
 
         return BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, encData);
     }
 
-    @Override
-    public String encodeEntryKeyData(byte[] keyData) {
+    public static String encodeKeyFingerprint(byte[] keyData) {
         if (NumberUtils.isEmpty(keyData)) {
-            return "";
+            return "";  // debug breakpoint
         }
 
         return BufferUtils.toHex(BufferUtils.EMPTY_HEX_SEPARATOR, keyData).toUpperCase();
@@ -77,11 +84,8 @@ public class PGPPublicKeyEntryDataResolver implements PublicKeyEntryDataResolver
      * @see PublicKeyEntry#registerKeyDataEntryResolver(String, PublicKeyEntryDataResolver)
      */
     public static void registerDefaultKeyEntryDataResolvers() {
-        // TODO fix this code once SSHD-757 fully implemented
-        PGPPublicKeyEntryDecoder dummy = new PGPPublicKeyEntryDecoder();
         for (String keyType : PGP_KEY_TYPES) {
             PublicKeyEntry.registerKeyDataEntryResolver(keyType, DEFAULT);
-            KeyUtils.registerPublicKeyEntryDecoderForKeyType(keyType, dummy);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java
deleted file mode 100644
index 9ad2313..0000000
--- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.loader.openpgp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.GeneralSecurityException;
-import java.security.KeyException;
-import java.security.KeyFactory;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder;
-import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class PGPPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<PublicKey, PrivateKey> {
-    public PGPPublicKeyEntryDecoder() {
-        super(PublicKey.class, PrivateKey.class, PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES);
-    }
-
-    @Override
-    public PublicKey decodePublicKeyByType(String keyType, InputStream keyData)
-            throws IOException, GeneralSecurityException {
-        return decodePublicKey(keyType, keyData);
-    }
-
-    @Override
-    public PublicKey decodePublicKey(String keyType, InputStream keyData)
-            throws IOException, GeneralSecurityException {
-        return decodePublicKey(keyType, IoUtils.toByteArray(keyData));
-    }
-
-    @Override
-    public PublicKey decodePublicKey(String keyType, byte[] keyData, int offset, int length)
-            throws IOException, GeneralSecurityException {
-        String fingerprint = BufferUtils.toHex(keyData, offset, length, BufferUtils.EMPTY_HEX_SEPARATOR).toString();
-        throw new KeyException("TODO decode key type=" + keyType + " for fingerprint=" + fingerprint);
-    }
-
-    @Override
-    public String encodePublicKey(OutputStream s, PublicKey key) throws IOException {
-        throw new UnsupportedOperationException("N/A");
-    }
-
-    @Override
-    public PublicKey clonePublicKey(PublicKey key) throws GeneralSecurityException {
-        throw new UnsupportedOperationException("N/A");
-    }
-
-    @Override
-    public PrivateKey clonePrivateKey(PrivateKey key) throws GeneralSecurityException {
-        throw new UnsupportedOperationException("N/A");
-    }
-
-    @Override
-    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
-        throw new UnsupportedOperationException("N/A");
-    }
-
-    @Override
-    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
-        throw new UnsupportedOperationException("N/A");
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java
new file mode 100644
index 0000000..cb68837
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java
@@ -0,0 +1,172 @@
+/*
+ * 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.loader.openpgp;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Objects;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.bcpg.BCPGKey;
+import org.bouncycastle.bcpg.DSAPublicBCPGKey;
+import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
+import org.bouncycastle.bcpg.ECPublicBCPGKey;
+import org.bouncycastle.bcpg.EdDSAPublicBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.RSAPublicBCPGKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.c02e.jpgpj.Subkey;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface PGPPublicKeyExtractor {
+    default PublicKey extractPublicKey(NamedResource resourceKey, Subkey sk)
+            throws IOException, GeneralSecurityException {
+        if (sk == null) {
+            return null;
+        }
+
+        PGPPublicKey pgpKey = Objects.requireNonNull(sk.getPublicKey(), "Missing sub-key public key");
+        PublicKeyPacket pgpPacket = Objects.requireNonNull(pgpKey.getPublicKeyPacket(), "Missing public key packet");
+        BCPGKey bcKey = Objects.requireNonNull(pgpPacket.getKey(), "Missing BC key");
+        if (bcKey instanceof RSAPublicBCPGKey) {
+            return extractRSAPublicKey(resourceKey, (RSAPublicBCPGKey) bcKey);
+        } else if (bcKey instanceof ECPublicBCPGKey) {
+            return extractECPublicKey(resourceKey, (ECPublicBCPGKey) bcKey);
+        } else if (bcKey instanceof DSAPublicBCPGKey) {
+            return extractDSSPublicKey(resourceKey, (DSAPublicBCPGKey) bcKey);
+        } else {
+            throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName());
+        }
+    }
+
+    default RSAPublicKey extractRSAPublicKey(NamedResource resourceKey, RSAPublicBCPGKey bcKey)
+            throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        BigInteger e = bcKey.getPublicExponent();
+        BigInteger n = bcKey.getModulus();
+        return generatePublicKey(KeyUtils.RSA_ALGORITHM, RSAPublicKey.class, new RSAPublicKeySpec(n, e));
+    }
+
+    default PublicKey extractECPublicKey(NamedResource resourceKey, ECPublicBCPGKey bcKey)
+            throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        } else if (bcKey instanceof EdDSAPublicBCPGKey) {
+            return extractEdDSAPublicKey(resourceKey, (EdDSAPublicBCPGKey) bcKey);
+        } else if (bcKey instanceof ECDSAPublicBCPGKey) {
+            return extractECDSAPublicKey(resourceKey, (ECDSAPublicBCPGKey) bcKey);
+        } else {
+            throw new NoSuchAlgorithmException("Unsupported EC public key type: " + bcKey.getClass().getSimpleName());
+        }
+    }
+
+    default ECPublicKey extractECDSAPublicKey(NamedResource resourceKey, ECDSAPublicBCPGKey bcKey)
+            throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        ASN1ObjectIdentifier asnId = bcKey.getCurveOID();
+        String oid = asnId.getId();
+        ECCurves curve = ECCurves.fromOID(oid);
+        if (curve == null) {
+            throw new InvalidKeySpecException("Not an EC curve OID: " + oid);
+        }
+
+        if (!SecurityUtils.isECCSupported()) {
+            throw new NoSuchProviderException("ECC not supported");
+        }
+
+        BigInteger encPoint = bcKey.getEncodedPoint();
+        byte[] octets = encPoint.toByteArray();
+        ECPoint w;
+        try {
+            w = ECCurves.octetStringToEcPoint(octets);
+            if (w == null) {
+                throw new InvalidKeySpecException("No ECPoint generated for curve=" + curve.getName()
+                        + " from octets=" + BufferUtils.toHex(':', octets));
+            }
+        } catch (RuntimeException e) {
+            throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to generate ECPoint for curve=" + curve.getName()
+                    + " from octets=" + BufferUtils.toHex(':', octets)
+                    + ": " + e.getMessage());
+        }
+
+        ECParameterSpec paramSpec = curve.getParameters();
+        return generatePublicKey(KeyUtils.EC_ALGORITHM, ECPublicKey.class, new ECPublicKeySpec(w, paramSpec));
+    }
+
+    default PublicKey extractEdDSAPublicKey(NamedResource resourceKey, EdDSAPublicBCPGKey bcKey)
+            throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        if (!SecurityUtils.isEDDSACurveSupported()) {
+            throw new NoSuchProviderException("EdDSA not supported");
+        }
+
+        throw new NoSuchAlgorithmException("Unsupported EdDSA public key type: " + bcKey.getClass().getSimpleName());
+    }
+
+    default DSAPublicKey extractDSSPublicKey(NamedResource resourceKey, DSAPublicBCPGKey bcKey)
+            throws IOException, GeneralSecurityException {
+        if (bcKey == null) {
+            return null;
+        }
+
+        BigInteger p = bcKey.getP();
+        BigInteger q = bcKey.getQ();
+        BigInteger g = bcKey.getG();
+        BigInteger y = bcKey.getY();
+        return generatePublicKey(KeyUtils.DSS_ALGORITHM, DSAPublicKey.class, new DSAPublicKeySpec(y, p, q, g));
+    }
+
+    <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec)
+            throws GeneralSecurityException;
+}