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 2022/02/17 15:46:52 UTC
[mina-sshd] 02/02: [SSHD-1246] Added SshKeyDumpMain utility
This is an automated email from the ASF dual-hosted git repository.
lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 8ea60dfa27b828f3122ff33dd14717eea1d30b45
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Thu Feb 17 10:43:16 2022 +0200
[SSHD-1246] Added SshKeyDumpMain utility
---
CHANGES.md | 3 +-
docs/client-setup.md | 53 ++-
sshd-cli/pom.xml | 13 +-
.../java/org/apache/sshd/cli/SshKeyDumpMain.java | 408 +++++++++++++++++++++
.../sshd/common/config/keys/PublicKeyEntry.java | 5 +
.../org/apache/sshd/common/util/io/PathUtils.java | 8 +-
.../OpenSSHKeyPairResourceParserTestSupport.java | 3 +-
.../apache/sshd/common/util/io/PathUtilsTest.java | 7 +-
.../GenerateOpenSSHClientCertificateTest.java | 6 +-
...GenerateOpenSshClientCertificateOracleTest.java | 4 +-
.../certificates/OpenSSHCertificateParserTest.java | 2 +-
.../ClientOpenSSHCertificatesTest.java | 13 +-
.../common/config/keys/AuthorizedKeyEntryTest.java | 2 +-
.../FileHostKeyCertificateProviderTest.java | 3 +-
.../common/signature/OpenSSHCertificateTest.java | 5 +-
15 files changed, 501 insertions(+), 34 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 894d234..fb283e5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -69,7 +69,6 @@ Was originally in *HostConfigEntry*.
* [SSHD-1233](https://issues.apache.org/jira/browse/SSHD-1233) Added support for "limits@openssh.com" SFTP extension
* [SSHD-1244](https://issues.apache.org/jira/browse/SSHD-1244) Fixed channel window adjustment handling of large UINT32 values
* [SSHD-1244](https://issues.apache.org/jira/browse/SSHD-1244) Re-defined channel identifiers as `long` rather than `int` to align with protocol UINT32 definition
-
-
+* [SSHD-1246](https://issues.apache.org/jira/browse/SSHD-1246) Added SshKeyDumpMain utility
diff --git a/docs/client-setup.md b/docs/client-setup.md
index 0fc059e..bd6df7e 100644
--- a/docs/client-setup.md
+++ b/docs/client-setup.md
@@ -50,6 +50,57 @@ and presenting them to the server as part of the authentication process. Reading
for the standard keys and formats. Using additional non-standard special features requires that the [Bouncy Castle](https://www.bouncycastle.org/) supporting
artifacts be available in the code's classpath.
+#### Loading key files
+
+In order to use password-less authentication the user needs to provide one or more `KeyPair`-s that are used to "prove" the client's identity for
+the server. The code supports most if not all of the currently used key file formats. See `SshKeyDumpMain` class for example of how to load files - basically:
+
+```java
+ KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser();
+ Collection<KeyPair> keys = loader.loadKeyPairs(null, filePath, passwordProvider);
+```
+
+For *PUTTY* key files one needs to include the *sshd-putty* module and use a different loader:
+
+```java
+ Collection<KeyPair> keys = PuttyKeyUtils.DEFAULT_INSTANCE.loadKeyPairs(null, filePath, passwordProvider);
+```
+
+**Note:** reminder - a user's "identity" is the file that contains the **private** key - there is no need to provide the public key file since the
+private key either already contains the public key in it, or it can be easily calculated from the private one.
+
+Once the keys are loaded, one simply needs to provide them to the client session:
+
+```java
+ try (ClientSession session = ...estblish initial session...) {
+ for (KeyPair kp : keys) {
+ session.addKeyIdentity(kp);
+ }
+
+ session.auth().await(...);
+ }
+```
+
+Instead of doing this on every session, it is possible to load the keys only **once** and then wrap them inside a `KeyIdentityProvider`
+that is setup during *SshClient* setup:
+
+```java
+ Collection<KeyPair> keys = ...load the keys ...
+ SshClient client = ...setup client...
+ client.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys));
+ client.start();
+```
+
+The provided keys will be used for **all* the sessions - *Note:*
+
+* One can **add** key identities to specific sessions.
+
+* A similar effect can be achiveved for **passwords** by registering a `PasswordIdentityProvider` with the *SshClient*, and
+thus forego the need to provide the password repeatedly for each session. In this context, one can go even one step forward
+and provide a **combined** `AuthenticationIdentitiesProvider` that provides **both** passwords and key pairs. Both type of providers
+are invoked with the established `SessionContext` so the user can actually pick which mechanism to use, what password/key to
+use according to the server's identity.
+
#### Providing passwords for encrypted key files
The `FilePasswordProvider` is required for all private key files that are encrypted and being loaded (not just the "identity" ones). If the user
@@ -100,7 +151,7 @@ This interface is required for full support of `keyboard-interactive` authentica
The client can handle a simple password request from the server, but if more complex challenge-response interaction is required, then this interface must be
provided - including support for `SSH_MSG_USERAUTH_PASSWD_CHANGEREQ` as described in [RFC 4252 section 8](https://tools.ietf.org/html/rfc4252#section-8).
-While ]RFC-4256](https://tools.ietf.org/html/rfc4256) support is the primary purpose of this interface, it can also be used to retrieve the server's
+While [RFC-4256](https://tools.ietf.org/html/rfc4256) support is the primary purpose of this interface, it can also be used to retrieve the server's
welcome banner as described in [RFC 4252 section 5.4](https://tools.ietf.org/html/rfc4252#section-5.4) as well as its initial identification string
as described in [RFC 4253 section 4.2](https://tools.ietf.org/html/rfc4253#section-4.2).
diff --git a/sshd-cli/pom.xml b/sshd-cli/pom.xml
index d2d099b..cb5d62b 100644
--- a/sshd-cli/pom.xml
+++ b/sshd-cli/pom.xml
@@ -55,6 +55,12 @@
<artifactId>sshd-putty</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- For ed25519 support -->
+ <dependency>
+ <groupId>net.i2p.crypto</groupId>
+ <artifactId>eddsa</artifactId>
+ <optional>true</optional>
+ </dependency>
<!-- Test dependencies -->
<dependency>
@@ -95,13 +101,6 @@
<artifactId>jzlib</artifactId>
<scope>test</scope>
</dependency>
- <!-- For ed25519 support -->
- <dependency>
- <groupId>net.i2p.crypto</groupId>
- <artifactId>eddsa</artifactId>
- <optional>true</optional>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/SshKeyDumpMain.java b/sshd-cli/src/test/java/org/apache/sshd/cli/SshKeyDumpMain.java
new file mode 100644
index 0000000..330d687
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/SshKeyDumpMain.java
@@ -0,0 +1,408 @@
+/*
+ * 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.cli;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.Key;
+import java.security.KeyPair;
+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.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECField;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.EllipticCurve;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import net.i2p.crypto.eddsa.EdDSAPrivateKey;
+import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import net.i2p.crypto.eddsa.math.Curve;
+import net.i2p.crypto.eddsa.math.Field;
+import net.i2p.crypto.eddsa.math.GroupElement;
+import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.PathUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.putty.PuttyKeyPairResourceParser;
+import org.apache.sshd.putty.PuttyKeyUtils;
+import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum SshKeyDumpMain {
+ /* Utility class */;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ public static void dumpRSAPublicKey(RSAPublicKey key, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("e: ").append(Objects.toString(key.getPublicExponent(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("n: ").append(Objects.toString(key.getModulus(), null))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpRSAPrivateKey(RSAPrivateKey key, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("d: ").append(Objects.toString(key.getPrivateExponent(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("n: ").append(Objects.toString(key.getModulus(), null))
+ .append(System.lineSeparator());
+ if (key instanceof RSAPrivateCrtKey) {
+ RSAPrivateCrtKey crt = RSAPrivateCrtKey.class.cast(key);
+ stdout.append(indent)
+ .append("e: ").append(Objects.toString(crt.getPublicExponent(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("P: ").append(Objects.toString(crt.getPrimeP(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("Q: ").append(Objects.toString(crt.getPrimeQ(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("expP: ").append(Objects.toString(crt.getPrimeExponentP(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("expQ: ").append(Objects.toString(crt.getPrimeExponentQ(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("coefficient: ").append(Objects.toString(crt.getCrtCoefficient(), null))
+ .append(System.lineSeparator());
+ }
+ }
+
+ public static void dumpDSAParams(DSAParams params, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("G: ").append(Objects.toString(params.getG(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("P: ").append(Objects.toString(params.getP(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("Q: ").append(Objects.toString(params.getQ(), null))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpDSAPublicKey(DSAPublicKey key, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("Y: ").append(Objects.toString(key.getY(), null))
+ .append(System.lineSeparator());
+ dumpDSAParams(key.getParams(), indent + " ",
+ stdout.append(indent).append("params:").append(System.lineSeparator()));
+ }
+
+ public static void dumpDSAPrivateKey(DSAPrivateKey key, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("X: ").append(Objects.toString(key.getX(), null))
+ .append(System.lineSeparator());
+ dumpDSAParams(key.getParams(), indent + " ",
+ stdout.append(indent).append("params:").append(System.lineSeparator()));
+ }
+
+ public static void dumpECPoint(ECPoint point, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("X: ").append(Objects.toString(point.getAffineX(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("Y: ").append(Objects.toString(point.getAffineY(), null))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpECField(ECField field, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("size: ").append(Integer.toString(field.getFieldSize()))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpEllipticCurve(EllipticCurve curve, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("A: ").append(Objects.toString(curve.getA(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("B: ").append(Objects.toString(curve.getB(), null))
+ .append(System.lineSeparator());
+ BufferUtils.appendHex(stdout.append(indent).append("seed: "), ' ', curve.getSeed()).append(System.lineSeparator());
+ dumpECField(curve.getField(), indent + " ",
+ stdout.append(indent).append("field:").append(System.lineSeparator()));
+ }
+
+ public static void dumpECParameterSpec(ECParameterSpec spec, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("order: ").append(Objects.toString(spec.getOrder(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("cofactor: ").append(Integer.toString(spec.getCofactor()))
+ .append(System.lineSeparator());
+ dumpEllipticCurve(spec.getCurve(), indent + " ",
+ stdout.append(indent).append("curve:").append(System.lineSeparator()));
+ dumpECPoint(spec.getGenerator(), indent + " ",
+ stdout.append(indent).append("generator:").append(System.lineSeparator()));
+ }
+
+ public static void dumpECPublicKey(ECPublicKey key, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("W: ").append(Objects.toString(key.getW(), null))
+ .append(System.lineSeparator());
+ dumpECParameterSpec(key.getParams(), indent + " ",
+ stdout.append(indent).append("params:").append(System.lineSeparator()));
+ }
+
+ public static void dumpECPrivateKey(ECPrivateKey key, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("S: ").append(Objects.toString(key.getS(), null))
+ .append(System.lineSeparator());
+ dumpECParameterSpec(key.getParams(), indent + " ",
+ stdout.append(indent).append("params:").append(System.lineSeparator()));
+ }
+
+ public static void dumpEdDSAField(Field field, CharSequence indent, Appendable stdout) throws IOException {
+ stdout.append(indent)
+ .append("Q: ").append(Objects.toString(field.getQ(), null))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpEdDSACurve(Curve curve, CharSequence indent, Appendable stdout) throws IOException {
+ dumpEdDSAField(curve.getField(), indent + " ",
+ stdout.append(indent).append("field: ").append(System.lineSeparator()));
+ stdout.append(indent)
+ .append("D: ").append(Objects.toString(curve.getD(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("I: ").append(Objects.toString(curve.getI(), null))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpEdDSAGroupElement(GroupElement group, CharSequence indent, Appendable stdout) throws IOException {
+ dumpEdDSACurve(group.getCurve(), indent + " ",
+ stdout.append(indent).append("curve:").append(System.lineSeparator()));
+ stdout.append(indent)
+ .append("X: ").append(Objects.toString(group.getX(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("Y: ").append(Objects.toString(group.getY(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("Z: ").append(Objects.toString(group.getZ(), null))
+ .append(System.lineSeparator());
+ stdout.append(indent)
+ .append("T: ").append(Objects.toString(group.getT(), null))
+ .append(System.lineSeparator());
+ }
+
+ public static void dumpEdDSAParameterSpec(EdDSAParameterSpec params, CharSequence indent, Appendable stdout)
+ throws IOException {
+ dumpEdDSAGroupElement(params.getB(), indent + " ",
+ stdout.append(indent).append("B:").append(System.lineSeparator()));
+ stdout.append(indent)
+ .append("hashAlgorith,: ").append(params.getHashAlgorithm())
+ .append(System.lineSeparator());
+ dumpEdDSACurve(params.getCurve(), indent + " ",
+ stdout.append(indent).append("curve:").append(System.lineSeparator()));
+ }
+
+ public static void dumpEdDSAPublicKey(EdDSAPublicKey key, CharSequence indent, Appendable stdout) throws IOException {
+ dumpEdDSAGroupElement(key.getA(), indent + " ",
+ stdout.append(indent).append("A:").append(System.lineSeparator()));
+ dumpEdDSAParameterSpec(key.getParams(), indent + " ",
+ stdout.append(indent).append("params:").append(System.lineSeparator()));
+ }
+
+ public static void dumpEdDSAPrivateKey(EdDSAPrivateKey key, CharSequence indent, Appendable stdout) throws IOException {
+ dumpEdDSAGroupElement(key.getA(), indent + " ",
+ stdout.append(indent).append("A:").append(System.lineSeparator()));
+ BufferUtils.appendHex(stdout.append(indent).append("seed: "), ' ', key.getSeed()).append(System.lineSeparator());
+ BufferUtils.appendHex(stdout.append(indent).append("H: "), ' ', key.getH()).append(System.lineSeparator());
+ dumpEdDSAParameterSpec(key.getParams(), indent + " ",
+ stdout.append(indent).append("params:").append(System.lineSeparator()));
+ }
+
+ public static void dumpPublicKey(PublicKey key, CharSequence indent, Appendable stdout, Appendable stderr)
+ throws IOException {
+ if (key instanceof RSAPublicKey) {
+ dumpRSAPublicKey(
+ RSAPublicKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("RSA").append(System.lineSeparator()));
+ return;
+ } else if (key instanceof DSAPublicKey) {
+ dumpDSAPublicKey(
+ DSAPublicKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("DSA").append(System.lineSeparator()));
+ return;
+ } else if (key instanceof ECPublicKey) {
+ dumpECPublicKey(
+ ECPublicKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("EC").append(System.lineSeparator()));
+ return;
+ } else if (SecurityUtils.isEDDSACurveSupported()) {
+ if (key instanceof EdDSAPublicKey) {
+ dumpEdDSAPublicKey(
+ EdDSAPublicKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("EdDSA").append(System.lineSeparator()));
+ return;
+ }
+ }
+
+ if (stderr != null) {
+ stderr.append(indent)
+ .append("Unsupported public key type: ")
+ .append(key.getClass().getName())
+ .append(System.lineSeparator());
+ } else {
+ throw new UnsupportedOperationException("Unsupported public key type: " + key.getClass().getName());
+ }
+ }
+
+ public static void dumpPrivateKey(PrivateKey key, CharSequence indent, Appendable stdout, Appendable stderr)
+ throws IOException {
+ if (key instanceof RSAPrivateKey) {
+ dumpRSAPrivateKey(RSAPrivateKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("RSA").append(System.lineSeparator()));
+ return;
+ } else if (key instanceof DSAPrivateKey) {
+ dumpDSAPrivateKey(DSAPrivateKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("DSA").append(System.lineSeparator()));
+ return;
+ } else if (key instanceof ECPrivateKey) {
+ dumpECPrivateKey(ECPrivateKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("EC").append(System.lineSeparator()));
+ return;
+ } else if (SecurityUtils.isEDDSACurveSupported()) {
+ if (key instanceof EdDSAPrivateKey) {
+ dumpEdDSAPrivateKey(EdDSAPrivateKey.class.cast(key), indent + " ",
+ stdout.append(indent).append("EC").append(System.lineSeparator()));
+ return;
+ }
+ }
+
+ if (stderr != null) {
+ stderr.append(indent)
+ .append("Unsupported private key type: ")
+ .append(key.getClass().getName())
+ .append(System.lineSeparator());
+ } else {
+ throw new UnsupportedOperationException("Unsupported private key type: " + key.getClass().getName());
+ }
+ }
+
+ public static void dumpKey(Key key, CharSequence indent, Appendable stdout, Appendable stderr) throws IOException {
+ if (key instanceof PublicKey) {
+ dumpPublicKey(PublicKey.class.cast(key), indent, stdout, stderr);
+ } else if (key instanceof PrivateKey) {
+ dumpPrivateKey(PrivateKey.class.cast(key), indent, stdout, stderr);
+ } else if (stderr != null) {
+ stderr.append(indent)
+ .append("Unknown key type: ").append(key.getClass().getSimpleName())
+ .append(System.lineSeparator());
+ } else {
+ throw new ClassCastException("Unknown key type: " + key.getClass().getSimpleName());
+ }
+ }
+
+ public static void dumpKeyFileData(Path filePath, String password, Appendable stdout, Appendable stderr) throws Exception {
+ FilePasswordProvider passwordProvider = GenericUtils.isEmpty(password)
+ ? FilePasswordProvider.EMPTY
+ : FilePasswordProvider.of(password);
+ String fileName = filePath.getFileName().toString();
+ Collection<KeyPair> keys;
+ if (fileName.endsWith(PuttyKeyPairResourceParser.PPK_FILE_SUFFIX)) {
+ keys = PuttyKeyUtils.DEFAULT_INSTANCE.loadKeyPairs(null, filePath, passwordProvider);
+ } else if (fileName.endsWith(PublicKeyEntry.PUBKEY_FILE_SUFFIX)
+ || AuthorizedKeysAuthenticator.STD_AUTHORIZED_KEYS_FILENAME.equals(fileName)) {
+ List<? extends PublicKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(filePath);
+ int numEntries = GenericUtils.size(entries);
+ keys = (numEntries <= 0)
+ ? Collections.emptyList()
+ : new ArrayList<>(entries.size());
+ for (int index = 0; index < numEntries; index++) {
+ PublicKeyEntry e = entries.get(index);
+ PublicKey pubKey = e.resolvePublicKey(null, Collections.emptyMap(), null);
+ if (pubKey == null) {
+ if (stderr != null) {
+ stderr.append("Cannot resolve public entry=").append(e.toString()).append(System.lineSeparator());
+ } else {
+ throw new UnsupportedOperationException("Cannot resolve public entry=" + e);
+ }
+ continue;
+ }
+
+ keys.add(new KeyPair(pubKey, null));
+ }
+ } else {
+ KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser();
+ keys = loader.loadKeyPairs(null, filePath, passwordProvider);
+ }
+
+ if (GenericUtils.isEmpty(keys)) {
+ if (stderr != null) {
+ stderr.append("No keys found in ").append(filePath.toString()).append(System.lineSeparator());
+ return;
+ } else {
+ throw new IllegalArgumentException("No keys found in " + filePath);
+ }
+ }
+
+ for (KeyPair kp : keys) {
+ PublicKey pubKey = kp.getPublic();
+ PublicKeyEntry.appendPublicKeyEntry(stdout.append("Public key: "), pubKey).append(System.lineSeparator());
+ dumpPublicKey(pubKey, " ", stdout, stderr);
+
+ PrivateKey prvKey = kp.getPrivate();
+ if (prvKey != null) {
+ stdout.append("Private key:").append(System.lineSeparator());
+ dumpPrivateKey(kp.getPrivate(), " ", stdout, stderr);
+ }
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+
+ public static void main(String[] args) throws Exception {
+ int numArgs = GenericUtils.length(args);
+ if (numArgs <= 0) {
+ System.err.println("Usage: path [password]");
+ return;
+ }
+
+ String filePath = PathUtils.normalizePath(args[0]);
+ String password = (numArgs > 1) ? args[1] : null;
+ dumpKeyFileData(Paths.get(filePath), password, System.out, System.err);
+ }
+}
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 882daa7..8e7b77e 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
@@ -67,6 +67,11 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator {
*/
public static final String STD_KEYFILE_FOLDER_NAME = ".ssh";
+ /**
+ * Standard suffix for SSH public key files
+ */
+ public static final String PUBKEY_FILE_SUFFIX = ".pub";
+
private static final long serialVersionUID = -585506072687602760L;
private static final NavigableMap<String, PublicKeyEntryDataResolver> KEY_DATA_RESOLVERS
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
index cd9ab9c..4598cd6 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
@@ -92,12 +92,12 @@ public final class PathUtils {
/**
* <UL>
- * <LI>Replaces <U>leading</U> '~' with user's HOME directory</LI>
- * <LI>Replaces any forward slashes with the O/S directory separator</LI>
+ * <LI>Replaces <U>leading</U> '~' with user's HOME directory</LI>
+ * <LI>Replaces any forward slashes with the O/S directory separator</LI>
* </UL>
*
- * @param path Input path - ignored if {@code null}/empty/blank
- * @return Adjusted path
+ * @param path Input path - ignored if {@code null}/empty/blank
+ * @return Adjusted path
*/
public static String normalizePath(String path) {
if (GenericUtils.isBlank(path)) {
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
index 7d0cd7e..24a071a 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParserTestSupport.java
@@ -30,6 +30,7 @@ import org.apache.sshd.common.cipher.BuiltinCiphers;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.BuiltinIdentities;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
+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.util.test.JUnitTestSupport;
@@ -80,7 +81,7 @@ public abstract class OpenSSHKeyPairResourceParserTestSupport extends JUnitTestS
throw e;
}
- URL urlPubKey = getClass().getResource(resourceKey + ".pub");
+ URL urlPubKey = getClass().getResource(resourceKey + PublicKeyEntry.PUBKEY_FILE_SUFFIX);
assertNotNull("Missing public key resource: " + resourceKey, urlPubKey);
List<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(urlPubKey);
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/io/PathUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/io/PathUtilsTest.java
index 32c0480..873190f 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/io/PathUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/io/PathUtilsTest.java
@@ -50,11 +50,10 @@ public class PathUtilsTest extends JUnitTestSupport {
public void testNormalizeLeadingUserHomePath() {
Path expected = PathUtils.getUserHomeFolder()
.resolve(getClass().getSimpleName())
- .resolve(getCurrentTestName())
- ;
+ .resolve(getCurrentTestName());
String actual = PathUtils.normalizePath(PathUtils.HOME_TILDE_CHAR
- + File.separator + getClass().getSimpleName()
- + File.separator + getCurrentTestName());
+ + File.separator + getClass().getSimpleName()
+ + File.separator + getCurrentTestName());
assertEquals(expected.toString(), actual);
}
diff --git a/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSSHClientCertificateTest.java b/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSSHClientCertificateTest.java
index 32462e8..4ca9e2c 100644
--- a/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSSHClientCertificateTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSSHClientCertificateTest.java
@@ -93,7 +93,7 @@ public class GenerateOpenSSHClientCertificateTest extends BaseTestSupport {
}
protected String getCAPublicKeyResource() {
- return getCAPrivateKeyResource() + ".pub";
+ return getCAPrivateKeyResource() + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
protected String getClientPrivateKeyResource() {
@@ -101,11 +101,11 @@ public class GenerateOpenSSHClientCertificateTest extends BaseTestSupport {
}
protected String getClientPublicKeyResource() {
- return getClientPrivateKeyResource() + ".pub";
+ return getClientPrivateKeyResource() + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
protected String getOracle() {
- return getClientPrivateKeyResource() + "-cert.pub";
+ return getClientPrivateKeyResource() + "-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
protected PublicKey readPublicKeyFromResource(String resource) throws Exception {
diff --git a/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSshClientCertificateOracleTest.java b/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSshClientCertificateOracleTest.java
index 3cb7ff8..a37d1bf 100644
--- a/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSshClientCertificateOracleTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/certificates/GenerateOpenSshClientCertificateOracleTest.java
@@ -72,11 +72,11 @@ public class GenerateOpenSshClientCertificateOracleTest extends BaseTestSupport
}
protected String getClientPublicKeyResource() {
- return getClientPrivateKeyResource() + ".pub";
+ return getClientPrivateKeyResource() + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
protected String getOracle() {
- return getClientPrivateKeyResource() + "-cert.pub";
+ return getClientPrivateKeyResource() + "-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
protected PublicKey readPublicKeyFromResource(String resource) throws Exception {
diff --git a/sshd-core/src/test/java/org/apache/sshd/certificates/OpenSSHCertificateParserTest.java b/sshd-core/src/test/java/org/apache/sshd/certificates/OpenSSHCertificateParserTest.java
index adcb0be..734f5ae 100644
--- a/sshd-core/src/test/java/org/apache/sshd/certificates/OpenSSHCertificateParserTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/certificates/OpenSSHCertificateParserTest.java
@@ -64,7 +64,7 @@ public class OpenSSHCertificateParserTest extends BaseTestSupport {
@SuppressWarnings("synthetic-access")
private String getCertificateResource() {
- return USER_KEY_PATH + params.privateKey + "-cert.pub";
+ return USER_KEY_PATH + params.privateKey + "-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
@Test
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/opensshcerts/ClientOpenSSHCertificatesTest.java b/sshd-core/src/test/java/org/apache/sshd/client/opensshcerts/ClientOpenSSHCertificatesTest.java
index 535f39f..5392a2e 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/opensshcerts/ClientOpenSSHCertificatesTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/opensshcerts/ClientOpenSSHCertificatesTest.java
@@ -88,10 +88,13 @@ public class ClientOpenSSHCertificatesTest extends BaseTestSupport {
.withFileFromClasspath("user02_authorized_keys",
"org/apache/sshd/client/opensshcerts/user/user02_authorized_keys")
.withFileFromClasspath("host01", "org/apache/sshd/client/opensshcerts/host/host01")
- .withFileFromClasspath("host01.pub", "org/apache/sshd/client/opensshcerts/host/host01.pub")
+ .withFileFromClasspath("host01" + PublicKeyEntry.PUBKEY_FILE_SUFFIX,
+ "org/apache/sshd/client/opensshcerts/host/host01" + PublicKeyEntry.PUBKEY_FILE_SUFFIX)
.withFileFromClasspath("host02", "org/apache/sshd/client/opensshcerts/host/host02")
- .withFileFromClasspath("host02.pub", "org/apache/sshd/client/opensshcerts/host/host02.pub")
- .withFileFromClasspath("ca.pub", "org/apache/sshd/client/opensshcerts/ca/ca.pub")
+ .withFileFromClasspath("host02" + PublicKeyEntry.PUBKEY_FILE_SUFFIX,
+ "org/apache/sshd/client/opensshcerts/host/host02" + PublicKeyEntry.PUBKEY_FILE_SUFFIX)
+ .withFileFromClasspath("ca" + PublicKeyEntry.PUBKEY_FILE_SUFFIX,
+ "org/apache/sshd/client/opensshcerts/ca/ca" + PublicKeyEntry.PUBKEY_FILE_SUFFIX)
.withFileFromClasspath("Dockerfile", "org/apache/sshd/client/opensshcerts/docker/Dockerfile"))
// must be set to "/keys/host/host01" or "/keys/host/host02"
.withEnv("SSH_HOST_KEY", "/keys/host/host01")
@@ -111,7 +114,7 @@ public class ClientOpenSSHCertificatesTest extends BaseTestSupport {
Security.addProvider(new BouncyCastleProvider());
}
- @Parameterized.Parameters(name = "key: {0}, cert: {0}-cert.pub")
+ @Parameterized.Parameters(name = "key: {0}, cert: {0}-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX)
public static Iterable<? extends String> privateKeyParams() {
return Arrays.asList(
"user01_rsa_sha2_256_2048",
@@ -129,7 +132,7 @@ public class ClientOpenSSHCertificatesTest extends BaseTestSupport {
}
private String getCertificateResource() {
- return getPrivateKeyResource() + "-cert.pub";
+ return getPrivateKeyResource() + "-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
}
@Test
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 aa83e15..3e4b53c 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
@@ -103,7 +103,7 @@ public class AuthorizedKeyEntryTest extends AuthorizedKeysTestSupport {
@Test
@Ignore("Used to test specific files")
public void testSpecificFile() throws Exception {
- Path path = Paths.get("C:" + File.separator + "Temp", "id_ed25519.pub");
+ Path path = Paths.get("C:" + File.separator + "Temp", "id_ed25519" + PublicKeyEntry.PUBKEY_FILE_SUFFIX);
testReadAuthorizedKeys(AuthorizedKeyEntry.readAuthorizedKeys(path));
}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProviderTest.java b/sshd-core/src/test/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProviderTest.java
index 8d0c967..06ef81b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProviderTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProviderTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.sshd.common.keyprovider;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.util.test.JUnitTestSupport;
import org.junit.Test;
@@ -30,7 +31,7 @@ public class FileHostKeyCertificateProviderTest extends JUnitTestSupport {
@Test
public void testLoadingUserCertificateFails() {
FileHostKeyCertificateProvider provider = new FileHostKeyCertificateProvider(
- getTestResourcesFolder().resolve("dummy_user-cert.pub"));
+ getTestResourcesFolder().resolve("dummy_user-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX));
Exception e = assertThrows(Exception.class, () -> provider.loadCertificates(null));
assertTrue("Expected error in line 1", e.getMessage().contains("line 1"));
assertTrue("Unexpected exception message: " + e.getMessage(),
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java b/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
index b4d7d6e..d266af8 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
@@ -30,6 +30,7 @@ import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.common.keyprovider.FileHostKeyCertificateProvider;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.util.GenericUtils;
@@ -103,8 +104,8 @@ public class OpenSSHCertificateTest extends BaseTestSupport {
List<Object[]> list = new ArrayList<>();
String key = "ssh_host_rsa_key";
- String certificate = "ssh_host_rsa_key_sha1-cert.pub";
- String certificateSha512 = "ssh_host_rsa_key-cert.pub";
+ String certificate = "ssh_host_rsa_key_sha1-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
+ String certificateSha512 = "ssh_host_rsa_key-cert" + PublicKeyEntry.PUBKEY_FILE_SUFFIX;
// default client
list.add(new Object[] {