[SSHD-757] Added some hooks to allow usage of OpenPGP keys inside authorized_keys files
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/2f92fe5d Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/2f92fe5d Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/2f92fe5d Branch: refs/heads/master Commit: 2f92fe5d219bb688f7e43ca28fb7fb81725aaaca Parents: fc7a8e7 Author: Lyor Goldstein <[email protected]> Authored: Mon Nov 19 16:29:38 2018 +0200 Committer: Lyor Goldstein <[email protected]> Committed: Thu Nov 22 07:05:16 2018 +0200 ---------------------------------------------------------------------- CHANGES.md | 5 + README.md | 14 ++ .../client/config/hosts/HostConfigEntry.java | 7 - .../client/config/hosts/KnownHostEntry.java | 7 - .../sshd/common/AlgorithmNameProvider.java | 27 +++ .../apache/sshd/common/cipher/BaseCipher.java | 11 + .../sshd/common/cipher/BaseRC4Cipher.java | 6 +- .../sshd/common/cipher/BuiltinCiphers.java | 8 +- .../sshd/common/cipher/CipherInformation.java | 12 +- .../apache/sshd/common/cipher/CipherNone.java | 5 + .../org/apache/sshd/common/cipher/ECCurves.java | 14 +- .../common/config/ConfigFileReaderSupport.java | 7 - .../common/config/keys/AuthorizedKeyEntry.java | 71 +++--- .../sshd/common/config/keys/Identity.java | 11 +- .../sshd/common/config/keys/KeyRandomArt.java | 10 +- .../sshd/common/config/keys/KeyUtils.java | 113 +++++++++- .../sshd/common/config/keys/PublicKeyEntry.java | 222 ++++++++++++++++--- .../config/keys/PublicKeyEntryDataResolver.java | 71 ++++++ .../config/keys/PublicKeyEntryDecoder.java | 19 +- .../loader/pem/KeyPairPEMResourceParser.java | 10 +- .../sshd/common/digest/DigestInformation.java | 12 +- .../common/keyprovider/KeySizeIndicator.java | 30 +++ .../common/keyprovider/KeyTypeIndicator.java | 31 +++ .../apache/sshd/common/mac/MacInformation.java | 11 +- .../apache/sshd/common/signature/Signature.java | 13 +- .../sshd/common/util/buffer/BufferUtils.java | 34 ++- .../AbstractGeneratorHostKeyProvider.java | 8 +- .../pubkey/KeySetPublickeyAuthenticator.java | 17 +- .../auth/pubkey/PublickeyAuthenticator.java | 17 +- .../keys/AuthorizedKeysAuthenticator.java | 28 ++- .../DefaultAuthorizedKeysAuthenticator.java | 5 +- .../config/keys/AuthorizedKeyEntryTest.java | 8 +- .../config/keys/AuthorizedKeysTestSupport.java | 9 +- .../signature/SignatureFactoriesTest.java | 26 ++- .../keys/AuthorizedKeysAuthenticatorTest.java | 15 +- .../openpgp/PGPPublicKeyEntryDataResolver.java | 106 +++++++++ .../openpgp/PGPPublicKeyEntryDecoder.java | 87 ++++++++ .../config/keys/loader/openpgp/PGPUtils.java | 115 ++++++++++ .../openpgp/PGPKeyPairResourceParserTest.java | 6 +- .../openpgp/PGPUtilsKeyFingerprintTest.java | 174 +++++++++++++++ 40 files changed, 1198 insertions(+), 204 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/CHANGES.md ---------------------------------------------------------------------- diff --git a/CHANGES.md b/CHANGES.md index 5074ade..db1e874 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -66,8 +66,13 @@ accept also an `AttributeRepository` connection context argument (propagated fro * The various `ClientIdentitiesWatcher`(s) use a type-safe `ClientIdentityLoaderHolder` and `FilePasswordProviderHolder` instead of the generic `Supplier` definition. +* Removed API(s) that used string file paths to create `FileInputStream`-s - using only `java.nio.file.Path`-s + ## Behavioral changes and enhancements +* [SSHD-757](https://issues.apache.org/jira/browse/SSHD-757) - Added hooks and some initial code to allow (limited) usage +of [OpenPGP](https://www.openpgp.org/) key files - e.g. in `authorized_keys` files or as client identities. + * [SSHD-849](https://issues.apache.org/jira/browse/SSHD-849) - Data forwarding code makes sure all pending packets have been sent to the peer channel when closing the tunnel gracefully. http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 3b291a0..811911d 100644 --- a/README.md +++ b/README.md @@ -1932,6 +1932,20 @@ This code relies on the [jpgpgj](https://github.com/justinludwig/jpgpj) support (which in turn automatically uses _Bouncycastle_ - so if one does not want _Bouncycastle_ one cannot use this module). +#### Using OpenPGP authorized keys entries + +In order to be able to read `authorized_keys` files that may contain _OpenPGP_ keys references, one needs to register +the relevant `PublicKeyEntryDataResolver`-s. This is done by calling `PGPPublicKeyEntryDataResolver#registerDefaultKeyEntryDataResolvers` +once during the _main_ code setup. This will enable the code to read authorized keys entries having the format +specified in the [OpenSSH PGP configuration](https://www.red-bean.com/~nemo/openssh-gpg/): + +``` + pgp-sign-dss 87C36E60187451050A4F26B134824FC95C781A18 with-comment + pgp-sign-rsa 87C36E60187451050A4F26B134824FC95C781A18 +``` + +Where the key data following the key type specification is the fingerprint value of the referenced key. + ## Useful extra components in _sshd-contrib_ * `InteractivePasswordIdentityProvider` - helps implement a `PasswordIdentityProvider` by delegating calls http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java index c2d3bcb..f94f775 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java @@ -20,7 +20,6 @@ package org.apache.sshd.client.config.hosts; import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -790,12 +789,6 @@ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHo } } - public static List<HostConfigEntry> readHostConfigEntries(String filePath) throws IOException { - try (InputStream inStream = new FileInputStream(filePath)) { - return readHostConfigEntries(inStream, true); - } - } - public static List<HostConfigEntry> readHostConfigEntries(InputStream inStream, boolean okToClose) throws IOException { try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) { return readHostConfigEntries(reader, true); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java index 4a8e9ca..bfb444f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/KnownHostEntry.java @@ -20,7 +20,6 @@ package org.apache.sshd.client.config.hosts; import java.io.BufferedReader; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -155,12 +154,6 @@ public class KnownHostEntry extends HostPatternsHolder { } } - public static List<KnownHostEntry> readKnownHostEntries(String filePath) throws IOException { - try (InputStream inStream = new FileInputStream(filePath)) { - return readKnownHostEntries(inStream, true); - } - } - public static List<KnownHostEntry> readKnownHostEntries(InputStream inStream, boolean okToClose) throws IOException { try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(inStream, okToClose), StandardCharsets.UTF_8)) { return readKnownHostEntries(reader, true); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java new file mode 100644 index 0000000..87d4959 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/AlgorithmNameProvider.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface AlgorithmNameProvider { + String getAlgorithm(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java index c3d9426..306b72d 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseCipher.java @@ -36,13 +36,19 @@ public class BaseCipher implements Cipher { private final int ivsize; private final int bsize; private final String algorithm; + private final int keySize; private final String transformation; private String s; public BaseCipher(int ivsize, int bsize, String algorithm, String transformation) { + this(ivsize, bsize, algorithm, bsize * Byte.SIZE, transformation); + } + + public BaseCipher(int ivsize, int bsize, String algorithm, int keySize, String transformation) { this.ivsize = ivsize; this.bsize = bsize; this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No algorithm"); + this.keySize = keySize; this.transformation = ValidateUtils.checkNotNullAndNotEmpty(transformation, "No transformation"); } @@ -52,6 +58,11 @@ public class BaseCipher implements Cipher { } @Override + public int getKeySize() { + return keySize; + } + + @Override public String getTransformation() { return transformation; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java index 2e3fc1e..5185fe2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BaseRC4Cipher.java @@ -30,7 +30,11 @@ public class BaseRC4Cipher extends BaseCipher { public static final int SKIP_SIZE = 1536; public BaseRC4Cipher(int ivsize, int bsize) { - super(ivsize, bsize, "ARCFOUR", "RC4"); + this(ivsize, bsize, bsize * Byte.SIZE); + } + + public BaseRC4Cipher(int ivsize, int bsize, int keySize) { + super(ivsize, bsize, "ARCFOUR", keySize, "RC4"); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java index 8884e15..55925f2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java @@ -67,7 +67,7 @@ public enum BuiltinCiphers implements CipherFactory { arcfour256(Constants.ARCFOUR256, 8, 32, "ARCFOUR", "RC4") { @Override public Cipher create() { - return new BaseRC4Cipher(getIVSize(), getBlockSize()); + return new BaseRC4Cipher(getIVSize(), getBlockSize(), getKeySize()); } }, blowfishcbc(Constants.BLOWFISH_CBC, 8, 16, "Blowfish", "Blowfish/CBC/NoPadding"), @@ -123,9 +123,7 @@ public enum BuiltinCiphers implements CipherFactory { return supported; } - /** - * @return The key size (in bits) for the cipher - */ + @Override public int getKeySize() { return keysize; } @@ -152,7 +150,7 @@ public enum BuiltinCiphers implements CipherFactory { @Override public Cipher create() { - return new BaseCipher(getIVSize(), getBlockSize(), getAlgorithm(), getTransformation()); + return new BaseCipher(getIVSize(), getBlockSize(), getAlgorithm(), getKeySize(), getTransformation()); } /** http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java index f17fd16..96e7d41 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherInformation.java @@ -19,15 +19,15 @@ package org.apache.sshd.common.cipher; +import org.apache.sshd.common.AlgorithmNameProvider; +import org.apache.sshd.common.keyprovider.KeySizeIndicator; + /** + * The reported algorithm name refers to the cipher base name - e.g., "AES", "ARCFOUR", etc. + * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface CipherInformation { - /** - * @return The cipher's algorithm - */ - String getAlgorithm(); - +public interface CipherInformation extends AlgorithmNameProvider, KeySizeIndicator { /** * @return The actual transformation used - e.g., AES/CBC/NoPadding */ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java index 15b6e9f..b0a21d4 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/CipherNone.java @@ -37,6 +37,11 @@ public class CipherNone implements Cipher { } @Override + public int getKeySize() { + return 0; + } + + @Override public String getTransformation() { return "none"; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java index cb8cd0d..3ccb671 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java @@ -45,6 +45,8 @@ import org.apache.sshd.common.config.keys.KeyEntryResolver; import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.Digest; import org.apache.sshd.common.digest.DigestFactory; +import org.apache.sshd.common.keyprovider.KeySizeIndicator; +import org.apache.sshd.common.keyprovider.KeyTypeIndicator; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.ValidateUtils; @@ -55,7 +57,7 @@ import org.apache.sshd.common.util.security.SecurityUtils; * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public enum ECCurves implements NamedResource, OptionalFeature { +public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator, NamedResource, OptionalFeature { nistp256(Constants.NISTP256, new int[]{1, 2, 840, 10045, 3, 1, 7}, new ECParameterSpec( new EllipticCurve( @@ -153,7 +155,7 @@ public enum ECCurves implements NamedResource, OptionalFeature { this.digestFactory = Objects.requireNonNull(digestFactory, "No digestFactory"); } - @Override // The curve name + @Override // The curve's standard name public final String getName() { return name; } @@ -166,9 +168,7 @@ public enum ECCurves implements NamedResource, OptionalFeature { return oidValue; } - /** - * @return The standard key type used to represent this curve - */ + @Override public final String getKeyType() { return keyType; } @@ -182,9 +182,7 @@ public enum ECCurves implements NamedResource, OptionalFeature { return params; } - /** - * @return The size (in bits) of the key - */ + @Override public final int getKeySize() { return keySize; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java index b6cab4f..f61d74e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java @@ -20,7 +20,6 @@ package org.apache.sshd.common.config; import java.io.BufferedReader; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -123,12 +122,6 @@ public final class ConfigFileReaderSupport { } } - public static Properties readConfigFile(String path) throws IOException { - try (InputStream input = new FileInputStream(path)) { - return readConfigFile(input, true); - } - } - public static Properties readConfigFile(InputStream input, boolean okToClose) throws IOException { try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(input, okToClose), StandardCharsets.UTF_8)) { return readConfigFile(reader, true); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 eb7e1fb..2e60968 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 @@ -20,7 +20,6 @@ package org.apache.sshd.common.config.keys; import java.io.BufferedReader; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -90,7 +89,8 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } @Override - public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException { + public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { Map<String, String> options = getLoginOptions(); if (!GenericUtils.isEmpty(options)) { int index = 0; @@ -146,7 +146,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { public static List<PublicKey> resolveAuthorizedKeys( PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries) - throws IOException, GeneralSecurityException { + throws IOException, GeneralSecurityException { if (GenericUtils.isEmpty(entries)) { return Collections.emptyList(); } @@ -163,7 +163,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } /** - * Reads read the contents of an <code>authorized_keys</code> file + * Reads read the contents of an {@code authorized_keys} file * * @param url The {@link URL} to read from * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there @@ -177,7 +177,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } /** - * Reads read the contents of an <code>authorized_keys</code> file + * Reads read the contents of an {@code authorized_keys} file * * @param path {@link Path} to read from * @param options The {@link OpenOption}s to use - if unspecified then appropriate @@ -194,41 +194,26 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } /** - * Reads read the contents of an <code>authorized_keys</code> file + * Reads read the contents of an {@code authorized_keys} file * - * @param filePath The file path to read from - * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there - * @throws IOException If failed to read or parse the entries - * @see #readAuthorizedKeys(InputStream, boolean) - */ - public static List<AuthorizedKeyEntry> readAuthorizedKeys(String filePath) throws IOException { - try (InputStream in = new FileInputStream(filePath)) { - return readAuthorizedKeys(in, true); - } - } - - /** - * Reads read the contents of an <code>authorized_keys</code> file - * - * @param in The {@link InputStream} - * @param okToClose <code>true</code> if method may close the input stream - * regardless of whether successful or failed + * @param in The {@link InputStream} to use to read the contents of an {@code authorized_keys} file + * @param okToClose {@code true if method may close the input regardless success or failure * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there * @throws IOException If failed to read or parse the entries * @see #readAuthorizedKeys(Reader, boolean) */ public static List<AuthorizedKeyEntry> readAuthorizedKeys(InputStream in, boolean okToClose) throws IOException { - try (Reader rdr = new InputStreamReader(NoCloseInputStream.resolveInputStream(in, okToClose), StandardCharsets.UTF_8)) { + try (Reader rdr = new InputStreamReader( + NoCloseInputStream.resolveInputStream(in, okToClose), StandardCharsets.UTF_8)) { return readAuthorizedKeys(rdr, true); } } /** - * Reads read the contents of an <code>authorized_keys</code> file + * Reads read the contents of an {@code authorized_keys} file * - * @param rdr The {@link Reader} - * @param okToClose <code>true</code> if method may close the input stream - * regardless of whether successful or failed + * @param rdr The {@link Reader} to use to read the contents of an {@code authorized_keys} file + * @param okToClose {@code true if method may close the input regardless success or failure * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there * @throws IOException If failed to read or parse the entries * @see #readAuthorizedKeys(BufferedReader) @@ -240,21 +225,19 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } /** - * @param rdr The {@link BufferedReader} to use to read the contents of - * an <code>authorized_keys</code> file + * @param rdr The {@link BufferedReader} to use to read the contents of an {@code authorized_keys} file * @return A {@link List} of all the {@link AuthorizedKeyEntry}-ies found there * @throws IOException If failed to read or parse the entries * @see #parseAuthorizedKeyEntry(String) */ public static List<AuthorizedKeyEntry> readAuthorizedKeys(BufferedReader rdr) throws IOException { List<AuthorizedKeyEntry> entries = null; - for (String line = rdr.readLine(); line != null; line = rdr.readLine()) { AuthorizedKeyEntry entry; try { entry = parseAuthorizedKeyEntry(line); - if (entry == null) { // null, empty or comment line - continue; + if (entry == null) { + continue; // null, empty or comment line } } catch (RuntimeException | Error e) { throw new StreamCorruptedException("Failed (" + e.getClass().getSimpleName() + ")" @@ -276,13 +259,27 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } /** - * @param value Original line from an <code>authorized_keys</code> file + * @param value Original line from an {@code authorized_keys} file * @return {@link AuthorizedKeyEntry} or {@code null} if the line is * {@code null}/empty or a comment line * @throws IllegalArgumentException If failed to parse/decode the line - * @see #COMMENT_CHAR + * @see #parseAuthorizedKeyEntry(String, PublicKeyEntryDataResolver) */ public static AuthorizedKeyEntry parseAuthorizedKeyEntry(String value) throws IllegalArgumentException { + return parseAuthorizedKeyEntry(value, null); + } + + /** + * @param value Original line from an {@code authorized_keys} file + * @param resolver The {@link PublicKeyEntryDataResolver} to use - if {@code null} + * one will be automatically resolved from the key type + * @return {@link AuthorizedKeyEntry} or {@code null} if the line is + * {@code null}/empty or a comment line + * @throws IllegalArgumentException If failed to parse/decode the line + */ + public static AuthorizedKeyEntry parseAuthorizedKeyEntry( + String value, PublicKeyEntryDataResolver resolver) + throws IllegalArgumentException { String line = GenericUtils.replaceWhitespaceAndTrim(value); if (GenericUtils.isEmpty(line) || (line.charAt(0) == COMMENT_CHAR) /* comment ? */) { return null; @@ -310,7 +307,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } else { String encData = (endPos < (line.length() - 1)) ? line.substring(0, endPos).trim() : line; String comment = (endPos < (line.length() - 1)) ? line.substring(endPos + 1).trim() : null; - entry = parsePublicKeyEntry(new AuthorizedKeyEntry(), encData); + entry = parsePublicKeyEntry(new AuthorizedKeyEntry(), encData, resolver); entry.setComment(comment); } @@ -318,7 +315,7 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } /** - * Parses a single line from an <code>authorized_keys</code> file that is <U>known</U> + * Parses a single line from an {@code authorized_keys} file that is <U>known</U> * to contain login options and separates it to the options and the rest of the line. * * @param entryLine The line to be parsed http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java index eaec413..0d9c854 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/Identity.java @@ -22,20 +22,17 @@ package org.apache.sshd.common.config.keys; import java.security.PrivateKey; import java.security.PublicKey; +import org.apache.sshd.common.AlgorithmNameProvider; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.OptionalFeature; /** - * Represents an SSH key type + * Represents an SSH key type - the referenced algorithm is the one used to generate + * the key - e.g., "RSA", "DSA", "EC". * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface Identity extends NamedResource, OptionalFeature { - /** - * @return The key algorithm - e.g., RSA, DSA, EC - */ - String getAlgorithm(); - +public interface Identity extends AlgorithmNameProvider, NamedResource, OptionalFeature { Class<? extends PublicKey> getPublicKeyType(); Class<? extends PrivateKey> getPrivateKeyType(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java index cf0b1c9..13c854c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyRandomArt.java @@ -31,9 +31,11 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; +import org.apache.sshd.common.AlgorithmNameProvider; import org.apache.sshd.common.Factory; import org.apache.sshd.common.digest.Digest; import org.apache.sshd.common.keyprovider.KeyIdentityProvider; +import org.apache.sshd.common.keyprovider.KeySizeIndicator; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; @@ -52,7 +54,7 @@ import org.apache.sshd.common.util.ValidateUtils; * @see <a href="http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf">Original article</a> * @see <a href="http://opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c">C implementation</a> */ -public class KeyRandomArt { +public class KeyRandomArt implements AlgorithmNameProvider, KeySizeIndicator { public static final int FLDBASE = 8; public static final int FLDSIZE_Y = FLDBASE + 1; public static final int FLDSIZE_X = FLDBASE * 2 + 1; @@ -117,10 +119,16 @@ public class KeyRandomArt { field[x][y] = (char) len; } + /** + * @return The algorithm that was used to generate the key - e.g., + * "RSA", "DSA", "EC". + */ + @Override public String getAlgorithm() { return algorithm; } + @Override public int getKeySize() { return keySize; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java index c8cfe9c..fec4f4d 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java @@ -55,9 +55,11 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.NavigableSet; import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.atomic.AtomicReference; import org.apache.sshd.common.Factory; @@ -292,8 +294,7 @@ public final class KeyUtils { /** * @param decoder The decoder to register - * @throws IllegalArgumentException if no decoder or not key type or no - * supported names for the decoder + * @throws IllegalArgumentException if no decoder or not key type or no supported names for the decoder * @see PublicKeyEntryDecoder#getPublicKeyType() * @see PublicKeyEntryDecoder#getSupportedTypeNames() */ @@ -307,21 +308,111 @@ public final class KeyUtils { BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder); } - Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key type"); + registerPublicKeyEntryDecoderKeyTypes(decoder); + } + + /** + * Registers the specified decoder for all the types it {@link PublicKeyEntryDecoder#getSupportedTypeNames() supports} + * + * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register + * @see #registerPublicKeyEntryDecoderForKeyType(String, PublicKeyEntryDecoder) + */ + public static void registerPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder<?, ?> decoder) { + Objects.requireNonNull(decoder, "No decoder specified"); + + Collection<String> names = + ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key types"); + for (String n : names) { + PublicKeyEntryDecoder<?, ?> prev = registerPublicKeyEntryDecoderForKeyType(n, decoder); + if (prev != null) { + //noinspection UnnecessaryContinue + continue; // debug breakpoint + } + } + } + + /** + * @param keyType The key (never {@code null}/empty) key type + * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register + * @return The previously registered decoder for this key type - {@code null} if none + */ + public static PublicKeyEntryDecoder<?, ?> registerPublicKeyEntryDecoderForKeyType(String keyType, PublicKeyEntryDecoder<?, ?> decoder) { + keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified"); + Objects.requireNonNull(decoder, "No decoder specified"); + synchronized (BY_KEY_TYPE_DECODERS_MAP) { - for (String n : names) { - PublicKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder); - if (prev != null) { - //noinspection UnnecessaryContinue - continue; // debug breakpoint - } + return BY_KEY_TYPE_DECODERS_MAP.put(keyType, decoder); + } + } + + /** + * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister + * @return The case <U>insensitive</U> {@link NavigableSet} of all the effectively un-registered key types + * out of all the {@link PublicKeyEntryDecoder#getSupportedTypeNames() supported} ones. + * @see #unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder) + */ + public static NavigableSet<String> unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) { + Objects.requireNonNull(decoder, "No decoder specified"); + + Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared"); + Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared"); + synchronized (BY_KEY_CLASS_DECODERS_MAP) { + BY_KEY_CLASS_DECODERS_MAP.remove(pubType); + BY_KEY_CLASS_DECODERS_MAP.remove(prvType); + } + + return unregisterPublicKeyEntryDecoderKeyTypes(decoder); + } + + /** + * Unregisters the specified decoder for all the types it supports + * + * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister + * @return The case <U>insensitive</U> {@link NavigableSet} of all the effectively un-registered key types + * out of all the {@link PublicKeyEntryDecoder#getSupportedTypeNames() supported} ones. + * @see #unregisterPublicKeyEntryDecoderForKeyType(String) + */ + public static NavigableSet<String> unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder<?, ?> decoder) { + Objects.requireNonNull(decoder, "No decoder specified"); + + Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty( + decoder.getSupportedTypeNames(), "No supported key types"); + NavigableSet<String> removed = Collections.emptyNavigableSet(); + for (String n : names) { + PublicKeyEntryDecoder<?, ?> prev = unregisterPublicKeyEntryDecoderForKeyType(n); + if (prev == null) { + continue; + } + + if (removed.isEmpty()) { + removed = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + } + + if (!removed.add(n)) { + //noinspection UnnecessaryContinue + continue; // debug breakpoint } } + + return removed; + } + + /** + * Unregister the decoder registered for the specified key type + * + * @param keyType The key (never {@code null}/empty) key type + * @return The unregistered {@link PublicKeyEntryDecoder} - {@code null} if none registered for this key type + */ + public static PublicKeyEntryDecoder<?, ?> unregisterPublicKeyEntryDecoderForKeyType(String keyType) { + keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified"); + + synchronized (BY_KEY_TYPE_DECODERS_MAP) { + return BY_KEY_TYPE_DECODERS_MAP.remove(keyType); + } } /** - * @param keyType The {@code OpenSSH} key type string - e.g., {@code ssh-rsa, ssh-dss} - * - ignored if {@code null}/empty + * @param keyType The {@code OpenSSH} key type string - e.g., {@code ssh-rsa, ssh-dss} - ignored if {@code null}/empty * @return The registered {@link PublicKeyEntryDecoder} or {code null} if not found */ public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(String keyType) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 1db3d2b..49a5e41 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 @@ -28,11 +28,15 @@ import java.security.GeneralSecurityException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; -import java.util.Base64; +import java.util.Collections; +import java.util.NavigableMap; import java.util.Objects; +import java.util.TreeMap; +import org.apache.sshd.common.keyprovider.KeyTypeIndicator; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; /** * <P>Represents a {@link PublicKey} whose data is formatted according to @@ -44,14 +48,12 @@ import org.apache.sshd.common.util.NumberUtils; * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class PublicKeyEntry implements Serializable { - +public class PublicKeyEntry implements Serializable, KeyTypeIndicator { /** * Character used to denote a comment line in the keys file */ public static final char COMMENT_CHAR = '#'; - /** * Standard folder name used by OpenSSH to hold key files */ @@ -59,8 +61,11 @@ public class PublicKeyEntry implements Serializable { private static final long serialVersionUID = -585506072687602760L; + private static final NavigableMap<String, PublicKeyEntryDataResolver> KEY_DATA_RESOLVERS = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private String keyType; private byte[] keyData; + private PublicKeyEntryDataResolver keyDataResolver = PublicKeyEntryDataResolver.DEFAULT; public PublicKeyEntry() { super(); @@ -71,6 +76,7 @@ public class PublicKeyEntry implements Serializable { this.keyData = keyData; } + @Override public String getKeyType() { return keyType; } @@ -87,6 +93,25 @@ public class PublicKeyEntry implements Serializable { this.keyData = value; } + public PublicKeyEntryDataResolver getKeyDataResolver() { + return keyDataResolver; + } + + public void setKeyDataResolver(PublicKeyEntryDataResolver keyDataResolver) { + this.keyDataResolver = keyDataResolver; + } + + /** + * If a {@link PublicKeyEntryDataResolver} has been set, then uses it - otherwise + * uses the {@link PublicKeyEntryDataResolver#DEFAULT default one}. + * + * @return The resolved instance + */ + public PublicKeyEntryDataResolver resolvePublicKeyEntryDataResolver() { + PublicKeyEntryDataResolver resolver = getKeyDataResolver(); + return (resolver == null) ? PublicKeyEntryDataResolver.DEFAULT : resolver; + } + /** * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if * none of the built-in ones can be used. If {@code null} and no built-in @@ -97,7 +122,8 @@ public class PublicKeyEntry implements Serializable { * @throws IOException If failed to decode the key * @throws GeneralSecurityException If failed to generate the key */ - public PublicKey resolvePublicKey(PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException { + public PublicKey resolvePublicKey(PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { String kt = getKeyType(); PublicKeyEntryResolver decoder = KeyUtils.getPublicKeyEntryDecoder(kt); if (decoder == null) { @@ -120,10 +146,11 @@ public class PublicKeyEntry implements Serializable { * @throws GeneralSecurityException If failed to generate the key * @see #resolvePublicKey(PublicKeyEntryResolver) */ - public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException { + public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { PublicKey key = resolvePublicKey(fallbackResolver); if (key != null) { - appendPublicKeyEntry(sb, key); + appendPublicKeyEntry(sb, key, resolvePublicKeyEntryDataResolver()); } return key; } @@ -161,9 +188,82 @@ public class PublicKeyEntry implements Serializable { @Override public String toString() { - byte[] data = getKeyData(); - Base64.Encoder encoder = Base64.getEncoder(); - return getKeyType() + " " + (NumberUtils.isEmpty(data) ? "<no-key>" : encoder.encodeToString(data)); + PublicKeyEntryDataResolver resolver = resolvePublicKeyEntryDataResolver(); + String encData = resolver.encodeEntryKeyData(getKeyData()); + return getKeyType() + " " + (GenericUtils.isEmpty(encData) ? "<no-key>" : encData); + } + + /** + * Registers a specialized decoder for the public key entry data bytes instead of the + * {@link PublicKeyEntryDataResolver#DEFAULT default} one. + * + * @param keyType The key-type value (case <U>insensitive</U>) that will trigger the + * usage of this decoder - e.g., "ssh-rsa", "pgp-sign-dss", etc. + * @param decoder The decoder to use + */ + public static void registerKeyDataEntryResolver(String keyType, PublicKeyEntryDataResolver resolver) { + ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); + Objects.requireNonNull(resolver, "No resolver provided"); + + synchronized (KEY_DATA_RESOLVERS) { + KEY_DATA_RESOLVERS.put(keyType, resolver); + } + } + + /** + * @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 registered resolver instance - {@code null} if none was registered + */ + public static PublicKeyEntryDataResolver unregisterKeyDataEntryResolver(String keyType) { + keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); + + synchronized (KEY_DATA_RESOLVERS) { + return KEY_DATA_RESOLVERS.remove(keyType); + } + } + + /** + * @param keyType keyType The key-type value (case <U>insensitive</U>) whose data is to + * be resolved - e.g., "ssh-rsa", "pgp-sign-dss", etc. + * @return If a specific resolver has been previously + * {@link #registerKeyDataDecoder(String, PublicKeyEntryDataResolver) registered} then uses it, + * otherwise the {@link PublicKeyEntryDataResolver#DEFAULT default} one. + */ + 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); + } + + if (resolver != null) { + return resolver; // debug breakpoint + } + + return PublicKeyEntryDataResolver.DEFAULT; + } + + /** + * @return A snapshot of the currently registered specialized {@link PublicKeyEntryDataResolver}-s, + * where key=the key-type value (case <U>insensitive</U>) - e.g., "ssh-rsa", + * "pgp-sign-dss", etc., value=the associated {@link PublicKeyEntryDataResolver} + * for the key type + */ + public static NavigableMap<String, PublicKeyEntryDataResolver> getRegisteredKeyDataEntryResolvers() { + NavigableMap<String, PublicKeyEntryDataResolver> decoders; + synchronized (KEY_DATA_RESOLVERS) { + if (KEY_DATA_RESOLVERS.isEmpty()) { + return Collections.emptyNavigableMap(); + } + + decoders = new TreeMap<>(KEY_DATA_RESOLVERS.comparator()); + decoders.putAll(KEY_DATA_RESOLVERS); + } + + return decoders; } /** @@ -171,27 +271,61 @@ public class PublicKeyEntry implements Serializable { * (anything beyond the BASE64 data is ignored) - ignored if {@code null}/empty * @return A {@link PublicKeyEntry} or {@code null} if no data * @throws IllegalArgumentException if bad format found - * @see #parsePublicKeyEntry(PublicKeyEntry, String) + * @see #parsePublicKeyEntry(String, PublicKeyEntryDataResolver) */ public static PublicKeyEntry parsePublicKeyEntry(String encData) throws IllegalArgumentException { + return parsePublicKeyEntry(encData, (PublicKeyEntryDataResolver) null); + } + + /** + * @param encData Assumed to contain at least {@code key-type base64-data} + * (anything beyond the BASE64 data is ignored) - ignored if {@code null}/empty + * @param decoder The {@link PublicKeyEntryDataResolver} to use in order to decode + * the key data string into its bytes - if {@code null} then one is + * automatically {@link #resolveKeyDataEntryResolver(String) resolved} + * @return A {@link PublicKeyEntry} or {@code null} if no data + * @throws IllegalArgumentException if bad format found + * @see #parsePublicKeyEntry(PublicKeyEntry, String, PublicKeyEntryDataResolver) + */ + public static PublicKeyEntry parsePublicKeyEntry( + String encData, PublicKeyEntryDataResolver decoder) + throws IllegalArgumentException { String data = GenericUtils.replaceWhitespaceAndTrim(encData); if (GenericUtils.isEmpty(data)) { return null; } else { - return parsePublicKeyEntry(new PublicKeyEntry(), data); + return parsePublicKeyEntry(new PublicKeyEntry(), data, decoder); } } /** - * @param <E> The generic entry type - * @param entry The {@link PublicKeyEntry} whose contents are to be - * updated - ignored if {@code null} + * @param <E> The generic entry type + * @param entry The {@link PublicKeyEntry} whose contents are to be updated - ignored if {@code null} * @param encData Assumed to contain at least {@code key-type base64-data} (anything - * beyond the BASE64 data is ignored) - ignored if {@code null}/empty + * beyond the BASE64 data is ignored) - ignored if {@code null}/empty * @return The updated entry instance * @throws IllegalArgumentException if bad format found + * @see #parsePublicKeyEntry(PublicKeyEntry, String, PublicKeyEntryDataResolver) */ - public static <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String encData) throws IllegalArgumentException { + public static <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String encData) + throws IllegalArgumentException { + return parsePublicKeyEntry(entry, encData, null); + } + + /** + * @param <E> The generic entry type + * @param entry The {@link PublicKeyEntry} whose contents are to be updated - ignored if {@code null} + * @param encData Assumed to contain at least {@code key-type base64-data} (anything + * beyond the BASE64 data is ignored) - ignored if {@code null}/empty + * @param decoder The {@link PublicKeyEntryDataResolver} to use in order to decode + * the key data string into its bytes - if {@code null} then one is + * automatically {@link #resolveKeyDataEntryResolver(String) resolved} + * @return The updated entry instance + * @throws IllegalArgumentException if bad format found + */ + public static <E extends PublicKeyEntry> E parsePublicKeyEntry( + E entry, String encData, PublicKeyEntryDataResolver decoder) + throws IllegalArgumentException { String data = GenericUtils.replaceWhitespaceAndTrim(encData); if (GenericUtils.isEmpty(data) || (entry == null)) { return entry; @@ -203,19 +337,22 @@ public class PublicKeyEntry implements Serializable { } int endPos = data.indexOf(' ', startPos + 1); - if (endPos <= startPos) { // OK if no continuation beyond the BASE64 encoded data + if (endPos <= startPos) { // OK if no continuation beyond the encoded key data endPos = data.length(); } String keyType = data.substring(0, startPos); + if (decoder == null) { + decoder = resolveKeyDataEntryResolver(keyType); + } String b64Data = data.substring(startPos + 1, endPos).trim(); - Base64.Decoder decoder = Base64.getDecoder(); - byte[] keyData = decoder.decode(b64Data); + byte[] keyData = decoder.decodeEntryKeyData(b64Data); if (NumberUtils.isEmpty(keyData)) { throw new IllegalArgumentException("Bad format (no BASE64 key data): " + data); } entry.setKeyType(keyType); + entry.setKeyDataResolver(decoder); entry.setKeyData(keyData); return entry; } @@ -224,11 +361,26 @@ public class PublicKeyEntry implements Serializable { * @param key The {@link PublicKey} * @return The {@code OpenSSH} encoded data * @throws IllegalArgumentException If failed to encode - * @see #appendPublicKeyEntry(Appendable, PublicKey) + * @see #toString(PublicKey, PublicKeyEntryDataResolver) */ public static String toString(PublicKey key) throws IllegalArgumentException { + return toString(key, null); + } + + /** + * @param key The {@link PublicKey} + * @param encoder The {@link PublicKeyEntryDataResolver} to use in order to encode + * the key data bytes into a string representation - if {@code null} then one is + * automatically {@link #resolveKeyDataEntryResolver(String) resolved} + * @return The {@code OpenSSH} encoded data + * @throws IllegalArgumentException If failed to encode + * @see #appendPublicKeyEntry(Appendable, PublicKey, PublicKeyEntryDataResolver) + */ + public static String toString( + PublicKey key, PublicKeyEntryDataResolver encoder) + throws IllegalArgumentException { try { - return appendPublicKeyEntry(new StringBuilder(Byte.MAX_VALUE), key).toString(); + return appendPublicKeyEntry(new StringBuilder(Byte.MAX_VALUE), key, encoder).toString(); } catch (IOException e) { throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ") to encode: " + e.getMessage(), e); } @@ -242,8 +394,25 @@ public class PublicKeyEntry implements Serializable { * @param key The {@link PublicKey} - ignored if {@code null} * @return The updated appendable instance * @throws IOException If failed to append the data + * @see #appendPublicKeyEntry(Appendable, PublicKey, PublicKeyEntryDataResolver) */ public static <A extends Appendable> A appendPublicKeyEntry(A sb, PublicKey key) throws IOException { + return appendPublicKeyEntry(sb, key, null); + } + + /** + * @param <A> The generic appendable class + * @param sb The {@link Appendable} instance to encode the data into + * @param key The {@link PublicKey} - ignored if {@code null} + * @param encoder The {@link PublicKeyEntryDataResolver} to use in order to encode + * the key data bytes into a string representation - if {@code null} then one is + * automatically {@link #resolveKeyDataEntryResolver(String) resolved} + * @return The updated appendable instance + * @throws IOException If failed to append the data + */ + public static <A extends Appendable> A appendPublicKeyEntry( + A sb, PublicKey key, PublicKeyEntryDataResolver encoder) + throws IOException { if (key == null) { return sb; } @@ -258,9 +427,12 @@ public class PublicKeyEntry implements Serializable { try (ByteArrayOutputStream s = new ByteArrayOutputStream(Byte.MAX_VALUE)) { String keyType = decoder.encodePublicKey(s, key); byte[] bytes = s.toByteArray(); - Base64.Encoder encoder = Base64.getEncoder(); - String b64Data = encoder.encodeToString(bytes); - sb.append(keyType).append(' ').append(b64Data); + if (encoder == null) { + encoder = resolveKeyDataEntryResolver(keyType); + } + + String encData = encoder.encodeEntryKeyData(bytes); + sb.append(keyType).append(' ').append(encData); } return sb; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java new file mode 100644 index 0000000..96a4bb9 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDataResolver.java @@ -0,0 +1,71 @@ +/* + * 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; + +import java.util.Base64; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.NumberUtils; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface PublicKeyEntryDataResolver { + PublicKeyEntryDataResolver DEFAULT = new PublicKeyEntryDataResolver() { + @Override + public String toString() { + return "DEFAULT"; + } + }; + + /** + * Decodes the public key entry data bytes from their string representation - by + * default it assume {@link Base64} encoding. + * + * @param encData The encoded data - ignored if {@code null}/empty + * @return The decoded data bytes + */ + default byte[] decodeEntryKeyData(String encData) { + if (GenericUtils.isEmpty(encData)) { + return GenericUtils.EMPTY_BYTE_ARRAY; + } + + Base64.Decoder decoder = Base64.getDecoder(); + return decoder.decode(encData); + } + + /** + * Encodes the public key entry data bytes into their string representation - by + * default it assume {@link Base64} encoding. + * + * @param keyData The key data bytes - ignored if {@code null}/empty + * @return The encoded data bytes + */ + default String encodeEntryKeyData(byte[] keyData) { + if (NumberUtils.isEmpty(keyData)) { + return ""; + } + + Base64.Encoder encoder = Base64.getEncoder(); + return encoder.encodeToString(keyData); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java index ad42781..1d6b7aa 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java @@ -50,34 +50,37 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); Collection<String> supported = getSupportedTypeNames(); if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) { - return decodePublicKey(keyData); + return decodePublicKey(keyType, keyData); } throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported); } /** - * @param keyData The key data bytes in {@code OpenSSH} format (after - * BASE64 decoding) - ignored if {@code null}/empty + * @param keyType The {@code OpenSSH} reported key type + * @param keyData The key data bytes in {@code OpenSSH} format (after BASE64 + * decoding) - ignored if {@code null}/empty * @return The decoded {@link PublicKey} - or {@code null} if no data * @throws IOException If failed to decode the key * @throws GeneralSecurityException If failed to generate the key */ - default PUB decodePublicKey(byte... keyData) throws IOException, GeneralSecurityException { - return decodePublicKey(keyData, 0, NumberUtils.length(keyData)); + default PUB decodePublicKey(String keyType, byte... keyData) throws IOException, GeneralSecurityException { + return decodePublicKey(keyType, keyData, 0, NumberUtils.length(keyData)); } - default PUB decodePublicKey(byte[] keyData, int offset, int length) throws IOException, GeneralSecurityException { + default PUB decodePublicKey(String keyType, byte[] keyData, int offset, int length) + throws IOException, GeneralSecurityException { if (length <= 0) { return null; } try (InputStream stream = new ByteArrayInputStream(keyData, offset, length)) { - return decodePublicKey(stream); + return decodePublicKeyByType(keyType, stream); } } - default PUB decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException { + default PUB decodePublicKeyByType(String keyType, InputStream keyData) + throws IOException, GeneralSecurityException { // the actual data is preceded by a string that repeats the key type String type = KeyEntryResolver.decodeString(keyData, KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH); if (GenericUtils.isEmpty(type)) { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java index 07c7e44..d9457f7 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/KeyPairPEMResourceParser.java @@ -19,17 +19,15 @@ package org.apache.sshd.common.config.keys.loader.pem; +import org.apache.sshd.common.AlgorithmNameProvider; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; /** + * The reported algorithm name refers to the encryption algorithm name - e.g., "RSA", "DSA" + * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface KeyPairPEMResourceParser extends KeyPairResourceParser { - /** - * @return The encryption algorithm name - e.g., "RSA", "DSA" - */ - String getAlgorithm(); - +public interface KeyPairPEMResourceParser extends AlgorithmNameProvider, KeyPairResourceParser { /** * @return The OID used to identify this algorithm in DER encodings - e.g., RSA=1.2.840.113549.1.1.1 */ http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java index ba181dc..400d14a 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/digest/DigestInformation.java @@ -19,18 +19,16 @@ package org.apache.sshd.common.digest; +import org.apache.sshd.common.AlgorithmNameProvider; + /** + * The reported algorithm name refers to the type of digest being calculated. + * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface DigestInformation { - /** - * @return The digest algorithm name - */ - String getAlgorithm(); - +public interface DigestInformation extends AlgorithmNameProvider { /** * @return The number of bytes in the digest's output */ int getBlockSize(); - } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java new file mode 100644 index 0000000..1a8e456 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeySizeIndicator.java @@ -0,0 +1,30 @@ +/* + * 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.keyprovider; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface KeySizeIndicator { + /** + * @return The number of bits used in the key + */ + int getKeySize(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 new file mode 100644 index 0000000..1830447 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyTypeIndicator.java @@ -0,0 +1,31 @@ +/* + * 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.keyprovider; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface KeyTypeIndicator { + /** + * @return The <U>SSH</U> key type name - e.g., "ssh-rsa", "sshd-dss" etc. + */ + String getKeyType(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java index 583165f..07a9321 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/mac/MacInformation.java @@ -19,15 +19,14 @@ package org.apache.sshd.common.mac; +import org.apache.sshd.common.AlgorithmNameProvider; + /** + * The reported algorithm name refers to the MAC being used + * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface MacInformation { - /** - * @return MAC algorithm name - */ - String getAlgorithm(); - +public interface MacInformation extends AlgorithmNameProvider { /** * @return MAC output block size in bytes - may be less than the default * - e.g., MD5-96 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java b/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java index fd88a1d..2c78ec1 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/signature/Signature.java @@ -21,20 +21,17 @@ package org.apache.sshd.common.signature; import java.security.PrivateKey; import java.security.PublicKey; +import org.apache.sshd.common.AlgorithmNameProvider; import org.apache.sshd.common.util.NumberUtils; /** - * Signature interface for SSH used to sign or verify packets - * Usually wraps a javax.crypto.Signature object + * Signature interface for SSH used to sign or verify packets. Usually wraps a + * {@code javax.crypto.Signature} object. The reported algorithm name refers to + * the signature type being applied. * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface Signature { - /** - * @return The signature algorithm name - */ - String getAlgorithm(); - +public interface Signature extends AlgorithmNameProvider { /** * @param key The {@link PublicKey} to be used for verifying signatures * @throws Exception If failed to initialize http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java index c13cd9e..7fff5d0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/BufferUtils.java @@ -65,19 +65,27 @@ public final class BufferUtils { throw new UnsupportedOperationException("No instance allowed"); } - public static void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte... data) { + public static void dumpHex( + SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte... data) { dumpHex(logger, level, prefix, resolver, sep, data, 0, NumberUtils.length(data)); } - public static void dumpHex(SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, char sep, byte[] data, int offset, int len) { - dumpHex(logger, level, prefix, sep, resolver.getIntProperty(HEXDUMP_CHUNK_SIZE, DEFAULT_HEXDUMP_CHUNK_SIZE), data, offset, len); + public static void dumpHex( + SimplifiedLog logger, Level level, String prefix, PropertyResolver resolver, + char sep, byte[] data, int offset, int len) { + dumpHex(logger, level, prefix, sep, + resolver.getIntProperty(HEXDUMP_CHUNK_SIZE, DEFAULT_HEXDUMP_CHUNK_SIZE), + data, offset, len); } - public static void dumpHex(SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte... data) { + public static void dumpHex( + SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte... data) { dumpHex(logger, level, prefix, sep, chunkSize, data, 0, NumberUtils.length(data)); } - public static void dumpHex(SimplifiedLog logger, Level level, String prefix, char sep, int chunkSize, byte[] data, int offset, int len) { + public static void dumpHex( + SimplifiedLog logger, Level level, String prefix, char sep, + int chunkSize, byte[] data, int offset, int len) { if ((logger == null) || (level == null) || (!logger.isEnabled(level))) { return; } @@ -151,7 +159,9 @@ public final class BufferUtils { return appendHex(sb, array, 0, NumberUtils.length(array), sep); } - public static <A extends Appendable> A appendHex(A sb, byte[] array, int offset, int len, char sep) throws IOException { + public static <A extends Appendable> A appendHex( + A sb, byte[] array, int offset, int len, char sep) + throws IOException { if (len <= 0) { return sb; } @@ -244,7 +254,9 @@ public final class BufferUtils { * @throws IllegalArgumentException If invalid HEX sequence length * @throws NumberFormatException If invalid HEX characters found */ - public static <S extends OutputStream> int decodeHex(S stream, char separator, CharSequence csq, int start, int end) throws IOException { + public static <S extends OutputStream> int decodeHex( + S stream, char separator, CharSequence csq, int start, int end) + throws IOException { int len = end - start; ValidateUtils.checkTrue(len >= 0, "Bad HEX sequence length: %d", len); @@ -399,7 +411,9 @@ public final class BufferUtils { * @throws IOException If failed to write the value or work buffer to small * @see #writeUInt(OutputStream, long, byte[], int, int) */ - public static void writeInt(OutputStream output, int value, byte[] buf, int off, int len) throws IOException { + public static void writeInt( + OutputStream output, int value, byte[] buf, int off, int len) + throws IOException { writeUInt(output, value & 0xFFFFFFFFL, buf, off, len); } @@ -427,7 +441,9 @@ public final class BufferUtils { * @throws IOException If failed to write the value or work buffer to small * @see #putUInt(long, byte[], int, int) */ - public static void writeUInt(OutputStream output, long value, byte[] buf, int off, int len) throws IOException { + public static void writeUInt( + OutputStream output, long value, byte[] buf, int off, int len) + throws IOException { try { int writeLen = putUInt(value, buf, off, len); output.write(buf, off, writeLen); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java index 287abec..1ed3a6c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/server/keyprovider/AbstractGeneratorHostKeyProvider.java @@ -35,11 +35,13 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; +import org.apache.sshd.common.AlgorithmNameProvider; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.cipher.ECCurves; import org.apache.sshd.common.config.keys.BuiltinIdentities; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider; +import org.apache.sshd.common.keyprovider.KeySizeIndicator; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.io.resource.PathResource; @@ -53,7 +55,9 @@ import org.apache.sshd.common.util.security.SecurityUtils; * * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public abstract class AbstractGeneratorHostKeyProvider extends AbstractKeyPairProvider { +public abstract class AbstractGeneratorHostKeyProvider + extends AbstractKeyPairProvider + implements AlgorithmNameProvider, KeySizeIndicator { public static final String DEFAULT_ALGORITHM = KeyUtils.RSA_ALGORITHM; public static final boolean DEFAULT_ALLOWED_TO_OVERWRITE = true; @@ -77,6 +81,7 @@ public abstract class AbstractGeneratorHostKeyProvider extends AbstractKeyPairPr this.path = (path == null) ? null : path.toAbsolutePath(); } + @Override public String getAlgorithm() { return algorithm; } @@ -85,6 +90,7 @@ public abstract class AbstractGeneratorHostKeyProvider extends AbstractKeyPairPr this.algorithm = algorithm; } + @Override public int getKeySize() { return keySize; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java index fcba948..3c70f2e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/KeySetPublickeyAuthenticator.java @@ -21,6 +21,7 @@ package org.apache.sshd.server.auth.pubkey; import java.security.PublicKey; import java.util.Collection; import java.util.Collections; +import java.util.Objects; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.util.GenericUtils; @@ -32,11 +33,20 @@ import org.apache.sshd.server.session.ServerSession; */ public class KeySetPublickeyAuthenticator extends AbstractLoggingBean implements PublickeyAuthenticator { private final Collection<? extends PublicKey> keySet; + private final Object id; - public KeySetPublickeyAuthenticator(Collection<? extends PublicKey> keySet) { + public KeySetPublickeyAuthenticator(Object id, Collection<? extends PublicKey> keySet) { + this.id = id; this.keySet = (keySet == null) ? Collections.emptyList() : keySet; } + /** + * @return Some kind of mnemonic identifier for the authenticator - used also in {@link #toString()} + */ + public Object getId() { + return id; + } + public final Collection<? extends PublicKey> getKeySet() { return keySet; } @@ -62,4 +72,9 @@ public class KeySetPublickeyAuthenticator extends AbstractLoggingBean implements } return matchFound; } + + @Override + public String toString() { + return Objects.toString(getId()); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 a498478..b6a8ab6 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 @@ -49,14 +49,23 @@ public interface PublickeyAuthenticator { */ boolean authenticate(String username, PublicKey key, ServerSession session) throws AsyncAuthException; + /** + * @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 + * @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( - PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries) - throws IOException, GeneralSecurityException { - Collection<PublicKey> keys = AuthorizedKeyEntry.resolveAuthorizedKeys(fallbackResolver, entries); + Object id, PublicKeyEntryResolver fallbackResolver, Collection<? extends AuthorizedKeyEntry> entries) + throws IOException, GeneralSecurityException { + Collection<PublicKey> keys = + AuthorizedKeyEntry.resolveAuthorizedKeys(fallbackResolver, entries); if (GenericUtils.isEmpty(keys)) { return RejectAllPublickeyAuthenticator.INSTANCE; } else { - return new KeySetPublickeyAuthenticator(keys); + return new KeySetPublickeyAuthenticator(id, keys); } } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2f92fe5d/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 a2d577f..3e9ca3a 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 @@ -82,7 +82,8 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement boolean debugEnabled = log.isDebugEnabled(); if (!isValidUsername(username, session)) { if (debugEnabled) { - log.debug("authenticate(" + username + ")[" + session + "][" + key.getAlgorithm() + "] invalid user name - file = " + getPath()); + log.debug("authenticate({})[{}][{}] invalid user name - file = {}", + username, session, key.getAlgorithm(), getPath()); } return false; } @@ -92,15 +93,15 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement Objects.requireNonNull(resolvePublickeyAuthenticator(username, session), "No delegate"); boolean accepted = delegate.authenticate(username, key, session); if (debugEnabled) { - log.debug("authenticate(" + username + ")[" + session + "][" + key.getAlgorithm() + "] accepted " + accepted + " from " + getPath()); + log.debug("authenticate({})[{}][{}] invalid user name - accepted={} from file = {}", + username, session, key.getAlgorithm(), accepted, getPath()); } return accepted; } catch (Throwable e) { if (debugEnabled) { - log.debug("authenticate(" + username + ")[" + session + "][" + getPath() + "]" - + " failed (" + e.getClass().getSimpleName() + ")" - + " to resolve delegate: " + e.getMessage()); + log.debug("authenticate({})[{}] failed ({}) to authenticate {} key from {}: {}", + username, session, e.getClass().getSimpleName(), key.getAlgorithm(), getPath(), e.getMessage()); } if (log.isTraceEnabled()) { @@ -127,24 +128,33 @@ public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher implement Collection<AuthorizedKeyEntry> entries = reloadAuthorizedKeys(path, username, session); if (GenericUtils.size(entries) > 0) { PublickeyAuthenticator authDelegate = - PublickeyAuthenticator.fromAuthorizedEntries(getFallbackPublicKeyEntryResolver(), entries); + createDelegateAuthenticator(path, entries, getFallbackPublicKeyEntryResolver()); delegateHolder.set(authDelegate); } } else { - log.info("resolvePublickeyAuthenticator(" + username + ")[" + session + "] no authorized keys file at " + path); + log.info("resolvePublickeyAuthenticator({})[{}] no authorized keys file at {}", username, session, path); } } return delegateHolder.get(); } + protected PublickeyAuthenticator createDelegateAuthenticator( + Path path, Collection<AuthorizedKeyEntry> entries, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { + return PublickeyAuthenticator.fromAuthorizedEntries(path, fallbackResolver, entries); + } + protected PublicKeyEntryResolver getFallbackPublicKeyEntryResolver() { return PublicKeyEntryResolver.IGNORING; } - protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, String username, ServerSession session) throws IOException { + protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys( + Path path, String username, ServerSession session) + throws IOException, GeneralSecurityException { Collection<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(path); - log.info("reloadAuthorizedKeys(" + username + ")[" + session + "] loaded " + GenericUtils.size(entries) + " keys from " + path); + log.info("reloadAuthorizedKeys({})[{}] loaded {} keys from {}", + username, session, GenericUtils.size(entries), path); updateReloadAttributes(); return entries; }
