[SSHD-703] Add support for reading an ED25519 private key from a file
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/0d7af8c8 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/0d7af8c8 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/0d7af8c8 Branch: refs/heads/master Commit: 0d7af8c8e052e1f7f980f18ec9795cff8f8af833 Parents: c951aa2 Author: Lyor Goldstein <[email protected]> Authored: Fri Oct 21 11:12:16 2016 +0300 Committer: Lyor Goldstein <[email protected]> Committed: Fri Oct 21 11:12:16 2016 +0300 ---------------------------------------------------------------------- README.md | 11 +- .../java/org/apache/sshd/client/SshClient.java | 14 +- .../org/apache/sshd/common/cipher/ECCurves.java | 10 + .../keys/AbstractPublicKeyEntryDecoder.java | 125 ------ .../common/config/keys/BuiltinIdentities.java | 7 + .../config/keys/DSSPublicKeyEntryDecoder.java | 117 ------ .../config/keys/ECDSAPublicKeyEntryDecoder.java | 379 ------------------ .../config/keys/FilePasswordProvider.java | 12 +- .../common/config/keys/KeyEntryResolver.java | 210 ++++++++++ .../sshd/common/config/keys/KeyUtils.java | 3 + .../config/keys/PrivateKeyEntryDecoder.java | 142 +++++++ .../config/keys/PrivateKeyEntryResolver.java | 70 ++++ .../config/keys/PublicKeyEntryDecoder.java | 143 +------ .../common/config/keys/RSAPublicKeyDecoder.java | 115 ------ .../keys/impl/AbstractKeyEntryResolver.java | 83 ++++ .../impl/AbstractPrivateKeyEntryDecoder.java | 40 ++ .../impl/AbstractPublicKeyEntryDecoder.java | 41 ++ .../keys/impl/DSSPublicKeyEntryDecoder.java | 119 ++++++ .../keys/impl/ECDSAPublicKeyEntryDecoder.java | 382 +++++++++++++++++++ .../config/keys/impl/RSAPublicKeyDecoder.java | 117 ++++++ .../loader/AbstractKeyPairResourceParser.java | 186 +++++++++ .../keys/loader/KeyPairResourceLoader.java | 129 +++++++ .../keys/loader/KeyPairResourceParser.java | 168 ++++++++ .../OpenSSHDSSPrivateKeyEntryDecoder.java | 146 +++++++ .../OpenSSHECDSAPrivateKeyEntryDecoder.java | 164 ++++++++ .../openssh/OpenSSHKeyPairResourceParser.java | 355 +++++++++++++++++ .../loader/openssh/OpenSSHParserContext.java | 83 ++++ .../openssh/OpenSSHRSAPrivateKeyDecoder.java | 152 ++++++++ .../java/org/apache/sshd/common/kex/ECDH.java | 2 +- ...actClassLoadableResourceKeyPairProvider.java | 90 ----- .../AbstractFileKeyPairProvider.java | 77 ---- .../AbstractResourceKeyPairProvider.java | 6 +- .../ClassLoadableResourceKeyPairProvider.java | 113 ++++++ .../common/keyprovider/FileKeyPairProvider.java | 92 +++++ .../apache/sshd/common/util/buffer/Buffer.java | 2 +- .../sshd/common/util/buffer/BufferUtils.java | 18 + .../buffer/keys/ECBufferPublicKeyParser.java | 2 +- .../org/apache/sshd/common/util/io/IoUtils.java | 71 ++++ .../common/util/security/SecurityUtils.java | 79 ++-- ...tleClassLoadableResourceKeyPairProvider.java | 42 -- .../BouncyCastleFileKeyPairProvider.java | 42 -- .../BouncyCastleGeneratorHostKeyProvider.java | 6 - .../BouncyCastleInputStreamLoader.java | 67 ---- .../BouncyCastleKeyPairResourceParser.java | 119 ++++++ .../security/eddsa/Ed25519PublicKeyDecoder.java | 20 +- .../security/eddsa/EdDSASecurityProvider.java | 6 + .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 137 +++++++ .../AbstractGeneratorHostKeyProvider.java | 4 +- .../OpenSSHKeyPairResourceParserTest.java | 102 +++++ .../sshd/common/random/RandomFactoryTest.java | 2 - .../signature/SignatureFactoriesTest.java | 51 ++- .../sshd/common/util/SecurityUtilsTest.java | 13 +- .../java/org/apache/sshd/util/test/Utils.java | 12 +- ...OpenSSHKeyPairResourceParserTest-DSA-KeyPair | 21 + ...SSHKeyPairResourceParserTest-DSA-KeyPair.pub | 1 + ...enSSHKeyPairResourceParserTest-ECDSA-KeyPair | 12 + ...HKeyPairResourceParserTest-ECDSA-KeyPair.pub | 1 + ...SSHKeyPairResourceParserTest-ED25519-KeyPair | 7 + ...eyPairResourceParserTest-ED25519-KeyPair.pub | 1 + ...OpenSSHKeyPairResourceParserTest-RSA-KeyPair | 49 +++ ...SSHKeyPairResourceParserTest-RSA-KeyPair.pub | 1 + 61 files changed, 3495 insertions(+), 1296 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 96c6144..16b0bde 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ Required only for reading/writing keys from/to PEM files or for special keys/cip ``` -**Caveat**: If _Bouncy Castle_ modules are available, then the code will use its implementation of the ciphers, keys, signatures, etc. rather than -the default JCE provided in the JVM. +**Caveat**: If _Bouncy Castle_ modules are available, then the code will use its implementation of the ciphers, keys, signatures, etc. rather than the default JCE provided in the JVM. * [MINA core](https://mina.apache.org/mina-project/) @@ -46,7 +45,7 @@ Optional dependency to enable choosing between NIO asynchronous sockets (the def ```xml - <dependency> + <dependency> <!-- For async. sockets I/O --> <groupId>org.apache.mina</groupId> <artifactId>mina-core</artifactId> </dependency> @@ -69,6 +68,7 @@ Required for supporting [ssh-ed25519](https://tools.ietf.org/html/draft-bjh21-ss ``` +The code contains support for reading _ed25519_ [OpenSSH formatted private keys](https://issues.apache.org/jira/browse/SSHD-703). # Set up an SSH client in 5 minutes @@ -107,7 +107,10 @@ Of course, one can implement the verifier in whatever other manner is suitable f ### ClientIdentityLoader/KeyPairProvider -One can set up the public/private keys to be used in case a password-less authentication is needed. By default, the client is configured to automatically detect and use the identity files residing in the user's *~/.ssh* folder (e.g., *id_rsa*, *id_ecdsa*) and present them as part of the authentication process. **Note:** if the identity files are encrypted via a password, one must configure a `FilePasswordProvider` so that the code can decrypt them before using and presenting them to the server as part of the authentication process. Reading key files in PEM format (including encrypted ones) requires that the [Bouncy Castle](https://www.bouncycastle.org/) supporting artifacts be available in the code's classpath. +One can set up the public/private keys to be used in case a password-less authentication is needed. By default, the client is configured to automatically detect and use the identity files residing in the user's *~/.ssh* folder (e.g., *id_rsa*, *id_ecdsa*) and present them as part of the authentication process. **Note:** if the identity files are encrypted via a password, one must configure a `FilePasswordProvider` so that the code can decrypt them before using and presenting them to the server as part of the authentication process. Reading key files in PEM format (including encrypted ones) requires that the [Bouncy Castle](https://www.bouncycastle.org/) supporting artifacts be available in the code's classpath. One can read files in +[OpenSSH](http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?rev=1.1&content-type=text/x-cvsweb-markup) +format without any specific extra artifacts (although for reading _ed25519_ keys one needs to add the _EdDSA_ support +artifacts). **Note:** for the time being, password encrypted _ed25519_ private key files are not supported. ### UserInteraction http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java index 49e3712..73e613f 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java @@ -106,7 +106,7 @@ import org.apache.sshd.common.helpers.AbstractFactoryManager; import org.apache.sshd.common.io.IoConnectFuture; import org.apache.sshd.common.io.IoConnector; import org.apache.sshd.common.io.IoSession; -import org.apache.sshd.common.keyprovider.AbstractFileKeyPairProvider; +import org.apache.sshd.common.keyprovider.FileKeyPairProvider; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.mac.BuiltinMacs; import org.apache.sshd.common.mac.Mac; @@ -119,7 +119,6 @@ import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.io.NoCloseInputStream; import org.apache.sshd.common.util.io.NoCloseOutputStream; import org.apache.sshd.common.util.net.SshdSocketAddress; -import org.apache.sshd.common.util.security.SecurityUtils; /** * <P> @@ -988,8 +987,8 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa } } - public static AbstractFileKeyPairProvider setupSessionIdentities(ClientFactoryManager client, Collection<File> identities, - final BufferedReader stdin, final PrintStream stdout, final PrintStream stderr) + public static FileKeyPairProvider setupSessionIdentities(ClientFactoryManager client, Collection<File> identities, + BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Throwable { client.setFilePasswordProvider(file -> { stdout.print("Enter password for private key file=" + file + ": "); @@ -1000,7 +999,12 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa return null; } - AbstractFileKeyPairProvider provider = SecurityUtils.createFileKeyPairProvider(); + FileKeyPairProvider provider = new FileKeyPairProvider() { + @Override + public String toString() { + return FileKeyPairProvider.class.getSimpleName() + "[clientIdentitiesProvider]"; + } + }; provider.setFiles(identities); client.setKeyPairProvider(provider); return provider; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java index f452fee..0e99ec0 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.cipher; import java.math.BigInteger; +import java.security.interfaces.ECKey; import java.security.spec.ECField; import java.security.spec.ECFieldFp; import java.security.spec.ECParameterSpec; @@ -216,6 +217,15 @@ public enum ECCurves implements NamedResource, OptionalFeature { } /** + * @param key The {@link ECKey} - ignored if {@code null} + * @return The matching {@link ECCurves} instance - {@code null} if no + * match found + */ + public static ECCurves fromECKey(ECKey key) { + return fromCurveParameters((key == null) ? null : key.getParams()); + } + + /** * @param params The curve's {@link ECParameterSpec} - ignored if {@code null} * @return The matching {@link ECCurves} value - {@code null} if no match found * @see #getCurveSize(ECParameterSpec) http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java deleted file mode 100644 index 23da5bb..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.sshd.common.config.keys; - -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.KeySpec; -import java.util.Collection; -import java.util.Objects; - -import org.apache.sshd.common.util.ValidateUtils; - -/** - * Useful base class implementation for a decoder of an {@code OpenSSH} encoded key data - * - * @param <PUB> Type of {@link PublicKey} - * @param <PRV> Type of {@link PrivateKey} - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public abstract class AbstractPublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> - implements PublicKeyEntryDecoder<PUB, PRV> { - private final Class<PUB> pubType; - private final Class<PRV> prvType; - private final Collection<String> names; - - protected AbstractPublicKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { - this.pubType = Objects.requireNonNull(pubType, "No public key type specified"); - this.prvType = Objects.requireNonNull(prvType, "No private key type specified"); - this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type names provided"); - } - - @Override - public final Class<PUB> getPublicKeyType() { - return pubType; - } - - @Override - public final Class<PRV> getPrivateKeyType() { - return prvType; - } - - @Override - public KeyPair cloneKeyPair(KeyPair kp) throws GeneralSecurityException { - if (kp == null) { - return null; - } - - PUB pubCloned = null; - PublicKey pubOriginal = kp.getPublic(); - Class<PUB> pubExpected = getPublicKeyType(); - if (pubOriginal != null) { - Class<?> orgType = pubOriginal.getClass(); - if (!pubExpected.isAssignableFrom(orgType)) { - throw new InvalidKeyException("Mismatched public key types: expected=" + pubExpected.getSimpleName() + ", actual=" + orgType.getSimpleName()); - } - - pubCloned = clonePublicKey(pubExpected.cast(pubOriginal)); - } - - PRV prvCloned = null; - PrivateKey prvOriginal = kp.getPrivate(); - Class<PRV> prvExpected = getPrivateKeyType(); - if (prvOriginal != null) { - Class<?> orgType = prvOriginal.getClass(); - if (!prvExpected.isAssignableFrom(orgType)) { - throw new InvalidKeyException("Mismatched private key types: expected=" + prvExpected.getSimpleName() + ", actual=" + orgType.getSimpleName()); - } - - prvCloned = clonePrivateKey(prvExpected.cast(prvOriginal)); - } - - return new KeyPair(pubCloned, prvCloned); - } - - @Override - public Collection<String> getSupportedTypeNames() { - return names; - } - - public PUB generatePublicKey(KeySpec keySpec) throws GeneralSecurityException { - KeyFactory factory = getKeyFactoryInstance(); - Class<PUB> keyType = getPublicKeyType(); - return keyType.cast(factory.generatePublic(keySpec)); - } - - public PRV generatePrivateKey(KeySpec keySpec) throws GeneralSecurityException { - KeyFactory factory = getKeyFactoryInstance(); - Class<PRV> keyType = getPrivateKeyType(); - return keyType.cast(factory.generatePrivate(keySpec)); - } - - @Override - public KeyPair generateKeyPair(int keySize) throws GeneralSecurityException { - KeyPairGenerator gen = getKeyPairGenerator(); - gen.initialize(keySize); - return gen.generateKeyPair(); - } - - @Override - public String toString() { - return getPublicKeyType().getSimpleName() + ": " + getSupportedTypeNames(); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java index dd60cf1..8dddfe7 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java @@ -181,6 +181,13 @@ public enum BuiltinIdentities implements Identity { for (BuiltinIdentities id : VALUES) { Class<?> pubType = id.getPublicKeyType(); Class<?> prvType = id.getPrivateKeyType(); + // Ignore placeholder classes (e.g., if ed25519 is not supported) + if ((prvType == null) || (pubType == null)) { + continue; + } + if ((prvType == PrivateKey.class) || (pubType == PublicKey.class)) { + continue; + } if (pubType.isAssignableFrom(clazz) || prvType.isAssignableFrom(clazz)) { return id; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java deleted file mode 100644 index 8384cac..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.sshd.common.config.keys; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.util.Collections; -import java.util.Objects; - -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.util.security.SecurityUtils; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAPublicKey, DSAPrivateKey> { - public static final DSSPublicKeyEntryDecoder INSTANCE = new DSSPublicKeyEntryDecoder(); - - public DSSPublicKeyEntryDecoder() { - super(DSAPublicKey.class, DSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS))); - } - - @Override - public DSAPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException { - if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly - throw new InvalidKeySpecException("Unexpected key type: " + keyType); - } - - BigInteger p = PublicKeyEntryDecoder.decodeBigInt(keyData); - BigInteger q = PublicKeyEntryDecoder.decodeBigInt(keyData); - BigInteger g = PublicKeyEntryDecoder.decodeBigInt(keyData); - BigInteger y = PublicKeyEntryDecoder.decodeBigInt(keyData); - - return generatePublicKey(new DSAPublicKeySpec(y, p, q, g)); - } - - @Override - public String encodePublicKey(OutputStream s, DSAPublicKey key) throws IOException { - Objects.requireNonNull(key, "No public key provided"); - - DSAParams keyParams = Objects.requireNonNull(key.getParams(), "No DSA params available"); - PublicKeyEntryDecoder.encodeString(s, KeyPairProvider.SSH_DSS); - PublicKeyEntryDecoder.encodeBigInt(s, keyParams.getP()); - PublicKeyEntryDecoder.encodeBigInt(s, keyParams.getQ()); - PublicKeyEntryDecoder.encodeBigInt(s, keyParams.getG()); - PublicKeyEntryDecoder.encodeBigInt(s, key.getY()); - - return KeyPairProvider.SSH_DSS; - } - - @Override - public DSAPublicKey clonePublicKey(DSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } - - DSAParams params = key.getParams(); - if (params == null) { - throw new InvalidKeyException("Missing parameters in key"); - } - - return generatePublicKey(new DSAPublicKeySpec(key.getY(), params.getP(), params.getQ(), params.getG())); - } - - @Override - public DSAPrivateKey clonePrivateKey(DSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } - - DSAParams params = key.getParams(); - if (params == null) { - throw new InvalidKeyException("Missing parameters in key"); - } - - return generatePrivateKey(new DSAPrivateKeySpec(key.getX(), params.getP(), params.getQ(), params.getG())); - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(KeyUtils.DSS_ALGORITHM); - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java deleted file mode 100644 index 9ffa705..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.sshd.common.config.keys; - -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.StreamCorruptedException; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchProviderException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Objects; -import java.util.Set; - -import org.apache.sshd.common.cipher.ECCurves; -import org.apache.sshd.common.util.NumberUtils; -import org.apache.sshd.common.util.buffer.BufferUtils; -import org.apache.sshd.common.util.security.SecurityUtils; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<ECPublicKey, ECPrivateKey> { - public static final ECDSAPublicKeyEntryDecoder INSTANCE = new ECDSAPublicKeyEntryDecoder(); - - // see rfc5480 section 2.2 - public static final byte ECPOINT_UNCOMPRESSED_FORM_INDICATOR = 0x04; - public static final byte ECPOINT_COMPRESSED_VARIANT_2 = 0x02; - public static final byte ECPOINT_COMPRESSED_VARIANT_3 = 0x02; - - public ECDSAPublicKeyEntryDecoder() { - super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES); - } - - @Override - public ECPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException { - ECCurves curve = ECCurves.fromKeyType(keyType); - if (curve == null) { - throw new InvalidKeySpecException("Not an EC curve name: " + keyType); - } - - if (!SecurityUtils.hasEcc()) { - throw new NoSuchProviderException("ECC not supported"); - } - - String keyCurveName = curve.getName(); - ECParameterSpec paramSpec = curve.getParameters(); - // see rfc5656 section 3.1 - String encCurveName = PublicKeyEntryDecoder.decodeString(keyData); - if (!keyCurveName.equals(encCurveName)) { - throw new InvalidKeySpecException("Mismatched key curve name (" + keyCurveName + ") vs. encoded one (" + encCurveName + ")"); - } - - byte[] octets = PublicKeyEntryDecoder.readRLEBytes(keyData); - final ECPoint w; - try { - w = octetStringToEcPoint(octets); - if (w == null) { - throw new InvalidKeySpecException("No ECPoint generated for curve=" + keyCurveName + " from octets=" + BufferUtils.toHex(':', octets)); - } - } catch (RuntimeException e) { - throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")" - + " to generate ECPoint for curve=" + keyCurveName - + " from octets=" + BufferUtils.toHex(':', octets) - + ": " + e.getMessage()); - } - - return generatePublicKey(new ECPublicKeySpec(w, paramSpec)); - } - - @Override - public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException { - if (!SecurityUtils.hasEcc()) { - throw new NoSuchProviderException("ECC not supported"); - } - - if (key == null) { - return null; - } - - ECParameterSpec params = key.getParams(); - if (params == null) { - throw new InvalidKeyException("Missing parameters in key"); - } - - return generatePublicKey(new ECPublicKeySpec(key.getW(), params)); - } - - @Override - public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException { - if (!SecurityUtils.hasEcc()) { - throw new NoSuchProviderException("ECC not supported"); - } - - if (key == null) { - return null; - } - - ECParameterSpec params = key.getParams(); - if (params == null) { - throw new InvalidKeyException("Missing parameters in key"); - } - - return generatePrivateKey(new ECPrivateKeySpec(key.getS(), params)); - } - - @Override - public String encodePublicKey(OutputStream s, ECPublicKey key) throws IOException { - Objects.requireNonNull(key, "No public key provided"); - - ECParameterSpec params = Objects.requireNonNull(key.getParams(), "No EC parameters available"); - ECCurves curve = Objects.requireNonNull(ECCurves.fromCurveParameters(params), "Cannot determine curve"); - String keyType = curve.getKeyType(); - String curveName = curve.getName(); - PublicKeyEntryDecoder.encodeString(s, keyType); - // see rfc5656 section 3.1 - PublicKeyEntryDecoder.encodeString(s, curveName); - ECPointCompression.UNCOMPRESSED.writeECPoint(s, curveName, key.getW()); - return keyType; - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - if (SecurityUtils.hasEcc()) { - return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM); - } else { - throw new NoSuchProviderException("ECC not supported"); - } - } - - @Override - public KeyPair generateKeyPair(int keySize) throws GeneralSecurityException { - ECCurves curve = ECCurves.fromCurveSize(keySize); - if (curve == null) { - throw new InvalidKeySpecException("Unknown curve for key size=" + keySize); - } - - KeyPairGenerator gen = getKeyPairGenerator(); - gen.initialize(curve.getParameters()); - return gen.generateKeyPair(); - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - if (SecurityUtils.hasEcc()) { - return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM); - } else { - throw new NoSuchProviderException("ECC not supported"); - } - } - - public static ECPoint octetStringToEcPoint(byte... octets) { - if (NumberUtils.isEmpty(octets)) { - return null; - } - - int startIndex = findFirstNonZeroIndex(octets); - if (startIndex < 0) { - throw new IllegalArgumentException("All zeroes ECPoint N/A"); - } - - byte indicator = octets[startIndex]; - ECPointCompression compression = ECPointCompression.fromIndicatorValue(indicator); - if (compression == null) { - throw new UnsupportedOperationException("Unknown compression indicator value: 0x" + Integer.toHexString(indicator & 0xFF)); - } - - // The coordinates actually start after the compression indicator - return compression.octetStringToEcPoint(octets, startIndex + 1, octets.length - startIndex - 1); - } - - private static int findFirstNonZeroIndex(byte... octets) { - if (NumberUtils.isEmpty(octets)) { - return -1; - } - - for (int index = 0; index < octets.length; index++) { - if (octets[index] != 0) { - return index; - } - } - - return -1; // all zeroes - } - - /** - * The various {@link ECPoint} representation compression indicators - * - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - * @see <A HREF="https://www.ietf.org/rfc/rfc5480.txt">RFC-5480 - section 2.2</A> - */ - public enum ECPointCompression { - // see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00 - // see http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point - VARIANT2((byte) 0x02) { - @Override - public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) { - byte[] xp = new byte[len]; - System.arraycopy(octets, startIndex, xp, 0, len); - BigInteger x = octetStringToInteger(xp); - - // TODO derive even Y... - throw new UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A"); - } - }, - VARIANT3((byte) 0x03) { - @Override - public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) { - byte[] xp = new byte[len]; - System.arraycopy(octets, startIndex, xp, 0, len); - BigInteger x = octetStringToInteger(xp); - - // TODO derive odd Y... - throw new UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A"); - } - }, - UNCOMPRESSED((byte) 0x04) { - @Override - public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) { - int numElements = len / 2; /* x, y */ - if (len != (numElements * 2)) { // make sure length is not odd - throw new IllegalArgumentException("octetStringToEcPoint(" + name() + ") " - + " invalid remainder octets representation: " - + " expected=" + (2 * numElements) + ", actual=" + len); - } - - byte[] xp = new byte[numElements]; - byte[] yp = new byte[numElements]; - System.arraycopy(octets, startIndex, xp, 0, numElements); - System.arraycopy(octets, startIndex + numElements, yp, 0, numElements); - - BigInteger x = octetStringToInteger(xp); - BigInteger y = octetStringToInteger(yp); - return new ECPoint(x, y); - } - - @Override - public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException { - ECCurves curve = ECCurves.fromCurveName(curveName); - if (curve == null) { - throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + curveName + "] cannot determine octets count"); - } - - int numElements = curve.getNumPointOctets(); - PublicKeyEntryDecoder.encodeInt(s, 1 /* the indicator */ + 2 * numElements); - s.write(getIndicatorValue()); - writeCoordinate(s, "X", p.getAffineX(), numElements); - writeCoordinate(s, "Y", p.getAffineY(), numElements); - } - - }; - - public static final Set<ECPointCompression> VALUES = - Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class)); - - private final byte indicatorValue; - - ECPointCompression(byte indicator) { - indicatorValue = indicator; - } - - public final byte getIndicatorValue() { - return indicatorValue; - } - - public abstract ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len); - - public byte[] ecPointToOctetString(String curveName, ECPoint p) { - try (ByteArrayOutputStream baos = new ByteArrayOutputStream((2 * 66) + Long.SIZE)) { - writeECPoint(baos, curveName, p); - return baos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException("ecPointToOctetString(" + curveName + ")" - + " failed (" + e.getClass().getSimpleName() + ")" - + " to write data: " + e.getMessage(), - e); - } - } - - public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException { - if (s == null) { - throw new EOFException("No output stream"); - } - - throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + p + "] N/A"); - } - - protected void writeCoordinate(OutputStream s, String n, BigInteger v, int numElements) throws IOException { - byte[] vp = v.toByteArray(); - int startIndex = 0; - int vLen = vp.length; - if (vLen > numElements) { - if (vp[0] == 0) { // skip artificial positive sign - startIndex++; - vLen--; - } - } - - if (vLen > numElements) { - throw new StreamCorruptedException("writeCoordinate(" + name() + ")[" + n + "]" - + " value length (" + vLen + ") exceeds max. (" + numElements + ")" - + " for " + v); - } - - if (vLen < numElements) { - byte[] tmp = new byte[numElements]; - System.arraycopy(vp, startIndex, tmp, numElements - vLen, vLen); - vp = tmp; - } - - s.write(vp, startIndex, vLen); - } - - public static ECPointCompression fromIndicatorValue(int value) { - if ((value < 0) || (value > 0xFF)) { - return null; // must be a byte value - } - - for (ECPointCompression c : VALUES) { - if (value == c.getIndicatorValue()) { - return c; - } - } - - return null; - } - - /** - * Converts the given octet string (defined by ASN.1 specifications) to a {@link BigInteger} - * As octet strings always represent positive integers, a zero-byte is prepended to - * the given array if necessary (if is MSB equal to 1), then this is converted to BigInteger - * The conversion is defined in the Section 2.3.8 - * - * @param octets - octet string bytes to be converted - * @return The {@link BigInteger} representation of the octet string - */ - public static BigInteger octetStringToInteger(byte... octets) { - if (octets == null) { - return null; - } else if (octets.length == 0) { - return BigInteger.ZERO; - } else { - return new BigInteger(1, octets); - } - } - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java index 647b03f..35f7558 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java @@ -29,17 +29,7 @@ public interface FilePasswordProvider { /** * An "empty" provider that returns {@code null} - i.e., unprotected key file */ - FilePasswordProvider EMPTY = new FilePasswordProvider() { - @Override - public String getPassword(String resourceKey) throws IOException { - return null; - } - - @Override - public String toString() { - return "EMPTY"; - } - }; + FilePasswordProvider EMPTY = resourceKey -> null; /** * @param resourceKey The resource key representing the <U>private</U> http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java new file mode 100644 index 0000000..8cf8fa6 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java @@ -0,0 +1,210 @@ +/* + * 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.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collection; + +import org.apache.sshd.common.util.io.IoUtils; + +/** + * @param <PUB> Type of {@link PublicKey} + * @param <PRV> Type of {@link PrivateKey} + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey> { + /** + * @return The {@link Class} of the {@link PublicKey} that is the result + * of decoding + */ + Class<PUB> getPublicKeyType(); + + /** + * @return The {@link Class} of the {@link PrivateKey} that matches the + * public one + */ + Class<PRV> getPrivateKeyType(); + + /** + * @return The {@link Collection} of {@code OpenSSH} key type names that + * are supported by this decoder - e.g., ECDSA keys have several curve names. + * <B>Caveat:</B> this collection may be un-modifiable... + */ + Collection<String> getSupportedTypeNames(); + + /** + * @param keySize Key size in bits + * @return A {@link KeyPair} with the specified key size + * @throws GeneralSecurityException if unable to generate the pair + */ + default KeyPair generateKeyPair(int keySize) throws GeneralSecurityException { + KeyPairGenerator gen = getKeyPairGenerator(); + gen.initialize(keySize); + return gen.generateKeyPair(); + } + + /** + * @param kp The {@link KeyPair} to be cloned - ignored if {@code null} + * @return A cloned pair (or {@code null} if no original pair) + * @throws GeneralSecurityException If failed to clone - e.g., provided key + * pair does not contain keys of the expected type + * @see #getPublicKeyType() + * @see #getPrivateKeyType() + */ + default KeyPair cloneKeyPair(KeyPair kp) throws GeneralSecurityException { + if (kp == null) { + return null; + } + + PUB pubCloned = null; + PublicKey pubOriginal = kp.getPublic(); + Class<PUB> pubExpected = getPublicKeyType(); + if (pubOriginal != null) { + Class<?> orgType = pubOriginal.getClass(); + if (!pubExpected.isAssignableFrom(orgType)) { + throw new InvalidKeyException("Mismatched public key types: expected=" + pubExpected.getSimpleName() + ", actual=" + orgType.getSimpleName()); + } + + pubCloned = clonePublicKey(pubExpected.cast(pubOriginal)); + } + + PRV prvCloned = null; + PrivateKey prvOriginal = kp.getPrivate(); + Class<PRV> prvExpected = getPrivateKeyType(); + if (prvOriginal != null) { + Class<?> orgType = prvOriginal.getClass(); + if (!prvExpected.isAssignableFrom(orgType)) { + throw new InvalidKeyException("Mismatched private key types: expected=" + prvExpected.getSimpleName() + ", actual=" + orgType.getSimpleName()); + } + + prvCloned = clonePrivateKey(prvExpected.cast(prvOriginal)); + } + + return new KeyPair(pubCloned, prvCloned); + } + + /** + * @param key The {@link PublicKey} to clone - ignored if {@code null} + * @return The cloned key (or {@code null} if no original key) + * @throws GeneralSecurityException If failed to clone the key + */ + PUB clonePublicKey(PUB key) throws GeneralSecurityException; + + /** + * @param key The {@link PrivateKey} to clone - ignored if {@code null} + * @return The cloned key (or {@code null} if no original key) + * @throws GeneralSecurityException If failed to clone the key + */ + PRV clonePrivateKey(PRV key) throws GeneralSecurityException; + + /** + * @return A {@link KeyPairGenerator} suitable for this decoder + * @throws GeneralSecurityException If failed to create the generator + */ + KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException; + + /** + * @return A {@link KeyFactory} suitable for the specific decoder type + * @throws GeneralSecurityException If failed to create one + */ + KeyFactory getKeyFactoryInstance() throws GeneralSecurityException; + + static int encodeString(OutputStream s, String v) throws IOException { + return encodeString(s, v, StandardCharsets.UTF_8); + } + + static int encodeString(OutputStream s, String v, String charset) throws IOException { + return encodeString(s, v, Charset.forName(charset)); + } + + static int encodeString(OutputStream s, String v, Charset cs) throws IOException { + return writeRLEBytes(s, v.getBytes(cs)); + } + + static int encodeBigInt(OutputStream s, BigInteger v) throws IOException { + return writeRLEBytes(s, v.toByteArray()); + } + + static int writeRLEBytes(OutputStream s, byte... bytes) throws IOException { + return writeRLEBytes(s, bytes, 0, bytes.length); + } + + static int writeRLEBytes(OutputStream s, byte[] bytes, int off, int len) throws IOException { + byte[] lenBytes = encodeInt(s, len); + s.write(bytes, off, len); + return lenBytes.length + len; + } + + static byte[] encodeInt(OutputStream s, int v) throws IOException { + byte[] bytes = { + (byte) ((v >> 24) & 0xFF), + (byte) ((v >> 16) & 0xFF), + (byte) ((v >> 8) & 0xFF), + (byte) (v & 0xFF) + }; + s.write(bytes); + return bytes; + } + + static String decodeString(InputStream s) throws IOException { + return decodeString(s, StandardCharsets.UTF_8); + } + + static String decodeString(InputStream s, String charset) throws IOException { + return decodeString(s, Charset.forName(charset)); + } + + static String decodeString(InputStream s, Charset cs) throws IOException { + byte[] bytes = readRLEBytes(s); + return new String(bytes, cs); + } + + static BigInteger decodeBigInt(InputStream s) throws IOException { + return new BigInteger(readRLEBytes(s)); + } + + static byte[] readRLEBytes(InputStream s) throws IOException { + int len = decodeInt(s); + byte[] bytes = new byte[len]; + IoUtils.readFully(s, bytes); + return bytes; + } + + static int decodeInt(InputStream s) throws IOException { + byte[] bytes = {0, 0, 0, 0}; + IoUtils.readFully(s, bytes); + return ((bytes[0] & 0xFF) << 24) + | ((bytes[1] & 0xFF) << 16) + | ((bytes[2] & 0xFF) << 8) + | (bytes[3] & 0xFF); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java index 56f6318..a4787cb 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java @@ -57,6 +57,9 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.sshd.common.Factory; import org.apache.sshd.common.cipher.ECCurves; +import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder; +import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder; +import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder; import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.Digest; import org.apache.sshd.common.digest.DigestFactory; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java new file mode 100644 index 0000000..6dbeee9 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java @@ -0,0 +1,142 @@ +/* + * 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.io.OutputStream; +import java.io.StreamCorruptedException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Collection; +import java.util.Objects; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.NumberUtils; +import org.apache.sshd.common.util.ValidateUtils; + +/** + * @param <PUB> Type of {@link PublicKey} + * @param <PRV> Type of {@link PrivateKey} + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> + extends KeyEntryResolver<PUB, PRV>, PrivateKeyEntryResolver { + + @Override + default PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException { + ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); + Collection<String> supported = getSupportedTypeNames(); + if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) { + return decodePrivateKey(FilePasswordProvider.EMPTY, keyData); + } + + throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported); + } + + /** + * @param passwordProvider The {@link FilePasswordProvider} to use + * in case the data is encrypted - may be {@code null} if no encrypted + * data is expected + * @param keyData The key data bytes in {@code OpenSSH} format (after + * BASE64 decoding) - ignored if {@code null}/empty + * @return The decoded {@link PrivateKey} - or {@code null} if no data + * @throws IOException If failed to decode the key + * @throws GeneralSecurityException If failed to generate the key + */ + default PRV decodePrivateKey(FilePasswordProvider passwordProvider, byte... keyData) + throws IOException, GeneralSecurityException { + return decodePrivateKey(passwordProvider, keyData, 0, NumberUtils.length(keyData)); + } + + default PRV decodePrivateKey(FilePasswordProvider passwordProvider, byte[] keyData, int offset, int length) + throws IOException, GeneralSecurityException { + if (length <= 0) { + return null; + } + + try (InputStream stream = new ByteArrayInputStream(keyData, offset, length)) { + return decodePrivateKey(passwordProvider, stream); + } + } + + default PRV decodePrivateKey(FilePasswordProvider passwordProvider, InputStream keyData) + throws IOException, GeneralSecurityException { + // the actual data is preceded by a string that repeats the key type + String type = KeyEntryResolver.decodeString(keyData); + if (GenericUtils.isEmpty(type)) { + throw new StreamCorruptedException("Missing key type string"); + } + + Collection<String> supported = getSupportedTypeNames(); + if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) { + throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported); + } + + return decodePrivateKey(type, passwordProvider, keyData); + } + + /** + * @param keyType The reported / encode key type + * @param passwordProvider The {@link FilePasswordProvider} to use + * in case the data is encrypted - may be {@code null} if no encrypted + * data is expected + * @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 PrivateKey} + * @throws IOException If failed to read from the data stream + * @throws GeneralSecurityException If failed to generate the key + */ + PRV decodePrivateKey(String keyType, FilePasswordProvider passwordProvider, InputStream keyData) + throws IOException, GeneralSecurityException; + + /** + * Encodes the {@link PrivateKey} using the {@code OpenSSH} format - same + * one used by the {@code decodePublicKey} method(s) + * + * @param s The {@link OutputStream} to write the data to + * @param key The {@link PrivateKey} - may not be {@code null} + * @return The key type value - one of the {@link #getSupportedTypeNames()} or + * {@code null} if encoding not supported + * @throws IOException If failed to generate the encoding + */ + default String encodePrivateKey(OutputStream s, PRV key) throws IOException { + Objects.requireNonNull(key, "No private key provided"); + return null; + } + + default boolean isPublicKeyRecoverySupported() { + return false; + } + + /** + * Attempts to recover the public key given the private one + * + * @param prvKey The {@link PrivateKey} + * @return The recovered {@link PublicKey} - {@code null} if cannot recover it + * @throws GeneralSecurityException If failed to generate the public key + */ + default PUB recoverPublicKey(PRV prvKey) throws GeneralSecurityException { + return null; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java new file mode 100644 index 0000000..1e4c91e --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java @@ -0,0 +1,70 @@ +/* + * 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.IOException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface PrivateKeyEntryResolver { + /** + * A resolver that ignores all input + */ + PrivateKeyEntryResolver IGNORING = new PrivateKeyEntryResolver() { + @Override + public PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException { + return null; + } + + @Override + public String toString() { + return "IGNORING"; + } + }; + + /** + * A resolver that fails on all input + */ + PrivateKeyEntryResolver FAILING = new PrivateKeyEntryResolver() { + @Override + public PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException { + throw new InvalidKeySpecException("Failing resolver on key type=" + keyType); + } + + @Override + public String toString() { + return "FAILING"; + } + }; + + /** + * @param keyType The {@code OpenSSH} reported key type + * @param keyData The {@code OpenSSH} encoded key data + * @return The extracted {@link PrivateKey} - ignored if {@code null} + * @throws IOException If failed to parse the key data + * @throws GeneralSecurityException If failed to generate the key + */ + PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException; +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java index 8d8f132..7462e4a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java @@ -24,13 +24,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StreamCorruptedException; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; @@ -39,7 +33,6 @@ import java.util.Collection; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.io.IoUtils; /** * Represents a decoder of an {@code OpenSSH} encoded key data @@ -48,62 +41,8 @@ import org.apache.sshd.common.util.io.IoUtils; * @param <PRV> Type of {@link PrivateKey} * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> extends PublicKeyEntryResolver { - /** - * @return The {@link Class} of the {@link PublicKey} that is the result - * of decoding - */ - Class<PUB> getPublicKeyType(); - - /** - * @return The {@link Class} of the {@link PrivateKey} that matches the - * public one - */ - Class<PRV> getPrivateKeyType(); - - /** - * @return The {@link Collection} of {@code OpenSSH} key type names that - * are supported by this decoder - e.g., ECDSA keys have several curve names. - * <B>Caveat:</B> this collection may be un-modifiable... - */ - Collection<String> getSupportedTypeNames(); - - /** - * @param keySize Key size in bits - * @return A {@link KeyPair} with the specified key size - * @throws GeneralSecurityException if unable to generate the pair - */ - KeyPair generateKeyPair(int keySize) throws GeneralSecurityException; - - /** - * @param kp The {@link KeyPair} to be cloned - ignored if {@code null} - * @return A cloned pair (or {@code null} if no original pair) - * @throws GeneralSecurityException If failed to clone - e.g., provided key - * pair does not contain keys of the expected type - * @see #getPublicKeyType() - * @see #getPrivateKeyType() - */ - KeyPair cloneKeyPair(KeyPair kp) throws GeneralSecurityException; - - /** - * @param key The {@link PublicKey} to clone - ignored if {@code null} - * @return The cloned key (or {@code null} if no original key) - * @throws GeneralSecurityException If failed to clone the key - */ - PUB clonePublicKey(PUB key) throws GeneralSecurityException; - - /** - * @param key The {@link PrivateKey} to clone - ignored if {@code null} - * @return The cloned key (or {@code null} if no original key) - * @throws GeneralSecurityException If failed to clone the key - */ - PRV clonePrivateKey(PRV key) throws GeneralSecurityException; - - /** - * @return A {@link KeyPairGenerator} suitable for this decoder - * @throws GeneralSecurityException If failed to create the generator - */ - KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException; +public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> + extends KeyEntryResolver<PUB, PRV>, PublicKeyEntryResolver { @Override default PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException { @@ -139,7 +78,7 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat default PUB decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException { // the actual data is preceded by a string that repeats the key type - String type = decodeString(keyData); + String type = KeyEntryResolver.decodeString(keyData); if (GenericUtils.isEmpty(type)) { throw new StreamCorruptedException("Missing key type string"); } @@ -172,80 +111,4 @@ public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends Privat * @throws IOException If failed to generate the encoding */ String encodePublicKey(OutputStream s, PUB key) throws IOException; - - /** - * @return A {@link KeyFactory} suitable for the specific decoder type - * @throws GeneralSecurityException If failed to create one - */ - KeyFactory getKeyFactoryInstance() throws GeneralSecurityException; - - static int encodeString(OutputStream s, String v) throws IOException { - return encodeString(s, v, StandardCharsets.UTF_8); - } - - static int encodeString(OutputStream s, String v, String charset) throws IOException { - return encodeString(s, v, Charset.forName(charset)); - } - - static int encodeString(OutputStream s, String v, Charset cs) throws IOException { - return writeRLEBytes(s, v.getBytes(cs)); - } - - static int encodeBigInt(OutputStream s, BigInteger v) throws IOException { - return writeRLEBytes(s, v.toByteArray()); - } - - static int writeRLEBytes(OutputStream s, byte... bytes) throws IOException { - return writeRLEBytes(s, bytes, 0, bytes.length); - } - - static int writeRLEBytes(OutputStream s, byte[] bytes, int off, int len) throws IOException { - byte[] lenBytes = encodeInt(s, len); - s.write(bytes, off, len); - return lenBytes.length + len; - } - - static byte[] encodeInt(OutputStream s, int v) throws IOException { - byte[] bytes = { - (byte) ((v >> 24) & 0xFF), - (byte) ((v >> 16) & 0xFF), - (byte) ((v >> 8) & 0xFF), - (byte) (v & 0xFF) - }; - s.write(bytes); - return bytes; - } - - static String decodeString(InputStream s) throws IOException { - return decodeString(s, StandardCharsets.UTF_8); - } - - static String decodeString(InputStream s, String charset) throws IOException { - return decodeString(s, Charset.forName(charset)); - } - - static String decodeString(InputStream s, Charset cs) throws IOException { - byte[] bytes = readRLEBytes(s); - return new String(bytes, cs); - } - - static BigInteger decodeBigInt(InputStream s) throws IOException { - return new BigInteger(readRLEBytes(s)); - } - - static byte[] readRLEBytes(InputStream s) throws IOException { - int len = decodeInt(s); - byte[] bytes = new byte[len]; - IoUtils.readFully(s, bytes); - return bytes; - } - - static int decodeInt(InputStream s) throws IOException { - byte[] bytes = {0, 0, 0, 0}; - IoUtils.readFully(s, bytes); - return ((bytes[0] & 0xFF) << 24) - | ((bytes[1] & 0xFF) << 16) - | ((bytes[2] & 0xFF) << 8) - | (bytes[3] & 0xFF); - } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java deleted file mode 100644 index a76b4b3..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.sshd.common.config.keys; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.interfaces.RSAPrivateCrtKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPrivateCrtKeySpec; -import java.security.spec.RSAPublicKeySpec; -import java.util.Collections; -import java.util.Objects; - -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.util.security.SecurityUtils; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> { - public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder(); - - public RSAPublicKeyDecoder() { - super(RSAPublicKey.class, RSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA))); - } - - @Override - public RSAPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException { - if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly - throw new InvalidKeySpecException("Unexpected key type: " + keyType); - } - - BigInteger e = PublicKeyEntryDecoder.decodeBigInt(keyData); - BigInteger n = PublicKeyEntryDecoder.decodeBigInt(keyData); - - return generatePublicKey(new RSAPublicKeySpec(n, e)); - } - - @Override - public String encodePublicKey(OutputStream s, RSAPublicKey key) throws IOException { - Objects.requireNonNull(key, "No public key provided"); - PublicKeyEntryDecoder.encodeString(s, KeyPairProvider.SSH_RSA); - PublicKeyEntryDecoder.encodeBigInt(s, key.getPublicExponent()); - PublicKeyEntryDecoder.encodeBigInt(s, key.getModulus()); - - return KeyPairProvider.SSH_RSA; - } - - @Override - public RSAPublicKey clonePublicKey(RSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent())); - } - } - - @Override - public RSAPrivateKey clonePrivateKey(RSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } - - if (!(key instanceof RSAPrivateCrtKey)) { - throw new InvalidKeyException("Cannot clone a non-RSAPrivateCrtKey: " + key.getClass().getSimpleName()); - } - - RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) key; - return generatePrivateKey( - new RSAPrivateCrtKeySpec( - rsaPrv.getModulus(), - rsaPrv.getPublicExponent(), - rsaPrv.getPrivateExponent(), - rsaPrv.getPrimeP(), - rsaPrv.getPrimeQ(), - rsaPrv.getPrimeExponentP(), - rsaPrv.getPrimeExponentQ(), - rsaPrv.getCrtCoefficient())); - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(KeyUtils.RSA_ALGORITHM); - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java new file mode 100644 index 0000000..e8a78f3 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java @@ -0,0 +1,83 @@ +/* + * 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.impl; + +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.KeySpec; +import java.util.Collection; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; + +/** + * @param <PUB> Type of {@link PublicKey} + * @param <PRV> Type of {@link PrivateKey} + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractKeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey> + extends AbstractLoggingBean + implements KeyEntryResolver<PUB, PRV> { + private final Class<PUB> pubType; + private final Class<PRV> prvType; + private final Collection<String> names; + + protected AbstractKeyEntryResolver(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { + this.pubType = Objects.requireNonNull(pubType, "No public key type specified"); + this.prvType = Objects.requireNonNull(prvType, "No private key type specified"); + this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type names provided"); + } + + @Override + public final Class<PUB> getPublicKeyType() { + return pubType; + } + + @Override + public final Class<PRV> getPrivateKeyType() { + return prvType; + } + + @Override + public Collection<String> getSupportedTypeNames() { + return names; + } + + public PUB generatePublicKey(KeySpec keySpec) throws GeneralSecurityException { + KeyFactory factory = getKeyFactoryInstance(); + Class<PUB> keyType = getPublicKeyType(); + return keyType.cast(factory.generatePublic(keySpec)); + } + + public PRV generatePrivateKey(KeySpec keySpec) throws GeneralSecurityException { + KeyFactory factory = getKeyFactoryInstance(); + Class<PRV> keyType = getPrivateKeyType(); + return keyType.cast(factory.generatePrivate(keySpec)); + } + + @Override + public String toString() { + return getPublicKeyType().getSimpleName() + ": " + getSupportedTypeNames(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java new file mode 100644 index 0000000..574412e --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java @@ -0,0 +1,40 @@ +/* + * 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.impl; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collection; + +import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; + +/** + * @param <PUB> Type of {@link PublicKey} + * @param <PRV> Type of {@link PrivateKey} + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractPrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> + extends AbstractKeyEntryResolver<PUB, PRV> + implements PrivateKeyEntryDecoder<PUB, PRV> { + protected AbstractPrivateKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { + super(pubType, prvType, names); + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java new file mode 100644 index 0000000..59cbf3a --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java @@ -0,0 +1,41 @@ +/* + * 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.impl; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collection; + +import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; + +/** + * Useful base class implementation for a decoder of an {@code OpenSSH} encoded key data + * + * @param <PUB> Type of {@link PublicKey} + * @param <PRV> Type of {@link PrivateKey} + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class AbstractPublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey> + extends AbstractKeyEntryResolver<PUB, PRV> + implements PublicKeyEntryDecoder<PUB, PRV> { + protected AbstractPublicKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { + super(pubType, prvType, names); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/0d7af8c8/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java new file mode 100644 index 0000000..464d2b0 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java @@ -0,0 +1,119 @@ +/* + * 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.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Collections; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.KeyEntryResolver; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAPublicKey, DSAPrivateKey> { + public static final DSSPublicKeyEntryDecoder INSTANCE = new DSSPublicKeyEntryDecoder(); + + public DSSPublicKeyEntryDecoder() { + super(DSAPublicKey.class, DSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS))); + } + + @Override + public DSAPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException { + if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly + throw new InvalidKeySpecException("Unexpected key type: " + keyType); + } + + BigInteger p = KeyEntryResolver.decodeBigInt(keyData); + BigInteger q = KeyEntryResolver.decodeBigInt(keyData); + BigInteger g = KeyEntryResolver.decodeBigInt(keyData); + BigInteger y = KeyEntryResolver.decodeBigInt(keyData); + + return generatePublicKey(new DSAPublicKeySpec(y, p, q, g)); + } + + @Override + public String encodePublicKey(OutputStream s, DSAPublicKey key) throws IOException { + Objects.requireNonNull(key, "No public key provided"); + + DSAParams keyParams = Objects.requireNonNull(key.getParams(), "No DSA params available"); + KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_DSS); + KeyEntryResolver.encodeBigInt(s, keyParams.getP()); + KeyEntryResolver.encodeBigInt(s, keyParams.getQ()); + KeyEntryResolver.encodeBigInt(s, keyParams.getG()); + KeyEntryResolver.encodeBigInt(s, key.getY()); + + return KeyPairProvider.SSH_DSS; + } + + @Override + public DSAPublicKey clonePublicKey(DSAPublicKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } + + DSAParams params = key.getParams(); + if (params == null) { + throw new InvalidKeyException("Missing parameters in key"); + } + + return generatePublicKey(new DSAPublicKeySpec(key.getY(), params.getP(), params.getQ(), params.getG())); + } + + @Override + public DSAPrivateKey clonePrivateKey(DSAPrivateKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } + + DSAParams params = key.getParams(); + if (params == null) { + throw new InvalidKeyException("Missing parameters in key"); + } + + return generatePrivateKey(new DSAPrivateKeySpec(key.getX(), params.getP(), params.getQ(), params.getG())); + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return SecurityUtils.getKeyPairGenerator(KeyUtils.DSS_ALGORITHM); + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM); + } +}
