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
The following commit(s) were added to refs/heads/master by this push: new 9959954 [SSHD-917] Add support for SSH2 public key file format 9959954 is described below commit 9959954f0d34278dc3691fffea688186fa61100d Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Wed May 15 16:18:25 2019 +0300 [SSHD-917] Add support for SSH2 public key file format --- CHANGES.md | 7 +- README.md | 3 +- .../sshd/client/config/hosts/HostConfigEntry.java | 3 +- .../config/hosts/HostConfigEntryResolver.java | 2 +- .../common/config/keys/AuthorizedKeyEntry.java | 20 ++ .../sshd/common/config/keys/KeyEntryResolver.java | 85 ++++++++ .../sshd/common/config/keys/PublicKeyEntry.java | 16 +- .../common/config/keys/PublicKeyEntryDecoder.java | 59 +----- .../common/config/keys/PublicKeyEntryResolver.java | 8 +- .../config/keys/PublicKeyRawDataDecoder.java | 82 ++++++++ .../common/config/keys/PublicKeyRawDataReader.java | 120 +++++++++++ .../config/keys/impl/DSSPublicKeyEntryDecoder.java | 6 +- .../keys/impl/ECDSAPublicKeyEntryDecoder.java | 6 +- .../config/keys/impl/RSAPublicKeyDecoder.java | 6 +- .../keys/loader/AbstractKeyPairResourceParser.java | 32 ++- .../openssh/OpenSSHKeyPairResourceParser.java | 14 +- .../pem/AbstractPEMResourceKeyPairParser.java | 37 ++-- .../loader/pem/DSSPEMResourceKeyPairParser.java | 3 +- .../loader/pem/ECDSAPEMResourceKeyPairParser.java | 2 +- .../loader/pem/PKCS8PEMResourceKeyPairParser.java | 3 +- .../loader/pem/RSAPEMResourceKeyPairParser.java | 3 +- .../loader/ssh2/Ssh2PublicKeyEntryDecoder.java | 224 +++++++++++++++++++++ .../org/apache/sshd/common/util/NumberUtils.java | 6 +- .../BouncyCastleKeyPairResourceParser.java | 7 +- .../security/eddsa/Ed25519PublicKeyDecoder.java | 6 +- .../KeyUtilsFingerprintCaseSensitivityTest.java | 3 +- .../keys/KeyUtilsFingerprintGenerationTest.java | 3 +- .../common/config/keys/PublicKeyEntryTest.java | 3 +- .../OpenSSHKeyPairResourceParserTestSupport.java | 4 +- .../Ssh2PublicKeyEntryDecoderByKeyTypeTest.java | 121 +++++++++++ .../ssh2/Ssh2PublicKeyEntryDecoderTest.java} | 46 +++-- .../loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub | 1 + .../loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2 | 6 + .../loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub | 1 + .../loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2 | 6 + .../loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub | 1 + .../loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2 | 7 + .../loader/ssh2/rfc4716-multi-line-comment.ssh2 | 13 ++ .../keys/loader/ssh2/rfc4716-multiple-headers.ssh2 | 7 + .../config/keys/loader/ssh2/ssh-dss-PublicKey.pub | 1 + .../config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2 | 12 ++ .../keys/loader/ssh2/ssh-ed25519-PublicKey.pub | 1 + .../keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2 | 4 + .../config/keys/loader/ssh2/ssh-rsa-PublicKey.pub | 1 + .../config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2 | 14 ++ .../KnownHostsServerKeyVerifierTest.java | 2 +- .../common/config/keys/AuthorizedKeyEntryTest.java | 7 +- .../pubkey/LdapPublickeyAuthenticatorTest.java | 4 +- .../openpgp/PGPAuthorizedKeyEntriesLoader.java | 5 +- .../loader/openpgp/PGPKeyPairResourceParser.java | 11 +- .../loader/openpgp/PGPUtilsKeyFingerprintTest.java | 5 +- .../keys/loader/putty/AbstractPuttyKeyDecoder.java | 33 +-- .../keys/loader/putty/DSSPuttyKeyDecoder.java | 3 +- .../keys/loader/putty/ECDSAPuttyKeyDecoder.java | 3 +- .../keys/loader/putty/EdDSAPuttyKeyDecoder.java | 3 +- .../keys/loader/putty/RSAPuttyKeyDecoder.java | 5 +- 56 files changed, 936 insertions(+), 160 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 182c736..4065378 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -43,6 +43,9 @@ when they are no longer needed once data has been successfully copied. * The `CommandFactory` and `ShellFactory` have been modified to accept the server's `ChannelSession` instance through which they are being invoked. +* The various implementations of public/private keys/pairs decoders/loaders are provided with a `Map` of any headers that +may be available in the relevant data file. + ## Minor code helpers * The `Session` object provides a `isServerSession` method that can be used to distinguish between @@ -126,6 +129,8 @@ and therefore closing all currently tracked file/directory handles. * [SSHD-909](https://issues.apache.org/jira/browse/SSHD-909) - SFTP versions extension handler ignores non-numerical versions when resolving the available ones. -* [SSHD-913](https://issues.apache.org/jira/browse/SSHD-913) - Provide channel session instance to command and/or shell factories creators +* [SSHD-913](https://issues.apache.org/jira/browse/SSHD-913) - Provide channel session instance to command and/or shell factories creators * [SSHD-912](https://issues.apache.org/jira/browse/SSHD-912) - Use separate locks for Future(s) and Session/Channel instances. + +* [SSHD-917](https://issues.apache.org/jira/browse/SSHD-917) - Add support for SSH2 public key file format. diff --git a/README.md b/README.md index 24ac222..49b4e38 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ based applications requiring SSH support. * [RFC 4335 - The Secure Shell (SSH) Session Channel Break Extension](https://tools.ietf.org/html/rfc4335) * [RFC 4344 - The Secure Shell (SSH) Transport Layer Encryption Modes](https://tools.ietf.org/html/rfc4344) * [RFC 4345 - Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol](https://tools.ietf.org/html/rfc4345) +* [RFC 4716 - The Secure Shell (SSH) Public Key File Format](https://tools.ietf.org/html/rfc4716) * [RFC 5480 - Elliptic Curve Cryptography Subject Public Key Information](https://tools.ietf.org/html/rfc5480) * [RFC 6668 - SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol](https://tools.ietf.org/html/rfc6668) * [RFC 8160 - IUTF8 Terminal Mode in Secure Shell (SSH)](https://tools.ietf.org/html/rfc8160) @@ -59,10 +60,8 @@ based applications requiring SSH support. * Java 8+ (as of version 1.3) - * [Slf4j](http://www.slf4j.org/) - The code only requires the core abstract [slf4j-api](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) module. The actual implementation of the logging API can be selected from the many existing adaptors. 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 475bd11..0ad02e8 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 @@ -61,9 +61,10 @@ import org.apache.sshd.common.util.io.NoCloseReader; /** * Represents an entry in the client's configuration file as defined by - * the <A HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config">ssh_config</A> + * the <A HREF="https://linux.die.net/man/5/ssh_config">ssh_config</A> * configuration file format * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + * @see <A HREF="https://www.cyberciti.biz/faq/create-ssh-config-file-on-linux-unix/">OpenSSH Config File Examples</A> */ public class HostConfigEntry extends HostPatternsHolder implements MutableUserHolder { /** diff --git a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java index f3fff52..ce36aae 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntryResolver.java @@ -26,7 +26,7 @@ import org.apache.sshd.common.AttributeRepository; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - * @see <A HREF="http://www.gsp.com/cgi-bin/man.cgi?topic=ssh_config">ssh_config</A> + * @see <A HREF="https://linux.die.net/man/5/ssh_config">ssh_config</A> */ @FunctionalInterface public interface HostConfigEntryResolver { 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 a573c44..304f10d 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 @@ -32,6 +32,7 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collections; @@ -88,6 +89,25 @@ public class AuthorizedKeyEntry extends PublicKeyEntry { } } + /** + * @param session The {@link SessionContext} for invoking this load command - may + * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). + * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if + * none of the built-in ones can be used. If {@code null} and no built-in + * resolver can be used then an {@link InvalidKeySpecException} is thrown. + * @return The resolved {@link PublicKey} - or {@code null} if could not be + * resolved. <B>Note:</B> may be called only after key type and data bytes + * have been set or exception(s) may be thrown + * @throws IOException If failed to decode the key + * @throws GeneralSecurityException If failed to generate the key + * @see PublicKeyEntry#resolvePublicKey(SessionContext, Map, PublicKeyEntryResolver) + */ + public PublicKey resolvePublicKey( + SessionContext session, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { + return resolvePublicKey(session, getLoginOptions(), fallbackResolver); + } + @Override public PublicKey appendPublicKey( SessionContext session, Appendable sb, PublicKeyEntryResolver fallbackResolver) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java index 8b6dcc8..a10322a 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java @@ -33,7 +33,10 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Map; +import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.io.IoUtils; /** @@ -200,4 +203,86 @@ public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey> | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); } + + static Map.Entry<String, Integer> decodeString(byte[] buf, int maxChars) { + return decodeString(buf, 0, NumberUtils.length(buf), maxChars); + } + + static Map.Entry<String, Integer> decodeString(byte[] buf, int offset, int available, int maxChars) { + return decodeString(buf, offset, available, StandardCharsets.UTF_8, maxChars); + } + + static Map.Entry<String, Integer> decodeString(byte[] buf, Charset cs, int maxChars) { + return decodeString(buf, 0, NumberUtils.length(buf), cs, maxChars); + } + + /** + * Decodes a run-length encoded string + * + * @param buf The buffer with the data bytes + * @param offset The offset in the buffer to decode the string + * @param available The max. available data starting from the offset + * @param cs The {@link Charset} to use to decode the string + * @param maxChars Max. allowed characters in string - if more than + * that is encoded then an {@link IndexOutOfBoundsException} will be thrown + * @return The decoded string + the offset of the next byte after it + * @see #readRLEBytes(byte[], int, int, int) + */ + static Map.Entry<String, Integer> decodeString( + byte[] buf, int offset, int available, Charset cs, int maxChars) { + Map.Entry<byte[], Integer> result = + readRLEBytes(buf, offset, available, maxChars * 4 /* in case UTF-8 with weird characters */); + byte[] bytes = result.getKey(); + Integer nextOffset = result.getValue(); + return new SimpleImmutableEntry<>(new String(bytes, cs), nextOffset); + } + + static Map.Entry<byte[], Integer> readRLEBytes(byte[] buf, int maxAllowed) { + return readRLEBytes(buf, 0, NumberUtils.length(buf), maxAllowed); + } + + /** + * Decodes a run-length encoded byte array + * + * @param buf The buffer with the data bytes + * @param offset The offset in the buffer to decode the array + * @param available The max. available data starting from the offset + * @param maxChars Max. allowed data in decoded buffer - if more than + * that is encoded then an {@link IndexOutOfBoundsException} will be thrown + * @return The decoded data buffer + the offset of the next byte after it + */ + static Map.Entry<byte[], Integer> readRLEBytes(byte[] buf, int offset, int available, int maxAllowed) { + int len = decodeInt(buf, offset, available); + if (len > maxAllowed) { + throw new IndexOutOfBoundsException("Requested block length (" + len + ") exceeds max. allowed (" + maxAllowed + ")"); + } + if (len < 0) { + throw new IndexOutOfBoundsException("Negative block length requested: " + len); + } + + available -= Integer.BYTES; + if (len > available) { + throw new IndexOutOfBoundsException("Requested block length (" + len + ") exceeds remaing (" + available + ")"); + } + + byte[] bytes = new byte[len]; + offset += Integer.BYTES; + System.arraycopy(buf, offset, bytes, 0, len); + return new SimpleImmutableEntry<>(bytes, Integer.valueOf(offset + len)); + } + + static int decodeInt(byte[] buf) { + return decodeInt(buf, 0, NumberUtils.length(buf)); + } + + static int decodeInt(byte[] buf, int offset, int available) { + if (available < Integer.BYTES) { + throw new IndexOutOfBoundsException("Available data length (" + available + ") cannot accommodate integer encoding"); + } + + return ((buf[offset] & 0xFF) << 24) + | ((buf[offset + 1] & 0xFF) << 16) + | ((buf[offset + 2] & 0xFF) << 8) + | (buf[offset + 3] & 0xFF); + } } 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 4cabe0d..1c6dfbd 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 @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.NavigableMap; import java.util.Objects; import java.util.TreeMap; @@ -119,6 +120,7 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { /** * @param session The {@link SessionContext} for invoking this load command - may * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). + * @param headers Any headers that may have been available when data was read * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if * none of the built-in ones can be used. If {@code null} and no built-in * resolver can be used then an {@link InvalidKeySpecException} is thrown. @@ -128,8 +130,9 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { * @throws IOException If failed to decode the key * @throws GeneralSecurityException If failed to generate the key */ - public PublicKey resolvePublicKey(SessionContext session, PublicKeyEntryResolver fallbackResolver) - throws IOException, GeneralSecurityException { + public PublicKey resolvePublicKey( + SessionContext session, Map<String, String> headers, PublicKeyEntryResolver fallbackResolver) + throws IOException, GeneralSecurityException { String kt = getKeyType(); PublicKeyEntryResolver decoder = KeyUtils.getPublicKeyEntryDecoder(kt); if (decoder == null) { @@ -139,7 +142,7 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { throw new InvalidKeySpecException("No decoder available for key type=" + kt); } - return decoder.resolve(session, kt, getKeyData()); + return decoder.resolve(session, kt, getKeyData(), headers); } /** @@ -157,7 +160,7 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { public PublicKey appendPublicKey( SessionContext session, Appendable sb, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException { - PublicKey key = resolvePublicKey(session, fallbackResolver); + PublicKey key = resolvePublicKey(session, Collections.emptyMap(), fallbackResolver); if (key != null) { appendPublicKeyEntry(sb, key, resolvePublicKeyEntryDataResolver()); } @@ -225,7 +228,10 @@ public class PublicKeyEntry implements Serializable, KeyTypeIndicator { List<PublicKey> keys = new ArrayList<>(numEntries); for (PublicKeyEntry e : entries) { - PublicKey k = e.resolvePublicKey(session, fallbackResolver); + Map<String, String> headers = (e instanceof AuthorizedKeyEntry) + ? ((AuthorizedKeyEntry) e).getLoginOptions() + : Collections.emptyMap(); + PublicKey k = e.resolvePublicKey(session, headers, fallbackResolver); if (k != null) { keys.add(k); } 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 a1704a1..d7285fd 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 @@ -19,7 +19,6 @@ package org.apache.sshd.common.config.keys; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -29,11 +28,11 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Collection; +import java.util.Map; import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.ValidateUtils; /** @@ -44,48 +43,25 @@ import org.apache.sshd.common.util.ValidateUtils; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> - extends KeyEntryResolver<PUB, PRV>, PublicKeyEntryResolver { + extends KeyEntryResolver<PUB, PRV>, PublicKeyRawDataDecoder<PUB>, PublicKeyEntryResolver { @Override - default PublicKey resolve(SessionContext session, String keyType, byte[] keyData) - throws IOException, GeneralSecurityException { + default PublicKey resolve( + SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); Collection<String> supported = getSupportedKeyTypes(); if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) { - return decodePublicKey(session, keyType, keyData); + return decodePublicKey(session, keyType, keyData, headers); } throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported); } - /** - * @param session The {@link SessionContext} for invoking this command - may - * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). - * @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(SessionContext session, String keyType, byte... keyData) - throws IOException, GeneralSecurityException { - return decodePublicKey(session, keyType, keyData, 0, NumberUtils.length(keyData)); - } - - default PUB decodePublicKey(SessionContext session, 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 decodePublicKeyByType(session, keyType, stream); - } - } - - default PUB decodePublicKeyByType(SessionContext session, String keyType, InputStream keyData) - throws IOException, GeneralSecurityException { + @Override + default PUB decodePublicKeyByType( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + 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)) { @@ -97,23 +73,10 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported); } - return decodePublicKey(session, type, keyData); + return decodePublicKey(session, type, keyData, headers); } /** - * @param session The {@link SessionContext} for invoking this command - may - * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). - * @param keyType The reported / encode key type - * @param keyData The key data bytes stream positioned after the key type decoding - * and making sure it is one of the supported types - * @return The decoded {@link PublicKey} - * @throws IOException If failed to read from the data stream - * @throws GeneralSecurityException If failed to generate the key - */ - PUB decodePublicKey(SessionContext session, String keyType, InputStream keyData) - throws IOException, GeneralSecurityException; - - /** * Encodes the {@link PublicKey} using the {@code OpenSSH} format - same * one used by the {@code decodePublicKey} method(s) * diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java index 5875251..f94665e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.Map; import org.apache.sshd.common.session.SessionContext; @@ -36,7 +37,7 @@ public interface PublicKeyEntryResolver { */ PublicKeyEntryResolver IGNORING = new PublicKeyEntryResolver() { @Override - public PublicKey resolve(SessionContext session, String keyType, byte[] keyData) + public PublicKey resolve(SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) throws IOException, GeneralSecurityException { return null; } @@ -52,7 +53,7 @@ public interface PublicKeyEntryResolver { */ PublicKeyEntryResolver FAILING = new PublicKeyEntryResolver() { @Override - public PublicKey resolve(SessionContext session, String keyType, byte[] keyData) + public PublicKey resolve(SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) throws IOException, GeneralSecurityException { throw new InvalidKeySpecException("Failing resolver on key type=" + keyType); } @@ -68,10 +69,11 @@ public interface PublicKeyEntryResolver { * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). * @param keyType The {@code OpenSSH} reported key type * @param keyData The {@code OpenSSH} encoded key data + * @param headers Any headers that may have been available when data was read * @return The extracted {@link PublicKey} - ignored if {@code null} * @throws IOException If failed to parse the key data * @throws GeneralSecurityException If failed to generate the key */ - PublicKey resolve(SessionContext session, String keyType, byte[] keyData) + PublicKey resolve(SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) throws IOException, GeneralSecurityException; } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java new file mode 100644 index 0000000..d1e345b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataDecoder.java @@ -0,0 +1,82 @@ +/* + * 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.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.PublicKey; +import java.util.Map; + +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.NumberUtils; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public interface PublicKeyRawDataDecoder<PUB extends PublicKey> { + /** + * @param session The {@link SessionContext} for invoking this command - may + * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). + * @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 + * @param headers Any headers that may have been available when data was read + * @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( + SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { + return decodePublicKey(session, keyType, keyData, 0, NumberUtils.length(keyData), headers); + } + + default PUB decodePublicKey( + SessionContext session, String keyType, byte[] keyData, int offset, int length, Map<String, String> headers) + throws IOException, GeneralSecurityException { + if (length <= 0) { + return null; + } + + try (InputStream stream = new ByteArrayInputStream(keyData, offset, length)) { + return decodePublicKeyByType(session, keyType, stream, headers); + } + } + + PUB decodePublicKeyByType( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException; + + /** + * @param session The {@link SessionContext} for invoking this command - may + * be {@code null} if not invoked within a session context (e.g., offline tool or session unknown). + * @param keyType The reported / encode key type + * @param keyData The key data bytes stream positioned after the key type decoding + * and making sure it is one of the supported types + * @param headers Any headers that may have been available when data was read + * @return The decoded {@link PublicKey} + * @throws IOException If failed to read from the data stream + * @throws GeneralSecurityException If failed to generate the key + */ + PUB decodePublicKey(SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException; +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataReader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataReader.java new file mode 100644 index 0000000..3567439 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyRawDataReader.java @@ -0,0 +1,120 @@ +/* + * 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.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.PublicKey; +import java.util.List; +import java.util.Objects; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.io.resource.IoResource; +import org.apache.sshd.common.util.io.resource.PathResource; +import org.apache.sshd.common.util.io.resource.URLResource; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public interface PublicKeyRawDataReader<PUB extends PublicKey> { + default PUB readPublicKey(SessionContext session, Path path, OpenOption... options) + throws IOException, GeneralSecurityException { + return readPublicKey(session, path, StandardCharsets.UTF_8, options); + } + + default PUB readPublicKey( + SessionContext session, Path path, Charset cs, OpenOption... options) + throws IOException, GeneralSecurityException { + return readPublicKey(session, new PathResource(path, options), cs); + } + + default PUB readPublicKey(SessionContext session, URL url) + throws IOException, GeneralSecurityException { + return readPublicKey(session, url, StandardCharsets.UTF_8); + } + + default PUB readPublicKey(SessionContext session, URL url, Charset cs) + throws IOException, GeneralSecurityException { + return readPublicKey(session, new URLResource(url), cs); + } + + default PUB readPublicKey(SessionContext session, IoResource<?> resource) + throws IOException, GeneralSecurityException { + return readPublicKey(session, resource, StandardCharsets.UTF_8); + } + + default PUB readPublicKey( + SessionContext session, IoResource<?> resource, Charset cs) + throws IOException, GeneralSecurityException { + try (InputStream stream = Objects.requireNonNull(resource, "No resource data").openInputStream()) { + return readPublicKey(session, resource, stream, cs); + } + } + + default PUB readPublicKey( + SessionContext session, NamedResource resourceKey, InputStream stream) + throws IOException, GeneralSecurityException { + return readPublicKey(session, resourceKey, stream, StandardCharsets.UTF_8); + } + + default PUB readPublicKey( + SessionContext session, NamedResource resourceKey, InputStream stream, Charset cs) + throws IOException, GeneralSecurityException { + try (Reader reader = new InputStreamReader( + Objects.requireNonNull(stream, "No stream instance"), Objects.requireNonNull(cs, "No charset"))) { + return readPublicKey(session, resourceKey, reader); + } + } + + default PUB readPublicKey( + SessionContext session, NamedResource resourceKey, Reader rdr) + throws IOException, GeneralSecurityException { + try (BufferedReader br = new BufferedReader(Objects.requireNonNull(rdr, "No reader instance"), IoUtils.DEFAULT_COPY_SIZE)) { + return readPublicKey(session, resourceKey, br); + } + } + + default PUB readPublicKey( + SessionContext session, NamedResource resourceKey, BufferedReader rdr) + throws IOException, GeneralSecurityException { + List<String> lines = IoUtils.readAllLines(rdr); + try { + return readPublicKey(session, resourceKey, lines); + } finally { + lines.clear(); // clean up sensitive data a.s.a.p. + } + } + + PUB readPublicKey(SessionContext session, NamedResource resourceKey, List<String> lines) + throws IOException, GeneralSecurityException; +} + + diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java index d429224..f57634b 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java @@ -34,6 +34,7 @@ import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Collections; +import java.util.Map; import java.util.Objects; import org.apache.sshd.common.config.keys.KeyEntryResolver; @@ -53,8 +54,9 @@ public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAP } @Override - public DSAPublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData) - throws IOException, GeneralSecurityException { + public DSAPublicKey decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly throw new InvalidKeySpecException("Unexpected key type: " + keyType); } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java index 24471ce..877b696 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java @@ -35,6 +35,7 @@ import java.security.spec.ECPoint; import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.InvalidKeySpecException; +import java.util.Map; import java.util.Objects; import org.apache.sshd.common.cipher.ECCurves; @@ -64,8 +65,9 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC } @Override - public ECPublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData) - throws IOException, GeneralSecurityException { + public ECPublicKey decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { ECCurves curve = ECCurves.fromKeyType(keyType); if (curve == null) { throw new InvalidKeySpecException("Not an EC curve name: " + keyType); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java index 9c2730d..1de4ce0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java @@ -35,6 +35,7 @@ import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.Arrays; import java.util.Collections; +import java.util.Map; import java.util.Objects; import org.apache.sshd.common.config.keys.KeyEntryResolver; @@ -59,8 +60,9 @@ public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublic } @Override - public RSAPublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData) - throws IOException, GeneralSecurityException { + public RSAPublicKey decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { // Not really required, but allow it String canonicalName = KeyUtils.getCanonicalKeyType(keyType); if (!KeyPairProvider.SSH_RSA.equals(canonicalName)) { // just in case we were invoked directly diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java index 457b0e4..5d0fae0 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java @@ -25,6 +25,7 @@ import java.io.InputStream; import java.io.StreamCorruptedException; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -109,8 +110,15 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean int endIndex = markerPos.getKey(); String endLine = lines.get(endIndex); - Collection<KeyPair> kps = extractKeyPairs(session, resourceKey, - startLine, endLine, passwordProvider, lines.subList(startIndex, endIndex)); + Map.Entry<? extends Map<String, String>, ? extends List<String>> result = + separateDataLinesFromHeaders( + session, resourceKey, startLine, endLine, lines.subList(startIndex, endIndex)); + Map<String, String> headers = result.getKey(); + List<String> dataLines = result.getValue(); + Collection<KeyPair> kps = extractKeyPairs( + session, resourceKey, startLine, endLine, passwordProvider, + (dataLines == null) ? Collections.emptyList() : dataLines, + (headers == null) ? Collections.emptyMap() : headers); if (GenericUtils.isNotEmpty(kps)) { if (GenericUtils.isEmpty(keyPairs)) { keyPairs = new LinkedList<>(kps); @@ -126,6 +134,12 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean return keyPairs; } + protected Map.Entry<Map<String, String>, List<String>> separateDataLinesFromHeaders( + SessionContext session, NamedResource resourceKey, String startLine, String endLine, List<String> dataLines) + throws IOException, GeneralSecurityException { + return new SimpleImmutableEntry<>(Collections.emptyMap(), dataLines); + } + /** * Extracts the key pairs within a <U>single</U> delimited by markers block of lines. By * default cleans up the empty lines, joins them and converts them from BASE64 @@ -138,6 +152,7 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean * @param passwordProvider The {@link FilePasswordProvider} to use * in case the data is encrypted - may be {@code null} if no encrypted * @param lines The block of lines between the markers + * @param headers Any headers that may have been available when data was read * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none. * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys @@ -146,11 +161,11 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - List<String> lines) + List<String> lines, Map<String, String> headers) throws IOException, GeneralSecurityException { byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines); try { - return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataBytes); + return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataBytes, headers); } finally { Arrays.fill(dataBytes, (byte) 0); // clean up sensitive data a.s.a.p. } @@ -165,6 +180,7 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean * @param passwordProvider The {@link FilePasswordProvider} to use * in case the data is encrypted - may be {@code null} if no encrypted * @param bytes The decoded bytes from the lines containing the data + * @param headers Any headers that may have been available when data was read * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none. * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys @@ -173,14 +189,14 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - byte[] bytes) + byte[] bytes, Map<String, String> headers) throws IOException, GeneralSecurityException { if (log.isTraceEnabled()) { BufferUtils.dumpHex(getSimplifiedLogger(), Level.FINER, beginMarker, ':', 16, bytes); } try (InputStream bais = new ByteArrayInputStream(bytes)) { - return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais); + return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers); } } @@ -193,6 +209,7 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean * @param passwordProvider The {@link FilePasswordProvider} to use * in case the data is encrypted - may be {@code null} if no encrypted * @param stream The decoded data {@link InputStream} + * @param headers Any headers that may have been available when data was read * @return The extracted {@link KeyPair}s - may be {@code null}/empty if none. * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys @@ -200,6 +217,7 @@ public abstract class AbstractKeyPairResourceParser extends AbstractLoggingBean public abstract Collection<KeyPair> extractKeyPairs( SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, InputStream stream) + FilePasswordProvider passwordProvider, + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException; } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java index 0144d57..7d22579 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java @@ -106,7 +106,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { boolean debugEnabled = log.isDebugEnabled(); @@ -114,7 +114,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser String cipher = KeyEntryResolver.decodeString(stream, MAX_CIPHER_NAME_LENGTH); OpenSSHKdfOptions kdfOptions = - resolveKdfOptions(session, resourceKey, beginMarker, endMarker, stream); + resolveKdfOptions(session, resourceKey, beginMarker, endMarker, stream, headers); OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfOptions); int numKeys = KeyEntryResolver.decodeInt(stream); if (numKeys <= 0) { @@ -131,7 +131,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser List<PublicKey> publicKeys = new ArrayList<>(numKeys); boolean traceEnabled = log.isTraceEnabled(); for (int index = 1; index <= numKeys; index++) { - PublicKey pubKey = readPublicKey(session, resourceKey, context, stream); + PublicKey pubKey = readPublicKey(session, resourceKey, context, stream, headers); ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", index, resourceKey); if (traceEnabled) { log.trace("extractKeyPairs({}) read public key #{}: {} {}", @@ -198,7 +198,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser protected OpenSSHKdfOptions resolveKdfOptions( SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, InputStream stream) + String beginMarker, String endMarker, InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { String kdfName = KeyEntryResolver.decodeString(stream, OpenSSHKdfOptions.MAX_KDF_NAME_LENGTH); byte[] kdfOptions = KeyEntryResolver.readRLEBytes(stream, OpenSSHKdfOptions.MAX_KDF_OPTIONS_SIZE); @@ -215,7 +215,9 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser } protected PublicKey readPublicKey( - SessionContext session, NamedResource resourceKey, OpenSSHParserContext context, InputStream stream) + SessionContext session, NamedResource resourceKey, + OpenSSHParserContext context, + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { byte[] keyData = KeyEntryResolver.readRLEBytes(stream, MAX_PUBLIC_KEY_DATA_SIZE); try (InputStream bais = new ByteArrayInputStream(keyData)) { @@ -225,7 +227,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser throw new NoSuchAlgorithmException("Unsupported key type (" + keyType + ") in " + resourceKey); } - return decoder.decodePublicKey(session, keyType, bais); + return decoder.decodePublicKey(session, keyType, bais, headers); } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java index 95438e5..75cfa64 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java @@ -30,6 +30,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import javax.security.auth.login.CredentialException; import javax.security.auth.login.FailedLoginException; @@ -79,7 +81,7 @@ public abstract class AbstractPEMResourceKeyPairParser SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - List<String> lines) + List<String> lines, Map<String, String> headers) throws IOException, GeneralSecurityException { if (GenericUtils.isEmpty(lines)) { return Collections.emptyList(); @@ -89,6 +91,7 @@ public abstract class AbstractPEMResourceKeyPairParser byte[] initVector = null; String algInfo = null; int dataStartIndex = -1; + boolean hdrsAvailable = GenericUtils.isNotEmpty(headers); for (int index = 0; index < lines.size(); index++) { String line = GenericUtils.trimToEmpty(lines.get(index)); if (GenericUtils.isEmpty(line)) { @@ -102,28 +105,36 @@ public abstract class AbstractPEMResourceKeyPairParser break; } - if (line.startsWith("Proc-Type:")) { + String hdrName = line.substring(0, headerPos).trim(); + String hdrValue = line.substring(headerPos + 1).trim(); + if (!hdrsAvailable) { + Map<String, String> accHeaders = GenericUtils.isEmpty(headers) + ? new TreeMap<>(String.CASE_INSENSITIVE_ORDER) + : headers; + accHeaders.put(hdrName, hdrValue); + } + + if (hdrName.equalsIgnoreCase("Proc-Type")) { if (encrypted != null) { throw new StreamCorruptedException("Multiple encryption indicators in " + resourceKey); } - line = line.substring(headerPos + 1).trim(); - line = line.toUpperCase(); + hdrValue = hdrValue.toUpperCase(); encrypted = Boolean.valueOf(line.contains("ENCRYPTED")); - } else if (line.startsWith("DEK-Info:")) { + } else if (hdrName.equalsIgnoreCase("DEK-Info")) { if ((initVector != null) || (algInfo != null)) { throw new StreamCorruptedException("Multiple encryption settings in " + resourceKey); } - line = line.substring(headerPos + 1).trim(); - headerPos = line.indexOf(','); - if (headerPos < 0) { - throw new StreamCorruptedException(resourceKey + ": Missing encryption data values separator in line '" + line + "'"); + int infoPos = hdrValue.indexOf(','); + if (infoPos < 0) { + throw new StreamCorruptedException( + resourceKey + ": Missing encryption data values separator in line '" + line + "'"); } - algInfo = line.substring(0, headerPos).trim(); + algInfo = hdrValue.substring(0, infoPos).trim(); - String algInitVector = line.substring(headerPos + 1).trim(); + String algInitVector = hdrValue.substring(infoPos + 1).trim(); initVector = BufferUtils.decodeHex(BufferUtils.EMPTY_HEX_SEPARATOR, algInitVector); } } @@ -156,7 +167,7 @@ public abstract class AbstractPEMResourceKeyPairParser encryptedData = KeyPairResourceParser.extractDataBytes(dataLines); decodedData = applyPrivateKeyCipher(encryptedData, encContext, false); try (InputStream bais = new ByteArrayInputStream(decodedData)) { - keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais); + keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers); } } finally { Arrays.fill(encryptedData, (byte) 0); // get rid of sensitive data a.s.a.p. @@ -188,7 +199,7 @@ public abstract class AbstractPEMResourceKeyPairParser } } - return super.extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataLines); + return super.extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataLines, headers); } protected byte[] applyPrivateKeyCipher( diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java index df645e1..220f357 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java @@ -33,6 +33,7 @@ import java.security.spec.DSAPublicKeySpec; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; @@ -73,7 +74,7 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false); return Collections.singletonList(kp); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java index 17b69a8..50c19e3 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java @@ -77,7 +77,7 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(stream, false); if (!SecurityUtils.isECCSupported()) { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java index c755ea8..7fb995e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java @@ -32,6 +32,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; @@ -70,7 +71,7 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { // Save the data before getting the algorithm OID since we will need it byte[] encBytes = IoUtils.toByteArray(stream); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java index b830063..8a227a8 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java @@ -34,6 +34,7 @@ import java.security.spec.RSAPublicKeySpec; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; @@ -74,7 +75,7 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false); return Collections.singletonList(kp); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java new file mode 100644 index 0000000..3d8141f --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoder.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.ssh2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.security.GeneralSecurityException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.TreeMap; + +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.KeyTypeNamesSupport; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; +import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; +import org.apache.sshd.common.config.keys.PublicKeyRawDataDecoder; +import org.apache.sshd.common.config.keys.PublicKeyRawDataReader; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; +import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * Decodes a public key file encoded according to + * <A HREF="https://tools.ietf.org/html/rfc4716">The Secure Shell (SSH) Public Key File Format</A> + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class Ssh2PublicKeyEntryDecoder + implements PublicKeyRawDataDecoder<PublicKey>, PublicKeyEntryResolver, + PublicKeyRawDataReader<PublicKey>, KeyTypeNamesSupport { + public static final NavigableSet<String> SUPPORTED_KEY_TYPES = + Collections.unmodifiableNavigableSet( + GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, + KeyPairProvider.SSH_RSA, KeyPairProvider.SSH_DSS, KeyPairProvider.SSH_ED25519, + KeyPairProvider.ECDSA_SHA2_NISTP256, KeyPairProvider.ECDSA_SHA2_NISTP384, KeyPairProvider.ECDSA_SHA2_NISTP521)); + + public static final String BEGIN_MARKER = "BEGIN SSH2 PUBLIC KEY"; + public static final List<String> START_MARKERS = Collections.singletonList(BEGIN_MARKER); + + public static final String END_MARKER = "END SSH2 PUBLIC KEY"; + public static final List<String> STOP_MARKERS = Collections.singletonList(END_MARKER); + + /** + * According to <A HREF="https://tools.ietf.org/html/rfc4716#section-3.3">RFC-4716 section 3.3</A>: + * + * <P><code> + * A line is continued if the last character in the line is a "\". If + * the last character of a line is a "\", then the logical contents of + * the line are formed by removing the "\" and the line termination + * characters, and appending the contents of the next line. + * </code></P> + */ + public static final char HEADER_CONTINUATION_INDICATOR = '\\'; + + public static final Ssh2PublicKeyEntryDecoder INSTANCE = new Ssh2PublicKeyEntryDecoder(); + + public Ssh2PublicKeyEntryDecoder() { + super(); + } + + @Override + public NavigableSet<String> getSupportedKeyTypes() { + return SUPPORTED_KEY_TYPES; + } + + @Override + public PublicKey resolve( + SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { + ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); + Collection<String> supported = getSupportedKeyTypes(); + if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) { + return decodePublicKey(session, keyType, keyData, headers); + } + + throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported); + } + + @Override + public PublicKey decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { + return decodePublicKeyByType(session, keyType, keyData, headers); + } + + @Override + public PublicKey decodePublicKeyByType( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { + PublicKeyEntryDecoder<?, ?> decoder = KeyUtils.getPublicKeyEntryDecoder(keyType); + if (decoder == null) { + throw new InvalidKeySpecException("No decoder for key type=" + keyType); + } + + return decoder.decodePublicKeyByType(session, keyType, keyData, headers); + } + + @Override + public PublicKey readPublicKey(SessionContext session, NamedResource resourceKey, List<String> lines) + throws IOException, GeneralSecurityException { + Map.Entry<Integer, Integer> markerPos = KeyPairResourceParser.findMarkerLine(lines, START_MARKERS); + if (markerPos == null) { + return null; // be lenient + } + + int startIndex = markerPos.getKey(); + String startLine = lines.get(startIndex); + startIndex++; // skip the starting marker + + markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, STOP_MARKERS); + if (markerPos == null) { + throw new StreamCorruptedException("Missing end marker (" + END_MARKER + ") after line #" + startIndex); + } + + int endIndex = markerPos.getKey(); + String endLine = lines.get(endIndex); + Map.Entry<? extends Map<String, String>, ? extends List<String>> result = + separateDataLinesFromHeaders( + session, resourceKey, startLine, endLine, lines.subList(startIndex, endIndex)); + Map<String, String> headers = result.getKey(); + List<String> dataLines = result.getValue(); + return readPublicKey(session, resourceKey, BEGIN_MARKER, END_MARKER, + (dataLines == null) ? Collections.emptyList() : dataLines, + (headers == null) ? Collections.emptyMap() : headers); + } + + public PublicKey readPublicKey( + SessionContext session, NamedResource resourceKey, + String beginMarker, String endMarker, + List<String> lines, Map<String, String> headers) + throws IOException, GeneralSecurityException { + byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines); + try { + return readPublicKey(session, resourceKey, beginMarker, endMarker, dataBytes, headers); + } finally { + Arrays.fill(dataBytes, (byte) 0); // clean up sensitive data a.s.a.p. + } + } + + public PublicKey readPublicKey( + SessionContext session, NamedResource resourceKey, + String beginMarker, String endMarker, + byte[] dataBytes, Map<String, String> headers) + throws IOException, GeneralSecurityException { + Map.Entry<String, Integer> result = + KeyEntryResolver.decodeString(dataBytes, KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH); + String keyType = result.getKey(); + return resolve(session, keyType, dataBytes, headers); + } + + protected Map.Entry<Map<String, String>, List<String>> separateDataLinesFromHeaders( + SessionContext session, NamedResource resourceKey, String startLine, String endLine, List<String> lines) + throws IOException, GeneralSecurityException { + // According to RFC-4716: The Header-tag is case-insensitive + Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + int len = lines.size(); + for (int index = 0; index < len; index++) { + String l = lines.get(index); + l = l.trim(); + if (l.isEmpty()) { + continue; + } + + int pos = l.indexOf(':'); + // assume all the rest are data lines + if (pos < 0) { + return new SimpleImmutableEntry<>(headers, lines.subList(index, len)); + } + + String name = l.substring(0, pos).trim(); + String value = l.substring(pos + 1).trim(); + int vLen = value.length(); + if (value.charAt(vLen - 1) == HEADER_CONTINUATION_INDICATOR) { + value = value.substring(0, vLen - 1); + for (index++ /* skip current line */; index < len; index++) { + l = lines.get(index); + vLen = l.length(); + + if (l.charAt(vLen - 1) == HEADER_CONTINUATION_INDICATOR) { + value += l.substring(0, vLen - 1); + continue; // still continuation + } + + value += l; + break; // no more continuations + } + } + + headers.put(name, value.trim()); + } + + throw new StreamCorruptedException( + "No viable data lines found in " + resourceKey.getName() + " after " + startLine); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java index 5cfe631..de6abde 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java @@ -247,15 +247,15 @@ public final class NumberUtils { } public static int length(byte... a) { - return a == null ? 0 : a.length; + return (a == null) ? 0 : a.length; } public static int length(int... a) { - return a == null ? 0 : a.length; + return (a == null) ? 0 : a.length; } public static int length(long... a) { - return a == null ? 0 : a.length; + return (a == null) ? 0 : a.length; } public static List<Integer> asList(int... values) { diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java index 7709606..2bf2042 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import javax.security.auth.login.CredentialException; import javax.security.auth.login.FailedLoginException; @@ -81,7 +82,7 @@ public class BouncyCastleKeyPairResourceParser extends AbstractKeyPairResourcePa SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - List<String> lines) + List<String> lines, Map<String, String> headers) throws IOException, GeneralSecurityException { StringBuilder writer = new StringBuilder(beginMarker.length() + endMarker.length() + lines.size() * 80); writer.append(beginMarker).append(IoUtils.EOL); @@ -91,7 +92,7 @@ public class BouncyCastleKeyPairResourceParser extends AbstractKeyPairResourcePa String data = writer.toString(); byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); try (InputStream bais = new ByteArrayInputStream(dataBytes)) { - return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais); + return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers); } } @@ -100,7 +101,7 @@ public class BouncyCastleKeyPairResourceParser extends AbstractKeyPairResourcePa SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { KeyPair kp = loadKeyPair(session, resourceKey, stream, passwordProvider); return (kp == null) ? Collections.emptyList() : Collections.singletonList(kp); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java index ad7d92e..9c57d2c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java @@ -25,6 +25,7 @@ import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPairGenerator; import java.util.Collections; +import java.util.Map; import java.util.Objects; import org.apache.sshd.common.config.keys.KeyEntryResolver; @@ -91,8 +92,9 @@ public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder } @Override - public EdDSAPublicKey decodePublicKey(SessionContext session, String keyType, InputStream keyData) - throws IOException, GeneralSecurityException { + public EdDSAPublicKey decodePublicKey( + SessionContext session, String keyType, InputStream keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed)); } diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java index a11c32f..a6e442d 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintCaseSensitivityTest.java @@ -25,6 +25,7 @@ import java.security.PublicKey; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; import org.apache.sshd.util.test.JUnitTestSupport; @@ -71,7 +72,7 @@ public class KeyUtilsFingerprintCaseSensitivityTest extends JUnitTestSupport { @BeforeClass public static void beforeClass() throws GeneralSecurityException, IOException { PublicKeyEntry keyEntry = PublicKeyEntry.parsePublicKeyEntry(KEY_STRING); - key = keyEntry.resolvePublicKey(null, PublicKeyEntryResolver.FAILING); + key = keyEntry.resolvePublicKey(null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING); } @Parameters(name = "expected={0}, test={1}") diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java index 968a405..73684dd 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyUtilsFingerprintGenerationTest.java @@ -119,7 +119,8 @@ public class KeyUtilsFingerprintGenerationTest extends JUnitTestSupport { String keyValue = kentry.getKey(); try { PublicKeyEntry keyEntry = PublicKeyEntry.parsePublicKeyEntry(keyValue); - PublicKey key = keyEntry.resolvePublicKey(null, PublicKeyEntryResolver.FAILING); + PublicKey key = keyEntry.resolvePublicKey( + null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING); for (Map.Entry<DigestFactory, String> dentry : kentry.getValue()) { DigestFactory factory = dentry.getKey(); String fingerprint = dentry.getValue(); diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java index 45c8921..c82a33d 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java @@ -23,6 +23,7 @@ import java.security.GeneralSecurityException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import java.util.Collections; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.util.test.JUnitTestSupport; @@ -51,7 +52,7 @@ public class PublicKeyEntryTest extends JUnitTestSupport { for (PublicKeyEntryResolver resolver : new PublicKeyEntryResolver[]{ null, PublicKeyEntryResolver.FAILING, PublicKeyEntryResolver.IGNORING}) { try { - PublicKey key = entry.resolvePublicKey(null, resolver); + PublicKey key = entry.resolvePublicKey(null, Collections.emptyMap(), resolver); assertSame("Mismatched successful resolver", PublicKeyEntryResolver.IGNORING, resolver); assertNull("Unexpected success for resolver=" + resolver + ": " + KeyUtils.getFingerPrint(key), key); } catch (GeneralSecurityException e) { 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 310be4e..d268f75 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 @@ -23,6 +23,7 @@ import java.net.URL; import java.security.KeyPair; import java.security.PublicKey; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; @@ -84,7 +85,8 @@ public abstract class OpenSSHKeyPairResourceParserTestSupport extends JUnitTestS assertEquals("Mismatched public keys count", 1, GenericUtils.size(entries)); AuthorizedKeyEntry entry = entries.get(0); - PublicKey pubEntry = entry.resolvePublicKey(null, PublicKeyEntryResolver.FAILING); + PublicKey pubEntry = entry.resolvePublicKey( + null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING); assertNotNull("Cannot retrieve public key", pubEntry); testLoadKeyPairs(encrypted, resourceKey, pairs, pubEntry); diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderByKeyTypeTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderByKeyTypeTest.java new file mode 100644 index 0000000..64c94be --- /dev/null +++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderByKeyTypeTest.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.common.config.keys.loader.ssh2; + +import java.io.InputStream; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.apache.sshd.common.cipher.ECCurves; +import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; +import org.apache.sshd.common.config.keys.PublicKeyEntry; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; +import org.apache.sshd.util.test.JUnitTestSupport; +import org.apache.sshd.util.test.NoIoTestCase; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.Parameterized.UseParametersRunnerFactory; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class) +@Category({ NoIoTestCase.class }) +public class Ssh2PublicKeyEntryDecoderByKeyTypeTest extends JUnitTestSupport { + private final String keyType; + + public Ssh2PublicKeyEntryDecoderByKeyTypeTest(String keyType) { + this.keyType = keyType; + } + + @Parameters(name = "{0}") + public static List<Object[]> parameters() { + return new ArrayList<Object[]>() { + // Not serializing it + private static final long serialVersionUID = 1L; + + { + addTestCases(Arrays.asList(KeyPairProvider.SSH_RSA, KeyPairProvider.SSH_DSS)); + if (SecurityUtils.isECCSupported()) { + addTestCases(ECCurves.KEY_TYPES); + } + if (SecurityUtils.isEDDSACurveSupported()) { + addKey(KeyPairProvider.SSH_ED25519); + } + } + + private void addTestCases(Collection<String> keys) { + for (String k : keys) { + addKey(k); + } + } + + private void addKey(String k) { + add(new Object[] {k}); + } + }; + } + + @Test + public void testDecodePublicKey() throws Exception { + PublicKey expected; + try (InputStream keyData = getPublicKeyDataStream("pub")) { + Collection<? extends PublicKeyEntry> entries = + AuthorizedKeyEntry.readAuthorizedKeys(keyData, true); + List<PublicKey> keys = + PublicKeyEntry.resolvePublicKeyEntries(null, entries, null); + assertEquals("Mismatched expected public entries count", 1, GenericUtils.size(keys)); + + expected = keys.get(0); + } + + PublicKey actual; + try (InputStream keyData = getPublicKeyDataStream("ssh2")) { + actual = Ssh2PublicKeyEntryDecoder.INSTANCE.readPublicKey(null, () -> keyType, keyData); + } + + assertKeyEquals(keyType, expected, actual); + } + + private InputStream getPublicKeyDataStream(String suffix) { + String resourceName = keyType + "-" + PublicKey.class.getSimpleName() + "." + suffix; + InputStream keyData = getClass().getResourceAsStream(resourceName); + return ValidateUtils.checkNotNull(keyData, "Missing %s file", resourceName); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + keyType + "]"; + } +} diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderTest.java similarity index 51% copy from sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java copy to sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderTest.java index 45c8921..439926a 100644 --- a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/PublicKeyEntryTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/loader/ssh2/Ssh2PublicKeyEntryDecoderTest.java @@ -17,14 +17,13 @@ * under the License. */ -package org.apache.sshd.common.config.keys; +package org.apache.sshd.common.config.keys.loader.ssh2; -import java.security.GeneralSecurityException; +import java.io.InputStream; import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.util.test.JUnitTestSupport; import org.apache.sshd.util.test.NoIoTestCase; import org.junit.FixMethodOrder; @@ -37,26 +36,31 @@ import org.junit.runners.MethodSorters; */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Category({ NoIoTestCase.class }) -public class PublicKeyEntryTest extends JUnitTestSupport { - public PublicKeyEntryTest() { +public class Ssh2PublicKeyEntryDecoderTest extends JUnitTestSupport { + public Ssh2PublicKeyEntryDecoderTest() { super(); } @Test - public void testFallbackResolver() throws Exception { - PublicKeyEntry entry = - PublicKeyEntry.parsePublicKeyEntry( - GenericUtils.join( - Arrays.asList(getCurrentTestName(), "AAAA", getClass().getSimpleName()), ' ')); - for (PublicKeyEntryResolver resolver : new PublicKeyEntryResolver[]{ - null, PublicKeyEntryResolver.FAILING, PublicKeyEntryResolver.IGNORING}) { - try { - PublicKey key = entry.resolvePublicKey(null, resolver); - assertSame("Mismatched successful resolver", PublicKeyEntryResolver.IGNORING, resolver); - assertNull("Unexpected success for resolver=" + resolver + ": " + KeyUtils.getFingerPrint(key), key); - } catch (GeneralSecurityException e) { - assertObjectInstanceOf("Mismatched thrown exception for resolver=" + resolver, InvalidKeySpecException.class, e); - } + public void testMultiLineComment() throws Exception { + testDecoder("rfc4716-multi-line-comment.ssh2"); + } + + @Test + public void testMultipleHeaders() throws Exception { + testDecoder("rfc4716-multiple-headers.ssh2"); + } + + private PublicKey testDecoder(String resourceName) throws Exception { + PublicKey key; + try (InputStream stream = ValidateUtils.checkNotNull( + getClass().getResourceAsStream(resourceName), "Missing test resource: %s", resourceName)) { + key = Ssh2PublicKeyEntryDecoder.INSTANCE.readPublicKey(null, () -> resourceName, stream); } + assertNotNull("No key loaded from " + resourceName, key); + + String keyType = KeyUtils.getKeyType(key); + assertNotNull("Unknown key type loaded from " + resourceName, keyType); + return key; } } diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub new file mode 100644 index 0000000..1c9763f --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKGx7cPcJC999nqfX4vBsOYPLBBsLJTaJz1Ag0emFGfZoznoZYi6RQyxy8ZSBWn61TPiyKWoI6USmXJ6Rj1kA5w= root@osv-linux \ No newline at end of file diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2 new file mode 100644 index 0000000..669f876 --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp256-PublicKey.ssh2 @@ -0,0 +1,6 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "256-bit ECDSA, converted by lyor@localhost.localdomain from " +AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKGx7cPcJC999nqfX4 +vBsOYPLBBsLJTaJz1Ag0emFGfZoznoZYi6RQyxy8ZSBWn61TPiyKWoI6USmXJ6Rj1kA5w= + +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub new file mode 100644 index 0000000..93ceef0 --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBFImZtcTj842stlcVHLFBFxTEx7lu3jW9aZCvd0r9fUNKZ6LbRPh6l1oJ4ozArnw7XreQBUc5oNd9HB5RNJ8jl1nWXY5cXBA7McZrKZrYmk+zxNhH6UL+kMLaJkyngJHQw== root@osv-linux \ No newline at end of file diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2 new file mode 100644 index 0000000..718b19f --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp384-PublicKey.ssh2 @@ -0,0 +1,6 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "384-bit ECDSA, converted by lyor@localhost.localdomain from " +AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBFImZtcTj842stlcVH +LFBFxTEx7lu3jW9aZCvd0r9fUNKZ6LbRPh6l1oJ4ozArnw7XreQBUc5oNd9HB5RNJ8jl1n +WXY5cXBA7McZrKZrYmk+zxNhH6UL+kMLaJkyngJHQw== +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub new file mode 100644 index 0000000..520b64e --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACg4siCq1iqr4U/spXmw6b2VwBMsof7XLQGoD9wfwUikb8XWthNSmPP1nL6rlzJ5j8Bezn9BSSDfVAJfgqxmGIHdgHRVc0mkdq1/Q/DKhBgRyjZc29eo0o2ck3SNGNVaAabRYj6ck/iub/U6trKM7bdqy/joYYMwZdxLyYW5YxkPbqEfQ== root@osv-linux \ No newline at end of file diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2 new file mode 100644 index 0000000..e938cf9 --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ecdsa-sha2-nistp521-PublicKey.ssh2 @@ -0,0 +1,7 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "521-bit ECDSA, converted by lyor@localhost.localdomain from " +AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACg4siCq1iqr4U/sp +Xmw6b2VwBMsof7XLQGoD9wfwUikb8XWthNSmPP1nL6rlzJ5j8Bezn9BSSDfVAJfgqxmGIH +dgHRVc0mkdq1/Q/DKhBgRyjZc29eo0o2ck3SNGNVaAabRYj6ck/iub/U6trKM7bdqy/joY +YMwZdxLyYW5YxkPbqEfQ== +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multi-line-comment.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multi-line-comment.ssh2 new file mode 100644 index 0000000..ca5089d --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multi-line-comment.ssh2 @@ -0,0 +1,13 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: This is my public key for use on \ +servers which I don't like. +AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET +W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH +YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c +vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf +J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA +vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB +AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS +n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5 +sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multiple-headers.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multiple-headers.ssh2 new file mode 100644 index 0000000..e4d4461 --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/rfc4716-multiple-headers.ssh2 @@ -0,0 +1,7 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "1024-bit RSA, converted from OpenSSH by m...@example.com" +x-command: /home/me/bin/lock-in-guest.sh +AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb +YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ +5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.pub b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.pub new file mode 100644 index 0000000..c3feece --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAPdrHLUwF2HhMkL5PbYMooN7uxecOuAlGzWMFwOX+o91AOguW+9N3ygeCBeQjMjpPHaV2IncoAMyQODWwFJTnN6civ3CKx8RA4s2p8g46KtH1nyNQsZWUM76k/CmzQ/1tw6RokIB8AE22Ho5rPc0mN8UPxmaZYNVo3atKLUneaOfAAAAFQDlt7TQxpK2LToeivbrlXwaC2OL/QAAAIBr0xTyQEnhnX5Sytau5WvvZ5yQTCM49xEV4DoyhIzvQYZ1hWQvJDvwwyMoAVlxux5hPeWyNr5zgtDvIStE4dIYWj4iZCdgZjcKizaZLy1EEe1nhiYqwGZLdmtviUR9q4DWmAOlwatQBq+eIogAH8L1ym+P4ilnfr6selqngkonuQAAAIBA/g1HOO1q7OsMm+zvdzQM9zMCQsBqf73Si68i4jQKYzEWtKosggRpfdwH8/h2Qq0GwyHQLHAmmhd+ [...] \ No newline at end of file diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2 new file mode 100644 index 0000000..31ee7dc --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-dss-PublicKey.ssh2 @@ -0,0 +1,12 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "1024-bit DSA, converted by lyor@localhost.localdomain from O" +AAAAB3NzaC1kc3MAAACBAPdrHLUwF2HhMkL5PbYMooN7uxecOuAlGzWMFwOX+o91AOguW+ +9N3ygeCBeQjMjpPHaV2IncoAMyQODWwFJTnN6civ3CKx8RA4s2p8g46KtH1nyNQsZWUM76 +k/CmzQ/1tw6RokIB8AE22Ho5rPc0mN8UPxmaZYNVo3atKLUneaOfAAAAFQDlt7TQxpK2LT +oeivbrlXwaC2OL/QAAAIBr0xTyQEnhnX5Sytau5WvvZ5yQTCM49xEV4DoyhIzvQYZ1hWQv +JDvwwyMoAVlxux5hPeWyNr5zgtDvIStE4dIYWj4iZCdgZjcKizaZLy1EEe1nhiYqwGZLdm +tviUR9q4DWmAOlwatQBq+eIogAH8L1ym+P4ilnfr6selqngkonuQAAAIBA/g1HOO1q7OsM +m+zvdzQM9zMCQsBqf73Si68i4jQKYzEWtKosggRpfdwH8/h2Qq0GwyHQLHAmmhd+f5M77W +PuLTwa4w416oHeU4efX8wHapQBmH+R1TxFSzFaSsbzQkx4/WR7DWwdqhF85Rnz5fD9Evuc +2aGGYclHeuSBHwShYQ== +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.pub b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.pub new file mode 100644 index 0000000..128e883 --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHROxqPSxyzx9goyWfPYydyTLnjk2ggI/14YwYimjkbd root@ubuntu-15 \ No newline at end of file diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2 new file mode 100644 index 0000000..b921867 --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-ed25519-PublicKey.ssh2 @@ -0,0 +1,4 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "256-bit ED25519, converted by lyor@localhost.localdomain fro" +AAAAC3NzaC1lZDI1NTE5AAAAIHROxqPSxyzx9goyWfPYydyTLnjk2ggI/14YwYimjkbd +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.pub b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.pub new file mode 100644 index 0000000..c59321c --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCuxjvYVRScrzIuppjdaSi28wWbZqvKhVU9toeJcyqYpNNA8EWyHB5GT7AQkETu2Hh8irYy5O76EzERDz6hgnjsprX4X3pATUXS67VO55z9ml64SJexMEClZnlJoAypDeAx+V+qCn3qKlXdI6kvIQb0YipUwlg0T+CDAhXtRzjJsLbVnLcjtA58eGjwF5oyNX/vTeuW7ZRazxPCOjtUxYu+cYxSM0zM0aqX1ai4sTMeyGaRRkFF0q+dOOxP2/HWrk7Jxof+AeizbcRs3OfnStuIib3c9xn+i0YGON9sdyxGaPFmGoLMuyzk2v3c8sh483wJ7P3VLtouMLBGLi6ArErQRnA0ecc+LTJHR4jV4x8ku211ies5mJLERVsKxmIN9PJu4FhQNofYS2CbMf03bkhl4k6OuMTrw1mGLBedoige9+mQ9ORYxIYuwtjcqDR1jBRsDL0S [...] diff --git a/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2 b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2 new file mode 100644 index 0000000..61a3c0b --- /dev/null +++ b/sshd-common/src/test/resources/org/apache/sshd/common/config/keys/loader/ssh2/ssh-rsa-PublicKey.ssh2 @@ -0,0 +1,14 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "4096-bit RSA, converted by lyor@localhost.localdomain from O" +AAAAB3NzaC1yc2EAAAADAQABAAACAQCuxjvYVRScrzIuppjdaSi28wWbZqvKhVU9toeJcy +qYpNNA8EWyHB5GT7AQkETu2Hh8irYy5O76EzERDz6hgnjsprX4X3pATUXS67VO55z9ml64 +SJexMEClZnlJoAypDeAx+V+qCn3qKlXdI6kvIQb0YipUwlg0T+CDAhXtRzjJsLbVnLcjtA +58eGjwF5oyNX/vTeuW7ZRazxPCOjtUxYu+cYxSM0zM0aqX1ai4sTMeyGaRRkFF0q+dOOxP +2/HWrk7Jxof+AeizbcRs3OfnStuIib3c9xn+i0YGON9sdyxGaPFmGoLMuyzk2v3c8sh483 +wJ7P3VLtouMLBGLi6ArErQRnA0ecc+LTJHR4jV4x8ku211ies5mJLERVsKxmIN9PJu4FhQ +NofYS2CbMf03bkhl4k6OuMTrw1mGLBedoige9+mQ9ORYxIYuwtjcqDR1jBRsDL0SuqRL8w +QyY3vQ7EjGpMiEVJnRu/cUFtlNmeX8YARaZX4pKNREq4mVL0QFgThm/NiEaLoruDchAlnC +tyrLqzy7bivm6oBAKKF+5YVpuridJgHXNzNDtCr5XzB6ZqTqWCgUarh/nyTcVSGQlvBBEU +mhBi9owjrd2Wz2rrtuShyPJywY+9YNTsgT11WzfrPsV6+VCOygfACvj7RPU8Wyoef74onS +ncCWikhXs5tLRw== +---- END SSH2 PUBLIC KEY ---- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java index e5043e8..d6964d8 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/keyverifier/KnownHostsServerKeyVerifierTest.java @@ -88,7 +88,7 @@ public class KnownHostsServerKeyVerifierTest extends BaseTestSupport { SshdSocketAddress hostIdentity = ke.getKey(); KnownHostEntry entry = ke.getValue(); AuthorizedKeyEntry authEntry = ValidateUtils.checkNotNull(entry.getKeyEntry(), "No key extracted from %s", entry); - PublicKey key = authEntry.resolvePublicKey(null, PublicKeyEntryResolver.FAILING); + PublicKey key = authEntry.resolvePublicKey(null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING); assertNull("Multiple keys for host=" + hostIdentity, HOST_KEYS.put(hostIdentity, key)); } } 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 a43aa1b..cf0c5c4 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 @@ -26,6 +26,7 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.PublicKey; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.apache.sshd.common.util.GenericUtils; @@ -110,8 +111,10 @@ public class AuthorizedKeyEntryTest extends AuthorizedKeysTestSupport { Exception err = null; for (AuthorizedKeyEntry entry : entries) { try { - ValidateUtils.checkNotNull(entry.resolvePublicKey(null, PublicKeyEntryResolver.FAILING), - "No public key resolved from %s", entry); + ValidateUtils.checkNotNull( + entry.resolvePublicKey(null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING), + "No public key resolved from %s", + entry); } catch (Exception e) { System.err.append("Failed (").append(e.getClass().getSimpleName()).append(')') .append(" to resolve key of entry=").append(entry.toString()) diff --git a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java index 99b7f32..e67637f 100644 --- a/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java +++ b/sshd-ldap/src/test/java/org/apache/sshd/server/auth/pubkey/LdapPublickeyAuthenticatorTest.java @@ -20,6 +20,7 @@ package org.apache.sshd.server.auth.pubkey; import java.security.PublicKey; +import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Objects; @@ -66,7 +67,8 @@ public class LdapPublickeyAuthenticatorTest extends BaseAuthenticatorTest { for (Map.Entry<String, String> ce : credentials.entrySet()) { String username = ce.getKey(); AuthorizedKeyEntry entry = AuthorizedKeyEntry.parseAuthorizedKeyEntry(ce.getValue()); - PublicKey key = Objects.requireNonNull(entry, "No key extracted").resolvePublicKey(null, PublicKeyEntryResolver.FAILING); + PublicKey key = Objects.requireNonNull(entry, "No key extracted") + .resolvePublicKey(null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING); KEYS_MAP.put(username, key); } } diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java index 3640ed3..d8c1926 100644 --- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPAuthorizedKeyEntriesLoader.java @@ -45,8 +45,9 @@ import org.bouncycastle.openpgp.PGPException; */ public interface PGPAuthorizedKeyEntriesLoader extends PGPPublicKeyExtractor, PublicKeyEntryResolver { @Override - default PublicKey resolve(SessionContext session, String keyType, byte[] keyData) - throws IOException, GeneralSecurityException { + default PublicKey resolve( + SessionContext session, String keyType, byte[] keyData, Map<String, String> headers) + throws IOException, GeneralSecurityException { if (!PGPPublicKeyEntryDataResolver.PGP_KEY_TYPES.contains(keyType)) { return null; } diff --git a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java index 4edcf83..19f5e2a 100644 --- a/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java +++ b/sshd-openpgp/src/main/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPKeyPairResourceParser.java @@ -35,6 +35,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; @@ -75,7 +76,7 @@ public class PGPKeyPairResourceParser SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - List<String> lines) + List<String> lines, Map<String, String> headers) throws IOException, GeneralSecurityException { // We need to re-construct the original data - including start/end markers String eol = System.lineSeparator(); @@ -93,7 +94,7 @@ public class PGPKeyPairResourceParser String keyData = sb.toString(); byte[] dataBytes = keyData.getBytes(StandardCharsets.US_ASCII); try { - return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataBytes); + return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataBytes, headers); } finally { Arrays.fill(dataBytes, (byte) 0); // clean up sensitive data a.s.a.p. } @@ -104,10 +105,12 @@ public class PGPKeyPairResourceParser SessionContext session, NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream) + InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { for (int retryCount = 0;; retryCount++) { - String password = (passwordProvider == null) ? null : passwordProvider.getPassword(session, resourceKey, retryCount); + String password = (passwordProvider == null) + ? null + : passwordProvider.getPassword(session, resourceKey, retryCount); Collection<KeyPair> keys; try { if (retryCount > 0) { diff --git a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java index a75928d..fb45826 100644 --- a/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java +++ b/sshd-openpgp/src/test/java/org/apache/sshd/common/config/keys/loader/openpgp/PGPUtilsKeyFingerprintTest.java @@ -211,10 +211,11 @@ public class PGPUtilsKeyFingerprintTest extends JUnitTestSupport { PGPAuthorizedEntriesTracker tracker = new PGPAuthorizedEntriesTracker(file); SessionContext session = Mockito.mock(SessionContext.class); for (PublicKeyEntry pke : available) { - Collection<PublicKey> keys = tracker.loadMatchingAuthorizedEntries(session, Collections.singletonList(pke)); + Collection<PublicKey> keys = + tracker.loadMatchingAuthorizedEntries(session, Collections.singletonList(pke)); assertEquals("Mismatched recovered keys count for " + pke, 1, GenericUtils.size(keys)); - PublicKey expected = pke.resolvePublicKey(session, tracker); + PublicKey expected = pke.resolvePublicKey(session, Collections.emptyMap(), tracker); PublicKey actual = GenericUtils.head(keys); assertKeyEquals(pke.toString(), expected, actual); } diff --git a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java index bdf3328..e2113b8 100644 --- a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java +++ b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java @@ -34,6 +34,8 @@ import java.util.Base64.Decoder; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import javax.security.auth.login.FailedLoginException; @@ -92,6 +94,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends throws IOException, GeneralSecurityException { List<String> pubLines = Collections.emptyList(); List<String> prvLines = Collections.emptyList(); + Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); String prvEncryption = null; for (int index = 0, numLines = lines.size(); index < numLines; index++) { String l = lines.get(index); @@ -103,6 +106,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends String hdrName = l.substring(0, pos).trim(); String hdrValue = l.substring(pos + 1).trim(); + headers.put(hdrName, hdrValue); switch (hdrName) { case ENCRYPTION_HEADER: if (prvEncryption != null) { @@ -122,7 +126,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends } } - return loadKeyPairs(session, resourceKey, pubLines, prvLines, prvEncryption, passwordProvider); + return loadKeyPairs(session, resourceKey, pubLines, prvLines, prvEncryption, passwordProvider, headers); } public static List<String> extractDataLines( @@ -151,17 +155,17 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends public Collection<KeyPair> loadKeyPairs( SessionContext session, NamedResource resourceKey, List<String> pubLines, List<String> prvLines, String prvEncryption, - FilePasswordProvider passwordProvider) + FilePasswordProvider passwordProvider, Map<String, String> headers) throws IOException, GeneralSecurityException { return loadKeyPairs(session, resourceKey, KeyPairResourceParser.joinDataLines(pubLines), KeyPairResourceParser.joinDataLines(prvLines), - prvEncryption, passwordProvider); + prvEncryption, passwordProvider, headers); } public Collection<KeyPair> loadKeyPairs( SessionContext session, NamedResource resourceKey, String pubData, String prvData, String prvEncryption, - FilePasswordProvider passwordProvider) + FilePasswordProvider passwordProvider, Map<String, String> headers) throws IOException, GeneralSecurityException { byte[] pubBytes = GenericUtils.EMPTY_BYTE_ARRAY; byte[] prvBytes = GenericUtils.EMPTY_BYTE_ARRAY; @@ -171,7 +175,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends prvBytes = b64Decoder.decode(prvData); if (GenericUtils.isEmpty(prvEncryption) || NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption)) { - return loadKeyPairs(resourceKey, pubBytes, prvBytes); + return loadKeyPairs(resourceKey, pubBytes, prvBytes, headers); } // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc" @@ -208,7 +212,7 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends byte[] decBytes = PuttyKeyPairResourceParser.decodePrivateKeyBytes( prvBytes, algName, numBits, mode, password); try { - keys = loadKeyPairs(resourceKey, pubBytes, decBytes); + keys = loadKeyPairs(resourceKey, pubBytes, decBytes, headers); } finally { Arrays.fill(decBytes, (byte) 0); // eliminate sensitive data a.s.a.p. } @@ -243,26 +247,29 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends } } - public Collection<KeyPair> loadKeyPairs(NamedResource resourceKey, byte[] pubData, byte[] prvData) - throws IOException, GeneralSecurityException { + public Collection<KeyPair> loadKeyPairs( + NamedResource resourceKey, byte[] pubData, byte[] prvData, Map<String, String> headers) + throws IOException, GeneralSecurityException { ValidateUtils.checkNotNullAndNotEmpty(pubData, "No public key data in %s", resourceKey); ValidateUtils.checkNotNullAndNotEmpty(prvData, "No private key data in %s", resourceKey); try (InputStream pubStream = new ByteArrayInputStream(pubData); InputStream prvStream = new ByteArrayInputStream(prvData)) { - return loadKeyPairs(resourceKey, pubStream, prvStream); + return loadKeyPairs(resourceKey, pubStream, prvStream, headers); } } - public Collection<KeyPair> loadKeyPairs(NamedResource resourceKey, InputStream pubData, InputStream prvData) - throws IOException, GeneralSecurityException { + public Collection<KeyPair> loadKeyPairs( + NamedResource resourceKey, InputStream pubData, InputStream prvData, Map<String, String> headers) + throws IOException, GeneralSecurityException { try (PuttyKeyReader pubReader = new PuttyKeyReader(ValidateUtils.checkNotNull(pubData, "No public key data in %s", resourceKey)); PuttyKeyReader prvReader = new PuttyKeyReader(ValidateUtils.checkNotNull(prvData, "No private key data in %s", resourceKey))) { - return loadKeyPairs(resourceKey, pubReader, prvReader); + return loadKeyPairs(resourceKey, pubReader, prvReader, headers); } } - public abstract Collection<KeyPair> loadKeyPairs(NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + public abstract Collection<KeyPair> loadKeyPairs( + NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader, Map<String, String> headers) throws IOException, GeneralSecurityException; } diff --git a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java index 0c2d3bd..44b41cf 100644 --- a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java +++ b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java @@ -32,6 +32,7 @@ import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; import java.util.Collection; import java.util.Collections; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.KeyUtils; @@ -50,7 +51,7 @@ public class DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder<DSAPublicKey, DS @Override public Collection<KeyPair> loadKeyPairs( - NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader, Map<String, String> headers) throws IOException, GeneralSecurityException { pubReader.skip(); // skip version diff --git a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java index 1d02aa4..e5b4ca5 100644 --- a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java +++ b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java @@ -35,6 +35,7 @@ import java.security.spec.ECPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Collection; import java.util.Collections; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.cipher.ECCurves; @@ -56,7 +57,7 @@ public class ECDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<ECPublicKey, E @Override public Collection<KeyPair> loadKeyPairs( - NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader, Map<String, String> headers) throws IOException, GeneralSecurityException { if (!SecurityUtils.isECCSupported()) { throw new NoSuchAlgorithmException("ECC not supported for " + resourceKey); diff --git a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java index 07e461e..e13cd09 100644 --- a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java +++ b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/EdDSAPuttyKeyDecoder.java @@ -27,6 +27,7 @@ import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Collection; import java.util.Collections; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.keyprovider.KeyPairProvider; @@ -50,7 +51,7 @@ public class EdDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<EdDSAPublicKey @Override public Collection<KeyPair> loadKeyPairs( - NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader, Map<String, String> headers) throws IOException, GeneralSecurityException { if (!SecurityUtils.isEDDSACurveSupported()) { throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported for " + resourceKey); diff --git a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java index e50a52f..a9b1215 100644 --- a/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java +++ b/sshd-putty/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java @@ -33,6 +33,7 @@ import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import java.util.Collection; import java.util.Collections; +import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.KeyUtils; @@ -51,7 +52,7 @@ public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<RSAPublicKey, RS @Override public Collection<KeyPair> loadKeyPairs( - NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + NamedResource resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader, Map<String, String> headers) throws IOException, GeneralSecurityException { pubReader.skip(); // skip version @@ -67,7 +68,7 @@ public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<RSAPublicKey, RS BigInteger primeExponentP = privateExp.mod(primeP.subtract(BigInteger.ONE)); BigInteger primeExponentQ = privateExp.mod(primeQ.subtract(BigInteger.ONE)); RSAPrivateKeySpec prvSpec = new RSAPrivateCrtKeySpec( - modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef); + modulus, publicExp, privateExp, primeP, primeQ, primeExponentP, primeExponentQ, crtCoef); PrivateKey prvKey = kf.generatePrivate(prvSpec); return Collections.singletonList(new KeyPair(pubKey, prvKey)); }