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:27 UTC

[4/7] mina-sshd git commit: [SSHD-757] Added some hooks to allow usage of OpenPGP keys inside authorized_keys files

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
index d950ee2..1fb3f52 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
@@ -24,6 +24,7 @@ import java.nio.file.FileSystemException;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.PosixFilePermission;
+import java.security.GeneralSecurityException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
@@ -97,7 +98,9 @@ public class DefaultAuthorizedKeysAuthenticator extends AuthorizedKeysAuthentica
     }
 
     @Override
-    protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, String username, ServerSession session) throws IOException {
+    protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(
+            Path path, String username, ServerSession session)
+                throws IOException, GeneralSecurityException {
         if (isStrict()) {
             if (log.isDebugEnabled()) {
                 log.debug("reloadAuthorizedKeys({})[{}] check permissions of {}", username, session, path);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 2b0dec4..99f5e76 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
@@ -19,9 +19,11 @@
 
 package org.apache.sshd.common.config.keys;
 
+import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
+import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.Collection;
 import java.util.List;
@@ -122,11 +124,13 @@ public class AuthorizedKeyEntryTest extends AuthorizedKeysTestSupport {
         return entries;
     }
 
-    private PublickeyAuthenticator testAuthorizedKeysAuth(Collection<AuthorizedKeyEntry> entries) throws Exception {
+    private PublickeyAuthenticator testAuthorizedKeysAuth(Collection<AuthorizedKeyEntry> entries)
+            throws IOException, GeneralSecurityException {
         Collection<PublicKey> keySet =
             AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries);
         PublickeyAuthenticator auth =
-            PublickeyAuthenticator.fromAuthorizedEntries(PublicKeyEntryResolver.FAILING, entries);
+            PublickeyAuthenticator.fromAuthorizedEntries(
+                getCurrentTestName(), PublicKeyEntryResolver.FAILING, entries);
         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/2f92fe5d/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
index 809de1c..112c7cd 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeysTestSupport.java
@@ -77,9 +77,9 @@ public abstract class AuthorizedKeysTestSupport extends BaseTestSupport {
 
     protected List<String> loadDefaultSupportedKeys() throws IOException {
         return loadSupportedKeys(
-                Objects.requireNonNull(
-                        getClass().getResource(AuthorizedKeysAuthenticator.STD_AUTHORIZED_KEYS_FILENAME),
-                        "Missing resource=" + AuthorizedKeysAuthenticator.STD_AUTHORIZED_KEYS_FILENAME));
+            Objects.requireNonNull(
+                getClass().getResource(AuthorizedKeysAuthenticator.STD_AUTHORIZED_KEYS_FILENAME),
+                "Missing resource=" + AuthorizedKeysAuthenticator.STD_AUTHORIZED_KEYS_FILENAME));
     }
 
     public static List<String> loadSupportedKeys(URL url) throws IOException {
@@ -87,7 +87,8 @@ public abstract class AuthorizedKeysTestSupport extends BaseTestSupport {
     }
 
     public static List<String> loadSupportedKeys(InputStream input, boolean okToClose) throws IOException {
-        try (Reader r = new InputStreamReader(NoCloseInputStream.resolveInputStream(input, okToClose), StandardCharsets.UTF_8)) {
+        try (Reader r = new InputStreamReader(
+                NoCloseInputStream.resolveInputStream(input, okToClose), StandardCharsets.UTF_8)) {
             return loadSupportedKeys(r, true);
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
index 7ae6c96..d99ff2e 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/signature/SignatureFactoriesTest.java
@@ -39,6 +39,8 @@ import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.keyprovider.KeySizeIndicator;
+import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
 import org.apache.sshd.common.session.SessionContext;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
@@ -63,7 +65,7 @@ import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
 @UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
-public class SignatureFactoriesTest extends BaseTestSupport implements OptionalFeature {
+public class SignatureFactoriesTest extends BaseTestSupport implements KeyTypeIndicator, KeySizeIndicator, OptionalFeature {
     private static SshServer sshd;
     private static SshClient client;
     private static int port;
@@ -86,11 +88,6 @@ public class SignatureFactoriesTest extends BaseTestSupport implements OptionalF
         this.pubKeyDecoder = supported ? Objects.requireNonNull(decoder, "No public key decoder provided") : null;
     }
 
-    @Override
-    public boolean isSupported() {
-        return supported;
-    }
-
     @Parameters(name = "type={0}, size={2}")
     public static List<Object[]> parameters() {
         List<Object[]> list = new ArrayList<>();
@@ -150,10 +147,17 @@ public class SignatureFactoriesTest extends BaseTestSupport implements OptionalF
         }
     }
 
+    @Override
+    public final boolean isSupported() {
+        return supported;
+    }
+
+    @Override
     public final int getKeySize() {
         return keySize;
     }
 
+    @Override
     public final String getKeyType() {
         return keyType;
     }
@@ -180,7 +184,7 @@ public class SignatureFactoriesTest extends BaseTestSupport implements OptionalF
 
     protected void testKeyPairProvider(
             String keyName, Factory<Iterable<KeyPair>> keyPairFactory, List<NamedFactory<Signature>> signatures)
-                    throws Exception {
+                throws Exception {
         Iterable<KeyPair> iter = keyPairFactory.create();
         testKeyPairProvider(new KeyPairProvider() {
             @Override
@@ -190,10 +194,14 @@ public class SignatureFactoriesTest extends BaseTestSupport implements OptionalF
         }, signatures);
     }
 
-    protected void testKeyPairProvider(KeyPairProvider provider, List<NamedFactory<Signature>> signatures) throws Exception {
+    protected void testKeyPairProvider(
+            KeyPairProvider provider, List<NamedFactory<Signature>> signatures)
+                throws Exception {
         sshd.setKeyPairProvider(provider);
         client.setSignatureFactories(signatures);
-        try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+        try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                .verify(7L, TimeUnit.SECONDS)
+                .getSession()) {
             s.addPasswordIdentity(getCurrentTestName());
             // allow a rather long timeout since generating some keys may take some time
             s.auth().verify(30L, TimeUnit.SECONDS);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 9d99b05..fc5facc 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
@@ -24,6 +24,7 @@ import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 import java.util.Collection;
 import java.util.List;
@@ -60,13 +61,16 @@ public class AuthorizedKeysAuthenticatorTest extends AuthorizedKeysTestSupport {
         AtomicInteger reloadCount = new AtomicInteger(0);
         PublickeyAuthenticator auth = new AuthorizedKeysAuthenticator(file) {
             @Override
-            protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, String username, ServerSession session) throws IOException {
+            protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(
+                    Path path, String username, ServerSession session)
+                        throws IOException, GeneralSecurityException {
                 assertSame("Mismatched reload path", file, path);
                 reloadCount.incrementAndGet();
                 return super.reloadAuthorizedKeys(path, username, session);
             }
         };
-        assertFalse("Unexpected authentication success for missing file " + file, auth.authenticate(getCurrentTestName(), Mockito.mock(PublicKey.class), null));
+        assertFalse("Unexpected authentication success for missing file " + file,
+            auth.authenticate(getCurrentTestName(), Mockito.mock(PublicKey.class), null));
 
         List<String> keyLines = loadDefaultSupportedKeys();
         assertHierarchyTargetFolderExists(file.getParent());
@@ -94,17 +98,18 @@ public class AuthorizedKeysAuthenticatorTest extends AuthorizedKeysTestSupport {
                 String keyData = keyLines.get(index);  // we know they are 1-1 matching
 
                 assertTrue("Failed to authenticate with key #" + (index + 1) + " " + k.getAlgorithm() + "[" + keyData + "] on file=" + file,
-                           auth.authenticate(getCurrentTestName(), k, null));
+                       auth.authenticate(getCurrentTestName(), k, null));
 
                 // we expect EXACTLY ONE re-load call since we did not modify the file during the authentication
                 assertEquals("Unexpected keys re-loading of " + keyLines.size() + " remaining at key #" + (index + 1) + " on file=" + file,
-                             1, reloadCount.get());
+                         1, reloadCount.get());
             }
 
             keyLines.remove(0);
         }
 
         assertTrue("File no longer exists: " + file, Files.exists(file));
-        assertFalse("Unexpected authentication success for empty file " + file, auth.authenticate(getCurrentTestName(), Mockito.mock(PublicKey.class), null));
+        assertFalse("Unexpected authentication success for empty file " + file,
+            auth.authenticate(getCurrentTestName(), Mockito.mock(PublicKey.class), null));
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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
new file mode 100644
index 0000000..c32103e
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java
@@ -0,0 +1,106 @@
+/*
+ * 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.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;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.openpgp.PGPPublicKey;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PGPPublicKeyEntryDataResolver implements PublicKeyEntryDataResolver {
+    public static final String PGP_RSA_KEY = "pgp-sign-rsa";
+    public static final String PGP_DSS_KEY = "pgp-sign-dss";
+
+    public static final NavigableSet<String> PGP_KEY_TYPES =
+        Collections.unmodifiableNavigableSet(
+            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER,
+                PGP_RSA_KEY,
+                PGP_DSS_KEY));
+
+    public static final PGPPublicKeyEntryDataResolver DEFAULT = new PGPPublicKeyEntryDataResolver();
+
+    public PGPPublicKeyEntryDataResolver() {
+        super();
+    }
+
+    @Override
+    public byte[] decodeEntryKeyData(String encData) {
+        if (GenericUtils.isEmpty(encData)) {
+            return GenericUtils.EMPTY_BYTE_ARRAY;
+        }
+
+        return BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, encData);
+    }
+
+    @Override
+    public String encodeEntryKeyData(byte[] keyData) {
+        if (NumberUtils.isEmpty(keyData)) {
+            return "";
+        }
+
+        return BufferUtils.toHex(BufferUtils.EMPTY_HEX_SEPARATOR, keyData).toUpperCase();
+    }
+
+    /**
+     * Used in order to add the {@link #DEFAULT default resolver} for all
+     * the {@link #PGP_KEY_TYPES standard PGP key types}.
+     *
+     * @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);
+        }
+    }
+
+    public static String getKeyType(PGPPublicKey key) {
+        int algo = (key == null) ? -1 : key.getAlgorithm();
+        switch (algo) {
+            case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+            case PublicKeyAlgorithmTags.RSA_GENERAL:
+            case PublicKeyAlgorithmTags.RSA_SIGN:
+                return PGP_RSA_KEY;
+
+            case PublicKeyAlgorithmTags.DSA:
+                return PGP_DSS_KEY;
+
+            case PublicKeyAlgorithmTags.ECDSA:  // TODO find out how these key types are called
+            case PublicKeyAlgorithmTags.EDDSA:  // TODO find out how these key types are called
+            default:
+                return null;
+
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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
new file mode 100644
index 0000000..9ad2313
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java
@@ -0,0 +1,87 @@
+/*
+ * 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/2f92fe5d/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
new file mode 100644
index 0000000..828dd54
--- /dev/null
+++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtils.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.c02e.jpgpj.CompressionAlgorithm;
+import org.c02e.jpgpj.EncryptionAlgorithm;
+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 final class PGPUtils {
+    public static final String DEFAULT_PGP_FILE_SUFFIX = ".gpg";
+
+    /** Default MIME type for PGP encrypted files */
+    public static final String PGP_ENCRYPTED_FILE = "application/pgp-encrypted";
+
+    /** Alias for {@link EncryptionAlgorithm#Unencrypted Unencrypted} */
+    public static final String NO_CIPHER_PLACEHOLDER = "none";
+
+    public static final Set<EncryptionAlgorithm> CIPHERS =
+        Collections.unmodifiableSet(EnumSet.allOf(EncryptionAlgorithm.class));
+
+    /** Alias for {@link CompressionAlgorithm#Uncompressed Uncompressed} */
+    public static final String NO_COMPRESSION_PLACEHOLDER = "none";
+
+    public static final Set<CompressionAlgorithm> COMPRESSIONS =
+        Collections.unmodifiableSet(EnumSet.allOf(CompressionAlgorithm.class));
+
+    private PGPUtils() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static EncryptionAlgorithm fromCipherName(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return null;
+        }
+
+        if (NO_CIPHER_PLACEHOLDER.equalsIgnoreCase(name)) {
+            return EncryptionAlgorithm.Unencrypted;
+        }
+
+        return CIPHERS.stream()
+            .filter(c -> name.equalsIgnoreCase(c.name()))
+            .findFirst()
+            .orElse(null);
+    }
+
+    public static CompressionAlgorithm fromCompressionName(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return null;
+        }
+
+        if (NO_COMPRESSION_PLACEHOLDER.equalsIgnoreCase(name)) {
+            return CompressionAlgorithm.Uncompressed;
+        } else {
+            return COMPRESSIONS.stream()
+                .filter(c -> name.equalsIgnoreCase(c.name()))
+                .findFirst()
+                .orElse(null);
+        }
+    }
+
+    /**
+     * @param key The {@link Key} whose sub-keys to scan - ignored if {@code null} or has no sub-keys
+     * @param fingerprint The fingerprint to match (case <U>insensitive</U>) - ignored if {@code null}/empty
+     * @return The first matching {@link Subkey} - {@code null} if no match found
+     * @see #findSubkeyByFingerprint(Collection, String)
+     */
+    public static Subkey findSubkeyByFingerprint(Key key, String fingerprint) {
+        return findSubkeyByFingerprint((key == null) ? Collections.emptyList() : key.getSubkeys(), fingerprint);
+    }
+
+    /**
+     * @param subKeys The {@link Subkey}-s to scan - ignored if {@code null}/empty
+     * @param fingerprint The fingerprint to match (case <U>insensitive</U>) - ignored if {@code null}/empty
+     * @return The first matching sub-key - {@code null} if no match found
+     */
+    public static Subkey findSubkeyByFingerprint(Collection<? extends Subkey> subKeys, String fingerprint) {
+        if (GenericUtils.isEmpty(subKeys) || GenericUtils.isEmpty(fingerprint)) {
+            return null;
+        }
+
+        return subKeys.stream()
+            .filter(k -> fingerprint.equalsIgnoreCase(k.getFingerprint()))
+            .findFirst()
+            .orElse(null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParserTest.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParserTest.java b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParserTest.java
index cd13da5..5b77181 100644
--- a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParserTest.java
+++ b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParserTest.java
@@ -40,6 +40,7 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
+import org.bouncycastle.openpgp.PGPException;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
@@ -137,13 +138,14 @@ public class PGPKeyPairResourceParserTest extends JUnitTestSupport {
     }
 
     @Test
-    public void testDecodePrivateKeyPair() throws IOException, GeneralSecurityException {
+    public void testDecodePrivateKeyPair() throws IOException, GeneralSecurityException, PGPException {
         InputStream stream = getClass().getResourceAsStream(resourceName);
         assertNotNull("Missing " + resourceName, stream);
 
         Collection<KeyPair> keys;
         try {
-            keys = PGPKeyPairResourceParser.INSTANCE.loadKeyPairs(null, NamedResource.ofName(resourceName), passwordProvider, stream);
+            keys = PGPKeyPairResourceParser.INSTANCE.loadKeyPairs(
+                null, NamedResource.ofName(resourceName), passwordProvider, stream);
         } catch (Exception e) {
             if (result != ResourceDecodeResult.TERMINATE) {
                 fail("Mismatched result mode for " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]");

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
----------------------------------------------------------------------
diff --git a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
new file mode 100644
index 0000000..3a41239
--- /dev/null
+++ b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+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;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.c02e.jpgpj.Key;
+import org.c02e.jpgpj.Subkey;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class PGPUtilsKeyFingerprintTest extends JUnitTestSupport {
+    private final String resourceName;
+    private final Key key;
+
+    public PGPUtilsKeyFingerprintTest(String resourceName) throws IOException, PGPException {
+        this.resourceName = resourceName;
+
+        InputStream stream = getClass().getResourceAsStream(resourceName);
+        assertNotNull("Missing " + resourceName, stream);
+
+        try {
+            key = new Key(stream);
+            key.setNoPassphrase(true);  // we are scanning public keys which are never encrypted
+        } finally {
+            stream.close();
+        }
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return parameterize(Arrays.asList(
+            "EC-348-v1p0-public.asc",
+            "RSA-2048-v1p0-public.asc",
+            "RSA-2048-v1p6p1-public.asc",
+            "RSA-4096-vp2p0p8-public.asc",
+            "DSA-2048-gpg4win-3.1.3.asc"));
+    }
+
+    @BeforeClass
+    @AfterClass
+    public static void clearAllRegisteredPublicKeyEntryDataResolvers() {
+        for (String keyType : PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES) {
+            PublicKeyEntry.unregisterKeyDataEntryResolver(keyType);
+            KeyUtils.unregisterPublicKeyEntryDecoderForKeyType(keyType);
+        }
+    }
+
+    @Before
+    public void setUp() {
+        clearAllRegisteredPublicKeyEntryDataResolvers();
+    }
+
+    @After
+    public void tearDown() {
+        clearAllRegisteredPublicKeyEntryDataResolvers();
+    }
+
+    @Test
+    public void testFindSubKeyByFingerprint() {
+        Collection<? extends Subkey> subKeys = key.getSubkeys();
+        assertFalse("No sub keys available in " + resourceName, GenericUtils.isEmpty(subKeys));
+
+        for (Subkey expected : subKeys) {
+            String fingerprint = expected.getFingerprint();
+            Subkey actual = PGPUtils.findSubkeyByFingerprint(key, fingerprint);
+            assertSame("Mismatched sub-key match for " + fingerprint, expected, actual);
+        }
+    }
+
+    @Test
+    public void testParseAuthorizedKeyEntry() throws IOException {
+        Path dir = getTempTargetRelativeFile(getClass().getSimpleName());
+        Path file = Files.createDirectories(dir).resolve(resourceName + ".authorized");
+        Collection<? extends Subkey> subKeys = key.getSubkeys();
+        assertFalse("No sub keys available in " + resourceName, GenericUtils.isEmpty(subKeys));
+
+        Collection<String> written = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        try (BufferedWriter out = Files.newBufferedWriter(file, StandardCharsets.UTF_8, IoUtils.EMPTY_OPEN_OPTIONS)) {
+            for (Subkey sk : subKeys) {
+                String fingerprint = sk.getFingerprint();
+                PGPPublicKey publicKey = sk.getPublicKey();
+                String keyType = PGPPublicKeyEntryDataResolver.getKeyType(publicKey);
+                if (GenericUtils.isEmpty(keyType)) {
+                    outputDebugMessage("%s: skip fingerprint=%s due to unknown key type", resourceName, fingerprint);
+                    continue;
+                }
+
+                out.append(keyType)
+                    .append(' ').append(fingerprint)
+                    .append(' ').append(resourceName)
+                    .append(System.lineSeparator());
+
+                assertTrue("Non-unique fingerprint: " + fingerprint, written.add(fingerprint));
+            }
+        }
+        // Can happen for ECC or EDDSA keys
+        Assume.assumeFalse(resourceName + " - no fingerprints written", written.isEmpty());
+
+        PGPPublicKeyEntryDataResolver.registerDefaultKeyEntryDataResolvers();
+        Collection<? extends PublicKeyEntry> authKeys =
+            AuthorizedKeyEntry.readAuthorizedKeys(file, IoUtils.EMPTY_OPEN_OPTIONS);
+        assertEquals("Mismatched key entries count", written.size(), authKeys.size());
+
+        for (PublicKeyEntry entry : authKeys) {
+            PublicKeyEntryDataResolver resolver = entry.getKeyDataResolver();
+            assertSame("Mismatched key data resolver for " + entry, PGPPublicKeyEntryDataResolver.DEFAULT, resolver);
+            String fingerprint = resolver.encodeEntryKeyData(entry.getKeyData());
+            assertTrue("Unknown fingerprint recovered: " + fingerprint, written.remove(fingerprint));
+        }
+
+        assertTrue(resourceName + " - incomplete fingerprints: " + written, written.isEmpty());
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + resourceName + "]";
+    }
+}