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 <lgoldst...@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 "lim...@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:d...@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[] {