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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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 + "]"; + } +}
