From bac77bbd0fe3fdba9a415d58db4018b6c54cea15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Wed, 29 May 2024 11:17:51 +0200 Subject: [PATCH] EC alawy enabled, EdEC always anabled, make EdEC JDK-based keys work, drop eddsa, xbib net, bouncycastle dependency --- .../sftp/test/SftpFileSystemTest.java | 4 +- files-sftp-fs/build.gradle | 1 - files-sftp-fs/src/test/java/module-info.java | 1 - .../sshd/fs/test/FileServiceProviderTest.java | 2 +- .../fs/test/SftpWithPrivateKeyReaderTest.java | 37 +- files-sftp/build.gradle | 3 - files-sftp/src/main/java/module-info.java | 2 - .../client/auth/pubkey/UserAuthPublicKey.java | 10 +- .../pubkey/UserAuthPublicKeyIterator.java | 17 +- .../keys/BuiltinClientIdentitiesWatcher.java | 22 +- .../keys/DefaultClientIdentitiesWatcher.java | 22 +- .../apache/sshd/common/cipher/ECCurves.java | 2 +- .../common/config/keys/BuiltinIdentities.java | 21 +- .../sshd/common/config/keys/KeyUtils.java | 44 +-- .../config/keys/PrivateKeyEntryDecoder.java | 11 +- .../keys/impl/ECDSAPublicKeyEntryDecoder.java | 25 +- .../impl/Ed25519PublicKeyEntryDecoder.java} | 81 ++--- ...der.java => RSAPublicKeyEntryDecoder.java} | 6 +- .../loader/AbstractKeyPairResourceParser.java | 71 ++-- .../OpenSSHECDSAPrivateKeyEntryDecoder.java | 25 +- .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 164 +++++++++ .../openssh/OpenSSHKeyPairResourceParser.java | 340 +++++------------- ... => OpenSSHRSAPrivateKeyEntryDecoder.java} | 20 +- .../pem/AbstractPEMResourceKeyPairParser.java | 12 +- .../pem/DSSPEMResourceKeyPairParser.java | 19 +- .../pem/ECDSAPEMResourceKeyPairParser.java | 29 +- .../pem/Ed25519PEMResourceKeyParser.java} | 45 ++- .../pem/PKCS8PEMResourceKeyPairParser.java | 43 +-- .../pem/RSAPEMResourceKeyPairParser.java | 19 +- .../common/signature/BuiltinSignatures.java | 48 +-- .../edec => signature}/SignatureEd25519.java | 5 +- .../apache/sshd/common/util/ArrayUtil.java | 23 ++ .../sshd/common/util/buffer/Buffer.java | 56 +-- .../keys/ED25519BufferPublicKeyParser.java | 25 +- .../common/util/security/SecurityUtils.java | 172 +-------- .../eddsa/Ed25519PEMResourceKeyParser.java | 182 ---------- .../eddsa/Ed25519PublicKeyDecoder.java | 105 ------ .../eddsa/EdDSASecurityProviderRegistrar.java | 97 ----- .../eddsa/EdDSASecurityProviderUtils.java | 203 ----------- .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 186 ---------- .../util/security/eddsa/SignatureEd25519.java | 48 --- .../edec/EdECSecurityProviderRegistrar.java | 45 --- .../OpenSSHEd25519PrivateKeyEntryDecoder.java | 177 --------- gradle.properties | 2 +- gradle/compile/java.gradle | 3 +- settings.gradle | 2 - 46 files changed, 563 insertions(+), 1914 deletions(-) rename files-sftp/src/main/java/org/apache/sshd/common/{util/security/edec/Ed25519PublicKeyDecoder.java => config/keys/impl/Ed25519PublicKeyEntryDecoder.java} (69%) rename files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/{RSAPublicKeyDecoder.java => RSAPublicKeyEntryDecoder.java} (95%) create mode 100644 files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHEd25519PrivateKeyEntryDecoder.java rename files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/{OpenSSHRSAPrivateKeyDecoder.java => OpenSSHRSAPrivateKeyEntryDecoder.java} (90%) rename files-sftp/src/main/java/org/apache/sshd/common/{util/security/edec/EdECPEMResourceKeyParser.java => config/keys/loader/pem/Ed25519PEMResourceKeyParser.java} (83%) rename files-sftp/src/main/java/org/apache/sshd/common/{util/security/edec => signature}/SignatureEd25519.java (84%) create mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/ArrayUtil.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java delete mode 100644 files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java diff --git a/files-jadaptive-sftp/src/test/java/org/xbib/files/jadaptive/sftp/test/SftpFileSystemTest.java b/files-jadaptive-sftp/src/test/java/org/xbib/files/jadaptive/sftp/test/SftpFileSystemTest.java index 67598c7..499af5e 100644 --- a/files-jadaptive-sftp/src/test/java/org/xbib/files/jadaptive/sftp/test/SftpFileSystemTest.java +++ b/files-jadaptive-sftp/src/test/java/org/xbib/files/jadaptive/sftp/test/SftpFileSystemTest.java @@ -1,5 +1,6 @@ package org.xbib.files.jadaptive.sftp.test; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -18,10 +19,11 @@ public class SftpFileSystemTest { public SftpFileSystemTest() { } + @Disabled("missing connection object") @Test public void testFileSystem() throws IOException { Map env = Map.of("username", "joerg"); - try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("abfs://alkmene:22"), env)) { + try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("abfs://xbib.org:22"), env)) { for (Path path : fileSystem.getRootDirectories()) { logger.log(Level.INFO, "root dir = " + path); } diff --git a/files-sftp-fs/build.gradle b/files-sftp-fs/build.gradle index 8dc9c20..524e4eb 100644 --- a/files-sftp-fs/build.gradle +++ b/files-sftp-fs/build.gradle @@ -1,5 +1,4 @@ dependencies { api project(':files-api') api project(':files-sftp') - testImplementation libs.net.security } diff --git a/files-sftp-fs/src/test/java/module-info.java b/files-sftp-fs/src/test/java/module-info.java index 6a88284..6811488 100644 --- a/files-sftp-fs/src/test/java/module-info.java +++ b/files-sftp-fs/src/test/java/module-info.java @@ -4,7 +4,6 @@ module org.xbib.files.sftp.fs.test { requires org.xbib.files; requires org.xbib.files.sftp; requires org.xbib.files.sftp.fs; - requires org.xbib.net.security; exports org.apache.sshd.fs.test; opens org.apache.sshd.fs.test to org.junit.platform.commons; } diff --git a/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/FileServiceProviderTest.java b/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/FileServiceProviderTest.java index 90b17a0..40d67b7 100644 --- a/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/FileServiceProviderTest.java +++ b/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/FileServiceProviderTest.java @@ -13,7 +13,7 @@ public class FileServiceProviderTest { @Test public void testSFTP() throws Exception { Map env = Map.of("username", "joerg"); - FileService fs = FileService.newInstance("sftp://alkmene:22", env); + FileService fs = FileService.newInstance("sftp://xbib.org:22", env); // close() is essential! try (Stream list = fs.list(".")) { list.forEach(p -> Logger.getAnonymousLogger().info(p.toString())); diff --git a/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/SftpWithPrivateKeyReaderTest.java b/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/SftpWithPrivateKeyReaderTest.java index 3d63575..816c83f 100644 --- a/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/SftpWithPrivateKeyReaderTest.java +++ b/files-sftp-fs/src/test/java/org/apache/sshd/fs/test/SftpWithPrivateKeyReaderTest.java @@ -1,6 +1,5 @@ package org.apache.sshd.fs.test; -import java.io.InputStream; import java.nio.file.Files; import org.apache.sshd.client.ClientBuilder; import org.apache.sshd.client.SshClient; @@ -9,16 +8,12 @@ import org.apache.sshd.fs.SftpFileSystemProvider; import org.junit.jupiter.api.Test; import java.net.URI; import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyPair; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; -import org.xbib.net.security.PrivateKeyReader; - import static org.junit.jupiter.api.Assertions.assertTrue; public class SftpWithPrivateKeyReaderTest { @@ -30,12 +25,8 @@ public class SftpWithPrivateKeyReaderTest { Map env = new HashMap<>(); env.put("username", "joerg"); URI uri = URI.create("sftp://xbib.org"); - Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519"); - PrivateKeyReader privateKeyReader = new PrivateKeyReader(); - try (InputStream inputStream = Files.newInputStream(privateKeyPath); - SshClient sshClient = ClientBuilder.builder().build()) { - KeyPair keyPair = privateKeyReader.readKeyPair(inputStream, null); - sshClient.addPublicKeyIdentity(keyPair); + try (SshClient sshClient = ClientBuilder.builder() + .build()) { sshClient.setNioWorkers(1); sshClient.start(); SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env); @@ -47,28 +38,4 @@ public class SftpWithPrivateKeyReaderTest { sshClient.stop(); } } - - @Test - public void testAlkmene() throws Exception { - Map env = new HashMap<>(); - env.put("username", "joerg"); - URI uri = URI.create("sftp://alkmene"); - Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519"); - PrivateKeyReader privateKeyReader = new PrivateKeyReader(); - try (InputStream inputStream = Files.newInputStream(privateKeyPath); - SshClient sshClient = ClientBuilder.builder().build()) { - KeyPair keyPair = privateKeyReader.readKeyPair(inputStream, null); - sshClient.addPublicKeyIdentity(keyPair); - sshClient.setNioWorkers(1); - sshClient.start(); - SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env); - assertTrue(Files.exists(fileSystem.getDefaultDir())); - try (Stream stream = Files.list(fileSystem.getDefaultDir())) { - stream.forEach(p -> logger.log(Level.INFO, "p = " + p)); - } - fileSystem.close(); - sshClient.stop(); - } - } - } diff --git a/files-sftp/build.gradle b/files-sftp/build.gradle index ce3e144..e69de29 100644 --- a/files-sftp/build.gradle +++ b/files-sftp/build.gradle @@ -1,3 +0,0 @@ -dependencies { - implementation libs.net.security -} diff --git a/files-sftp/src/main/java/module-info.java b/files-sftp/src/main/java/module-info.java index 293fbee..22f32bb 100644 --- a/files-sftp/src/main/java/module-info.java +++ b/files-sftp/src/main/java/module-info.java @@ -1,5 +1,4 @@ module org.xbib.files.sftp { - requires org.xbib.net.security; requires java.logging; exports org.apache.sshd.client; exports org.apache.sshd.client.auth; @@ -79,7 +78,6 @@ module org.xbib.files.sftp { exports org.apache.sshd.common.util.io.resource; exports org.apache.sshd.common.util.net; exports org.apache.sshd.common.util.security; - exports org.apache.sshd.common.util.security.eddsa; exports org.apache.sshd.common.util.threads; uses org.apache.sshd.common.io.IoServiceFactoryFactory; } \ No newline at end of file diff --git a/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java b/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java index 6886006..56c531c 100644 --- a/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java +++ b/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java @@ -50,6 +50,7 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer; * @author Apache MINA SSHD Project */ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFactoriesManager { + public static final String NAME = UserAuthPublicKeyFactory.NAME; protected Iterator keys; @@ -107,7 +108,6 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact } catch (Error e) { throw new RuntimeSshException(e); } - PublicKey pubKey = keyPair.getPublic(); String keyType = KeyUtils.getKeyType(pubKey); NamedFactory factory; @@ -118,14 +118,11 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact } else { factory = SignatureFactory.resolveSignatureFactory(keyType, getSignatureFactories()); } - String algo = (factory == null) ? keyType : factory.getName(); String name = getName(); - if (reporter != null) { reporter.signalAuthenticationAttempt(session, service, keyPair, algo); } - Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST); buffer.putString(session.getUsername()); buffer.putString(service); @@ -141,17 +138,14 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact if ((keys != null) && keys.hasNext()) { return keys.next(); } - UserInteraction ui = session.getUserInteraction(); if ((ui == null) || (!ui.isInteractionAllowed(session))) { return null; } - KeyPair kp = ui.resolveAuthPublicKeyIdentityAttempt(session); if (kp == null) { return null; } - return new KeyPairIdentity(this, session, kp); } @@ -257,8 +251,6 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact } catch (Error e) { throw new RuntimeSshException(e); } - - bs.clear(); bs.putString(algo); bs.putBytes(sig); diff --git a/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java b/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java index fb59bb6..4ffd5d2 100644 --- a/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java +++ b/files-sftp/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java @@ -42,19 +42,20 @@ import org.apache.sshd.common.util.helper.LazyMatchingTypeIterator; * @author Apache MINA SSHD Project */ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator implements Channel { + private final AtomicBoolean open = new AtomicBoolean(true); - private Iterator current; + + private final Iterator current; public UserAuthPublicKeyIterator(ClientSession session, SignatureFactoriesManager signatureFactories) throws Exception { super(session); try { - Collection> identities = new ArrayList<>(2); + Collection> identities = new ArrayList<>(); Iterable sessionIds = initializeSessionIdentities(session, signatureFactories); if (sessionIds != null) { identities.add(sessionIds); } - if (identities.isEmpty()) { current = Collections.emptyIterator(); } else { @@ -66,10 +67,9 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator initializeSessionIdentities( ClientSession session, SignatureFactoriesManager signatureFactories) { - return new Iterable() { + return new Iterable<>() { private final String sessionId = session.toString(); private final AtomicReference> keysHolder = new AtomicReference<>(); @@ -81,14 +81,11 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator() { + return new Iterator<>() { private final Iterator keys; { diff --git a/files-sftp/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java b/files-sftp/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java index 07b73d1..6fc9682 100644 --- a/files-sftp/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java +++ b/files-sftp/src/main/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcher.java @@ -27,11 +27,12 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.BuiltinIdentities; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.FilePasswordProviderHolder; -import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; @@ -41,13 +42,19 @@ import org.apache.sshd.common.util.GenericUtils; public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher { private final boolean supportedOnly; - public BuiltinClientIdentitiesWatcher(Path keysFolder, boolean supportedOnly, - ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) { + public BuiltinClientIdentitiesWatcher(Path keysFolder, + boolean supportedOnly, + ClientIdentityLoader loader, + FilePasswordProvider provider, + boolean strict) { this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict); } - public BuiltinClientIdentitiesWatcher(Path keysFolder, Collection ids, boolean supportedOnly, - ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) { + public BuiltinClientIdentitiesWatcher(Path keysFolder, + Collection ids, + boolean supportedOnly, + ClientIdentityLoader loader, + FilePasswordProvider provider, boolean strict) { this(keysFolder, ids, supportedOnly, ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")), FilePasswordProviderHolder.providerHolderOf(Objects.requireNonNull(provider, "No password provider")), @@ -90,18 +97,17 @@ public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher { return getBuiltinIdentitiesPaths(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES)); } - public static List getBuiltinIdentitiesPaths(Path keysFolder, Collection ids) { + public static List getBuiltinIdentitiesPaths(Path keysFolder, + Collection ids) { Objects.requireNonNull(keysFolder, "No keys folder"); if (GenericUtils.isEmpty(ids)) { return Collections.emptyList(); } - List paths = new ArrayList<>(ids.size()); for (String id : ids) { String fileName = ClientIdentity.getIdentityFileName(id); paths.add(keysFolder.resolve(fileName)); } - return paths; } } diff --git a/files-sftp/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java b/files-sftp/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java index 67430ed..f9d68ad 100644 --- a/files-sftp/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java +++ b/files-sftp/src/main/java/org/apache/sshd/client/config/keys/DefaultClientIdentitiesWatcher.java @@ -31,16 +31,21 @@ import org.apache.sshd.common.config.keys.PublicKeyEntry; * @author Apache MINA SSHD Project */ public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatcher { - public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider) { + + public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, + FilePasswordProvider provider) { this(loader, provider, true); } - public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) { + public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, + FilePasswordProvider provider, + boolean strict) { this(true, loader, provider, strict); } - public DefaultClientIdentitiesWatcher( - boolean supportedOnly, ClientIdentityLoader loader, FilePasswordProvider provider, + public DefaultClientIdentitiesWatcher(boolean supportedOnly, + ClientIdentityLoader loader, + FilePasswordProvider provider, boolean strict) { this(supportedOnly, ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")), @@ -48,17 +53,20 @@ public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatch strict); } - public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider) { + public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, + FilePasswordProviderHolder provider) { this(loader, provider, true); } - public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider, + public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, + FilePasswordProviderHolder provider, boolean strict) { this(true, loader, provider, strict); } public DefaultClientIdentitiesWatcher(boolean supportedOnly, - ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider, + ClientIdentityLoaderHolder loader, + FilePasswordProviderHolder provider, boolean strict) { super(PublicKeyEntry.getDefaultKeysFolderPath(), supportedOnly, loader, provider, strict); } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/files-sftp/src/main/java/org/apache/sshd/common/cipher/ECCurves.java index 4b09d01..14fbe72 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/cipher/ECCurves.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/cipher/ECCurves.java @@ -198,7 +198,7 @@ public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator, NamedResourc @Override public final boolean isSupported() { - return SecurityUtils.isECCSupported() && digestFactory.isSupported(); + return digestFactory.isSupported(); } public final ECParameterSpec getParameters() { diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java index 2b6c9f6..5f09fc5 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/BuiltinIdentities.java @@ -27,6 +27,8 @@ import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.interfaces.EdECPrivateKey; +import java.security.interfaces.EdECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Collection; @@ -43,29 +45,20 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.keyprovider.KeyTypeIndicator; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.security.SecurityUtils; /** * @author Apache MINA SSHD Project */ public enum BuiltinIdentities implements Identity { - RSA(Constants.RSA, RSAPublicKey.class, RSAPrivateKey.class, KeyPairProvider.SSH_RSA), DSA(Constants.DSA, DSAPublicKey.class, DSAPrivateKey.class, KeyPairProvider.SSH_DSS), + RSA(Constants.RSA, RSAPublicKey.class, RSAPrivateKey.class, KeyPairProvider.SSH_RSA), ECDSA(Constants.ECDSA, KeyUtils.EC_ALGORITHM, ECPublicKey.class, ECPrivateKey.class, ECCurves.VALUES.stream().map(KeyTypeIndicator::getKeyType).collect(Collectors.toList())) { - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, - ED25119(Constants.ED25519, SecurityUtils.EDDSA, - SecurityUtils.getEDDSAPublicKeyType(), - SecurityUtils.getEDDSAPrivateKeyType(), - KeyPairProvider.SSH_ED25519) { - @Override - public boolean isSupported() { - return SecurityUtils.isEDDSACurveSupported(); - } + ED25119(Constants.ED25519, "EdDSA", + EdECPublicKey.class, + EdECPrivateKey.class, + "ssh-ed25519") { }; public static final Set VALUES = Collections.unmodifiableSet(EnumSet.allOf(BuiltinIdentities.class)); diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java index c11b8df..6bb34f5 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java @@ -39,6 +39,9 @@ import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.interfaces.EdECKey; +import java.security.interfaces.EdECPrivateKey; +import java.security.interfaces.EdECPublicKey; import java.security.interfaces.RSAKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPrivateKey; @@ -68,8 +71,9 @@ 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.Ed25519PublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder; -import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder; +import org.apache.sshd.common.config.keys.impl.RSAPublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder; import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.Digest; @@ -156,16 +160,11 @@ public final class KeyUtils { static { registerPublicKeyEntryDecoder(OpenSSHCertificateDecoder.INSTANCE); - registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE); + registerPublicKeyEntryDecoder(RSAPublicKeyEntryDecoder.INSTANCE); registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE); - - if (SecurityUtils.isECCSupported()) { - registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE); - registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE); - } - if (SecurityUtils.isEDDSACurveSupported()) { - registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder()); - } + registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE); + registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE); + registerPublicKeyEntryDecoder(Ed25519PublicKeyEntryDecoder.INSTANCE); } private KeyUtils() { @@ -810,7 +809,7 @@ public final class KeyUtils { } else { return curve.getKeyType(); } - } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) { + } else if (key instanceof EdECKey) { return KeyPairProvider.SSH_ED25519; } else if (key instanceof OpenSshCertificate) { return ((OpenSshCertificate) key).getKeyType(); @@ -946,10 +945,9 @@ public final class KeyUtils { if (curve != null) { return curve.getKeySize(); } - } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) { - return SecurityUtils.getEDDSAKeySize(key); + } else if (key instanceof EdECKey) { + return 256; } - return -1; } @@ -1005,9 +1003,8 @@ public final class KeyUtils { return compareECKeys(ECPublicKey.class.cast(k1), ECPublicKey.class.cast(k2)); } else if ((k1 instanceof SkEcdsaPublicKey) && (k2 instanceof SkEcdsaPublicKey)) { return compareSkEcdsaKeys(SkEcdsaPublicKey.class.cast(k1), SkEcdsaPublicKey.class.cast(k2)); - } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm()) - && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) { - return SecurityUtils.compareEDDSAPPublicKeys(k1, k2); + } else if ((k1 instanceof EdECPublicKey) && (k2 instanceof EdECPublicKey)) { + return compareEDDSAPPublicKeys(k1, k2); } else { return false; // either key is null or not of same class } @@ -1030,9 +1027,8 @@ public final class KeyUtils { return compareDSAKeys(DSAPrivateKey.class.cast(k1), DSAPrivateKey.class.cast(k2)); } else if ((k1 instanceof ECPrivateKey) && (k2 instanceof ECPrivateKey)) { return compareECKeys(ECPrivateKey.class.cast(k1), ECPrivateKey.class.cast(k2)); - } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm()) - && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) { - return SecurityUtils.compareEDDSAPrivateKeys(k1, k2); + } else if ((k1 instanceof EdECPrivateKey) && (k2 instanceof EdECPrivateKey)) { + return compareEDDSAPrivateKeys(k1, k2); } else { return false; // either key is null or not of same class } @@ -1177,4 +1173,12 @@ public final class KeyUtils { && compareECKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey()); } } + + public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { + return Arrays.equals(k1.getEncoded(), k2.getEncoded()); + } + + public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { + return Arrays.equals(k1.getEncoded(), k2.getEncoded()); + } } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java index 0e61acc..41718a8 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java @@ -46,15 +46,13 @@ public interface PrivateKeyEntryDecoder, PrivateKeyEntryResolver { @Override - default PrivateKey resolve( - SessionContext session, String keyType, byte[] keyData) + default PrivateKey resolve(SessionContext session, String keyType, byte[] keyData) throws IOException, GeneralSecurityException { ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided"); Collection supported = getSupportedKeyTypes(); if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) { return decodePrivateKey(session, FilePasswordProvider.EMPTY, keyData); } - throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported); } @@ -87,20 +85,19 @@ public interface PrivateKeyEntryDecoder supported = getSupportedKeyTypes(); if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) { throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported); } - return decodePrivateKey(session, type, passwordProvider, keyData); } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java index 41a9262..b81d0dc 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java @@ -27,7 +27,6 @@ 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; @@ -77,10 +76,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder { +public final class Ed25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder { - public static final int MAX_ALLOWED_SEED_LEN = 1024; + public static final Ed25519PublicKeyEntryDecoder INSTANCE = new Ed25519PublicKeyEntryDecoder(); - public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); - - private Ed25519PublicKeyDecoder() { + private Ed25519PublicKeyEntryDecoder() { super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519)); } + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return KeyPairGenerator.getInstance("Ed25519"); + } + + @Override + public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return KeyFactory.getInstance("EdDSA"); + } + + @Override + public EdECPublicKey decodePublicKey(SessionContext session, + String keyType, + InputStream keyData, + Map headers) + throws IOException, GeneralSecurityException { + byte[] arr = keyData.readAllBytes(); + byte msb = arr[arr.length - 1]; + boolean xOdd = (msb & 0x80) != 0; + arr[arr.length - 1] &= (byte) 0x7F; + ArrayUtil.reverse(arr); + BigInteger y = new BigInteger(1, arr); + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPoint edECPoint = new EdECPoint(xOdd, y); + return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint)); + } + @Override public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException { if (key == null) { @@ -50,40 +79,4 @@ public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder return generatePrivateKey(new EdECPrivateKeySpec(namedParameterSpec, key.getEncoded())); } } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return KeyPairGenerator.getInstance("Ed25519"); - } - - @Override - public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException { - // TODO - return "ssh-ed25519"; - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return KeyFactory.getInstance("EdDSA"); - } - - @Override - public EdECPublicKey decodePublicKey(SessionContext session, - String keyType, - InputStream keyData, - Map headers) - throws IOException, GeneralSecurityException { - byte[] pk = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); - boolean xisodd = false; - int lastbyteInt = pk[pk.length - 1]; - if ((lastbyteInt & 255) >> 7 == 1) { - xisodd = true; - } - pk[pk.length - 1] &= 127; - BigInteger y = new BigInteger(1, pk); - NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; - EdECPoint ep = new EdECPoint(xisodd, y); - EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, ep); - return EdECPublicKey.class.cast(generatePublicKey(spec)); - } } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyEntryDecoder.java similarity index 95% rename from files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java rename to files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyEntryDecoder.java index 1cbe32b..09cfca1 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyDecoder.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/impl/RSAPublicKeyEntryDecoder.java @@ -47,10 +47,10 @@ import org.apache.sshd.common.util.security.SecurityUtils; /** * @author Apache MINA SSHD Project */ -public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder { - public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder(); +public class RSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder { + public static final RSAPublicKeyEntryDecoder INSTANCE = new RSAPublicKeyEntryDecoder(); - public RSAPublicKeyDecoder() { + public RSAPublicKeyEntryDecoder() { super(RSAPublicKey.class, RSAPrivateKey.class, Collections.unmodifiableList( Arrays.asList(KeyPairProvider.SSH_RSA, diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java index 0d82ac5..cdfe70f 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/AbstractKeyPairResourceParser.java @@ -19,9 +19,7 @@ package org.apache.sshd.common.config.keys.loader; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.io.StreamCorruptedException; import java.security.GeneralSecurityException; import java.security.KeyPair; @@ -44,6 +42,7 @@ import org.apache.sshd.common.util.ValidateUtils; * @author Apache MINA SSHD Project */ public abstract class AbstractKeyPairResourceParser implements KeyPairResourceParser { + private final List beginners; private final List enders; private final List> endingMarkers; @@ -86,10 +85,12 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa } @Override - public Collection loadKeyPairs( - SessionContext session, NamedResource resourceKey, FilePasswordProvider passwordProvider, List lines) + public Collection loadKeyPairs(SessionContext session, + NamedResource resourceKey, + FilePasswordProvider passwordProvider, + List lines) throws IOException, GeneralSecurityException { - Collection keyPairs = Collections.emptyList(); + Collection keyPairs = new LinkedList<>(); List beginMarkers = getBeginners(); List> endMarkers = getEndingMarkers(); for (Map.Entry markerPos = KeyPairResourceParser.findMarkerLine(lines, beginMarkers); @@ -97,22 +98,19 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa int startIndex = markerPos.getKey(); String startLine = lines.get(startIndex); startIndex++; - int markerIndex = markerPos.getValue(); List ender = endMarkers.get(markerIndex); markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, ender); if (markerPos == null) { throw new StreamCorruptedException("Missing end marker (" + ender + ") after line #" + startIndex); } - int endIndex = markerPos.getKey(); String endLine = lines.get(endIndex); Map.Entry, ? extends List> result = separateDataLinesFromHeaders( session, resourceKey, startLine, endLine, lines.subList(startIndex, endIndex)); Map headers = result.getKey(); List dataLines = result.getValue(); - Collection kps = extractKeyPairs( - session, resourceKey, startLine, endLine, passwordProvider, + Collection kps = extractKeyPairs(session, resourceKey, startLine, endLine, passwordProvider, (dataLines == null) ? Collections.emptyList() : dataLines, (headers == null) ? Collections.emptyMap() : headers); if (GenericUtils.isNotEmpty(kps)) { @@ -122,11 +120,8 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa keyPairs.addAll(kps); } } - - // see if there are more markerPos = KeyPairResourceParser.findMarkerLine(lines, endIndex + 1, beginMarkers); } - return keyPairs; } @@ -153,11 +148,13 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa * @throws IOException If failed to parse the data * @throws GeneralSecurityException If failed to generate the keys */ - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - List lines, Map headers) + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, + String endMarker, + FilePasswordProvider passwordProvider, + List lines, + Map headers) throws IOException, GeneralSecurityException { byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines); try { @@ -175,42 +172,18 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa * @param endMarker The line containing the end marker * @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 bytes The 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 */ - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - byte[] bytes, Map headers) - throws IOException, GeneralSecurityException { - - try (InputStream bais = new ByteArrayInputStream(bytes)) { - return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers); - } - } - - /** - * @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 resourceKey A hint as to the origin of the text lines - * @param beginMarker The line containing the begin marker - * @param endMarker The line containing the end marker - * @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 - */ - public abstract Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - InputStream stream, Map headers) + public abstract Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, + String endMarker, + FilePasswordProvider passwordProvider, + byte[] bytes, + Map headers) throws IOException, GeneralSecurityException; } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java index ee33129..fb691d7 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHECDSAPrivateKeyEntryDecoder.java @@ -64,11 +64,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD if (curve == null) { throw new InvalidKeySpecException("Not an EC curve name: " + keyType); } - - if (!SecurityUtils.isECCSupported()) { - throw new NoSuchProviderException("ECC not supported"); - } - String keyCurveName = curve.getName(); // see rfc5656 section 3.1 String encCurveName = KeyEntryResolver.decodeString(keyData, ECDSAPublicKeyEntryDecoder.MAX_CURVE_NAME_LENGTH); @@ -117,10 +112,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD @Override public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException { - if (!SecurityUtils.isECCSupported()) { - throw new NoSuchProviderException("ECC not supported"); - } - if (key == null) { return null; } @@ -135,10 +126,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD @Override public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException { - if (!SecurityUtils.isECCSupported()) { - throw new NoSuchProviderException("ECC not supported"); - } - if (key == null) { return null; } @@ -153,11 +140,7 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD @Override public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - if (SecurityUtils.isECCSupported()) { - return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM); - } else { - throw new NoSuchProviderException("ECC not supported"); - } + return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM); } @Override @@ -174,10 +157,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD @Override public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - if (SecurityUtils.isECCSupported()) { - return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM); - } else { - throw new NoSuchProviderException("ECC not supported"); - } + return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM); } } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHEd25519PrivateKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHEd25519PrivateKeyEntryDecoder.java new file mode 100644 index 0000000..e9c9e40 --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHEd25519PrivateKeyEntryDecoder.java @@ -0,0 +1,164 @@ +package org.apache.sshd.common.config.keys.loader.openssh; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.interfaces.EdECPrivateKey; +import java.security.interfaces.EdECPublicKey; +import java.security.spec.EdECPoint; +import java.security.spec.EdECPrivateKeySpec; +import java.security.spec.EdECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; +import java.util.Collections; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.io.SecureByteArrayOutputStream; +import org.apache.sshd.common.util.io.der.ASN1Object; +import org.apache.sshd.common.util.io.der.ASN1Type; +import org.apache.sshd.common.util.io.der.DERParser; + +public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { + + public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); + + public OpenSSHEd25519PrivateKeyEntryDecoder() { + super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519)); + } + + @Override + public EdECPrivateKey decodePrivateKey(SessionContext session, + String keyType, + FilePasswordProvider passwordProvider, + InputStream keyData) + throws IOException, GeneralSecurityException { + if (!"ssh-ed25519".equals(keyType)) { + throw new InvalidKeyException("Unsupported key type: " + keyType); + } + byte[] b = keyData.readAllBytes(); + OpenSsh openSsh = decodeFromOpenSsh(b); + NamedParameterSpec params = NamedParameterSpec.ED25519; + EdECPrivateKeySpec spec = new EdECPrivateKeySpec(params, openSsh.privatekey); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return (EdECPrivateKey) factory.generatePrivate(spec); + } + + @Override + public String encodePrivateKey(SecureByteArrayOutputStream s, + EdECPrivateKey key, + EdECPublicKey pubKey) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException { + throw new UnsupportedOperationException(); + } + + @Override + public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPoint edECPoint = key.getPoint(); + EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint); + return generatePublicKey(spec); + } + } + + @Override + public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException { + if (key == null) { + return null; + } else { + try (DERParser parser = new DERParser(key.getEncoded())) { + ASN1Object obj = parser.readObject(); + if (obj == null) { + throw new StreamCorruptedException("Missing key data container"); + } + ASN1Type objType = obj.getObjType(); + if (objType != ASN1Type.OCTET_STRING) { + throw new StreamCorruptedException("Mismatched key data container type: " + objType); + } + NamedParameterSpec spec = NamedParameterSpec.ED25519; + EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, obj.getValue()); + KeyFactory factory = KeyFactory.getInstance("EdDSA"); + return (EdECPrivateKey) factory.generatePrivate(keySpec); + } catch (IOException e) { + throw new GeneralSecurityException(e); + } + } + } + + @Override + public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { + return KeyPairGenerator.getInstance("Ed25519"); + } + + @Override + public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { + return KeyFactory.getInstance("EdDSA"); + } + + private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException { + OpenSsh openSsh = new OpenSsh(); + openSsh.verify = Arrays.copyOfRange(rawKey, 0, OPENSSH_KEY_V1.length()); + if (!new String(openSsh.verify).equals(OPENSSH_KEY_V1)) { + throw new InvalidKeySpecException("invalid OpenSSH key file"); + } + boolean occurred = false; + int index = 0; + for (int i = 0; i < rawKey.length; i++) { + if (rawKey[i] == 's' + && rawKey[i + 1] == 's' + && rawKey[i + 2] == 'h' + && rawKey[i + 3] == '-' + && rawKey[i + 4] == 'e' + && rawKey[i + 5] == 'd' + && rawKey[i + 6] == '2' + && rawKey[i + 7] == '5' + && rawKey[i + 8] == '5' + && rawKey[i + 9] == '1' + && rawKey[i + 10] == '9' + && rawKey[i + 11] == 0x00 + && rawKey[i + 12] == 0x00 + && rawKey[i + 13] == 0x00 + && rawKey[i + 14] == ' ') { + index = i + 15; + if (occurred) { + break; + } + occurred = true; + } + } + openSsh.publickey = Arrays.copyOfRange(rawKey, index, index + 32); + index += 32; + for (int i = index; i < rawKey.length; i++) { + if (rawKey[i] == 0x00 + && rawKey[i + 1] == 0x00 + && rawKey[i + 2] == 0x00 + && rawKey[i + 3] == '@') { + index = i + 4; + break; + } + } + openSsh.privatekey = Arrays.copyOfRange(rawKey, index, index + 32); + return openSsh; + } + + private static final String OPENSSH_KEY_V1 = "openssh-key-v1"; + + private static class OpenSsh { + byte[] verify; + byte[] publickey; + byte[] privatekey; + } +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java index 7f473fd..db40f07 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java @@ -18,22 +18,17 @@ */ package org.apache.sshd.common.config.keys.loader.openssh; -import java.io.ByteArrayInputStream; -import java.io.EOFException; import java.io.IOException; -import java.io.InputStream; -import java.io.StreamCorruptedException; -import java.net.ProtocolException; -import java.nio.charset.StandardCharsets; +import java.math.BigInteger; import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; import java.security.Key; +import java.security.KeyFactory; import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.ArrayList; +import java.security.spec.EdECPoint; +import java.security.spec.EdECPrivateKeySpec; +import java.security.spec.EdECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.NamedParameterSpec; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -43,24 +38,14 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; -import javax.security.auth.login.FailedLoginException; - import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; -import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser; -import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions; -import org.apache.sshd.common.config.keys.loader.openssh.kdf.RawKdfOptions; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.ArrayUtil; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.buffer.BufferUtils; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.security.SecurityUtils; /** * Basic support for Apache MINA SSHD Project */ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser { + public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY"; - public static final List BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER)); + public static final List BEGINNERS = Collections.singletonList(BEGIN_MARKER); public static final String END_MARKER = "END OPENSSH PRIVATE KEY"; - public static final List ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER)); + public static final List ENDERS = Collections.singletonList(END_MARKER); public static final String AUTH_MAGIC = "openssh-key-v1"; public static final OpenSSHKeyPairResourceParser INSTANCE = new OpenSSHKeyPairResourceParser(); - private static final byte[] AUTH_MAGIC_BYTES = AUTH_MAGIC.getBytes(StandardCharsets.UTF_8); private static final Map> BY_KEY_TYPE_DECODERS_MAP = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private static final Map, PrivateKeyEntryDecoder> BY_KEY_CLASS_DECODERS_MAP = new HashMap<>(); static { - registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyDecoder.INSTANCE); + registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyEntryDecoder.INSTANCE); registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE); - - if (SecurityUtils.isECCSupported()) { - registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE); - } - if (SecurityUtils.isEDDSACurveSupported()) { - registerPrivateKeyEntryDecoder(SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder()); - } + registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE); + registerPrivateKeyEntryDecoder(OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE); } public OpenSSHKeyPairResourceParser() { super(BEGINNERS, ENDERS); } - @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - InputStream stream, Map headers) - throws IOException, GeneralSecurityException { - - stream = validateStreamMagicMarker(session, resourceKey, stream); - - String cipher = KeyEntryResolver.decodeString(stream, MAX_CIPHER_NAME_LENGTH); - OpenSSHKdfOptions kdfOptions = resolveKdfOptions(session, resourceKey, beginMarker, endMarker, stream, headers); - OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfOptions); - int numKeys = KeyEntryResolver.decodeInt(stream); - if (numKeys <= 0) { - return Collections.emptyList(); - } - - - List publicKeys = new ArrayList<>(numKeys); - for (int index = 1; index <= numKeys; index++) { - PublicKey pubKey = readPublicKey(session, resourceKey, context, stream, headers); - ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", index, resourceKey); - publicKeys.add(pubKey); - } - - byte[] privateData = KeyEntryResolver.readRLEBytes(stream, MAX_PRIVATE_KEY_DATA_SIZE); - try { - if (!context.isEncrypted()) { - try (InputStream bais = new ByteArrayInputStream(privateData)) { - return readPrivateKeys(session, resourceKey, context, publicKeys, passwordProvider, bais); - } - } - - if (passwordProvider == null) { - throw new FailedLoginException("No password provider for encrypted key in " + resourceKey); - } - - for (int retryCount = 0;; retryCount++) { - String pwd = passwordProvider.getPassword(session, resourceKey, retryCount); - if (GenericUtils.isEmpty(pwd)) { - return Collections.emptyList(); - } - - List keys; - try { - byte[] decryptedData = kdfOptions.decodePrivateKeyBytes( - session, resourceKey, context.getCipherName(), privateData, pwd); - try (InputStream bais = new ByteArrayInputStream(decryptedData)) { - keys = readPrivateKeys(session, resourceKey, context, publicKeys, passwordProvider, bais); - } finally { - Arrays.fill(decryptedData, (byte) 0); // get rid of sensitive data a.s.a.p. - } - } catch (IOException | GeneralSecurityException | RuntimeException e) { - ResourceDecodeResult result - = passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryCount, pwd, e); - pwd = null; // get rid of sensitive data a.s.a.p. - if (result == null) { - result = ResourceDecodeResult.TERMINATE; - } - - switch (result) { - case TERMINATE: - throw e; - case RETRY: - continue; - case IGNORE: - return Collections.emptyList(); - default: - throw new ProtocolException( - "Unsupported decode attempt result (" + result + ") for " + resourceKey); - } - } - - passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryCount, pwd, null); - pwd = null; // get rid of sensitive data a.s.a.p. - return keys; - } - } finally { - Arrays.fill(privateData, (byte) 0); // get rid of sensitive data a.s.a.p. - } - } - - protected OpenSSHKdfOptions resolveKdfOptions( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, InputStream stream, Map headers) - throws IOException, GeneralSecurityException { - String kdfName = KeyEntryResolver.decodeString(stream, OpenSSHKdfOptions.MAX_KDF_NAME_LENGTH); - byte[] kdfOptions = KeyEntryResolver.readRLEBytes(stream, OpenSSHKdfOptions.MAX_KDF_OPTIONS_SIZE); - OpenSSHKdfOptions options; - // TODO define a factory class where users can register extra KDF options - if (BCryptKdfOptions.NAME.equalsIgnoreCase(kdfName)) { - options = new BCryptKdfOptions(); - } else { - options = new RawKdfOptions(); - } - - options.initialize(kdfName, kdfOptions); - return options; - } - - protected PublicKey readPublicKey( - SessionContext session, NamedResource resourceKey, - OpenSSHParserContext context, - InputStream stream, Map headers) - throws IOException, GeneralSecurityException { - byte[] keyData = KeyEntryResolver.readRLEBytes(stream, MAX_PUBLIC_KEY_DATA_SIZE); - try (InputStream bais = new ByteArrayInputStream(keyData)) { - String keyType = KeyEntryResolver.decodeString(bais, MAX_KEY_TYPE_NAME_LENGTH); - PublicKeyEntryDecoder decoder = KeyUtils.getPublicKeyEntryDecoder(keyType); - if (decoder == null) { - throw new NoSuchAlgorithmException("Unsupported key type (" + keyType + ") in " + resourceKey); - } - - return decoder.decodePublicKey(session, keyType, bais, headers); - } - } - - /* - * NOTE: called AFTER decrypting the original bytes, however we still propagate the password provider - just in case - * some "sub-encryption" is detected + /** + * Ed25519 only. No password. + * + * @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 resourceKey A hint as to the origin of the text lines + * @param beginMarker The line containing the begin marker + * @param endMarker The line containing the end marker + * @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be + * {@code null} if no encrypted + * @param rawKey The bytes from the lines containing the data + * @param headers Any headers that may have been available when data was read + * @return + * @throws IOException + * @throws GeneralSecurityException */ - protected List readPrivateKeys( - SessionContext session, NamedResource resourceKey, - OpenSSHParserContext context, Collection publicKeys, - FilePasswordProvider passwordProvider, InputStream stream) + @Override + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, + String endMarker, + FilePasswordProvider passwordProvider, + byte[] rawKey, + Map headers) throws IOException, GeneralSecurityException { - if (GenericUtils.isEmpty(publicKeys)) { - return Collections.emptyList(); - } - - int check1 = KeyEntryResolver.decodeInt(stream); - int check2 = KeyEntryResolver.decodeInt(stream); - - /* - * According to the documentation: - * - * Before the key is encrypted, a random integer is assigned to both checkint fields so successful decryption - * can be quickly checked by verifying that both checkint fields hold the same value. - */ - if (check1 != check2) { - throw new StreamCorruptedException( - "Mismatched private key check values (" - + Integer.toHexString(check1) + "/" + Integer.toHexString(check2) + ") in " - + resourceKey); - } - - List keyPairs = new ArrayList<>(publicKeys.size()); - for (PublicKey pubKey : publicKeys) { - String pubType = KeyUtils.getKeyType(pubKey); - int keyIndex = keyPairs.size() + 1; - - Map.Entry prvData - = readPrivateKey(session, resourceKey, context, pubType, passwordProvider, stream); - PrivateKey prvKey = (prvData == null) ? null : prvData.getKey(); - ValidateUtils.checkNotNull(prvKey, "Empty private key #%d in %s", keyIndex, resourceKey); - - String prvType = KeyUtils.getKeyType(prvKey); - ValidateUtils.checkTrue(Objects.equals(pubType, prvType), - "Mismatched public (%s) vs. private (%s) key type #%d in %s", - pubType, prvType, keyIndex, resourceKey); - - keyPairs.add(new KeyPair(pubKey, prvKey)); - } - - return keyPairs; + OpenSsh openSsh = decodeFromOpenSsh(rawKey); + NamedParameterSpec params = NamedParameterSpec.ED25519; + EdECPrivateKeySpec edECPrivateKeySpec = new EdECPrivateKeySpec(params, openSsh.privatekey); + byte[] arr = openSsh.publickey; + byte msb = arr[arr.length - 1]; + boolean xOdd = (msb & 0x80) != 0; + arr[arr.length - 1] &= (byte) 0x7F; + ArrayUtil.reverse(arr); + BigInteger y = new BigInteger(1, arr); + EdECPoint edECPoint = new EdECPoint(xOdd, y); + EdECPublicKeySpec edECPublicKeySpec = new EdECPublicKeySpec(params, edECPoint); + KeyFactory keyFactory = KeyFactory.getInstance("EdDSA"); + KeyPair keyPair = new KeyPair(keyFactory.generatePublic(edECPublicKeySpec), keyFactory.generatePrivate(edECPrivateKeySpec)); + return List.of(keyPair); } - protected Map.Entry readPrivateKey( - SessionContext session, NamedResource resourceKey, - OpenSSHParserContext context, String keyType, - FilePasswordProvider passwordProvider, InputStream stream) - throws IOException, GeneralSecurityException { - String prvType = KeyEntryResolver.decodeString(stream, MAX_KEY_TYPE_NAME_LENGTH); - if (!Objects.equals(keyType, prvType)) { - throw new StreamCorruptedException( - "Mismatched private key type: " - + ", expected=" + keyType + ", actual=" + prvType - + " in " + resourceKey); + private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException { + OpenSsh openSsh = new OpenSsh(); + openSsh.verify = Arrays.copyOfRange(rawKey, 0, OPENSSH_KEY_V1.length()); + if (!new String(openSsh.verify).equals(OPENSSH_KEY_V1)) { + throw new InvalidKeySpecException("invalid OpenSSH key file"); } - - PrivateKeyEntryDecoder decoder = getPrivateKeyEntryDecoder(prvType); - if (decoder == null) { - throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey); + boolean occurred = false; + int index = 0; + for (int i = 0; i < rawKey.length; i++) { + if (rawKey[i] == 's' + && rawKey[i + 1] == 's' + && rawKey[i + 2] == 'h' + && rawKey[i + 3] == '-' + && rawKey[i + 4] == 'e' + && rawKey[i + 5] == 'd' + && rawKey[i + 6] == '2' + && rawKey[i + 7] == '5' + && rawKey[i + 8] == '5' + && rawKey[i + 9] == '1' + && rawKey[i + 10] == '9' + && rawKey[i + 11] == 0x00 + && rawKey[i + 12] == 0x00 + && rawKey[i + 13] == 0x00 + && rawKey[i + 14] == ' ') { + index = i + 15; + if (occurred) { + break; + } + occurred = true; + } } - - PrivateKey prvKey = decoder.decodePrivateKey(session, prvType, passwordProvider, stream); - if (prvKey == null) { - throw new InvalidKeyException("Cannot parse key type (" + prvType + ") in " + resourceKey); + openSsh.publickey = Arrays.copyOfRange(rawKey, index, index + 32); + index += 32; + for (int i = index; i < rawKey.length; i++) { + if (rawKey[i] == 0x00 + && rawKey[i + 1] == 0x00 + && rawKey[i + 2] == 0x00 + && rawKey[i + 3] == '@') { + index = i + 4; + break; + } } - - String comment = KeyEntryResolver.decodeString(stream, MAX_KEY_COMMENT_LENGTH); - return new SimpleImmutableEntry<>(prvKey, comment); + openSsh.privatekey = Arrays.copyOfRange(rawKey, index, index + 32); + return openSsh; } - protected S validateStreamMagicMarker( - SessionContext session, NamedResource resourceKey, S stream) - throws IOException { - byte[] actual = new byte[AUTH_MAGIC_BYTES.length]; - IoUtils.readFully(stream, actual); - if (!Arrays.equals(AUTH_MAGIC_BYTES, actual)) { - throw new StreamCorruptedException( - resourceKey + ": Mismatched magic marker value: " + BufferUtils.toHex(':', actual)); - } + private static final String OPENSSH_KEY_V1 = "openssh-key-v1"; - int eos = stream.read(); - if (eos == -1) { - throw new EOFException(resourceKey + ": Premature EOF after magic marker value"); - } - - if (eos != 0) { - throw new StreamCorruptedException( - resourceKey + ": Missing EOS after magic marker value: 0x" + Integer.toHexString(eos)); - } - - return stream; + private static class OpenSsh { + byte[] verify; + byte[] publickey; + byte[] privatekey; } /** @@ -341,7 +198,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser for (String n : names) { PrivateKeyEntryDecoder prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder); if (prev != null) { - // noinspection UnnecessaryContinue continue; // debug breakpoint } } @@ -404,7 +260,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) { return null; } - synchronized (BY_KEY_TYPE_DECODERS_MAP) { PrivateKeyEntryDecoder decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType); if (decoder != null) { @@ -420,7 +275,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser } } } - return null; } } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyEntryDecoder.java similarity index 90% rename from files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java rename to files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyEntryDecoder.java index 55b610b..ca63477 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyDecoder.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHRSAPrivateKeyEntryDecoder.java @@ -47,23 +47,25 @@ import org.apache.sshd.common.util.security.SecurityUtils; /** * @author Apache MINA SSHD Project */ -public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder { - public static final BigInteger DEFAULT_PUBLIC_EXPONENT = new BigInteger("65537"); - public static final OpenSSHRSAPrivateKeyDecoder INSTANCE = new OpenSSHRSAPrivateKeyDecoder(); +public class OpenSSHRSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { - public OpenSSHRSAPrivateKeyDecoder() { - super(RSAPublicKey.class, RSAPrivateKey.class, - Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA))); + public static final BigInteger DEFAULT_PUBLIC_EXPONENT = new BigInteger("65537"); + + public static final OpenSSHRSAPrivateKeyEntryDecoder INSTANCE = new OpenSSHRSAPrivateKeyEntryDecoder(); + + public OpenSSHRSAPrivateKeyEntryDecoder() { + super(RSAPublicKey.class, RSAPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_RSA)); } @Override - public RSAPrivateKey decodePrivateKey( - SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) + public RSAPrivateKey decodePrivateKey(SessionContext session, + String keyType, + FilePasswordProvider passwordProvider, + 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 n = KeyEntryResolver.decodeBigInt(keyData); BigInteger e = KeyEntryResolver.decodeBigInt(keyData); if (!Objects.equals(e, DEFAULT_PUBLIC_EXPONENT)) { diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java index d659b7b..42a9aeb 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java @@ -18,9 +18,7 @@ */ package org.apache.sshd.common.config.keys.loader.pem; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.io.StreamCorruptedException; import java.net.ProtocolException; import java.security.GeneralSecurityException; @@ -59,8 +57,10 @@ public abstract class AbstractPEMResourceKeyPairParser private final String algo; private final String algId; - protected AbstractPEMResourceKeyPairParser( - String algo, String algId, List beginners, List enders) { + protected AbstractPEMResourceKeyPairParser(String algo, + String algId, + List beginners, + List enders) { super(beginners, enders); this.algo = ValidateUtils.checkNotNullAndNotEmpty(algo, "No encryption algorithm provided"); this.algId = ValidateUtils.checkNotNullAndNotEmpty(algId, "No algorithm identifier provided"); @@ -166,10 +166,8 @@ public abstract class AbstractPEMResourceKeyPairParser try { 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, decodedData, headers); - } } finally { Arrays.fill(encryptedData, (byte) 0); // get rid of sensitive data a.s.a.p. Arrays.fill(decodedData, (byte) 0); // get rid of sensitive data a.s.a.p. diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java index 796bf60..536bda8 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/DSSPEMResourceKeyPairParser.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.config.keys.loader.pem; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; @@ -66,14 +67,18 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse } @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - InputStream stream, Map headers) + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, + String endMarker, + FilePasswordProvider passwordProvider, + byte[] rawKey, + Map headers) throws IOException, GeneralSecurityException { - KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false); - return Collections.singletonList(kp); + try (InputStream stream = new ByteArrayInputStream(rawKey)) { + KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false); + return Collections.singletonList(kp); + } } /** diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java index 08fe839..631fa06 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/ECDSAPEMResourceKeyPairParser.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.config.keys.loader.pem; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; @@ -26,7 +27,6 @@ import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; -import java.security.NoSuchProviderException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECPoint; @@ -55,10 +55,10 @@ import org.apache.sshd.common.util.security.SecurityUtils; */ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser { public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY"; - public static final List BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER)); + public static final List BEGINNERS = Collections.singletonList(BEGIN_MARKER); public static final String END_MARKER = "END EC PRIVATE KEY"; - public static final List ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER)); + public static final List ENDERS = Collections.singletonList(END_MARKER); /** * @see RFC-3279 section 2.3.5 @@ -72,15 +72,18 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar } @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - InputStream stream, Map headers) + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, + String endMarker, + FilePasswordProvider passwordProvider, + byte[] rawKey, + Map headers) throws IOException, GeneralSecurityException { - - KeyPair kp = parseECKeyPair(stream, false); - return Collections.singletonList(kp); + try (InputStream stream = new ByteArrayInputStream(rawKey)) { + KeyPair kp = parseECKeyPair(stream, false); + return Collections.singletonList(kp); + } } public static KeyPair parseECKeyPair( @@ -104,10 +107,6 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar throws IOException, GeneralSecurityException { ASN1Object sequence = parser.readObject(); Map.Entry spec = decodeECPrivateKeySpec(curve, sequence); - if (!SecurityUtils.isECCSupported()) { - throw new NoSuchProviderException("ECC not supported"); - } - KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM); ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey()); ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue()); diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECPEMResourceKeyParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/Ed25519PEMResourceKeyParser.java similarity index 83% rename from files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECPEMResourceKeyParser.java rename to files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/Ed25519PEMResourceKeyParser.java index dcae448..5af41c1 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECPEMResourceKeyParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/Ed25519PEMResourceKeyParser.java @@ -1,14 +1,13 @@ -package org.apache.sshd.common.util.security.edec; +package org.apache.sshd.common.config.keys.loader.pem; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; 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.PrivateKey; import java.security.interfaces.EdECPrivateKey; import java.security.interfaces.EdECPublicKey; import java.security.spec.EdECPoint; @@ -21,15 +20,15 @@ import java.util.List; import java.util.Map; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.ArrayUtil; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.NoCloseInputStream; import org.apache.sshd.common.util.io.der.ASN1Object; import org.apache.sshd.common.util.io.der.ASN1Type; import org.apache.sshd.common.util.io.der.DERParser; -public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { +public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY"; @@ -43,9 +42,9 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { */ public static final String ED25519_OID = "1.3.101.112"; - public static final EdECPEMResourceKeyParser INSTANCE = new EdECPEMResourceKeyParser(); + public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); - public EdECPEMResourceKeyParser() { + public Ed25519PEMResourceKeyParser() { super("Ed25519", ED25519_OID, BEGINNERS, ENDERS); } @@ -54,10 +53,13 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { NamedResource resourceKey, String beginMarker, String endMarker, FilePasswordProvider passwordProvider, - InputStream stream, - Map headers) throws IOException, GeneralSecurityException { - KeyPair kp = parseEd25519KeyPair(stream, false); - return Collections.singletonList(kp); + byte[] rawKey, + Map headers) + throws IOException, GeneralSecurityException { + try (InputStream stream = new ByteArrayInputStream(rawKey)) { + KeyPair kp = parseEd25519KeyPair(stream, false); + return Collections.singletonList(kp); + } } public static KeyPair parseEd25519KeyPair(InputStream inputStream, @@ -145,18 +147,15 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { return (EdECPrivateKey) factory.generatePrivate(keySpec); } - private static EdECPublicKey getPublicKey(byte[] keyData) throws GeneralSecurityException { - byte[] pk = keyData; - boolean xisodd = false; - int lastbyteInt = pk[pk.length - 1]; - if ((lastbyteInt & 255) >> 7 == 1) { - xisodd = true; - } - pk[pk.length - 1] &= 127; - BigInteger y = new BigInteger(1, pk); - NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519"); - EdECPoint ep = new EdECPoint(xisodd, y); - EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep); + private static EdECPublicKey getPublicKey(byte[] arr) throws GeneralSecurityException { + byte msb = arr[arr.length - 1]; + boolean xOdd = (msb & 0x80) != 0; + arr[arr.length - 1] &= (byte) 0x7F; + ArrayUtil.reverse(arr); + BigInteger y = new BigInteger(1, arr); + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPoint edECPoint = new EdECPoint(xOdd, y); + EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(namedParameterSpec, edECPoint); KeyFactory factory = KeyFactory.getInstance("EdDSA"); return (EdECPublicKey) factory.generatePublic(publicKeySpec); } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java index 5438a2f..c05e702 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java @@ -20,7 +20,6 @@ package org.apache.sshd.common.config.keys.loader.pem; import java.io.IOException; -import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; @@ -40,12 +39,10 @@ import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.io.der.ASN1Object; import org.apache.sshd.common.util.io.der.ASN1Type; import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.security.edec.EdECPEMResourceKeyParser; /** * @author Apache MINA SSHD Project @@ -54,10 +51,10 @@ import org.apache.sshd.common.util.security.edec.EdECPEMResourceKeyParser; public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser { // Not exactly according to standard but good enough public static final String BEGIN_MARKER = "BEGIN PRIVATE KEY"; - public static final List BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER)); + public static final List BEGINNERS = Collections.singletonList(BEGIN_MARKER); public static final String END_MARKER = "END PRIVATE KEY"; - public static final List ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER)); + public static final List ENDERS = Collections.singletonList(END_MARKER); public static final String PKCS8_FORMAT = "PKCS#8"; @@ -68,31 +65,31 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar } @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - InputStream stream, Map headers) + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, String endMarker, + FilePasswordProvider passwordProvider, + byte[] encBytes, + Map headers) throws IOException, GeneralSecurityException { // Save the data before getting the algorithm OID since we will need it - byte[] encBytes = IoUtils.toByteArray(stream); PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes); - return extractKeyPairs( - session, resourceKey, beginMarker, endMarker, + return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, encBytes, pkcs8Info, headers); } - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, byte[] encBytes, - PKCS8PrivateKeyInfo pkcs8Info, Map headers) + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, String endMarker, + FilePasswordProvider passwordProvider, + byte[] encBytes, + PKCS8PrivateKeyInfo pkcs8Info, + Map headers) throws IOException, GeneralSecurityException { List oidAlgorithm = pkcs8Info.getAlgorithmIdentifier(); String oid = GenericUtils.join(oidAlgorithm, '.'); KeyPair kp; - if (SecurityUtils.isECCSupported() - && ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) { + if (ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) { ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes(); ASN1Object extraInfo = pkcs8Info.getAlgorithmParameter(); ASN1Type objType = (extraInfo == null) ? ASN1Type.NULL : extraInfo.getObjType(); @@ -104,14 +101,12 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar throw new NoSuchAlgorithmException("Cannot match EC curve OID=" + oidCurve); } } - try (DERParser parser = privateKeyBytes.createParser()) { kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser); } - } else if (SecurityUtils.isEDDSACurveSupported() - && EdECPEMResourceKeyParser.ED25519_OID.endsWith(oid)) { + } else if (Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) { ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes(); - kp = EdECPEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes()); + kp = Ed25519PEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes()); } else { PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes); PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey), diff --git a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java index 161a0ac..bb2dd7f 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/config/keys/loader/pem/RSAPEMResourceKeyPairParser.java @@ -19,6 +19,7 @@ package org.apache.sshd.common.config.keys.loader.pem; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; @@ -67,14 +68,18 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse } @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, - String beginMarker, String endMarker, - FilePasswordProvider passwordProvider, - InputStream stream, Map headers) + public Collection extractKeyPairs(SessionContext session, + NamedResource resourceKey, + String beginMarker, + String endMarker, + FilePasswordProvider passwordProvider, + byte[] rawKey, + Map headers) throws IOException, GeneralSecurityException { - KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false); - return Collections.singletonList(kp); + try (InputStream stream = new ByteArrayInputStream(rawKey)) { + KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false); + return Collections.singletonList(kp); + } } /** diff --git a/files-sftp/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java b/files-sftp/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java index 4b64462..db47c39 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java @@ -157,97 +157,53 @@ public enum BuiltinSignatures implements SignatureFactory { return new SignatureECDSA.SignatureECDSA256(); } - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, nistp256_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT) { @Override public Signature create() { return new SignatureECDSA.SignatureECDSA256(); } - - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, nistp384(KeyPairProvider.ECDSA_SHA2_NISTP384) { @Override public Signature create() { return new SignatureECDSA.SignatureECDSA384(); } - - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, nistp384_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT) { @Override public Signature create() { return new SignatureECDSA.SignatureECDSA384(); } - - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, nistp521(KeyPairProvider.ECDSA_SHA2_NISTP521) { @Override public Signature create() { return new SignatureECDSA.SignatureECDSA521(); } - - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, nistp521_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT) { @Override public Signature create() { return new SignatureECDSA.SignatureECDSA521(); } - - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, sk_ecdsa_sha2_nistp256(SkECDSAPublicKeyEntryDecoder.KEY_TYPE) { @Override public Signature create() { return new SignatureSkECDSA(); } - - @Override - public boolean isSupported() { - return SecurityUtils.isECCSupported(); - } }, ed25519(KeyPairProvider.SSH_ED25519) { @Override public Signature create() { - return SecurityUtils.getEDDSASigner(); - } - - @Override - public boolean isSupported() { - return SecurityUtils.isEDDSACurveSupported(); + return new SignatureEd25519(); } }, ed25519_cert(KeyPairProvider.SSH_ED25519_CERT) { @Override public Signature create() { - return SecurityUtils.getEDDSASigner(); - } - - @Override - public boolean isSupported() { - return SecurityUtils.isEDDSACurveSupported(); + return new SignatureEd25519(); } }; diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/SignatureEd25519.java b/files-sftp/src/main/java/org/apache/sshd/common/signature/SignatureEd25519.java similarity index 84% rename from files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/SignatureEd25519.java rename to files-sftp/src/main/java/org/apache/sshd/common/signature/SignatureEd25519.java index b27081f..556dcfb 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/SignatureEd25519.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/signature/SignatureEd25519.java @@ -1,14 +1,13 @@ -package org.apache.sshd.common.util.security.edec; +package org.apache.sshd.common.signature; import java.util.Map; import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.signature.AbstractSignature; import org.apache.sshd.common.util.ValidateUtils; public class SignatureEd25519 extends AbstractSignature { public SignatureEd25519() { - super("NONEwithEdDSA"); + super("EdDSA"); } @Override diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/ArrayUtil.java b/files-sftp/src/main/java/org/apache/sshd/common/util/ArrayUtil.java new file mode 100644 index 0000000..e0c3f80 --- /dev/null +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/ArrayUtil.java @@ -0,0 +1,23 @@ +package org.apache.sshd.common.util; + +public class ArrayUtil { + + private ArrayUtil() { + } + + public static void reverse(byte[] arr) { + int i = 0; + int j = arr.length - 1; + while (i < j) { + swap(arr, i, j); + i++; + j--; + } + } + + private static void swap(byte[] arr, int i, int j) { + byte tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } +} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java b/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java index dfb14a0..fad1a8b 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java @@ -30,12 +30,9 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.EdECPublicKey; -import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; @@ -61,6 +58,7 @@ import org.apache.sshd.common.cipher.ECCurves; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.OpenSshCertificate; import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.ArrayUtil; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.Readable; @@ -73,6 +71,7 @@ import org.apache.sshd.common.util.security.SecurityUtils; * @author Apache MINA SSHD Project */ public abstract class Buffer implements Readable { + protected final byte[] workBuf = new byte[Long.BYTES]; protected Buffer() { @@ -514,7 +513,7 @@ public abstract class Buffer implements Readable { pub = keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); prv = keyFactory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)); } else if (KeyPairProvider.SSH_ED25519.equals(keyAlg)) { - return SecurityUtils.extractEDDSAKeyPair(this, keyAlg); + throw new UnsupportedOperationException(); } else { ECCurves curve = ECCurves.fromKeyType(keyAlg); if (curve == null) { @@ -900,10 +899,6 @@ public abstract class Buffer implements Readable { putString(curve.getName()); putBytes(ecPoint); } - case EdECPublicKey edECPublicKey -> { - byte[] b = edECPublicKey.getEncoded(); - putBytes(b); - } case OpenSshCertificate cert -> { putBytes(cert.getNonce()); putRawPublicKeyBytes(cert.getServerHostKey()); @@ -923,47 +918,12 @@ public abstract class Buffer implements Readable { putBytes(tmpBuffer.getCompactData()); putBytes(cert.getSignature()); } - default -> throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm()); - } - } - - public void putKeyPair(KeyPair kp) { - PublicKey pubKey = kp.getPublic(); - PrivateKey prvKey = kp.getPrivate(); - if (prvKey instanceof RSAPrivateCrtKey rsaPrv) { - RSAPublicKey rsaPub = (RSAPublicKey) pubKey; - putString(KeyPairProvider.SSH_RSA); - putMPInt(rsaPub.getPublicExponent()); - putMPInt(rsaPub.getModulus()); - putMPInt(rsaPrv.getPrivateExponent()); - putMPInt(rsaPrv.getCrtCoefficient()); - putMPInt(rsaPrv.getPrimeQ()); - putMPInt(rsaPrv.getPrimeP()); - } else if (pubKey instanceof DSAPublicKey dsaPub) { - DSAParams dsaParams = dsaPub.getParams(); - DSAPrivateKey dsaPrv = (DSAPrivateKey) prvKey; - putString(KeyPairProvider.SSH_DSS); - putMPInt(dsaParams.getP()); - putMPInt(dsaParams.getQ()); - putMPInt(dsaParams.getG()); - putMPInt(dsaPub.getY()); - putMPInt(dsaPrv.getX()); - } else if (pubKey instanceof ECPublicKey ecPub) { - ECPrivateKey ecPriv = (ECPrivateKey) prvKey; - ECParameterSpec ecParams = ecPub.getParams(); - ECCurves curve = ECCurves.fromCurveParameters(ecParams); - if (curve == null) { - throw new BufferException("Unsupported EC curve parameters"); + case EdECPublicKey edECPublicKey -> { + byte[] b = edECPublicKey.getPoint().getY().toByteArray(); + ArrayUtil.reverse(b); + putBytes(b); } - byte[] ecPoint = ECCurves.encodeECPoint(ecPub.getW(), ecParams); - putString(curve.getKeyType()); - putString(curve.getName()); - putBytes(ecPoint); - putMPInt(ecPriv.getS()); - } else if (SecurityUtils.EDDSA.equals(pubKey.getAlgorithm())) { - SecurityUtils.putEDDSAKeyPair(this, pubKey, prvKey); - } else { - throw new BufferException("Unsupported key pair algorithm: " + pubKey.getAlgorithm()); + default -> throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm()); } } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java b/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java index 36289ad..8724516 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/buffer/keys/ED25519BufferPublicKeyParser.java @@ -19,13 +19,20 @@ package org.apache.sshd.common.util.buffer.keys; +import java.math.BigInteger; import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.security.spec.EdECPoint; +import java.security.spec.EdECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.NamedParameterSpec; import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.util.ArrayUtil; import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.Buffer; -import org.apache.sshd.common.util.security.SecurityUtils; /** * TODO complete this when SSHD-440 is done @@ -43,6 +50,20 @@ public class ED25519BufferPublicKeyParser extends AbstractBufferPublicKeyParser< public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException { ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType); byte[] seed = buffer.getBytes(); - return SecurityUtils.generateEDDSAPublicKey(keyType, seed); + return generateEDDSAPublicKey(keyType, seed); + } + + private static PublicKey generateEDDSAPublicKey(String keyType, byte[] arr) + throws NoSuchAlgorithmException, InvalidKeySpecException { + byte msb = arr[arr.length - 1]; + boolean xOdd = (msb & 0x80) != 0; + arr[arr.length - 1] &= (byte) 0x7F; + ArrayUtil.reverse(arr); + BigInteger y = new BigInteger(1, arr); + EdECPoint edECPoint = new EdECPoint(xOdd, y); + NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; + EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint); + KeyFactory keyFactory = KeyFactory.getInstance("EdDSA"); + return keyFactory.generatePublic(spec); } } diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java index e77b937..901baf6 100644 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java +++ b/files-sftp/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java @@ -22,16 +22,11 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateFactory; import java.util.Collection; @@ -48,8 +43,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.crypto.Cipher; import javax.crypto.KeyAgreement; import javax.crypto.Mac; @@ -58,20 +51,14 @@ import javax.crypto.spec.DHParameterSpec; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.PropertyResolverUtils; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.KeyUtils; -import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; -import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser; import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils; -import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.random.JceRandomFactory; import org.apache.sshd.common.random.RandomFactory; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.buffer.Buffer; -import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderUtils; import org.apache.sshd.common.util.threads.ThreadUtils; /** @@ -85,15 +72,6 @@ public final class SecurityUtils { */ public static final String BOUNCY_CASTLE = "BC"; - /** - * EDDSA support - should match {@code EdDSAKey.KEY_ALGORITHM} - */ - public static final String EDDSA = "EdDSA"; - - // A copy-paste from the original, but we don't want to drag the classes into the classpath - // See EdDSAEngine.SIGNATURE_ALGORITHM - public static final String CURVE_ED25519_SHA512 = "NONEwithEdDSA"; - /** * System property used to configure the value for the minimum supported Diffie-Hellman Group Exchange key size. If * not set, then an internal auto-discovery mechanism is employed. If set to negative value then Diffie-Hellman @@ -124,9 +102,7 @@ public final class SecurityUtils { */ public static final String SECURITY_PROVIDER_REGISTRARS = "org.apache.sshd.security.registrars"; - public static final List DEFAULT_SECURITY_PROVIDER_REGISTRARS = - List.of("org.apache.sshd.common.util.security.edec.EdECSecurityProviderRegistrar"); - //Collections.unmodifiableList(Arrays.asList("org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar")); + public static final List DEFAULT_SECURITY_PROVIDER_REGISTRARS = List.of(); /** * System property used to control whether Elliptic Curves are supported or not. If not set then the support is @@ -201,28 +177,6 @@ public final class SecurityUtils { } } - /** - * @return {@code true} if Elliptic Curve Cryptography is supported - * @see #ECC_SUPPORTED_PROP - */ - public static boolean isECCSupported() { - if (hasEcc == null) { - String propValue = System.getProperty(ECC_SUPPORTED_PROP); - if (GenericUtils.isEmpty(propValue)) { - try { - getKeyPairGenerator(KeyUtils.EC_ALGORITHM); - hasEcc = Boolean.TRUE; - } catch (Throwable t) { - hasEcc = Boolean.FALSE; - } - } else { - hasEcc = Boolean.valueOf(propValue); - } - } - - return hasEcc; - } - /** * @return {@code true} if Diffie-Hellman Group Exchange is supported * @see #getMinDHGroupExchangeKeySize() @@ -351,25 +305,6 @@ public final class SecurityUtils { DEFAULT_PROVIDER_HOLDER.set(choice); } - /** - * @return A copy of the currently registered security providers - */ - public static Set getRegisteredProviders() { - // returns a COPY of the providers in order to avoid modifications - synchronized (REGISTERED_PROVIDERS) { - return new TreeSet<>(REGISTERED_PROVIDERS.keySet()); - } - } - - public static boolean isBouncyCastleRegistered() { - register(); - return isProviderRegistered(BOUNCY_CASTLE); - } - - public static boolean isProviderRegistered(String provider) { - return getRegisteredProvider(provider) != null; - } - public static SecurityProviderRegistrar getRegisteredProvider(String provider) { ValidateUtils.checkNotNullAndNotEmpty(provider, "No provider name specified"); synchronized (REGISTERED_PROVIDERS) { @@ -377,10 +312,6 @@ public final class SecurityUtils { } } - public static boolean isRegistrationCompleted() { - return REGISTRATION_STATE_HOLDER.get(); - } - private static void register() { synchronized (REGISTRATION_STATE_HOLDER) { if (REGISTRATION_STATE_HOLDER.get()) { @@ -491,107 +422,6 @@ public final class SecurityUtils { return JceRandomFactory.INSTANCE; } - ///////////////////////////// ED25519 support /////////////////////////////// - - /** - * @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported - */ - public static boolean isEDDSACurveSupported() { - register(); - - SecurityProviderRegistrar r = getRegisteredProvider(EDDSA); - return (r != null) && r.isEnabled() && r.isSupported(); - } - - /* -------------------------------------------------------------------- */ - - public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - if (!isEDDSACurveSupported()) { - throw new UnsupportedOperationException(EDDSA + " provider N/A"); - } - return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder(); - } - - public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - if (!isEDDSACurveSupported()) { - throw new UnsupportedOperationException(EDDSA + " provider N/A"); - } - - return EdDSASecurityProviderUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder(); - } - - public static org.apache.sshd.common.signature.Signature getEDDSASigner() { - if (isEDDSACurveSupported()) { - return EdDSASecurityProviderUtils.getEDDSASignature(); - } - - throw new UnsupportedOperationException(EDDSA + " Signer not available"); - } - - public static int getEDDSAKeySize(Key key) { - return EdDSASecurityProviderUtils.getEDDSAKeySize(key); - } - - public static Class getEDDSAPublicKeyType() { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class; - } - - public static Class getEDDSAPrivateKeyType() { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPrivateKeyType() : PrivateKey.class; - } - - public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPPublicKeys(k1, k2) : false; - } - - public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { - return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.compareEDDSAPrivateKeys(k1, k2) : false; - } - - public static PublicKey generateEDDSAPublicKey(String keyType, byte[] seed) throws GeneralSecurityException { - if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { - throw new InvalidKeyException("Unsupported key type: " + keyType); - } - - if (!isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); - } - - return EdDSASecurityProviderUtils.generateEDDSAPublicKey(seed); - } - - public static B putRawEDDSAPublicKey(B buffer, PublicKey key) { - if (!isEDDSACurveSupported()) { - throw new UnsupportedOperationException(EDDSA + " provider not supported"); - } - - return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key); - } - - public static B putEDDSAKeyPair(B buffer, KeyPair kp) { - return putEDDSAKeyPair(buffer, Objects.requireNonNull(kp, "No key pair").getPublic(), kp.getPrivate()); - } - - public static B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { - if (!isEDDSACurveSupported()) { - throw new UnsupportedOperationException(EDDSA + " provider not supported"); - } - - return EdDSASecurityProviderUtils.putEDDSAKeyPair(buffer, pubKey, prvKey); - } - - public static KeyPair extractEDDSAKeyPair(Buffer buffer, String keyType) throws GeneralSecurityException { - if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { - throw new InvalidKeyException("Unsupported key type: " + keyType); - } - - if (!isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(EDDSA + " provider not supported"); - } - - throw new GeneralSecurityException("Full SSHD-440 implementation N/A"); - } - public static KeyPairResourceParser getKeyPairResourceParser() { KeyPairResourceParser parser; synchronized (KEYPAIRS_PARSER_HODLER) { diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java deleted file mode 100644 index b14dc16..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PEMResourceKeyParser.java +++ /dev/null @@ -1,182 +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.util.security.eddsa; - -import java.io.IOException; -import java.io.InputStream; -import java.io.StreamCorruptedException; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -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; -import org.apache.sshd.common.config.keys.loader.pem.AbstractPEMResourceKeyPairParser; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.io.NoCloseInputStream; -import org.apache.sshd.common.util.io.der.ASN1Object; -import org.apache.sshd.common.util.io.der.ASN1Type; -import org.apache.sshd.common.util.io.der.DERParser; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.xbib.net.security.eddsa.EdDSAKey; -import org.xbib.net.security.eddsa.EdDSAPrivateKey; -import org.xbib.net.security.eddsa.EdDSAPublicKey; -import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable; -import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec; -import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec; - -/** - * @author Apache MINA SSHD Project - */ -public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParser { - // TODO find out how the markers really look like for now provide something - public static final String BEGIN_MARKER = "BEGIN EDDSA PRIVATE KEY"; - public static final List BEGINNERS = Collections.singletonList(BEGIN_MARKER); - - public static final String END_MARKER = "END EDDSA PRIVATE KEY"; - public static final List ENDERS = Collections.singletonList(END_MARKER); - - /** - * @see RFC8412 section 3 - */ - public static final String ED25519_OID = "1.3.101.112"; - - public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser(); - - public Ed25519PEMResourceKeyParser() { - super(EdDSAKey.KEY_ALGORITHM, ED25519_OID, BEGINNERS, ENDERS); - } - - @Override - public Collection extractKeyPairs( - SessionContext session, NamedResource resourceKey, String beginMarker, - String endMarker, FilePasswordProvider passwordProvider, - InputStream stream, Map headers) - throws IOException, GeneralSecurityException { - KeyPair kp = parseEd25519KeyPair(stream, false); - return Collections.singletonList(kp); - } - - public static KeyPair parseEd25519KeyPair( - InputStream inputStream, boolean okToClose) - throws IOException, GeneralSecurityException { - try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { - return parseEd25519KeyPair(parser); - } - } - - /* - * See https://tools.ietf.org/html/rfc8410#section-7 - * - * SEQUENCE { - * INTEGER 0x00 (0 decimal) - * SEQUENCE { - * OBJECTIDENTIFIER 1.3.101.112 - * } - * OCTETSTRING keyData - * } - * - * NOTE: there is another variant that also has some extra parameters - * but it has the same "prefix" structure so we don't care - */ - public static KeyPair parseEd25519KeyPair(DERParser parser) throws IOException, GeneralSecurityException { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing version value"); - } - - BigInteger version = obj.asInteger(); - if (!BigInteger.ZERO.equals(version)) { - throw new StreamCorruptedException("Invalid version: " + version); - } - - obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing OID container"); - } - - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.SEQUENCE) { - throw new StreamCorruptedException("Unexpected OID object type: " + objType); - } - - List curveOid; - try (DERParser oidParser = obj.createParser()) { - obj = oidParser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing OID value"); - } - - curveOid = obj.asOID(); - } - - String oid = GenericUtils.join(curveOid, '.'); - // TODO modify if more curves supported - if (!ED25519_OID.equals(oid)) { - throw new StreamCorruptedException("Unsupported curve OID: " + oid); - } - - obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data"); - } - - return decodeEd25519KeyPair(obj.getValue()); - } - - public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException { - EdDSAPrivateKey privateKey = decodeEdDSAPrivateKey(keyData); - EdDSAPublicKey publicKey = EdDSASecurityProviderUtils.recoverEDDSAPublicKey(privateKey); - return new KeyPair(publicKey, privateKey); - } - - public static EdDSAPrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException { - try (DERParser parser = new DERParser(keyData)) { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data container"); - } - - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.OCTET_STRING) { - throw new StreamCorruptedException("Mismatched key data container type: " + objType); - } - - return generateEdDSAPrivateKey(obj.getValue()); - } - } - - public static EdDSAPrivateKey generateEdDSAPrivateKey(byte[] seed) throws GeneralSecurityException { - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); - } - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512); - EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - return EdDSAPrivateKey.class.cast(factory.generatePrivate(keySpec)); - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java deleted file mode 100644 index 6295243..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/Ed25519PublicKeyDecoder.java +++ /dev/null @@ -1,105 +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.util.security.eddsa; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -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; -import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder; -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.xbib.net.security.eddsa.EdDSAPrivateKey; -import org.xbib.net.security.eddsa.EdDSAPublicKey; -import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec; -import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec; - -/** - * @author Apache MINA SSHD Project - */ -public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder { - public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this - - public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder(); - - private Ed25519PublicKeyDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); - } - - @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams())); - } - } - - @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams())); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); - } - - @Override - public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException { - Objects.requireNonNull(key, "No public key provided"); - KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519); - byte[] seed = getSeedValue(key); - KeyEntryResolver.writeRLEBytes(s, seed); - return KeyPairProvider.SSH_ED25519; - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - } - - @Override - public EdDSAPublicKey decodePublicKey( - SessionContext session, String keyType, InputStream keyData, Map headers) - throws IOException, GeneralSecurityException { - byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN); - return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed)); - } - - public static byte[] getSeedValue(EdDSAPublicKey key) { - // a bit of reverse-engineering on the EdDSAPublicKeySpec - return (key == null) ? null : key.getAbyte(); - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java deleted file mode 100644 index 1b0bd2c..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderRegistrar.java +++ /dev/null @@ -1,97 +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.util.security.eddsa; - -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.Provider; -import java.security.Signature; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.ReflectionUtils; -import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.apache.sshd.common.util.threads.ThreadUtils; - -/** - * @author Apache MINA SSHD Project - */ -public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegistrar { - public static final String PROVIDER_CLASS = "org.xbib.net.security.eddsa.EdDSASecurityProvider"; - // Do not define a static registrar instance to minimize class loading issues - private final AtomicReference supportHolder = new AtomicReference<>(null); - - public EdDSASecurityProviderRegistrar() { - super(SecurityUtils.EDDSA); - } - - @Override - public boolean isEnabled() { - return super.isEnabled(); - } - - @Override - public Provider getSecurityProvider() { - try { - return getOrCreateProvider(PROVIDER_CLASS); - } catch (ReflectiveOperationException t) { - Throwable e = GenericUtils.peelException(t); - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - - throw new RuntimeException(e); - } - } - - @Override - public boolean isSecurityEntitySupported(Class entityType, String name) { - if (!isSupported()) { - return false; - } - - if (KeyPairGenerator.class.isAssignableFrom(entityType) - || KeyFactory.class.isAssignableFrom(entityType)) { - return Objects.compare(name, getName(), String.CASE_INSENSITIVE_ORDER) == 0; - } else if (Signature.class.isAssignableFrom(entityType)) { - return Objects.compare(SecurityUtils.CURVE_ED25519_SHA512, name, String.CASE_INSENSITIVE_ORDER) == 0; - } else { - return false; - } - } - - @Override - public boolean isSupported() { - Boolean supported; - synchronized (supportHolder) { - supported = supportHolder.get(); - if (supported != null) { - return supported.booleanValue(); - } - - ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass()); - supported = ReflectionUtils.isClassAvailable(cl, "org.xbib.net.security.eddsa.EdDSAKey"); - supportHolder.set(supported); - } - - return supported.booleanValue(); - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java deleted file mode 100644 index cfe4c9c..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java +++ /dev/null @@ -1,203 +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.util.security.eddsa; - -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Arrays; -import java.util.Objects; - -import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; -import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; -import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.buffer.Buffer; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.xbib.net.security.eddsa.EdDSAEngine; -import org.xbib.net.security.eddsa.EdDSAKey; -import org.xbib.net.security.eddsa.EdDSAPrivateKey; -import org.xbib.net.security.eddsa.EdDSAPublicKey; -import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable; -import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec; -import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec; -import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec; - -/** - * @author Apache MINA SSHD Project - */ -public final class EdDSASecurityProviderUtils { - // See EdDSANamedCurveTable - public static final String CURVE_ED25519_SHA512 = "Ed25519"; - public static final int KEY_SIZE = 256; - - private EdDSASecurityProviderUtils() { - throw new UnsupportedOperationException("No instance"); - } - - public static Class getEDDSAPublicKeyType() { - return EdDSAPublicKey.class; - } - - public static Class getEDDSAPrivateKeyType() { - return EdDSAPrivateKey.class; - } - - public static boolean isEDDSAKey(Key key) { - return getEDDSAKeySize(key) == KEY_SIZE; - } - - public static int getEDDSAKeySize(Key key) { - return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? KEY_SIZE : -1; - } - - public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) { - if (!SecurityUtils.isEDDSACurveSupported()) { - return false; - } - - if ((k1 instanceof EdDSAPublicKey) && (k2 instanceof EdDSAPublicKey)) { - if (Objects.equals(k1, k2)) { - return true; - } else if (k1 == null || k2 == null) { - return false; // both null is covered by Objects#equals - } - - EdDSAPublicKey ed1 = (EdDSAPublicKey) k1; - EdDSAPublicKey ed2 = (EdDSAPublicKey) k2; - return Arrays.equals(ed1.getAbyte(), ed2.getAbyte()) - && compareEDDSAKeyParams(ed1.getParams(), ed2.getParams()); - } - - return false; - } - - public static boolean isEDDSASignatureAlgorithm(String algorithm) { - return EdDSAEngine.SIGNATURE_ALGORITHM.equalsIgnoreCase(algorithm); - } - - public static EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - if (!(key instanceof EdDSAPrivateKey)) { - throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA); - } - - EdDSAPrivateKey prvKey = (EdDSAPrivateKey) key; - EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getAbyte(), prvKey.getParams()); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec)); - } - - public static org.apache.sshd.common.signature.Signature getEDDSASignature() { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - return new SignatureEd25519(); - } - - public static boolean isEDDSAKeyFactoryAlgorithm(String algorithm) { - return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm); - } - - public static boolean isEDDSAKeyPairGeneratorAlgorithm(String algorithm) { - return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm); - } - - public static PublicKeyEntryDecoder getEDDSAPublicKeyEntryDecoder() { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - return Ed25519PublicKeyDecoder.INSTANCE; - } - - public static PrivateKeyEntryDecoder getOpenSSHEDDSAPrivateKeyEntryDecoder() { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE; - } - - public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) { - if (!SecurityUtils.isEDDSACurveSupported()) { - return false; - } - - if ((k1 instanceof EdDSAPrivateKey) && (k2 instanceof EdDSAPrivateKey)) { - if (Objects.equals(k1, k2)) { - return true; - } else if (k1 == null || k2 == null) { - return false; // both null is covered by Objects#equals - } - - EdDSAPrivateKey ed1 = (EdDSAPrivateKey) k1; - EdDSAPrivateKey ed2 = (EdDSAPrivateKey) k2; - return Arrays.equals(ed1.getSeed(), ed2.getSeed()) - && compareEDDSAKeyParams(ed1.getParams(), ed2.getParams()); - } - - return false; - } - - public static boolean compareEDDSAKeyParams(EdDSAParameterSpec s1, EdDSAParameterSpec s2) { - if (Objects.equals(s1, s2)) { - return true; - } else if (s1 == null || s2 == null) { - return false; // both null is covered by Objects#equals - } else { - return Objects.equals(s1.getHashAlgorithm(), s2.getHashAlgorithm()) - && Objects.equals(s1.getCurve(), s2.getCurve()) - && Objects.equals(s1.getB(), s2.getB()); - } - } - - public static PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException { - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported"); - } - - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512); - EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(seed, params); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - return factory.generatePublic(keySpec); - } - - public static PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException { - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported"); - } - - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512); - EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params); - KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - return factory.generatePrivate(keySpec); - } - - public static B putRawEDDSAPublicKey(B buffer, PublicKey key) { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key); - byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(edKey); - ValidateUtils.checkNotNull(seed, "No seed extracted from key: %s", edKey.getA()); - buffer.putBytes(seed); - return buffer; - } - - public static B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) { - ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported"); - ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey); - ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey); - throw new UnsupportedOperationException("Full SSHD-440 implementation N/A"); - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java deleted file mode 100644 index 8cff8fe..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ /dev/null @@ -1,186 +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.util.security.eddsa; - -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; -import java.util.Objects; - -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; -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.io.SecureByteArrayOutputStream; -import org.apache.sshd.common.util.security.SecurityUtils; -import org.xbib.net.security.eddsa.EdDSAPrivateKey; -import org.xbib.net.security.eddsa.EdDSAPublicKey; -import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable; -import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec; -import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec; -import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec; - -/** - * @author Apache MINA SSHD Project - */ -public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { - public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); - private static final int PK_SIZE = 32; - private static final int SK_SIZE = 32; - private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; - - public OpenSSHEd25519PrivateKeyEntryDecoder() { - super(EdDSAPublicKey.class, EdDSAPrivateKey.class, - Collections.unmodifiableList( - Collections.singletonList( - KeyPairProvider.SSH_ED25519))); - } - - @Override - public EdDSAPrivateKey decodePrivateKey( - SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData) - throws IOException, GeneralSecurityException { - if (!KeyPairProvider.SSH_ED25519.equals(keyType)) { - throw new InvalidKeyException("Unsupported key type: " + keyType); - } - - if (!SecurityUtils.isEDDSACurveSupported()) { - throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported"); - } - - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we expect to find two byte arrays with the following structure (type:size): - // [pk:32], [sk:32,pk:32] - - byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; - byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; - try { - pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); - keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); - if (pk.length != PK_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); - } - - if (keypair.length != KEYPAIR_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, - KEYPAIR_SIZE)); - } - - // verify that the keypair contains the expected pk - // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface - if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { - throw new InvalidKeyException("Keypair did not contain the public key."); - } - - byte[] sk = Arrays.copyOf(keypair, SK_SIZE); - EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512); - EdDSAPrivateKey privateKey = generatePrivateKey(new EdDSAPrivateKeySpec(sk, params)); - - // the private key class contains the calculated public key (Abyte) - // pointers to the corresponding code: - // EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a); - // EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray(); - - // we can now verify the generated pk matches the one we read - if (!Arrays.equals(privateKey.getAbyte(), pk)) { - throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk."); - } - - return privateKey; - } finally { - // get rid of sensitive data a.s.a.p - Arrays.fill(pk, (byte) 0); - Arrays.fill(keypair, (byte) 0); - } - } - - @Override - public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey key, EdDSAPublicKey pubKey) - throws IOException { - Objects.requireNonNull(key, "No private key provided"); - - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we are expected to write the following arrays (type:size): - // [pk:32], [sk:32,pk:32] - - byte[] sk = key.getSeed(); - byte[] pk = key.getAbyte(); - - Objects.requireNonNull(sk, "No seed"); - - byte[] keypair = new byte[KEYPAIR_SIZE]; - System.arraycopy(sk, 0, keypair, 0, SK_SIZE); - System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE); - - KeyEntryResolver.writeRLEBytes(s, pk); - KeyEntryResolver.writeRLEBytes(s, keypair); - - return KeyPairProvider.SSH_ED25519; - } - - @Override - public boolean isPublicKeyRecoverySupported() { - return true; - } - - @Override - public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException { - return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(prvKey); - } - - @Override - public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams())); - } - } - - @Override - public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams())); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA); - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA); - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java deleted file mode 100644 index 8e961f7..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/eddsa/SignatureEd25519.java +++ /dev/null @@ -1,48 +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.util.security.eddsa; - -import java.util.Map; - -import org.apache.sshd.common.keyprovider.KeyPairProvider; -import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.signature.AbstractSignature; -import org.apache.sshd.common.util.ValidateUtils; -import org.xbib.net.security.eddsa.EdDSAEngine; - -/** - * @author Apache MINA SSHD Project - */ -public class SignatureEd25519 extends AbstractSignature { - public SignatureEd25519() { - super(EdDSAEngine.SIGNATURE_ALGORITHM); - } - - @Override - public boolean verify(SessionContext session, byte[] sig) throws Exception { - byte[] data = sig; - Map.Entry encoding = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase); - if (encoding != null) { - String keyType = encoding.getKey(); - ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType); - data = encoding.getValue(); - } - return doVerify(data); - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java deleted file mode 100644 index fd0d9a2..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/EdECSecurityProviderRegistrar.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.apache.sshd.common.util.security.edec; - -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.Provider; -import java.security.Security; -import java.security.Signature; -import java.util.Objects; -import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar; - -public class EdECSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar { - - public EdECSecurityProviderRegistrar() { - super("EdDSA"); - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public Provider getSecurityProvider() { - return Security.getProvider("SunJCE"); - } - - @Override - public boolean isSecurityEntitySupported(Class entityType, String name) { - if (!isSupported()) { - return false; - } - if (KeyPairGenerator.class.isAssignableFrom(entityType) || KeyFactory.class.isAssignableFrom(entityType)) { - return Objects.compare(name, getName(), String.CASE_INSENSITIVE_ORDER) == 0; - } else if (Signature.class.isAssignableFrom(entityType)) { - return Objects.compare("NONEwithEdDSA", name, String.CASE_INSENSITIVE_ORDER) == 0; - } else { - return false; - } - } - - @Override - public boolean isSupported() { - return true; - } -} diff --git a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java b/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java deleted file mode 100644 index 57893fd..0000000 --- a/files-sftp/src/main/java/org/apache/sshd/common/util/security/edec/OpenSSHEd25519PrivateKeyEntryDecoder.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.apache.sshd.common.util.security.edec; - -import java.io.IOException; -import java.io.InputStream; -import java.io.StreamCorruptedException; -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPairGenerator; -import java.security.interfaces.EdECPrivateKey; -import java.security.interfaces.EdECPublicKey; -import java.security.spec.EdECPoint; -import java.security.spec.EdECPrivateKeySpec; -import java.security.spec.EdECPublicKeySpec; -import java.security.spec.NamedParameterSpec; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; -import java.util.Objects; -import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder; -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.io.SecureByteArrayOutputStream; -import org.apache.sshd.common.util.io.der.ASN1Object; -import org.apache.sshd.common.util.io.der.ASN1Type; -import org.apache.sshd.common.util.io.der.DERParser; - - -public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder { - - public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder(); - - private static final int PK_SIZE = 32; - - private static final int SK_SIZE = 32; - - private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE; - - public OpenSSHEd25519PrivateKeyEntryDecoder() { - super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519)); - } - - @Override - public EdECPrivateKey decodePrivateKey(SessionContext session, - String keyType, - FilePasswordProvider passwordProvider, - InputStream keyData) - throws IOException, GeneralSecurityException { - if (!"ssh-ed25519".equals(keyType)) { - throw new InvalidKeyException("Unsupported key type: " + keyType); - } - // ed25519 bernstein naming: pk .. public key, sk .. secret key - // we expect to find two byte arrays with the following structure (type:size): - // [pk:32], [sk:32,pk:32] - byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY; - byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY; - try { - pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2); - keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2); - if (pk.length != PK_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE)); - } - if (keypair.length != KEYPAIR_SIZE) { - throw new InvalidKeyException( - String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length, - KEYPAIR_SIZE)); - } - // verify that the keypair contains the expected pk - // yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface - if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) { - throw new InvalidKeyException("Keypair did not contain the public key."); - } - byte[] seed = Arrays.copyOf(keypair, SK_SIZE); - NamedParameterSpec spec = NamedParameterSpec.ED25519; - EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed); - KeyFactory factory = KeyFactory.getInstance("EdDSA"); - return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec)); - } finally { - // get rid of sensitive data a.s.a.p - Arrays.fill(pk, (byte) 0); - Arrays.fill(keypair, (byte) 0); - } - } - - @Override - public String encodePrivateKey(SecureByteArrayOutputStream s, - EdECPrivateKey key, - EdECPublicKey pubKey) throws IOException { - Objects.requireNonNull(key, "No private key provided"); - // TODO - return "ssh-ed25519"; - } - - @Override - public boolean isPublicKeyRecoverySupported() { - return true; - } - - @Override - public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException { - // TODO - byte[] pk = key.getEncoded(); - boolean xisodd = false; - int lastbyteInt = pk[pk.length - 1]; - if ((lastbyteInt & 255) >> 7 == 1) { - xisodd = true; - } - pk[pk.length - 1] &= 127; - BigInteger y = new BigInteger(1, pk); - NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519"); - EdECPoint ep = new EdECPoint(xisodd, y); - EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep); - KeyFactory factory = KeyFactory.getInstance("EdDSA"); - return EdECPublicKey.class.cast(factory.generatePublic(publicKeySpec)); - - } - - @Override - public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519; - EdECPoint edECPoint = key.getPoint(); - EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint); - return generatePublicKey(spec); - } - } - - @Override - public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException { - if (key == null) { - return null; - } else { - return getPrivateKey(key.getEncoded()); - } - } - - @Override - public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException { - return KeyPairGenerator.getInstance("Ed25519"); - } - - @Override - public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException { - return KeyFactory.getInstance("EdDSA"); - } - - private static EdECPrivateKey getPrivateKey(byte[] keyData) throws GeneralSecurityException { - try (DERParser parser = new DERParser(keyData)) { - ASN1Object obj = parser.readObject(); - if (obj == null) { - throw new StreamCorruptedException("Missing key data container"); - } - ASN1Type objType = obj.getObjType(); - if (objType != ASN1Type.OCTET_STRING) { - throw new StreamCorruptedException("Mismatched key data container type: " + objType); - } - return generatePrivateKey(obj.getValue()); - } catch (IOException e) { - throw new GeneralSecurityException(e); - } - } - - private static EdECPrivateKey generatePrivateKey(byte[] seed) throws GeneralSecurityException { - NamedParameterSpec spec = NamedParameterSpec.ED25519; - EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed); - KeyFactory factory = KeyFactory.getInstance("EdDSA"); - return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec)); - } - -} diff --git a/gradle.properties b/gradle.properties index 61d54c7..a66ad21 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ group = org.xbib name = files -version = 4.6.0 +version = 4.7.0 diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle index 6998a1d..99c6a51 100644 --- a/gradle/compile/java.gradle +++ b/gradle/compile/java.gradle @@ -22,7 +22,8 @@ tasks.withType(JavaCompile).configureEach { options.forkOptions.jvmArgs += ['-Duser.language=en', '-Duser.country=US'] options.encoding = 'UTF-8' options.compilerArgs.add('-Xlint:all') - // enforce presence of module-info.java + options.compilerArgs.add("--module-version") + options.compilerArgs.add(project.version as String) options.compilerArgs.add("--module-path") options.compilerArgs.add(classpath.asPath) classpath = files() diff --git a/settings.gradle b/settings.gradle index fa92fa6..de99828 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,8 +16,6 @@ dependencyResolutionManagement { versionCatalogs { libs { version('gradle', '8.7') - version('net', '4.6.1') - library('net-security', 'org.xbib', 'net-security').versionRef('net') library('maverick-synergy-client', 'com.sshtools', 'maverick-synergy-client').version('3.1.1') } testLibs {