[SSHD-757] Added initial code to allow using OpenPGP public keys for public key authentication via 'authorized_keys' file(s)
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/193c9326 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/193c9326 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/193c9326 Branch: refs/heads/master Commit: 193c93269bc3775eee5e33f9040f1e433a3f148e Parents: 3efd1ed Author: Lyor Goldstein <[email protected]> Authored: Tue Nov 20 16:18:16 2018 +0200 Committer: Lyor Goldstein <[email protected]> Committed: Thu Nov 22 07:05:16 2018 +0200 ---------------------------------------------------------------------- CHANGES.md | 4 + README.md | 26 +- .../common/config/keys/AuthorizedKeyEntry.java | 27 +- .../sshd/common/config/keys/PublicKeyEntry.java | 53 +++- .../config/keys/PublicKeyEntryResolver.java | 9 +- .../FileWatcherKeyPairResourceLoader.java | 109 ++++++++ .../common/keyprovider/KeyTypeIndicator.java | 23 ++ .../common/util/io/ModifiableFileWatcher.java | 10 + .../auth/pubkey/PublickeyAuthenticator.java | 7 +- .../keys/AuthorizedKeysAuthenticator.java | 9 +- .../config/keys/AuthorizedKeyEntryTest.java | 4 +- .../keys/AuthorizedKeysAuthenticatorTest.java | 2 +- .../DefaultAuthorizedKeysAuthenticatorTest.java | 4 +- .../openpgp/PGPAuthorizedEntriesTracker.java | 270 +++++++++++++++++++ .../keys/loader/openpgp/PGPKeyFileWatcher.java | 51 ++++ .../keys/loader/openpgp/PGPKeyLoader.java | 79 ++++++ .../openpgp/PGPKeyPairResourceParser.java | 240 +---------------- .../loader/openpgp/PGPPrivateKeyExtractor.java | 152 +++++++++++ .../openpgp/PGPPublicKeyEntryDataResolver.java | 20 +- .../openpgp/PGPPublicKeyEntryDecoder.java | 87 ------ .../loader/openpgp/PGPPublicKeyExtractor.java | 172 ++++++++++++ .../loader/openpgp/PGPPublicKeyFileWatcher.java | 91 +++++++ .../config/keys/loader/openpgp/PGPUtils.java | 39 +++ .../openpgp/PGPUtilsKeyFingerprintTest.java | 48 ++++ 24 files changed, 1170 insertions(+), 366 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/CHANGES.md ---------------------------------------------------------------------- diff --git a/CHANGES.md b/CHANGES.md index 0c59a22..caffb47 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -71,6 +71,10 @@ accept also an `AttributeRepository` connection context argument (propagated fro * Converted most of the key-pair identity loaders (e.g., `ClientIdentityLoader`, `ClientIdentityProvider`, etc.) to return an `Iterable<KeyPair>` instead of single `KeyPair` instance. +* Code that converts authorized keys entries into `PublicKey`-s has been renamed to `resolvePublicKeyEntries` +and moved to `PublicKeyEntry` class. + * Note that the parameters **order** has also been modified + ## Behavioral changes and enhancements * [SSHD-757](https://issues.apache.org/jira/browse/SSHD-757) - Added hooks and some initial code to allow (limited) usage http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 6c8a1cb..529308c 100644 --- a/README.md +++ b/README.md @@ -1944,7 +1944,31 @@ specified in the [OpenSSH PGP configuration](https://www.red-bean.com/~nemo/open pgp-sign-rsa 87C36E60187451050A4F26B134824FC95C781A18 ``` -Where the key data following the key type specification is the fingerprint value of the referenced key. +Where the key data following the key type specification is the fingerprint value of the referenced key. In order to +use a "mixed mode" file (i.e., one that has both SSH and _OpenPGP_ keys) one needs to replace the default `AuthorizedKeysAuthenticator` +instance with one that is derived from it and overrides the `createDelegateAuthenticator` method in a manner similar +as shown below: + +```java +public class MyAuthorizedKeysAuthenticatorWithBothPGPAndSsh extends AuthorizedKeysAuthenticator { + ... constructor(s) ... + + @Override + protected PublickeyAuthenticator createDelegateAuthenticator( + String username, ServerSession session, Path path, + Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { + PGPAuthorizedEntriesTracker tracker = ... obtain an instance ... + // Note: need to catch the PGPException and transform it into either an IOException or a GeneralSecurityException + Collection<PublicKey> keys = tracker.resolveAuthorizedEntries(session, entries, fallbackResolver); + if (GenericUtils.isEmpty(keys)) { + return RejectAllPublickeyAuthenticator.INSTANCE; + } else { + return new KeySetPublickeyAuthenticator(id, keys); + } + } +} +``` ## Useful extra components in _sshd-contrib_ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java index 2e60968..3492f33 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry.java @@ -34,7 +34,6 @@ import java.security.GeneralSecurityException; import java.security.PublicKey; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -104,7 +103,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { sb.append(key); // TODO figure out a way to remember which options where quoted // TODO figure out a way to remember which options had no value - if (!Boolean.TRUE.toString().equals(value)) { + if (!"true".equals(value)) { sb.append('=').append(value); } index++; @@ -144,24 +143,6 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { + (GenericUtils.isEmpty(kc) ? "" : " " + kc); } - public static List<PublicKey> resolveAuthorizedKeys( - PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries) - throws IOException, GeneralSecurityException { - if (GenericUtils.isEmpty(entries)) { - return Collections.emptyList(); - } - - List<PublicKey> keys = new ArrayList<>(entries.size()); - for (AuthorizedKeyEntry e : entries) { - PublicKey k = e.resolvePublicKey(fallbackResolver); - if (k != null) { - keys.add(k); - } - } - - return keys; - } - /** * Reads read the contents of an {@code authorized_keys} file * @@ -296,7 +277,11 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } String keyType = line.substring(0, startPos); - PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType); + Object decoder = PublicKeyEntry.getKeyDataEntryResolver(keyType); + if (decoder == null) { + decoder = KeyUtils.getPublicKeyEntryDecoder(keyType); + } + AuthorizedKeyEntry entry; // assume this is due to the fact that it starts with login options if (decoder == null) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java index 49a5e41..ee64e5b 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java @@ -27,8 +27,11 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.NavigableMap; import java.util.Objects; import java.util.TreeMap; @@ -194,6 +197,36 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { } /** + * @param entries The entries to convert - ignored if {@code null}/empty + * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if + * none of the built-in ones can be used. If {@code null} and no built-in + * resolver can be used then an {@link InvalidKeySpecException} is thrown. + * @return The {@link List} of all {@link PublicKey}-s that have been resolved + * @throws IOException If failed to decode the key data + * @throws GeneralSecurityException If failed to generate the {@link PublicKey} + * from the decoded data + * @see #resolvePublicKey(PublicKeyEntryResolver) + */ + public static List<PublicKey> resolvePublicKeyEntries( + Collection<? extends PublicKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { + int numEntries = GenericUtils.size(entries); + if (numEntries <= 0) { + return Collections.emptyList(); + } + + List<PublicKey> keys = new ArrayList<>(numEntries); + for (PublicKeyEntry e : entries) { + PublicKey k = e.resolvePublicKey(fallbackResolver); + if (k != null) { + keys.add(k); + } + } + + return keys; + } + + /** * Registers a specialized decoder for the public key entry data bytes instead of the * {@link PublicKeyEntryDataResolver#DEFAULT default} one. * @@ -216,6 +249,20 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { * - e.g., "ssh-rsa", "pgp-sign-dss", etc. * @return The registered resolver instance - {@code null} if none was registered */ + public static PublicKeyEntryDataResolver getKeyDataEntryResolver(String keyType) { + keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); + + synchronized (KEY_DATA_RESOLVERS) { + return KEY_DATA_RESOLVERS.get(keyType); + } + } + + /** + * @param keyType The key-type value (case <U>insensitive</U>) that may have been + * previously {@link #registerKeyDataDecoder(String, PublicKeyEntryDataResolver) registered} + * - e.g., "ssh-rsa", "pgp-sign-dss", etc. + * @return The un-registered resolver instance - {@code null} if none was registered + */ public static PublicKeyEntryDataResolver unregisterKeyDataEntryResolver(String keyType) { keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); @@ -234,11 +281,7 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { public static PublicKeyEntryDataResolver resolveKeyDataEntryResolver(String keyType) { keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); - PublicKeyEntryDataResolver resolver; - synchronized (KEY_DATA_RESOLVERS) { - resolver = KEY_DATA_RESOLVERS.get(keyType); - } - + PublicKeyEntryDataResolver resolver = getKeyDataEntryResolver(keyType); if (resolver != null) { return resolver; // debug breakpoint } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java index e5eb0ed..3d3cc45 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java @@ -34,7 +34,8 @@ public interface PublicKeyEntryResolver { */ PublicKeyEntryResolver IGNORING = new PublicKeyEntryResolver() { @Override - public PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException { + public PublicKey resolve(String keyType, byte[] keyData) + throws IOException, GeneralSecurityException { return null; } @@ -49,7 +50,8 @@ public interface PublicKeyEntryResolver { */ PublicKeyEntryResolver FAILING = new PublicKeyEntryResolver() { @Override - public PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException { + public PublicKey resolve(String keyType, byte[] keyData) + throws IOException, GeneralSecurityException { throw new InvalidKeySpecException("Failing resolver on key type=" + keyType); } @@ -66,5 +68,6 @@ public interface PublicKeyEntryResolver { * @throws IOException If failed to parse the key data * @throws GeneralSecurityException If failed to generate the key */ - PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException; + PublicKey resolve(String keyType, byte[] keyData) + throws IOException, GeneralSecurityException; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java new file mode 100644 index 0000000..b476d13 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/FileWatcherKeyPairResourceLoader.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader; + +import java.io.IOException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.io.ModifiableFileWatcher; +import org.apache.sshd.common.util.io.resource.PathResource; + +/** + * Tracks a file containing {@link KeyPair}-s an re-loads it whenever a change + * has been sensed in the monitored file (if it exists) + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class FileWatcherKeyPairResourceLoader extends ModifiableFileWatcher implements KeyPairResourceLoader { + protected final AtomicReference<Collection<KeyPair>> keysHolder = new AtomicReference<>(Collections.emptyList()); + private KeyPairResourceLoader delegateLoader; + + public FileWatcherKeyPairResourceLoader(Path file, KeyPairResourceLoader delegateLoader) { + this(file, delegateLoader, IoUtils.getLinkOptions(true)); + } + + public FileWatcherKeyPairResourceLoader( + Path file, KeyPairResourceLoader delegateLoader, LinkOption... options) { + super(file, options); + this.delegateLoader = Objects.requireNonNull(delegateLoader, "No delegate loader provided"); + } + + public KeyPairResourceLoader getKeyPairResourceLoader() { + return delegateLoader; + } + + public void setKeyPairResourceLoader(KeyPairResourceLoader loader) { + this.delegateLoader = Objects.requireNonNull(loader, "No delegate loader provided"); + } + + @Override + public Collection<KeyPair> loadKeyPairs( + SessionContext session, NamedResource resourceKey, + FilePasswordProvider passwordProvider, List<String> lines) + throws IOException, GeneralSecurityException { + + Collection<KeyPair> ids = keysHolder.get(); + if (GenericUtils.isEmpty(ids) || checkReloadRequired()) { + keysHolder.set(Collections.emptyList()); // mark stale + + if (!exists()) { + return keysHolder.get(); + } + + Path path = getPath(); + ids = reloadKeyPairs(session, new PathResource(path), passwordProvider, lines); + int numKeys = GenericUtils.size(ids); + if (log.isDebugEnabled()) { + log.debug("loadKeyPairs({})[{}] reloaded {} keys from {}", + session, resourceKey, numKeys, path); + } + + if (numKeys > 0) { + keysHolder.set(ids); + updateReloadAttributes(); + } + } + + return ids; + } + + protected Collection<KeyPair> reloadKeyPairs( + SessionContext session, NamedResource resourceKey, + FilePasswordProvider passwordProvider, List<String> lines) + throws IOException, GeneralSecurityException { + KeyPairResourceLoader loader = + ValidateUtils.checkNotNull(getKeyPairResourceLoader(), "No resource loader for %s", resourceKey.getName()); + return loader.loadKeyPairs(session, resourceKey, passwordProvider, lines); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java index 1830447..3a6b16b 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java @@ -19,6 +19,15 @@ package org.apache.sshd.common.keyprovider; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.sshd.common.util.GenericUtils; + /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ @@ -28,4 +37,18 @@ public interface KeyTypeIndicator { * @return The <U>SSH</U> key type name - e.g., "ssh-rsa", "sshd-dss" etc. */ String getKeyType(); + + /** + * @param <I> The {@link KeyTypeIndicator} + * @param indicators The indicators to group + * @return A {@link NavigableMap} where key=the case <U>insensitive</U> {@link #getKeyType() key type}, + * value = the {@link List} of all indicators having the same key type + */ + static <I extends KeyTypeIndicator> NavigableMap<String, List<I>> groupByKeyType(Collection<? extends I> indicators) { + return GenericUtils.isEmpty(indicators) + ? Collections.emptyNavigableMap() + : indicators.stream() + .collect(Collectors.groupingBy( + KeyTypeIndicator::getKeyType, () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Collectors.toList())); + } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java index 0d8009d..72f6674 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java @@ -22,6 +22,7 @@ package org.apache.sshd.common.util.io; import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; +import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; @@ -38,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.OsUtils; +import org.apache.sshd.common.util.io.resource.PathResource; import org.apache.sshd.common.util.logging.AbstractLoggingBean; /** @@ -180,6 +182,14 @@ public class ModifiableFileWatcher extends AbstractLoggingBean { resetReloadAttributes(); } + public PathResource toPathResource() { + return toPathResource(IoUtils.EMPTY_OPEN_OPTIONS); + } + + public PathResource toPathResource(OpenOption... options) { + return new PathResource(getPath(), options); + } + @Override public String toString() { return Objects.toString(getPath()); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java index b6a8ab6..592e215 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java @@ -24,6 +24,7 @@ import java.security.PublicKey; import java.util.Collection; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; +import org.apache.sshd.common.config.keys.PublicKeyEntry; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.server.auth.AsyncAuthException; @@ -51,17 +52,17 @@ public interface PublickeyAuthenticator { /** * @param id Some kind of mnemonic identifier for the authenticator - used also in {@link #toString()} - * @param fallbackResolver The public key resolver to use if none of the default registered ones works * @param entries The entries to parse - ignored if {@code null}/empty + * @param fallbackResolver The public key resolver to use if none of the default registered ones works * @return A wrapper with all the parsed keys * @throws IOException If failed to parse the keys data * @throws GeneralSecurityException If failed to generate the relevant keys from the parsed data */ static PublickeyAuthenticator fromAuthorizedEntries( - Object id, PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries) + Object id, Collection<? extends AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException { Collection<PublicKey> keys = - AuthorizedKeyEntry.resolveAuthorizedKeys(fallbackResolver, entries); + PublicKeyEntry.resolvePublicKeyEntries(entries, fallbackResolver); if (GenericUtils.isEmpty(keys)) { return RejectAllPublickeyAuthenticator.INSTANCE; } else { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java index 3e9ca3a..d2e918d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java @@ -70,7 +70,7 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement new AtomicReference<>(RejectAllPublickeyAuthenticator.INSTANCE); public AuthorizedKeysAuthenticator(Path file) { - this(file, IoUtils.EMPTY_LINK_OPTIONS); + this(file, IoUtils.getLinkOptions(false)); } public AuthorizedKeysAuthenticator(Path file, LinkOption... options) { @@ -128,7 +128,7 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement Collection<AuthorizedKeyEntry> entries = reloadAuthorizedKeys(path, username, session); if (GenericUtils.size(entries) > 0) { PublickeyAuthenticator authDelegate = - createDelegateAuthenticator(path, entries, getFallbackPublicKeyEntryResolver()); + createDelegateAuthenticator(username, session, path, entries, getFallbackPublicKeyEntryResolver()); delegateHolder.set(authDelegate); } } else { @@ -140,9 +140,10 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement } protected PublickeyAuthenticator createDelegateAuthenticator( - Path path, Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) + String username, ServerSession session, Path path, + Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException { - return PublickeyAuthenticator.fromAuthorizedEntries(path, fallbackResolver, entries); + return PublickeyAuthenticator.fromAuthorizedEntries(path, entries, fallbackResolver); } protected PublicKeyEntryResolver getFallbackPublicKeyEntryResolver() { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java index 99f5e76..d626572 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryTest.java @@ -127,10 +127,10 @@ public class AuthorizedKeyEntryTest extends AuthorizedKeysTestSupport { private PublickeyAuthenticator testAuthorizedKeysAuth(Collection<AuthorizedKeyEntry> entries) throws IOException, GeneralSecurityException { Collection<PublicKey> keySet = - AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries); + PublicKeyEntry.resolvePublicKeyEntries(entries, PublicKeyEntryResolver.FAILING); PublickeyAuthenticator auth = PublickeyAuthenticator.fromAuthorizedEntries( - getCurrentTestName(), PublicKeyEntryResolver.FAILING, entries); + getCurrentTestName(), entries, PublicKeyEntryResolver.FAILING); for (PublicKey key : keySet) { assertTrue("Failed to authenticate with key=" + key.getAlgorithm(), auth.authenticate(getCurrentTestName(), key, null)); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java index fc5facc..cdd2122 100644 --- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java @@ -89,7 +89,7 @@ public class AuthorizedKeysAuthenticatorTest extends AuthorizedKeysTestSupport { List<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(file); assertEquals("Mismatched number of loaded entries", keyLines.size(), entries.size()); - List<PublicKey> keySet = AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries); + List<PublicKey> keySet = PublicKeyEntry.resolvePublicKeyEntries(entries, PublicKeyEntryResolver.FAILING); assertEquals("Mismatched number of loaded keys", entries.size(), keySet.size()); reloadCount.set(0); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java index 5d6b22a..d93abe7 100644 --- a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java @@ -25,6 +25,7 @@ import java.util.Collection; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.AuthorizedKeysTestSupport; +import org.apache.sshd.common.config.keys.PublicKeyEntry; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.util.OsUtils; import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; @@ -47,7 +48,8 @@ public class DefaultAuthorizedKeysAuthenticatorTest extends AuthorizedKeysTestSu writeDefaultSupportedKeys(file); Collection<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(file); - Collection<PublicKey> keySet = AuthorizedKeyEntry.resolveAuthorizedKeys(PublicKeyEntryResolver.FAILING, entries); + Collection<PublicKey> keySet = + PublicKeyEntry.resolvePublicKeyEntries(entries, PublicKeyEntryResolver.FAILING); PublickeyAuthenticator auth = new DefaultAuthorizedKeysAuthenticator(file, false); String thisUser = OsUtils.getCurrentUser(); for (String username : new String[]{null, "", thisUser, getClass().getName() + "#" + getCurrentTestName()}) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java new file mode 100644 index 0000000..e8d9715 --- /dev/null +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedEntriesTracker.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.openpgp; + +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.FilePasswordProviderManager; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.config.keys.PublicKeyEntry; +import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; +import org.apache.sshd.common.keyprovider.KeyTypeIndicator; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.resource.PathResource; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.openpgp.PGPException; +import org.c02e.jpgpj.Key; +import org.c02e.jpgpj.Subkey; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class PGPAuthorizedEntriesTracker + extends AbstractLoggingBean + implements PGPPublicKeyExtractor, FilePasswordProviderManager, PublicKeyEntryResolver { + private FilePasswordProvider filePasswordProvider; + private final List<PGPPublicKeyFileWatcher> keyFiles; + + public PGPAuthorizedEntriesTracker() { + this(Collections.emptyList()); + } + + public PGPAuthorizedEntriesTracker(Path path) { + this(path, null); + } + + public PGPAuthorizedEntriesTracker(Path path, FilePasswordProvider passwordProvider) { + this(Collections.singletonList(Objects.requireNonNull(path, "No path provided")), passwordProvider); + } + + public PGPAuthorizedEntriesTracker(Collection<? extends Path> keys) { + this(keys, null); + } + + public PGPAuthorizedEntriesTracker(Collection<? extends Path> keys, FilePasswordProvider passwordProvider) { + this.keyFiles = GenericUtils.isEmpty(keys) + ? new ArrayList<>() + : keys.stream() + .map(k -> new PGPPublicKeyFileWatcher(k)) + .collect(Collectors.toCollection(() -> new ArrayList<>(keys.size()))); + } + + @Override + public FilePasswordProvider getFilePasswordProvider() { + return filePasswordProvider; + } + + @Override + public void setFilePasswordProvider(FilePasswordProvider filePasswordProvider) { + this.filePasswordProvider = filePasswordProvider; + } + + public List<PGPPublicKeyFileWatcher> getWatchedFiles() { + return keyFiles; + } + + @Override + public PublicKey resolve(String keyType, byte[] keyData) + throws IOException, GeneralSecurityException { + if (!PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType)) { + return null; + } + + String fingerprint = PGPPublicKeyEntryDataResolver.encodeKeyFingerprint(keyData); + if (GenericUtils.isEmpty(fingerprint)) { + return null; + } + + Collection<PublicKey> keys; + try { + keys = loadMatchingKeyFingerprints(null, Collections.singletonList(fingerprint)); + } catch (PGPException e) { + throw new InvalidKeyException("Failed (" + e.getClass().getSimpleName() + ")" + + " to load key type=" + keyType + " with fingerprint=" + fingerprint + + ": " + e.getMessage(), e); + } + + int numKeys = GenericUtils.size(keys); + if (numKeys > 1) { + throw new StreamCorruptedException("Multiple matches (" + numKeys + ")" + + " for " + keyType + " fingerprint=" + fingerprint); + } + + return GenericUtils.head(keys); + } + + public void addWatchedFile(Path p) { + Objects.requireNonNull(p, "No file provided"); + List<PGPPublicKeyFileWatcher> files = getWatchedFiles(); + files.add(new PGPPublicKeyFileWatcher(p)); + } + + public List<PublicKey> resolveAuthorizedEntries( + SessionContext session, Collection<? extends PublicKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException, PGPException { + Map<String, ? extends Collection<PublicKeyEntry>> typesMap = KeyTypeIndicator.groupByKeyType(entries); + if (GenericUtils.isEmpty(typesMap)) { + return Collections.emptyList(); + } + + List<PublicKey> keys = new ArrayList<>(entries.size()); + for (Map.Entry<String, ? extends Collection<PublicKeyEntry>> te : typesMap.entrySet()) { + String keyType = te.getKey(); + Collection<PublicKeyEntry> keyEntries = te.getValue(); + Collection<PublicKey> subKeys = PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType) + ? loadMatchingAuthorizedEntries(session, keyEntries) + : PublicKeyEntry.resolvePublicKeyEntries(keyEntries, fallbackResolver); + if (GenericUtils.isEmpty(subKeys)) { + continue; + } + + keys.addAll(subKeys); + } + + return keys; + } + + public List<PublicKey> loadMatchingAuthorizedEntries( + SessionContext session, Collection<? extends PublicKeyEntry> entries) + throws IOException, GeneralSecurityException, PGPException { + int numEntries = GenericUtils.size(entries); + if (numEntries <= 0) { + return Collections.emptyList(); + } + + Collection<String> fingerprints = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + for (PublicKeyEntry pke : entries) { + String keyType = pke.getKeyType(); + if (GenericUtils.isEmpty(keyType) + || (!PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType))) { + continue; + } + + String fp = PGPPublicKeyEntryDataResolver.DEFAULT.encodeEntryKeyData(pke.getKeyData()); + if (GenericUtils.isEmpty(fp)) { + continue; + } + + if (!fingerprints.add(fp)) { + //noinspection UnnecessaryContinue + continue; // debug breakpoint + } + } + + return loadMatchingKeyFingerprints(session, fingerprints); + } + + public List<PublicKey> loadMatchingKeyFingerprints( + SessionContext session, Collection<String> fingerprints) + throws IOException, GeneralSecurityException, PGPException { + int numEntries = GenericUtils.size(fingerprints); + if (numEntries <= 0) { + return Collections.emptyList(); + } + + Collection<PGPPublicKeyFileWatcher> files = getWatchedFiles(); + int numFiles = GenericUtils.size(files); + if (numFiles <= 0) { + return Collections.emptyList(); + } + + List<PublicKey> keys = new ArrayList<>(Math.min(numEntries, numFiles)); + FilePasswordProvider provider = getFilePasswordProvider(); + boolean debugEnabled = log.isDebugEnabled(); + for (PGPPublicKeyFileWatcher f : files) { + PathResource resourceKey = f.toPathResource(); + Key container = f.loadPublicKey(session, resourceKey, provider); + Map<String, Subkey> fpMap = PGPUtils.mapSubKeysByFingerprint(container); + int numSubKeys = GenericUtils.size(fpMap); + Collection<Subkey> matches = (numSubKeys <= 0) + ? Collections.emptyList() + : fpMap.entrySet() + .stream() + .filter(e -> fingerprints.contains(e.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toCollection(() -> new ArrayList<>(numSubKeys))); + int numMatches = GenericUtils.size(matches); + if (debugEnabled) { + log.debug("loadMatchingKeyFingerprints({}) found {}/{} matches in {}", + session, numMatches, numEntries, resourceKey); + } + if (numMatches <= 0) { + continue; // debug breakpoint + } + + for (Subkey sk : matches) { + PublicKey pk; + try { + pk = extractPublicKey(resourceKey, sk); + if (pk == null) { + continue; // debug breakpoint + } + } catch (IOException | GeneralSecurityException | RuntimeException e) { + log.error("loadMatchingKeyFingerprints({}) failed ({}) to convert {} from {} to public key: {}", + session, e.getClass().getSimpleName(), sk, resourceKey, e.getMessage()); + if (debugEnabled) { + log.debug("loadMatchingKeyFingerprints(" + session + ")[" + resourceKey + "][" + sk + "] conversion failure details", e); + } + throw e; + } + + if (debugEnabled) { + log.debug("loadMatchingKeyFingerprints({}) loaded key={}, fingerprint={}, hash={} from {}", + session, KeyUtils.getKeyType(pk), sk.getFingerprint(), KeyUtils.getFingerPrint(pk), resourceKey); + } + keys.add(pk); + } + } + + return keys; + } + + @Override + public <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec) + throws GeneralSecurityException { + KeyFactory factory = getKeyFactory(algorithm); + PublicKey pubKey = factory.generatePublic(keySpec); + return keyType.cast(pubKey); + } + + protected KeyFactory getKeyFactory(String algorithm) throws GeneralSecurityException { + return SecurityUtils.getKeyFactory(algorithm); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java new file mode 100644 index 0000000..d2567fb --- /dev/null +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyFileWatcher.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.openpgp; + +import java.nio.file.LinkOption; +import java.nio.file.Path; + +import org.apache.sshd.common.config.keys.loader.FileWatcherKeyPairResourceLoader; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; +import org.apache.sshd.common.util.io.IoUtils; + +/** + * Tracks the contents of a PGP key file - uses the default {@link PGPKeyPairResourceParser#INSTANCE instance} + * unless otherwise specified. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class PGPKeyFileWatcher extends FileWatcherKeyPairResourceLoader { + public PGPKeyFileWatcher(Path file) { + this(file, IoUtils.getLinkOptions(true)); + } + + public PGPKeyFileWatcher(Path file, LinkOption... options) { + this(file, PGPKeyPairResourceParser.INSTANCE, options); + } + + public PGPKeyFileWatcher(Path file, KeyPairResourceLoader delegateLoader) { + this(file, delegateLoader, IoUtils.getLinkOptions(true)); + } + + public PGPKeyFileWatcher(Path file, KeyPairResourceLoader delegateLoader, LinkOption... options) { + super(file, delegateLoader, options); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java new file mode 100644 index 0000000..2d9feb6 --- /dev/null +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyLoader.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.openpgp; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.security.GeneralSecurityException; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.resource.IoResource; +import org.apache.sshd.common.util.io.resource.PathResource; +import org.apache.sshd.common.util.io.resource.URLResource; +import org.bouncycastle.openpgp.PGPException; +import org.c02e.jpgpj.Key; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface PGPKeyLoader { + default Key loadPGPKey( + SessionContext session, URL url, FilePasswordProvider passwordProvider) + throws IOException, GeneralSecurityException, PGPException { + return loadPGPKey(session, new URLResource(url), passwordProvider); + } + + default Key loadPGPKey( + SessionContext session, Path path, FilePasswordProvider passwordProvider, OpenOption... options) + throws IOException, GeneralSecurityException, PGPException { + return loadPGPKey(session, new PathResource(path, options), passwordProvider); + } + + default Key loadPGPKey( + SessionContext session, IoResource<?> resourceKey, FilePasswordProvider passwordProvider) + throws IOException, GeneralSecurityException, PGPException { + try (InputStream input = resourceKey.openInputStream()) { + return loadPGPKey(session, resourceKey, passwordProvider, input); + } + } + + default Key loadPGPKey( + SessionContext session, NamedResource resourceKey, FilePasswordProvider passwordProvider, InputStream input) + throws IOException, GeneralSecurityException, PGPException { + return loadPGPKey(input, (passwordProvider == null) ? null : passwordProvider.getPassword(session, resourceKey, 0)); + } + + static Key loadPGPKey(InputStream input, String password) throws IOException, PGPException { + boolean withPassword = GenericUtils.isNotEmpty(password); + Key key = withPassword ? new Key(input, password) : new Key(input); + if (!withPassword) { + key.setNoPassphrase(true); + } + return key; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java index e6ae158..740edc3 100644 --- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java @@ -22,72 +22,39 @@ package org.apache.sshd.common.config.keys.loader.openpgp; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; -import java.math.BigInteger; import java.net.ProtocolException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; -import java.security.spec.RSAPrivateCrtKeySpec; -import java.security.spec.RSAPublicKeySpec; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Objects; import org.apache.sshd.common.NamedResource; -import org.apache.sshd.common.cipher.ECCurves; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.security.SecurityUtils; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.bcpg.BCPGKey; -import org.bouncycastle.bcpg.DSAPublicBCPGKey; -import org.bouncycastle.bcpg.DSASecretBCPGKey; -import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; -import org.bouncycastle.bcpg.ECPublicBCPGKey; -import org.bouncycastle.bcpg.ECSecretBCPGKey; -import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; -import org.bouncycastle.bcpg.EdSecretBCPGKey; -import org.bouncycastle.bcpg.PublicKeyPacket; -import org.bouncycastle.bcpg.RSAPublicBCPGKey; -import org.bouncycastle.bcpg.RSASecretBCPGKey; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; import org.c02e.jpgpj.Key; import org.c02e.jpgpj.Subkey; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class PGPKeyPairResourceParser extends AbstractKeyPairResourceParser { +public class PGPKeyPairResourceParser + extends AbstractKeyPairResourceParser + implements PGPKeyLoader, + PGPPublicKeyExtractor, + PGPPrivateKeyExtractor { public static final String BEGIN_MARKER = "BEGIN PGP PRIVATE KEY BLOCK"; public static final List<String> BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER)); @@ -142,13 +109,7 @@ public class PGPKeyPairResourceParser extends AbstractKeyPairResourceParser { stream.reset(); } - Key key = new Key(stream, password); - if (GenericUtils.isEmpty(password)) { - key.setNoPassphrase(true); - } else { - key.setPassphrase(password); - } - + Key key = PGPKeyLoader.loadPGPKey(stream, password); keys = extractKeyPairs(resourceKey, key.getSubkeys()); } catch (IOException | GeneralSecurityException | PGPException | RuntimeException e) { ResourceDecodeResult result = (passwordProvider != null) @@ -250,197 +211,16 @@ public class PGPKeyPairResourceParser extends AbstractKeyPairResourceParser { return kpList; } - public PublicKey extractPublicKey(NamedResource resourceKey, Subkey sk) throws IOException, GeneralSecurityException { - if (sk == null) { - return null; - } - - PGPPublicKey pgpKey = Objects.requireNonNull(sk.getPublicKey(), "Missing sub-key public key"); - PublicKeyPacket pgpPacket = Objects.requireNonNull(pgpKey.getPublicKeyPacket(), "Missing public key packet"); - BCPGKey bcKey = Objects.requireNonNull(pgpPacket.getKey(), "Missing BC key"); - if (bcKey instanceof RSAPublicBCPGKey) { - return extractRSAPublicKey(resourceKey, (RSAPublicBCPGKey) bcKey); - } else if (bcKey instanceof ECPublicBCPGKey) { - return extractECPublicKey(resourceKey, (ECPublicBCPGKey) bcKey); - } else if (bcKey instanceof DSAPublicBCPGKey) { - return extractDSSPublicKey(resourceKey, (DSAPublicBCPGKey) bcKey); - } else { - throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName()); - } - } - - public RSAPublicKey extractRSAPublicKey(NamedResource resourceKey, RSAPublicBCPGKey bcKey) throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - BigInteger e = bcKey.getPublicExponent(); - BigInteger n = bcKey.getModulus(); - return generatePublicKey(KeyUtils.RSA_ALGORITHM, RSAPublicKey.class, new RSAPublicKeySpec(n, e)); - } - - public PublicKey extractECPublicKey(NamedResource resourceKey, ECPublicBCPGKey bcKey) throws GeneralSecurityException { - if (bcKey == null) { - return null; - } else if (bcKey instanceof EdDSAPublicBCPGKey) { - return extractEdDSAPublicKey(resourceKey, (EdDSAPublicBCPGKey) bcKey); - } else if (bcKey instanceof ECDSAPublicBCPGKey) { - return extractECDSAPublicKey(resourceKey, (ECDSAPublicBCPGKey) bcKey); - } else { - throw new NoSuchAlgorithmException("Unsupported EC public key type: " + bcKey.getClass().getSimpleName()); - } - } - - public ECPublicKey extractECDSAPublicKey(NamedResource resourceKey, ECDSAPublicBCPGKey bcKey) throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - ASN1ObjectIdentifier asnId = bcKey.getCurveOID(); - String oid = asnId.getId(); - ECCurves curve = ECCurves.fromOID(oid); - if (curve == null) { - throw new InvalidKeySpecException("Not an EC curve OID: " + oid); - } - - if (!SecurityUtils.isECCSupported()) { - throw new NoSuchProviderException("ECC not supported"); - } - - BigInteger encPoint = bcKey.getEncodedPoint(); - byte[] octets = encPoint.toByteArray(); - ECPoint w; - try { - w = ECCurves.octetStringToEcPoint(octets); - if (w == null) { - throw new InvalidKeySpecException("No ECPoint generated for curve=" + curve.getName() - + " from octets=" + BufferUtils.toHex(':', octets)); - } - } catch (RuntimeException e) { - throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")" - + " to generate ECPoint for curve=" + curve.getName() - + " from octets=" + BufferUtils.toHex(':', octets) - + ": " + e.getMessage()); - } - - ECParameterSpec paramSpec = curve.getParameters(); - return generatePublicKey(KeyUtils.EC_ALGORITHM, ECPublicKey.class, new ECPublicKeySpec(w, paramSpec)); - } - - public PublicKey extractEdDSAPublicKey(NamedResource resourceKey, EdDSAPublicBCPGKey bcKey) throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchProviderException("EdDSA not supported"); - } - - throw new NoSuchAlgorithmException("Unsupported EdDSA public key type: " + bcKey.getClass().getSimpleName()); - } - - public DSAPublicKey extractDSSPublicKey(NamedResource resourceKey, DSAPublicBCPGKey bcKey) throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - BigInteger p = bcKey.getP(); - BigInteger q = bcKey.getQ(); - BigInteger g = bcKey.getG(); - BigInteger y = bcKey.getY(); - return generatePublicKey(KeyUtils.DSS_ALGORITHM, DSAPublicKey.class, new DSAPublicKeySpec(y, p, q, g)); - } - - protected <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec) + @Override + public <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec) throws GeneralSecurityException { KeyFactory factory = getKeyFactory(algorithm); PublicKey pubKey = factory.generatePublic(keySpec); return keyType.cast(pubKey); } - public PrivateKey extractPrivateKey(NamedResource resourceKey, Subkey sk, PublicKey pubKey) - throws IOException, GeneralSecurityException, PGPException { - if (sk == null) { - return null; - } - - PGPPrivateKey pgpKey = Objects.requireNonNull(sk.getPrivateKey(), "Missing sub-key private key"); - BCPGKey bcKey = Objects.requireNonNull(pgpKey.getPrivateKeyDataPacket(), "Missing BC key"); - if (bcKey instanceof RSASecretBCPGKey) { - return extractRSAPrivateKey(resourceKey, (RSAPublicKey) pubKey, (RSASecretBCPGKey) bcKey); - } else if (bcKey instanceof ECSecretBCPGKey) { - return extractECDSAPrivateKey(resourceKey, (ECPublicKey) pubKey, (ECSecretBCPGKey) bcKey); - } else if (bcKey instanceof EdSecretBCPGKey) { - return extractEdDSAPrivateKey(resourceKey, pubKey, (EdSecretBCPGKey) bcKey); - } else if (bcKey instanceof DSASecretBCPGKey) { - return extractDSSPrivateKey(resourceKey, (DSAPublicKey) pubKey, (DSASecretBCPGKey) bcKey); - } else { - throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName()); - } - } - - public ECPrivateKey extractECDSAPrivateKey(NamedResource resourceKey, ECPublicKey pubKey, ECSecretBCPGKey bcKey) - throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - if (!SecurityUtils.isECCSupported()) { - throw new NoSuchProviderException("ECC not supported"); - } - - ECParameterSpec params = pubKey.getParams(); - BigInteger x = bcKey.getX(); - return generatePrivateKey(KeyUtils.EC_ALGORITHM, ECPrivateKey.class, new ECPrivateKeySpec(x, params)); - } - - public PrivateKey extractEdDSAPrivateKey(NamedResource resourceKey, PublicKey pubKey, EdSecretBCPGKey bcKey) - throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchProviderException("EdDSA not supported"); - } - - throw new NoSuchAlgorithmException("Unsupported EdDSA private key type: " + bcKey.getClass().getSimpleName()); - } - - public RSAPrivateKey extractRSAPrivateKey(NamedResource resourceKey, RSAPublicKey pubKey, RSASecretBCPGKey bcKey) - throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - return generatePrivateKey(KeyUtils.RSA_ALGORITHM, RSAPrivateKey.class, - new RSAPrivateCrtKeySpec( - bcKey.getModulus(), - pubKey.getPublicExponent(), - bcKey.getPrivateExponent(), - bcKey.getPrimeP(), - bcKey.getPrimeQ(), - bcKey.getPrimeExponentP(), - bcKey.getPrimeExponentQ(), - bcKey.getCrtCoefficient())); - } - - public DSAPrivateKey extractDSSPrivateKey(NamedResource resourceKey, DSAPublicKey pubKey, DSASecretBCPGKey bcKey) - throws GeneralSecurityException { - if (bcKey == null) { - return null; - } - - DSAParams params = pubKey.getParams(); - if (params == null) { - throw new InvalidKeyException("Missing parameters in public key"); - } - - return generatePrivateKey(KeyUtils.DSS_ALGORITHM, DSAPrivateKey.class, - new DSAPrivateKeySpec(bcKey.getX(), params.getP(), params.getQ(), params.getG())); - } - - protected <K extends PrivateKey> K generatePrivateKey(String algorithm, Class<K> keyType, KeySpec keySpec) + @Override + public <K extends PrivateKey> K generatePrivateKey(String algorithm, Class<K> keyType, KeySpec keySpec) throws GeneralSecurityException { KeyFactory factory = getKeyFactory(algorithm); PrivateKey prvKey = factory.generatePrivate(keySpec); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java new file mode 100644 index 0000000..e797071 --- /dev/null +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPrivateKeyExtractor.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.openpgp; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.util.Objects; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.bcpg.BCPGKey; +import org.bouncycastle.bcpg.DSASecretBCPGKey; +import org.bouncycastle.bcpg.ECSecretBCPGKey; +import org.bouncycastle.bcpg.EdSecretBCPGKey; +import org.bouncycastle.bcpg.RSASecretBCPGKey; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.c02e.jpgpj.Subkey; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface PGPPrivateKeyExtractor { + default PrivateKey extractPrivateKey( + NamedResource resourceKey, Subkey sk, PublicKey pubKey) + throws IOException, GeneralSecurityException, PGPException { + if (sk == null) { + return null; + } + + PGPPrivateKey pgpKey = Objects.requireNonNull(sk.getPrivateKey(), "Missing sub-key private key"); + BCPGKey bcKey = Objects.requireNonNull(pgpKey.getPrivateKeyDataPacket(), "Missing BC key"); + if (bcKey instanceof RSASecretBCPGKey) { + return extractRSAPrivateKey(resourceKey, (RSAPublicKey) pubKey, (RSASecretBCPGKey) bcKey); + } else if (bcKey instanceof ECSecretBCPGKey) { + return extractECDSAPrivateKey(resourceKey, (ECPublicKey) pubKey, (ECSecretBCPGKey) bcKey); + } else if (bcKey instanceof EdSecretBCPGKey) { + return extractEdDSAPrivateKey(resourceKey, pubKey, (EdSecretBCPGKey) bcKey); + } else if (bcKey instanceof DSASecretBCPGKey) { + return extractDSSPrivateKey(resourceKey, (DSAPublicKey) pubKey, (DSASecretBCPGKey) bcKey); + } else { + throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName()); + } + } + + default ECPrivateKey extractECDSAPrivateKey( + NamedResource resourceKey, ECPublicKey pubKey, ECSecretBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + if (!SecurityUtils.isECCSupported()) { + throw new NoSuchProviderException("ECC not supported"); + } + + ECParameterSpec params = pubKey.getParams(); + BigInteger x = bcKey.getX(); + return generatePrivateKey(KeyUtils.EC_ALGORITHM, ECPrivateKey.class, new ECPrivateKeySpec(x, params)); + } + + default PrivateKey extractEdDSAPrivateKey( + NamedResource resourceKey, PublicKey pubKey, EdSecretBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + if (!SecurityUtils.isEDDSACurveSupported()) { + throw new NoSuchProviderException("EdDSA not supported"); + } + + throw new NoSuchAlgorithmException("Unsupported EdDSA private key type: " + bcKey.getClass().getSimpleName()); + } + + default RSAPrivateKey extractRSAPrivateKey( + NamedResource resourceKey, RSAPublicKey pubKey, RSASecretBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + return generatePrivateKey(KeyUtils.RSA_ALGORITHM, RSAPrivateKey.class, + new RSAPrivateCrtKeySpec( + bcKey.getModulus(), + pubKey.getPublicExponent(), + bcKey.getPrivateExponent(), + bcKey.getPrimeP(), + bcKey.getPrimeQ(), + bcKey.getPrimeExponentP(), + bcKey.getPrimeExponentQ(), + bcKey.getCrtCoefficient())); + } + + default DSAPrivateKey extractDSSPrivateKey( + NamedResource resourceKey, DSAPublicKey pubKey, DSASecretBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + DSAParams params = pubKey.getParams(); + if (params == null) { + throw new InvalidKeyException("Missing parameters in public key"); + } + + return generatePrivateKey(KeyUtils.DSS_ALGORITHM, DSAPrivateKey.class, + new DSAPrivateKeySpec(bcKey.getX(), params.getP(), params.getQ(), params.getG())); + } + + <K extends PrivateKey> K generatePrivateKey(String algorithm, Class<K> keyType, KeySpec keySpec) + throws GeneralSecurityException; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java index c32103e..49dd912 100644 --- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDataResolver.java @@ -22,7 +22,6 @@ package org.apache.sshd.common.config.keys.loader.openpgp; import java.util.Collections; import java.util.NavigableSet; -import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PublicKeyEntry; import org.apache.sshd.common.config.keys.PublicKeyEntryDataResolver; import org.apache.sshd.common.util.GenericUtils; @@ -54,17 +53,25 @@ public class PGPPublicKeyEntryDataResolver implements PublicKeyEntryDataResolver @Override public byte[] decodeEntryKeyData(String encData) { + return decodeKeyFingerprint(encData); + } + + @Override + public String encodeEntryKeyData(byte[] keyData) { + return encodeKeyFingerprint(keyData); + } + + public static byte[] decodeKeyFingerprint(String encData) { if (GenericUtils.isEmpty(encData)) { - return GenericUtils.EMPTY_BYTE_ARRAY; + return GenericUtils.EMPTY_BYTE_ARRAY; // debug breakpoint } return BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, encData); } - @Override - public String encodeEntryKeyData(byte[] keyData) { + public static String encodeKeyFingerprint(byte[] keyData) { if (NumberUtils.isEmpty(keyData)) { - return ""; + return ""; // debug breakpoint } return BufferUtils.toHex(BufferUtils.EMPTY_HEX_SEPARATOR, keyData).toUpperCase(); @@ -77,11 +84,8 @@ public class PGPPublicKeyEntryDataResolver implements PublicKeyEntryDataResolver * @see PublicKeyEntry#registerKeyDataEntryResolver(String, PublicKeyEntryDataResolver) */ public static void registerDefaultKeyEntryDataResolvers() { - // TODO fix this code once SSHD-757 fully implemented - PGPPublicKeyEntryDecoder dummy = new PGPPublicKeyEntryDecoder(); for (String keyType : PGP_KEY_TYPES) { PublicKeyEntry.registerKeyDataEntryResolver(keyType, DEFAULT); - KeyUtils.registerPublicKeyEntryDecoderForKeyType(keyType, dummy); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java deleted file mode 100644 index 9ad2313..0000000 --- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyEntryDecoder.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.sshd.common.config.keys.loader.openpgp; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.KeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; - -import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; -import org.apache.sshd.common.util.buffer.BufferUtils; -import org.apache.sshd.common.util.io.IoUtils; - -/** - * @author <a href="mailto:[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/193c9326/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java ---------------------------------------------------------------------- diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java new file mode 100644 index 0000000..cb68837 --- /dev/null +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPPublicKeyExtractor.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.openpgp; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Objects; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.cipher.ECCurves; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.bcpg.BCPGKey; +import org.bouncycastle.bcpg.DSAPublicBCPGKey; +import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; +import org.bouncycastle.bcpg.ECPublicBCPGKey; +import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.RSAPublicBCPGKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.c02e.jpgpj.Subkey; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface PGPPublicKeyExtractor { + default PublicKey extractPublicKey(NamedResource resourceKey, Subkey sk) + throws IOException, GeneralSecurityException { + if (sk == null) { + return null; + } + + PGPPublicKey pgpKey = Objects.requireNonNull(sk.getPublicKey(), "Missing sub-key public key"); + PublicKeyPacket pgpPacket = Objects.requireNonNull(pgpKey.getPublicKeyPacket(), "Missing public key packet"); + BCPGKey bcKey = Objects.requireNonNull(pgpPacket.getKey(), "Missing BC key"); + if (bcKey instanceof RSAPublicBCPGKey) { + return extractRSAPublicKey(resourceKey, (RSAPublicBCPGKey) bcKey); + } else if (bcKey instanceof ECPublicBCPGKey) { + return extractECPublicKey(resourceKey, (ECPublicBCPGKey) bcKey); + } else if (bcKey instanceof DSAPublicBCPGKey) { + return extractDSSPublicKey(resourceKey, (DSAPublicBCPGKey) bcKey); + } else { + throw new NoSuchAlgorithmException("Unsupported BC public key type: " + bcKey.getClass().getSimpleName()); + } + } + + default RSAPublicKey extractRSAPublicKey(NamedResource resourceKey, RSAPublicBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + BigInteger e = bcKey.getPublicExponent(); + BigInteger n = bcKey.getModulus(); + return generatePublicKey(KeyUtils.RSA_ALGORITHM, RSAPublicKey.class, new RSAPublicKeySpec(n, e)); + } + + default PublicKey extractECPublicKey(NamedResource resourceKey, ECPublicBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } else if (bcKey instanceof EdDSAPublicBCPGKey) { + return extractEdDSAPublicKey(resourceKey, (EdDSAPublicBCPGKey) bcKey); + } else if (bcKey instanceof ECDSAPublicBCPGKey) { + return extractECDSAPublicKey(resourceKey, (ECDSAPublicBCPGKey) bcKey); + } else { + throw new NoSuchAlgorithmException("Unsupported EC public key type: " + bcKey.getClass().getSimpleName()); + } + } + + default ECPublicKey extractECDSAPublicKey(NamedResource resourceKey, ECDSAPublicBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + ASN1ObjectIdentifier asnId = bcKey.getCurveOID(); + String oid = asnId.getId(); + ECCurves curve = ECCurves.fromOID(oid); + if (curve == null) { + throw new InvalidKeySpecException("Not an EC curve OID: " + oid); + } + + if (!SecurityUtils.isECCSupported()) { + throw new NoSuchProviderException("ECC not supported"); + } + + BigInteger encPoint = bcKey.getEncodedPoint(); + byte[] octets = encPoint.toByteArray(); + ECPoint w; + try { + w = ECCurves.octetStringToEcPoint(octets); + if (w == null) { + throw new InvalidKeySpecException("No ECPoint generated for curve=" + curve.getName() + + " from octets=" + BufferUtils.toHex(':', octets)); + } + } catch (RuntimeException e) { + throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")" + + " to generate ECPoint for curve=" + curve.getName() + + " from octets=" + BufferUtils.toHex(':', octets) + + ": " + e.getMessage()); + } + + ECParameterSpec paramSpec = curve.getParameters(); + return generatePublicKey(KeyUtils.EC_ALGORITHM, ECPublicKey.class, new ECPublicKeySpec(w, paramSpec)); + } + + default PublicKey extractEdDSAPublicKey(NamedResource resourceKey, EdDSAPublicBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + if (!SecurityUtils.isEDDSACurveSupported()) { + throw new NoSuchProviderException("EdDSA not supported"); + } + + throw new NoSuchAlgorithmException("Unsupported EdDSA public key type: " + bcKey.getClass().getSimpleName()); + } + + default DSAPublicKey extractDSSPublicKey(NamedResource resourceKey, DSAPublicBCPGKey bcKey) + throws IOException, GeneralSecurityException { + if (bcKey == null) { + return null; + } + + BigInteger p = bcKey.getP(); + BigInteger q = bcKey.getQ(); + BigInteger g = bcKey.getG(); + BigInteger y = bcKey.getY(); + return generatePublicKey(KeyUtils.DSS_ALGORITHM, DSAPublicKey.class, new DSAPublicKeySpec(y, p, q, g)); + } + + <K extends PublicKey> K generatePublicKey(String algorithm, Class<K> keyType, KeySpec keySpec) + throws GeneralSecurityException; +}
