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 + "]";
+ }
+}