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