EC alawy enabled, EdEC always anabled, make EdEC JDK-based keys work, drop eddsa, xbib net, bouncycastle dependency
This commit is contained in:
parent
9af09a4176
commit
bac77bbd0f
46 changed files with 563 additions and 1914 deletions
|
@ -1,5 +1,6 @@
|
||||||
package org.xbib.files.jadaptive.sftp.test;
|
package org.xbib.files.jadaptive.sftp.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -18,10 +19,11 @@ public class SftpFileSystemTest {
|
||||||
public SftpFileSystemTest() {
|
public SftpFileSystemTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Disabled("missing connection object")
|
||||||
@Test
|
@Test
|
||||||
public void testFileSystem() throws IOException {
|
public void testFileSystem() throws IOException {
|
||||||
Map<String, ?> env = Map.of("username", "joerg");
|
Map<String, ?> 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()) {
|
for (Path path : fileSystem.getRootDirectories()) {
|
||||||
logger.log(Level.INFO, "root dir = " + path);
|
logger.log(Level.INFO, "root dir = " + path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':files-api')
|
api project(':files-api')
|
||||||
api project(':files-sftp')
|
api project(':files-sftp')
|
||||||
testImplementation libs.net.security
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ module org.xbib.files.sftp.fs.test {
|
||||||
requires org.xbib.files;
|
requires org.xbib.files;
|
||||||
requires org.xbib.files.sftp;
|
requires org.xbib.files.sftp;
|
||||||
requires org.xbib.files.sftp.fs;
|
requires org.xbib.files.sftp.fs;
|
||||||
requires org.xbib.net.security;
|
|
||||||
exports org.apache.sshd.fs.test;
|
exports org.apache.sshd.fs.test;
|
||||||
opens org.apache.sshd.fs.test to org.junit.platform.commons;
|
opens org.apache.sshd.fs.test to org.junit.platform.commons;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ public class FileServiceProviderTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSFTP() throws Exception {
|
public void testSFTP() throws Exception {
|
||||||
Map<String, ?> env = Map.of("username", "joerg");
|
Map<String, ?> 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!
|
// close() is essential!
|
||||||
try (Stream<Path> list = fs.list(".")) {
|
try (Stream<Path> list = fs.list(".")) {
|
||||||
list.forEach(p -> Logger.getAnonymousLogger().info(p.toString()));
|
list.forEach(p -> Logger.getAnonymousLogger().info(p.toString()));
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.apache.sshd.fs.test;
|
package org.apache.sshd.fs.test;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import org.apache.sshd.client.ClientBuilder;
|
import org.apache.sshd.client.ClientBuilder;
|
||||||
import org.apache.sshd.client.SshClient;
|
import org.apache.sshd.client.SshClient;
|
||||||
|
@ -9,16 +8,12 @@ import org.apache.sshd.fs.SftpFileSystemProvider;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.xbib.net.security.PrivateKeyReader;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class SftpWithPrivateKeyReaderTest {
|
public class SftpWithPrivateKeyReaderTest {
|
||||||
|
@ -30,12 +25,8 @@ public class SftpWithPrivateKeyReaderTest {
|
||||||
Map<String, String> env = new HashMap<>();
|
Map<String, String> env = new HashMap<>();
|
||||||
env.put("username", "joerg");
|
env.put("username", "joerg");
|
||||||
URI uri = URI.create("sftp://xbib.org");
|
URI uri = URI.create("sftp://xbib.org");
|
||||||
Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519");
|
try (SshClient sshClient = ClientBuilder.builder()
|
||||||
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
|
.build()) {
|
||||||
try (InputStream inputStream = Files.newInputStream(privateKeyPath);
|
|
||||||
SshClient sshClient = ClientBuilder.builder().build()) {
|
|
||||||
KeyPair keyPair = privateKeyReader.readKeyPair(inputStream, null);
|
|
||||||
sshClient.addPublicKeyIdentity(keyPair);
|
|
||||||
sshClient.setNioWorkers(1);
|
sshClient.setNioWorkers(1);
|
||||||
sshClient.start();
|
sshClient.start();
|
||||||
SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env);
|
SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env);
|
||||||
|
@ -47,28 +38,4 @@ public class SftpWithPrivateKeyReaderTest {
|
||||||
sshClient.stop();
|
sshClient.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAlkmene() throws Exception {
|
|
||||||
Map<String, String> 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<Path> stream = Files.list(fileSystem.getDefaultDir())) {
|
|
||||||
stream.forEach(p -> logger.log(Level.INFO, "p = " + p));
|
|
||||||
}
|
|
||||||
fileSystem.close();
|
|
||||||
sshClient.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
dependencies {
|
|
||||||
implementation libs.net.security
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
module org.xbib.files.sftp {
|
module org.xbib.files.sftp {
|
||||||
requires org.xbib.net.security;
|
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
exports org.apache.sshd.client;
|
exports org.apache.sshd.client;
|
||||||
exports org.apache.sshd.client.auth;
|
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.io.resource;
|
||||||
exports org.apache.sshd.common.util.net;
|
exports org.apache.sshd.common.util.net;
|
||||||
exports org.apache.sshd.common.util.security;
|
exports org.apache.sshd.common.util.security;
|
||||||
exports org.apache.sshd.common.util.security.eddsa;
|
|
||||||
exports org.apache.sshd.common.util.threads;
|
exports org.apache.sshd.common.util.threads;
|
||||||
uses org.apache.sshd.common.io.IoServiceFactoryFactory;
|
uses org.apache.sshd.common.io.IoServiceFactoryFactory;
|
||||||
}
|
}
|
|
@ -50,6 +50,7 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFactoriesManager {
|
public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFactoriesManager {
|
||||||
|
|
||||||
public static final String NAME = UserAuthPublicKeyFactory.NAME;
|
public static final String NAME = UserAuthPublicKeyFactory.NAME;
|
||||||
|
|
||||||
protected Iterator<PublicKeyIdentity> keys;
|
protected Iterator<PublicKeyIdentity> keys;
|
||||||
|
@ -107,7 +108,6 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
throw new RuntimeSshException(e);
|
throw new RuntimeSshException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
PublicKey pubKey = keyPair.getPublic();
|
PublicKey pubKey = keyPair.getPublic();
|
||||||
String keyType = KeyUtils.getKeyType(pubKey);
|
String keyType = KeyUtils.getKeyType(pubKey);
|
||||||
NamedFactory<? extends Signature> factory;
|
NamedFactory<? extends Signature> factory;
|
||||||
|
@ -118,14 +118,11 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
|
||||||
} else {
|
} else {
|
||||||
factory = SignatureFactory.resolveSignatureFactory(keyType, getSignatureFactories());
|
factory = SignatureFactory.resolveSignatureFactory(keyType, getSignatureFactories());
|
||||||
}
|
}
|
||||||
|
|
||||||
String algo = (factory == null) ? keyType : factory.getName();
|
String algo = (factory == null) ? keyType : factory.getName();
|
||||||
String name = getName();
|
String name = getName();
|
||||||
|
|
||||||
if (reporter != null) {
|
if (reporter != null) {
|
||||||
reporter.signalAuthenticationAttempt(session, service, keyPair, algo);
|
reporter.signalAuthenticationAttempt(session, service, keyPair, algo);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
|
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
|
||||||
buffer.putString(session.getUsername());
|
buffer.putString(session.getUsername());
|
||||||
buffer.putString(service);
|
buffer.putString(service);
|
||||||
|
@ -141,17 +138,14 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
|
||||||
if ((keys != null) && keys.hasNext()) {
|
if ((keys != null) && keys.hasNext()) {
|
||||||
return keys.next();
|
return keys.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
UserInteraction ui = session.getUserInteraction();
|
UserInteraction ui = session.getUserInteraction();
|
||||||
if ((ui == null) || (!ui.isInteractionAllowed(session))) {
|
if ((ui == null) || (!ui.isInteractionAllowed(session))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPair kp = ui.resolveAuthPublicKeyIdentityAttempt(session);
|
KeyPair kp = ui.resolveAuthPublicKeyIdentityAttempt(session);
|
||||||
if (kp == null) {
|
if (kp == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KeyPairIdentity(this, session, kp);
|
return new KeyPairIdentity(this, session, kp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,8 +251,6 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
throw new RuntimeSshException(e);
|
throw new RuntimeSshException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bs.clear();
|
bs.clear();
|
||||||
bs.putString(algo);
|
bs.putString(algo);
|
||||||
bs.putBytes(sig);
|
bs.putBytes(sig);
|
||||||
|
|
|
@ -42,19 +42,20 @@ import org.apache.sshd.common.util.helper.LazyMatchingTypeIterator;
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel {
|
public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel {
|
||||||
|
|
||||||
private final AtomicBoolean open = new AtomicBoolean(true);
|
private final AtomicBoolean open = new AtomicBoolean(true);
|
||||||
private Iterator<? extends PublicKeyIdentity> current;
|
|
||||||
|
private final Iterator<? extends PublicKeyIdentity> current;
|
||||||
|
|
||||||
public UserAuthPublicKeyIterator(ClientSession session, SignatureFactoriesManager signatureFactories) throws Exception {
|
public UserAuthPublicKeyIterator(ClientSession session, SignatureFactoriesManager signatureFactories) throws Exception {
|
||||||
super(session);
|
super(session);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Collection<Iterable<? extends PublicKeyIdentity>> identities = new ArrayList<>(2);
|
Collection<Iterable<? extends PublicKeyIdentity>> identities = new ArrayList<>();
|
||||||
Iterable<? extends PublicKeyIdentity> sessionIds = initializeSessionIdentities(session, signatureFactories);
|
Iterable<? extends PublicKeyIdentity> sessionIds = initializeSessionIdentities(session, signatureFactories);
|
||||||
if (sessionIds != null) {
|
if (sessionIds != null) {
|
||||||
identities.add(sessionIds);
|
identities.add(sessionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identities.isEmpty()) {
|
if (identities.isEmpty()) {
|
||||||
current = Collections.emptyIterator();
|
current = Collections.emptyIterator();
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,10 +67,9 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:anoninnerlength")
|
|
||||||
protected Iterable<KeyPairIdentity> initializeSessionIdentities(
|
protected Iterable<KeyPairIdentity> initializeSessionIdentities(
|
||||||
ClientSession session, SignatureFactoriesManager signatureFactories) {
|
ClientSession session, SignatureFactoriesManager signatureFactories) {
|
||||||
return new Iterable<KeyPairIdentity>() {
|
return new Iterable<>() {
|
||||||
private final String sessionId = session.toString();
|
private final String sessionId = session.toString();
|
||||||
private final AtomicReference<Iterable<KeyPair>> keysHolder = new AtomicReference<>();
|
private final AtomicReference<Iterable<KeyPair>> keysHolder = new AtomicReference<>();
|
||||||
|
|
||||||
|
@ -81,14 +81,11 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKey
|
||||||
KeyIdentityProvider sessionKeysProvider = ClientSession.providerOf(session);
|
KeyIdentityProvider sessionKeysProvider = ClientSession.providerOf(session);
|
||||||
keysHolder.set(sessionKeysProvider.loadKeys(session));
|
keysHolder.set(sessionKeysProvider.loadKeys(session));
|
||||||
} catch (IOException | GeneralSecurityException e) {
|
} catch (IOException | GeneralSecurityException e) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException("Unexpected " + e.getClass().getSimpleName() + ")" + " keys loading exception: " + e.getMessage(), e);
|
||||||
"Unexpected " + e.getClass().getSimpleName() + ")"
|
|
||||||
+ " keys loading exception: " + e.getMessage(),
|
|
||||||
e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Iterator<KeyPairIdentity>() {
|
return new Iterator<>() {
|
||||||
private final Iterator<KeyPair> keys;
|
private final Iterator<KeyPair> keys;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,11 +27,12 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
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.NamedResource;
|
||||||
import org.apache.sshd.common.config.keys.BuiltinIdentities;
|
import org.apache.sshd.common.config.keys.BuiltinIdentities;
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProviderHolder;
|
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.session.SessionContext;
|
||||||
import org.apache.sshd.common.util.GenericUtils;
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
|
|
||||||
|
@ -41,13 +42,19 @@ import org.apache.sshd.common.util.GenericUtils;
|
||||||
public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher {
|
public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher {
|
||||||
private final boolean supportedOnly;
|
private final boolean supportedOnly;
|
||||||
|
|
||||||
public BuiltinClientIdentitiesWatcher(Path keysFolder, boolean supportedOnly,
|
public BuiltinClientIdentitiesWatcher(Path keysFolder,
|
||||||
ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
|
boolean supportedOnly,
|
||||||
|
ClientIdentityLoader loader,
|
||||||
|
FilePasswordProvider provider,
|
||||||
|
boolean strict) {
|
||||||
this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict);
|
this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuiltinClientIdentitiesWatcher(Path keysFolder, Collection<String> ids, boolean supportedOnly,
|
public BuiltinClientIdentitiesWatcher(Path keysFolder,
|
||||||
ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
|
Collection<String> ids,
|
||||||
|
boolean supportedOnly,
|
||||||
|
ClientIdentityLoader loader,
|
||||||
|
FilePasswordProvider provider, boolean strict) {
|
||||||
this(keysFolder, ids, supportedOnly,
|
this(keysFolder, ids, supportedOnly,
|
||||||
ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")),
|
ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")),
|
||||||
FilePasswordProviderHolder.providerHolderOf(Objects.requireNonNull(provider, "No password provider")),
|
FilePasswordProviderHolder.providerHolderOf(Objects.requireNonNull(provider, "No password provider")),
|
||||||
|
@ -90,18 +97,17 @@ public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher {
|
||||||
return getBuiltinIdentitiesPaths(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES));
|
return getBuiltinIdentitiesPaths(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Path> getBuiltinIdentitiesPaths(Path keysFolder, Collection<String> ids) {
|
public static List<Path> getBuiltinIdentitiesPaths(Path keysFolder,
|
||||||
|
Collection<String> ids) {
|
||||||
Objects.requireNonNull(keysFolder, "No keys folder");
|
Objects.requireNonNull(keysFolder, "No keys folder");
|
||||||
if (GenericUtils.isEmpty(ids)) {
|
if (GenericUtils.isEmpty(ids)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Path> paths = new ArrayList<>(ids.size());
|
List<Path> paths = new ArrayList<>(ids.size());
|
||||||
for (String id : ids) {
|
for (String id : ids) {
|
||||||
String fileName = ClientIdentity.getIdentityFileName(id);
|
String fileName = ClientIdentity.getIdentityFileName(id);
|
||||||
paths.add(keysFolder.resolve(fileName));
|
paths.add(keysFolder.resolve(fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,16 +31,21 @@ import org.apache.sshd.common.config.keys.PublicKeyEntry;
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatcher {
|
public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatcher {
|
||||||
public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider) {
|
|
||||||
|
public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader,
|
||||||
|
FilePasswordProvider provider) {
|
||||||
this(loader, provider, true);
|
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);
|
this(true, loader, provider, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultClientIdentitiesWatcher(
|
public DefaultClientIdentitiesWatcher(boolean supportedOnly,
|
||||||
boolean supportedOnly, ClientIdentityLoader loader, FilePasswordProvider provider,
|
ClientIdentityLoader loader,
|
||||||
|
FilePasswordProvider provider,
|
||||||
boolean strict) {
|
boolean strict) {
|
||||||
this(supportedOnly,
|
this(supportedOnly,
|
||||||
ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")),
|
ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")),
|
||||||
|
@ -48,17 +53,20 @@ public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatch
|
||||||
strict);
|
strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider) {
|
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader,
|
||||||
|
FilePasswordProviderHolder provider) {
|
||||||
this(loader, provider, true);
|
this(loader, provider, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider,
|
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader,
|
||||||
|
FilePasswordProviderHolder provider,
|
||||||
boolean strict) {
|
boolean strict) {
|
||||||
this(true, loader, provider, strict);
|
this(true, loader, provider, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultClientIdentitiesWatcher(boolean supportedOnly,
|
public DefaultClientIdentitiesWatcher(boolean supportedOnly,
|
||||||
ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider,
|
ClientIdentityLoaderHolder loader,
|
||||||
|
FilePasswordProviderHolder provider,
|
||||||
boolean strict) {
|
boolean strict) {
|
||||||
super(PublicKeyEntry.getDefaultKeysFolderPath(), supportedOnly, loader, provider, strict);
|
super(PublicKeyEntry.getDefaultKeysFolderPath(), supportedOnly, loader, provider, strict);
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,7 +198,7 @@ public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator, NamedResourc
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isSupported() {
|
public final boolean isSupported() {
|
||||||
return SecurityUtils.isECCSupported() && digestFactory.isSupported();
|
return digestFactory.isSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final ECParameterSpec getParameters() {
|
public final ECParameterSpec getParameters() {
|
||||||
|
|
|
@ -27,6 +27,8 @@ import java.security.interfaces.DSAPrivateKey;
|
||||||
import java.security.interfaces.DSAPublicKey;
|
import java.security.interfaces.DSAPublicKey;
|
||||||
import java.security.interfaces.ECPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.security.interfaces.EdECPrivateKey;
|
||||||
|
import java.security.interfaces.EdECPublicKey;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.Collection;
|
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.keyprovider.KeyTypeIndicator;
|
||||||
import org.apache.sshd.common.util.GenericUtils;
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
import org.apache.sshd.common.util.ValidateUtils;
|
||||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public enum BuiltinIdentities implements Identity {
|
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),
|
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,
|
ECDSA(Constants.ECDSA, KeyUtils.EC_ALGORITHM, ECPublicKey.class, ECPrivateKey.class,
|
||||||
ECCurves.VALUES.stream().map(KeyTypeIndicator::getKeyType).collect(Collectors.toList())) {
|
ECCurves.VALUES.stream().map(KeyTypeIndicator::getKeyType).collect(Collectors.toList())) {
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ED25119(Constants.ED25519, SecurityUtils.EDDSA,
|
ED25119(Constants.ED25519, "EdDSA",
|
||||||
SecurityUtils.getEDDSAPublicKeyType(),
|
EdECPublicKey.class,
|
||||||
SecurityUtils.getEDDSAPrivateKeyType(),
|
EdECPrivateKey.class,
|
||||||
KeyPairProvider.SSH_ED25519) {
|
"ssh-ed25519") {
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isEDDSACurveSupported();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final Set<BuiltinIdentities> VALUES = Collections.unmodifiableSet(EnumSet.allOf(BuiltinIdentities.class));
|
public static final Set<BuiltinIdentities> VALUES = Collections.unmodifiableSet(EnumSet.allOf(BuiltinIdentities.class));
|
||||||
|
|
|
@ -39,6 +39,9 @@ import java.security.interfaces.DSAPublicKey;
|
||||||
import java.security.interfaces.ECKey;
|
import java.security.interfaces.ECKey;
|
||||||
import java.security.interfaces.ECPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
import java.security.interfaces.ECPublicKey;
|
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.RSAKey;
|
||||||
import java.security.interfaces.RSAPrivateCrtKey;
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
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.cipher.ECCurves;
|
||||||
import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder;
|
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.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.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.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
|
||||||
import org.apache.sshd.common.digest.BuiltinDigests;
|
import org.apache.sshd.common.digest.BuiltinDigests;
|
||||||
import org.apache.sshd.common.digest.Digest;
|
import org.apache.sshd.common.digest.Digest;
|
||||||
|
@ -156,16 +160,11 @@ public final class KeyUtils {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
registerPublicKeyEntryDecoder(OpenSSHCertificateDecoder.INSTANCE);
|
registerPublicKeyEntryDecoder(OpenSSHCertificateDecoder.INSTANCE);
|
||||||
registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
|
registerPublicKeyEntryDecoder(RSAPublicKeyEntryDecoder.INSTANCE);
|
||||||
registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
|
registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
|
||||||
|
|
||||||
if (SecurityUtils.isECCSupported()) {
|
|
||||||
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
|
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
|
||||||
registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
|
registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
|
||||||
}
|
registerPublicKeyEntryDecoder(Ed25519PublicKeyEntryDecoder.INSTANCE);
|
||||||
if (SecurityUtils.isEDDSACurveSupported()) {
|
|
||||||
registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyUtils() {
|
private KeyUtils() {
|
||||||
|
@ -810,7 +809,7 @@ public final class KeyUtils {
|
||||||
} else {
|
} else {
|
||||||
return curve.getKeyType();
|
return curve.getKeyType();
|
||||||
}
|
}
|
||||||
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
|
} else if (key instanceof EdECKey) {
|
||||||
return KeyPairProvider.SSH_ED25519;
|
return KeyPairProvider.SSH_ED25519;
|
||||||
} else if (key instanceof OpenSshCertificate) {
|
} else if (key instanceof OpenSshCertificate) {
|
||||||
return ((OpenSshCertificate) key).getKeyType();
|
return ((OpenSshCertificate) key).getKeyType();
|
||||||
|
@ -946,10 +945,9 @@ public final class KeyUtils {
|
||||||
if (curve != null) {
|
if (curve != null) {
|
||||||
return curve.getKeySize();
|
return curve.getKeySize();
|
||||||
}
|
}
|
||||||
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
|
} else if (key instanceof EdECKey) {
|
||||||
return SecurityUtils.getEDDSAKeySize(key);
|
return 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1005,9 +1003,8 @@ public final class KeyUtils {
|
||||||
return compareECKeys(ECPublicKey.class.cast(k1), ECPublicKey.class.cast(k2));
|
return compareECKeys(ECPublicKey.class.cast(k1), ECPublicKey.class.cast(k2));
|
||||||
} else if ((k1 instanceof SkEcdsaPublicKey) && (k2 instanceof SkEcdsaPublicKey)) {
|
} else if ((k1 instanceof SkEcdsaPublicKey) && (k2 instanceof SkEcdsaPublicKey)) {
|
||||||
return compareSkEcdsaKeys(SkEcdsaPublicKey.class.cast(k1), SkEcdsaPublicKey.class.cast(k2));
|
return compareSkEcdsaKeys(SkEcdsaPublicKey.class.cast(k1), SkEcdsaPublicKey.class.cast(k2));
|
||||||
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
|
} else if ((k1 instanceof EdECPublicKey) && (k2 instanceof EdECPublicKey)) {
|
||||||
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
|
return compareEDDSAPPublicKeys(k1, k2);
|
||||||
return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
|
|
||||||
} else {
|
} else {
|
||||||
return false; // either key is null or not of same class
|
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));
|
return compareDSAKeys(DSAPrivateKey.class.cast(k1), DSAPrivateKey.class.cast(k2));
|
||||||
} else if ((k1 instanceof ECPrivateKey) && (k2 instanceof ECPrivateKey)) {
|
} else if ((k1 instanceof ECPrivateKey) && (k2 instanceof ECPrivateKey)) {
|
||||||
return compareECKeys(ECPrivateKey.class.cast(k1), ECPrivateKey.class.cast(k2));
|
return compareECKeys(ECPrivateKey.class.cast(k1), ECPrivateKey.class.cast(k2));
|
||||||
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
|
} else if ((k1 instanceof EdECPrivateKey) && (k2 instanceof EdECPrivateKey)) {
|
||||||
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
|
return compareEDDSAPrivateKeys(k1, k2);
|
||||||
return SecurityUtils.compareEDDSAPrivateKeys(k1, k2);
|
|
||||||
} else {
|
} else {
|
||||||
return false; // either key is null or not of same class
|
return false; // either key is null or not of same class
|
||||||
}
|
}
|
||||||
|
@ -1177,4 +1173,12 @@ public final class KeyUtils {
|
||||||
&& compareECKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey());
|
&& 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,15 +46,13 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
|
||||||
extends KeyEntryResolver<PUB, PRV>, PrivateKeyEntryResolver {
|
extends KeyEntryResolver<PUB, PRV>, PrivateKeyEntryResolver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default PrivateKey resolve(
|
default PrivateKey resolve(SessionContext session, String keyType, byte[] keyData)
|
||||||
SessionContext session, String keyType, byte[] keyData)
|
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
|
ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
|
||||||
Collection<String> supported = getSupportedKeyTypes();
|
Collection<String> supported = getSupportedKeyTypes();
|
||||||
if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) {
|
if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) {
|
||||||
return decodePrivateKey(session, FilePasswordProvider.EMPTY, keyData);
|
return decodePrivateKey(session, FilePasswordProvider.EMPTY, keyData);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported);
|
throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,20 +85,19 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default PRV decodePrivateKey(
|
default PRV decodePrivateKey(SessionContext session,
|
||||||
SessionContext session, FilePasswordProvider passwordProvider, InputStream keyData)
|
FilePasswordProvider passwordProvider,
|
||||||
|
InputStream keyData)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
// the actual data is preceded by a string that repeats the key type
|
// the actual data is preceded by a string that repeats the key type
|
||||||
String type = KeyEntryResolver.decodeString(keyData, KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH);
|
String type = KeyEntryResolver.decodeString(keyData, KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH);
|
||||||
if (GenericUtils.isEmpty(type)) {
|
if (GenericUtils.isEmpty(type)) {
|
||||||
throw new StreamCorruptedException("Missing key type string");
|
throw new StreamCorruptedException("Missing key type string");
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<String> supported = getSupportedKeyTypes();
|
Collection<String> supported = getSupportedKeyTypes();
|
||||||
if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) {
|
if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) {
|
||||||
throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported);
|
throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported);
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodePrivateKey(session, type, passwordProvider, keyData);
|
return decodePrivateKey(session, type, passwordProvider, keyData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.security.InvalidKeyException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.interfaces.ECPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
|
@ -77,10 +76,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
|
||||||
}
|
}
|
||||||
|
|
||||||
ECPublicKey decodePublicKey(ECCurves curve, InputStream keyData) throws IOException, GeneralSecurityException {
|
ECPublicKey decodePublicKey(ECCurves curve, InputStream keyData) throws IOException, GeneralSecurityException {
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
String keyCurveName = curve.getName();
|
String keyCurveName = curve.getName();
|
||||||
// see rfc5656 section 3.1
|
// see rfc5656 section 3.1
|
||||||
String encCurveName = KeyEntryResolver.decodeString(keyData, MAX_CURVE_NAME_LENGTH);
|
String encCurveName = KeyEntryResolver.decodeString(keyData, MAX_CURVE_NAME_LENGTH);
|
||||||
|
@ -109,10 +104,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
|
public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -127,10 +118,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
|
public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -164,11 +151,7 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
||||||
if (SecurityUtils.isECCSupported()) {
|
|
||||||
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
|
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
|
||||||
} else {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -185,10 +168,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
||||||
if (SecurityUtils.isECCSupported()) {
|
|
||||||
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
|
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
|
||||||
} else {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.apache.sshd.common.util.security.edec;
|
package org.apache.sshd.common.config.keys.impl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -15,21 +15,50 @@ import java.security.spec.EdECPublicKeySpec;
|
||||||
import java.security.spec.NamedParameterSpec;
|
import java.security.spec.NamedParameterSpec;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
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.keyprovider.KeyPairProvider;
|
||||||
import org.apache.sshd.common.session.SessionContext;
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
|
import org.apache.sshd.common.util.ArrayUtil;
|
||||||
|
|
||||||
public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
|
public final class Ed25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
|
||||||
|
|
||||||
public static final int MAX_ALLOWED_SEED_LEN = 1024;
|
public static final Ed25519PublicKeyEntryDecoder INSTANCE = new Ed25519PublicKeyEntryDecoder();
|
||||||
|
|
||||||
public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
|
private Ed25519PublicKeyEntryDecoder() {
|
||||||
|
|
||||||
private Ed25519PublicKeyDecoder() {
|
|
||||||
super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
|
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<String, String> 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
|
@Override
|
||||||
public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
|
public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
|
@ -50,40 +79,4 @@ public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder
|
||||||
return generatePrivateKey(new EdECPrivateKeySpec(namedParameterSpec, key.getEncoded()));
|
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<String, String> 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -47,10 +47,10 @@ import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
|
public class RSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
|
||||||
public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder();
|
public static final RSAPublicKeyEntryDecoder INSTANCE = new RSAPublicKeyEntryDecoder();
|
||||||
|
|
||||||
public RSAPublicKeyDecoder() {
|
public RSAPublicKeyEntryDecoder() {
|
||||||
super(RSAPublicKey.class, RSAPrivateKey.class,
|
super(RSAPublicKey.class, RSAPrivateKey.class,
|
||||||
Collections.unmodifiableList(
|
Collections.unmodifiableList(
|
||||||
Arrays.asList(KeyPairProvider.SSH_RSA,
|
Arrays.asList(KeyPairProvider.SSH_RSA,
|
|
@ -19,9 +19,7 @@
|
||||||
|
|
||||||
package org.apache.sshd.common.config.keys.loader;
|
package org.apache.sshd.common.config.keys.loader;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
@ -44,6 +42,7 @@ import org.apache.sshd.common.util.ValidateUtils;
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractKeyPairResourceParser implements KeyPairResourceParser {
|
public abstract class AbstractKeyPairResourceParser implements KeyPairResourceParser {
|
||||||
|
|
||||||
private final List<String> beginners;
|
private final List<String> beginners;
|
||||||
private final List<String> enders;
|
private final List<String> enders;
|
||||||
private final List<List<String>> endingMarkers;
|
private final List<List<String>> endingMarkers;
|
||||||
|
@ -86,10 +85,12 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<KeyPair> loadKeyPairs(
|
public Collection<KeyPair> loadKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
|
NamedResource resourceKey,
|
||||||
|
FilePasswordProvider passwordProvider,
|
||||||
|
List<String> lines)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
Collection<KeyPair> keyPairs = Collections.emptyList();
|
Collection<KeyPair> keyPairs = new LinkedList<>();
|
||||||
List<String> beginMarkers = getBeginners();
|
List<String> beginMarkers = getBeginners();
|
||||||
List<List<String>> endMarkers = getEndingMarkers();
|
List<List<String>> endMarkers = getEndingMarkers();
|
||||||
for (Map.Entry<Integer, Integer> markerPos = KeyPairResourceParser.findMarkerLine(lines, beginMarkers);
|
for (Map.Entry<Integer, Integer> markerPos = KeyPairResourceParser.findMarkerLine(lines, beginMarkers);
|
||||||
|
@ -97,22 +98,19 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
|
||||||
int startIndex = markerPos.getKey();
|
int startIndex = markerPos.getKey();
|
||||||
String startLine = lines.get(startIndex);
|
String startLine = lines.get(startIndex);
|
||||||
startIndex++;
|
startIndex++;
|
||||||
|
|
||||||
int markerIndex = markerPos.getValue();
|
int markerIndex = markerPos.getValue();
|
||||||
List<String> ender = endMarkers.get(markerIndex);
|
List<String> ender = endMarkers.get(markerIndex);
|
||||||
markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, ender);
|
markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, ender);
|
||||||
if (markerPos == null) {
|
if (markerPos == null) {
|
||||||
throw new StreamCorruptedException("Missing end marker (" + ender + ") after line #" + startIndex);
|
throw new StreamCorruptedException("Missing end marker (" + ender + ") after line #" + startIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int endIndex = markerPos.getKey();
|
int endIndex = markerPos.getKey();
|
||||||
String endLine = lines.get(endIndex);
|
String endLine = lines.get(endIndex);
|
||||||
Map.Entry<? extends Map<String, String>, ? extends List<String>> result = separateDataLinesFromHeaders(
|
Map.Entry<? extends Map<String, String>, ? extends List<String>> result = separateDataLinesFromHeaders(
|
||||||
session, resourceKey, startLine, endLine, lines.subList(startIndex, endIndex));
|
session, resourceKey, startLine, endLine, lines.subList(startIndex, endIndex));
|
||||||
Map<String, String> headers = result.getKey();
|
Map<String, String> headers = result.getKey();
|
||||||
List<String> dataLines = result.getValue();
|
List<String> dataLines = result.getValue();
|
||||||
Collection<KeyPair> kps = extractKeyPairs(
|
Collection<KeyPair> kps = extractKeyPairs(session, resourceKey, startLine, endLine, passwordProvider,
|
||||||
session, resourceKey, startLine, endLine, passwordProvider,
|
|
||||||
(dataLines == null) ? Collections.emptyList() : dataLines,
|
(dataLines == null) ? Collections.emptyList() : dataLines,
|
||||||
(headers == null) ? Collections.emptyMap() : headers);
|
(headers == null) ? Collections.emptyMap() : headers);
|
||||||
if (GenericUtils.isNotEmpty(kps)) {
|
if (GenericUtils.isNotEmpty(kps)) {
|
||||||
|
@ -122,11 +120,8 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
|
||||||
keyPairs.addAll(kps);
|
keyPairs.addAll(kps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if there are more
|
|
||||||
markerPos = KeyPairResourceParser.findMarkerLine(lines, endIndex + 1, beginMarkers);
|
markerPos = KeyPairResourceParser.findMarkerLine(lines, endIndex + 1, beginMarkers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyPairs;
|
return keyPairs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,11 +148,13 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
|
||||||
* @throws IOException If failed to parse the data
|
* @throws IOException If failed to parse the data
|
||||||
* @throws GeneralSecurityException If failed to generate the keys
|
* @throws GeneralSecurityException If failed to generate the keys
|
||||||
*/
|
*/
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker,
|
||||||
|
String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
List<String> lines, Map<String, String> headers)
|
List<String> lines,
|
||||||
|
Map<String, String> headers)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines);
|
byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines);
|
||||||
try {
|
try {
|
||||||
|
@ -175,42 +172,18 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
|
||||||
* @param endMarker The line containing the end marker
|
* @param endMarker The line containing the end marker
|
||||||
* @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be
|
* @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be
|
||||||
* {@code null} if no encrypted
|
* {@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
|
* @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.
|
* @return The extracted {@link KeyPair}s - may be {@code null}/empty if none.
|
||||||
* @throws IOException If failed to parse the data
|
* @throws IOException If failed to parse the data
|
||||||
* @throws GeneralSecurityException If failed to generate the keys
|
* @throws GeneralSecurityException If failed to generate the keys
|
||||||
*/
|
*/
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public abstract Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker,
|
||||||
|
String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
byte[] bytes, Map<String, String> headers)
|
byte[] bytes,
|
||||||
throws IOException, GeneralSecurityException {
|
Map<String, String> headers)
|
||||||
|
|
||||||
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<KeyPair> extractKeyPairs(
|
|
||||||
SessionContext session, NamedResource resourceKey,
|
|
||||||
String beginMarker, String endMarker,
|
|
||||||
FilePasswordProvider passwordProvider,
|
|
||||||
InputStream stream, Map<String, String> headers)
|
|
||||||
throws IOException, GeneralSecurityException;
|
throws IOException, GeneralSecurityException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,11 +64,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
|
||||||
if (curve == null) {
|
if (curve == null) {
|
||||||
throw new InvalidKeySpecException("Not an EC curve name: " + keyType);
|
throw new InvalidKeySpecException("Not an EC curve name: " + keyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
String keyCurveName = curve.getName();
|
String keyCurveName = curve.getName();
|
||||||
// see rfc5656 section 3.1
|
// see rfc5656 section 3.1
|
||||||
String encCurveName = KeyEntryResolver.decodeString(keyData, ECDSAPublicKeyEntryDecoder.MAX_CURVE_NAME_LENGTH);
|
String encCurveName = KeyEntryResolver.decodeString(keyData, ECDSAPublicKeyEntryDecoder.MAX_CURVE_NAME_LENGTH);
|
||||||
|
@ -117,10 +112,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
|
public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -135,10 +126,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
|
public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -153,11 +140,7 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
|
||||||
if (SecurityUtils.isECCSupported()) {
|
|
||||||
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
|
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
|
||||||
} else {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -174,10 +157,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
|
||||||
if (SecurityUtils.isECCSupported()) {
|
|
||||||
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
|
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
|
||||||
} else {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<EdECPublicKey, EdECPrivateKey> {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,22 +18,17 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.sshd.common.config.keys.loader.openssh;
|
package org.apache.sshd.common.config.keys.loader.openssh;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.math.BigInteger;
|
||||||
import java.io.StreamCorruptedException;
|
|
||||||
import java.net.ProtocolException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.spec.EdECPoint;
|
||||||
import java.security.PrivateKey;
|
import java.security.spec.EdECPrivateKeySpec;
|
||||||
import java.security.PublicKey;
|
import java.security.spec.EdECPublicKeySpec;
|
||||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.util.ArrayList;
|
import java.security.spec.NamedParameterSpec;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -43,24 +38,14 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import javax.security.auth.login.FailedLoginException;
|
|
||||||
|
|
||||||
import org.apache.sshd.common.NamedResource;
|
import org.apache.sshd.common.NamedResource;
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
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.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.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.session.SessionContext;
|
||||||
|
import org.apache.sshd.common.util.ArrayUtil;
|
||||||
import org.apache.sshd.common.util.GenericUtils;
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
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 <A HREF=
|
* Basic support for <A HREF=
|
||||||
|
@ -70,253 +55,125 @@ import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser {
|
public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser {
|
||||||
|
|
||||||
public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY";
|
public static final String BEGIN_MARKER = "BEGIN OPENSSH PRIVATE KEY";
|
||||||
public static final List<String> BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
|
public static final List<String> BEGINNERS = Collections.singletonList(BEGIN_MARKER);
|
||||||
|
|
||||||
public static final String END_MARKER = "END OPENSSH PRIVATE KEY";
|
public static final String END_MARKER = "END OPENSSH PRIVATE KEY";
|
||||||
public static final List<String> ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER));
|
public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
|
||||||
|
|
||||||
public static final String AUTH_MAGIC = "openssh-key-v1";
|
public static final String AUTH_MAGIC = "openssh-key-v1";
|
||||||
public static final OpenSSHKeyPairResourceParser INSTANCE = new OpenSSHKeyPairResourceParser();
|
public static final OpenSSHKeyPairResourceParser INSTANCE = new OpenSSHKeyPairResourceParser();
|
||||||
|
|
||||||
private static final byte[] AUTH_MAGIC_BYTES = AUTH_MAGIC.getBytes(StandardCharsets.UTF_8);
|
|
||||||
private static final Map<String, PrivateKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP
|
private static final Map<String, PrivateKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP
|
||||||
= new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
= new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
private static final Map<Class<?>, PrivateKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP = new HashMap<>();
|
private static final Map<Class<?>, PrivateKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyDecoder.INSTANCE);
|
registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyEntryDecoder.INSTANCE);
|
||||||
registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE);
|
registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE);
|
||||||
|
|
||||||
if (SecurityUtils.isECCSupported()) {
|
|
||||||
registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
|
registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
|
||||||
}
|
registerPrivateKeyEntryDecoder(OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE);
|
||||||
if (SecurityUtils.isEDDSACurveSupported()) {
|
|
||||||
registerPrivateKeyEntryDecoder(SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenSSHKeyPairResourceParser() {
|
public OpenSSHKeyPairResourceParser() {
|
||||||
super(BEGINNERS, ENDERS);
|
super(BEGINNERS, ENDERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
* Ed25519 only. No password.
|
||||||
SessionContext session, NamedResource resourceKey,
|
|
||||||
String beginMarker, String endMarker,
|
|
||||||
FilePasswordProvider passwordProvider,
|
|
||||||
InputStream stream, Map<String, String> 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<PublicKey> 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<KeyPair> 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<String, String> headers)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
String kdfName = KeyEntryResolver.decodeString(stream, OpenSSHKdfOptions.MAX_KDF_NAME_LENGTH);
|
|
||||||
byte[] kdfOptions = KeyEntryResolver.readRLEBytes(stream, OpenSSHKdfOptions.MAX_KDF_OPTIONS_SIZE);
|
|
||||||
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<String, String> 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
|
|
||||||
*/
|
|
||||||
protected List<KeyPair> readPrivateKeys(
|
|
||||||
SessionContext session, NamedResource resourceKey,
|
|
||||||
OpenSSHParserContext context, Collection<? extends PublicKey> publicKeys,
|
|
||||||
FilePasswordProvider passwordProvider, InputStream stream)
|
|
||||||
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
|
* @param session The {@link SessionContext} for invoking this load command - may be {@code null}
|
||||||
* can be quickly checked by verifying that both checkint fields hold the same value.
|
* 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
|
||||||
*/
|
*/
|
||||||
if (check1 != check2) {
|
@Override
|
||||||
throw new StreamCorruptedException(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
"Mismatched private key check values ("
|
NamedResource resourceKey,
|
||||||
+ Integer.toHexString(check1) + "/" + Integer.toHexString(check2) + ") in "
|
String beginMarker,
|
||||||
+ resourceKey);
|
String endMarker,
|
||||||
}
|
FilePasswordProvider passwordProvider,
|
||||||
|
byte[] rawKey,
|
||||||
List<KeyPair> keyPairs = new ArrayList<>(publicKeys.size());
|
Map<String, String> headers)
|
||||||
for (PublicKey pubKey : publicKeys) {
|
|
||||||
String pubType = KeyUtils.getKeyType(pubKey);
|
|
||||||
int keyIndex = keyPairs.size() + 1;
|
|
||||||
|
|
||||||
Map.Entry<PrivateKey, String> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Map.Entry<PrivateKey, String> readPrivateKey(
|
|
||||||
SessionContext session, NamedResource resourceKey,
|
|
||||||
OpenSSHParserContext context, String keyType,
|
|
||||||
FilePasswordProvider passwordProvider, InputStream stream)
|
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
String prvType = KeyEntryResolver.decodeString(stream, MAX_KEY_TYPE_NAME_LENGTH);
|
OpenSsh openSsh = decodeFromOpenSsh(rawKey);
|
||||||
if (!Objects.equals(keyType, prvType)) {
|
NamedParameterSpec params = NamedParameterSpec.ED25519;
|
||||||
throw new StreamCorruptedException(
|
EdECPrivateKeySpec edECPrivateKeySpec = new EdECPrivateKeySpec(params, openSsh.privatekey);
|
||||||
"Mismatched private key type: "
|
byte[] arr = openSsh.publickey;
|
||||||
+ ", expected=" + keyType + ", actual=" + prvType
|
byte msb = arr[arr.length - 1];
|
||||||
+ " in " + resourceKey);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivateKeyEntryDecoder<?, ?> decoder = getPrivateKeyEntryDecoder(prvType);
|
private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException {
|
||||||
if (decoder == null) {
|
OpenSsh openSsh = new OpenSsh();
|
||||||
throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivateKey prvKey = decoder.decodePrivateKey(session, prvType, passwordProvider, stream);
|
private static final String OPENSSH_KEY_V1 = "openssh-key-v1";
|
||||||
if (prvKey == null) {
|
|
||||||
throw new InvalidKeyException("Cannot parse key type (" + prvType + ") in " + resourceKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
String comment = KeyEntryResolver.decodeString(stream, MAX_KEY_COMMENT_LENGTH);
|
private static class OpenSsh {
|
||||||
return new SimpleImmutableEntry<>(prvKey, comment);
|
byte[] verify;
|
||||||
}
|
byte[] publickey;
|
||||||
|
byte[] privatekey;
|
||||||
protected <S extends InputStream> 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -341,7 +198,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
|
||||||
for (String n : names) {
|
for (String n : names) {
|
||||||
PrivateKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
|
PrivateKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
// noinspection UnnecessaryContinue
|
|
||||||
continue; // debug breakpoint
|
continue; // debug breakpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,7 +260,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
|
||||||
if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
|
if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
|
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
|
||||||
PrivateKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
|
PrivateKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
|
||||||
if (decoder != null) {
|
if (decoder != null) {
|
||||||
|
@ -420,7 +275,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,23 +47,25 @@ import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public class OpenSSHRSAPrivateKeyDecoder extends AbstractPrivateKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
|
public class OpenSSHRSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
|
||||||
public static final BigInteger DEFAULT_PUBLIC_EXPONENT = new BigInteger("65537");
|
|
||||||
public static final OpenSSHRSAPrivateKeyDecoder INSTANCE = new OpenSSHRSAPrivateKeyDecoder();
|
|
||||||
|
|
||||||
public OpenSSHRSAPrivateKeyDecoder() {
|
public static final BigInteger DEFAULT_PUBLIC_EXPONENT = new BigInteger("65537");
|
||||||
super(RSAPublicKey.class, RSAPrivateKey.class,
|
|
||||||
Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
|
public static final OpenSSHRSAPrivateKeyEntryDecoder INSTANCE = new OpenSSHRSAPrivateKeyEntryDecoder();
|
||||||
|
|
||||||
|
public OpenSSHRSAPrivateKeyEntryDecoder() {
|
||||||
|
super(RSAPublicKey.class, RSAPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_RSA));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RSAPrivateKey decodePrivateKey(
|
public RSAPrivateKey decodePrivateKey(SessionContext session,
|
||||||
SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
|
String keyType,
|
||||||
|
FilePasswordProvider passwordProvider,
|
||||||
|
InputStream keyData)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
|
if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
|
||||||
throw new InvalidKeySpecException("Unexpected key type: " + keyType);
|
throw new InvalidKeySpecException("Unexpected key type: " + keyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
BigInteger n = KeyEntryResolver.decodeBigInt(keyData);
|
BigInteger n = KeyEntryResolver.decodeBigInt(keyData);
|
||||||
BigInteger e = KeyEntryResolver.decodeBigInt(keyData);
|
BigInteger e = KeyEntryResolver.decodeBigInt(keyData);
|
||||||
if (!Objects.equals(e, DEFAULT_PUBLIC_EXPONENT)) {
|
if (!Objects.equals(e, DEFAULT_PUBLIC_EXPONENT)) {
|
|
@ -18,9 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.sshd.common.config.keys.loader.pem;
|
package org.apache.sshd.common.config.keys.loader.pem;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
@ -59,8 +57,10 @@ public abstract class AbstractPEMResourceKeyPairParser
|
||||||
private final String algo;
|
private final String algo;
|
||||||
private final String algId;
|
private final String algId;
|
||||||
|
|
||||||
protected AbstractPEMResourceKeyPairParser(
|
protected AbstractPEMResourceKeyPairParser(String algo,
|
||||||
String algo, String algId, List<String> beginners, List<String> enders) {
|
String algId,
|
||||||
|
List<String> beginners,
|
||||||
|
List<String> enders) {
|
||||||
super(beginners, enders);
|
super(beginners, enders);
|
||||||
this.algo = ValidateUtils.checkNotNullAndNotEmpty(algo, "No encryption algorithm provided");
|
this.algo = ValidateUtils.checkNotNullAndNotEmpty(algo, "No encryption algorithm provided");
|
||||||
this.algId = ValidateUtils.checkNotNullAndNotEmpty(algId, "No algorithm identifier provided");
|
this.algId = ValidateUtils.checkNotNullAndNotEmpty(algId, "No algorithm identifier provided");
|
||||||
|
@ -166,10 +166,8 @@ public abstract class AbstractPEMResourceKeyPairParser
|
||||||
try {
|
try {
|
||||||
encryptedData = KeyPairResourceParser.extractDataBytes(dataLines);
|
encryptedData = KeyPairResourceParser.extractDataBytes(dataLines);
|
||||||
decodedData = applyPrivateKeyCipher(encryptedData, encContext, false);
|
decodedData = applyPrivateKeyCipher(encryptedData, encContext, false);
|
||||||
try (InputStream bais = new ByteArrayInputStream(decodedData)) {
|
keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, decodedData,
|
||||||
keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais,
|
|
||||||
headers);
|
headers);
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(encryptedData, (byte) 0); // get rid of sensitive data a.s.a.p.
|
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.
|
Arrays.fill(decodedData, (byte) 0); // get rid of sensitive data a.s.a.p.
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.sshd.common.config.keys.loader.pem;
|
package org.apache.sshd.common.config.keys.loader.pem;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
|
@ -66,15 +67,19 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker,
|
||||||
|
String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
InputStream stream, Map<String, String> headers)
|
byte[] rawKey,
|
||||||
|
Map<String, String> headers)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
|
||||||
KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
|
KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
|
||||||
return Collections.singletonList(kp);
|
return Collections.singletonList(kp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.sshd.common.config.keys.loader.pem;
|
package org.apache.sshd.common.config.keys.loader.pem;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
|
@ -26,7 +27,6 @@ import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.interfaces.ECPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.security.spec.ECPoint;
|
import java.security.spec.ECPoint;
|
||||||
|
@ -55,10 +55,10 @@ import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
*/
|
*/
|
||||||
public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
|
public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
|
||||||
public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
|
public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
|
||||||
public static final List<String> BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
|
public static final List<String> BEGINNERS = Collections.singletonList(BEGIN_MARKER);
|
||||||
|
|
||||||
public static final String END_MARKER = "END EC PRIVATE KEY";
|
public static final String END_MARKER = "END EC PRIVATE KEY";
|
||||||
public static final List<String> ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER));
|
public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.5">RFC-3279 section 2.3.5</A>
|
* @see <A HREF="https://tools.ietf.org/html/rfc3279#section-2.3.5">RFC-3279 section 2.3.5</A>
|
||||||
|
@ -72,16 +72,19 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker,
|
||||||
|
String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
InputStream stream, Map<String, String> headers)
|
byte[] rawKey,
|
||||||
|
Map<String, String> headers)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
|
||||||
KeyPair kp = parseECKeyPair(stream, false);
|
KeyPair kp = parseECKeyPair(stream, false);
|
||||||
return Collections.singletonList(kp);
|
return Collections.singletonList(kp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static KeyPair parseECKeyPair(
|
public static KeyPair parseECKeyPair(
|
||||||
InputStream inputStream, boolean okToClose)
|
InputStream inputStream, boolean okToClose)
|
||||||
|
@ -104,10 +107,6 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
ASN1Object sequence = parser.readObject();
|
ASN1Object sequence = parser.readObject();
|
||||||
Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(curve, sequence);
|
Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(curve, sequence);
|
||||||
if (!SecurityUtils.isECCSupported()) {
|
|
||||||
throw new NoSuchProviderException("ECC not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
|
KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
|
||||||
ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey());
|
ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey());
|
||||||
ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue());
|
ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue());
|
||||||
|
|
|
@ -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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.interfaces.EdECPrivateKey;
|
import java.security.interfaces.EdECPrivateKey;
|
||||||
import java.security.interfaces.EdECPublicKey;
|
import java.security.interfaces.EdECPublicKey;
|
||||||
import java.security.spec.EdECPoint;
|
import java.security.spec.EdECPoint;
|
||||||
|
@ -21,15 +20,15 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.sshd.common.NamedResource;
|
import org.apache.sshd.common.NamedResource;
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
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.session.SessionContext;
|
||||||
|
import org.apache.sshd.common.util.ArrayUtil;
|
||||||
import org.apache.sshd.common.util.GenericUtils;
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
import org.apache.sshd.common.util.io.NoCloseInputStream;
|
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.ASN1Object;
|
||||||
import org.apache.sshd.common.util.io.der.ASN1Type;
|
import org.apache.sshd.common.util.io.der.ASN1Type;
|
||||||
import org.apache.sshd.common.util.io.der.DERParser;
|
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";
|
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 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);
|
super("Ed25519", ED25519_OID, BEGINNERS, ENDERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,11 +53,14 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
|
||||||
NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker, String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
InputStream stream,
|
byte[] rawKey,
|
||||||
Map<String, String> headers) throws IOException, GeneralSecurityException {
|
Map<String, String> headers)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
|
||||||
KeyPair kp = parseEd25519KeyPair(stream, false);
|
KeyPair kp = parseEd25519KeyPair(stream, false);
|
||||||
return Collections.singletonList(kp);
|
return Collections.singletonList(kp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static KeyPair parseEd25519KeyPair(InputStream inputStream,
|
public static KeyPair parseEd25519KeyPair(InputStream inputStream,
|
||||||
boolean okToClose) throws IOException, GeneralSecurityException {
|
boolean okToClose) throws IOException, GeneralSecurityException {
|
||||||
|
@ -145,18 +147,15 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
|
||||||
return (EdECPrivateKey) factory.generatePrivate(keySpec);
|
return (EdECPrivateKey) factory.generatePrivate(keySpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EdECPublicKey getPublicKey(byte[] keyData) throws GeneralSecurityException {
|
private static EdECPublicKey getPublicKey(byte[] arr) throws GeneralSecurityException {
|
||||||
byte[] pk = keyData;
|
byte msb = arr[arr.length - 1];
|
||||||
boolean xisodd = false;
|
boolean xOdd = (msb & 0x80) != 0;
|
||||||
int lastbyteInt = pk[pk.length - 1];
|
arr[arr.length - 1] &= (byte) 0x7F;
|
||||||
if ((lastbyteInt & 255) >> 7 == 1) {
|
ArrayUtil.reverse(arr);
|
||||||
xisodd = true;
|
BigInteger y = new BigInteger(1, arr);
|
||||||
}
|
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
|
||||||
pk[pk.length - 1] &= 127;
|
EdECPoint edECPoint = new EdECPoint(xOdd, y);
|
||||||
BigInteger y = new BigInteger(1, pk);
|
EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(namedParameterSpec, edECPoint);
|
||||||
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
|
|
||||||
EdECPoint ep = new EdECPoint(xisodd, y);
|
|
||||||
EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep);
|
|
||||||
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
KeyFactory factory = KeyFactory.getInstance("EdDSA");
|
||||||
return (EdECPublicKey) factory.generatePublic(publicKeySpec);
|
return (EdECPublicKey) factory.generatePublic(publicKeySpec);
|
||||||
}
|
}
|
|
@ -20,7 +20,6 @@
|
||||||
package org.apache.sshd.common.config.keys.loader.pem;
|
package org.apache.sshd.common.config.keys.loader.pem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
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.session.SessionContext;
|
||||||
import org.apache.sshd.common.util.GenericUtils;
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
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.ASN1Object;
|
||||||
import org.apache.sshd.common.util.io.der.ASN1Type;
|
import org.apache.sshd.common.util.io.der.ASN1Type;
|
||||||
import org.apache.sshd.common.util.io.der.DERParser;
|
import org.apache.sshd.common.util.io.der.DERParser;
|
||||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
import org.apache.sshd.common.util.security.edec.EdECPEMResourceKeyParser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
|
@ -54,10 +51,10 @@ import org.apache.sshd.common.util.security.edec.EdECPEMResourceKeyParser;
|
||||||
public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
|
public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
|
||||||
// Not exactly according to standard but good enough
|
// Not exactly according to standard but good enough
|
||||||
public static final String BEGIN_MARKER = "BEGIN PRIVATE KEY";
|
public static final String BEGIN_MARKER = "BEGIN PRIVATE KEY";
|
||||||
public static final List<String> BEGINNERS = Collections.unmodifiableList(Collections.singletonList(BEGIN_MARKER));
|
public static final List<String> BEGINNERS = Collections.singletonList(BEGIN_MARKER);
|
||||||
|
|
||||||
public static final String END_MARKER = "END PRIVATE KEY";
|
public static final String END_MARKER = "END PRIVATE KEY";
|
||||||
public static final List<String> ENDERS = Collections.unmodifiableList(Collections.singletonList(END_MARKER));
|
public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
|
||||||
|
|
||||||
public static final String PKCS8_FORMAT = "PKCS#8";
|
public static final String PKCS8_FORMAT = "PKCS#8";
|
||||||
|
|
||||||
|
@ -68,31 +65,31 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker, String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
InputStream stream, Map<String, String> headers)
|
byte[] encBytes,
|
||||||
|
Map<String, String> headers)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
// Save the data before getting the algorithm OID since we will need it
|
// Save the data before getting the algorithm OID since we will need it
|
||||||
byte[] encBytes = IoUtils.toByteArray(stream);
|
|
||||||
PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes);
|
PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes);
|
||||||
return extractKeyPairs(
|
return extractKeyPairs(session, resourceKey, beginMarker, endMarker,
|
||||||
session, resourceKey, beginMarker, endMarker,
|
|
||||||
passwordProvider, encBytes, pkcs8Info, headers);
|
passwordProvider, encBytes, pkcs8Info, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker, String endMarker,
|
||||||
FilePasswordProvider passwordProvider, byte[] encBytes,
|
FilePasswordProvider passwordProvider,
|
||||||
PKCS8PrivateKeyInfo pkcs8Info, Map<String, String> headers)
|
byte[] encBytes,
|
||||||
|
PKCS8PrivateKeyInfo pkcs8Info,
|
||||||
|
Map<String, String> headers)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
List<Integer> oidAlgorithm = pkcs8Info.getAlgorithmIdentifier();
|
List<Integer> oidAlgorithm = pkcs8Info.getAlgorithmIdentifier();
|
||||||
String oid = GenericUtils.join(oidAlgorithm, '.');
|
String oid = GenericUtils.join(oidAlgorithm, '.');
|
||||||
KeyPair kp;
|
KeyPair kp;
|
||||||
if (SecurityUtils.isECCSupported()
|
if (ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
|
||||||
&& ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
|
|
||||||
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
|
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
|
||||||
ASN1Object extraInfo = pkcs8Info.getAlgorithmParameter();
|
ASN1Object extraInfo = pkcs8Info.getAlgorithmParameter();
|
||||||
ASN1Type objType = (extraInfo == null) ? ASN1Type.NULL : extraInfo.getObjType();
|
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);
|
throw new NoSuchAlgorithmException("Cannot match EC curve OID=" + oidCurve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (DERParser parser = privateKeyBytes.createParser()) {
|
try (DERParser parser = privateKeyBytes.createParser()) {
|
||||||
kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
|
kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
|
||||||
}
|
}
|
||||||
} else if (SecurityUtils.isEDDSACurveSupported()
|
} else if (Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
|
||||||
&& EdECPEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
|
|
||||||
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
|
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
|
||||||
kp = EdECPEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes());
|
kp = Ed25519PEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes());
|
||||||
} else {
|
} else {
|
||||||
PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
|
PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
|
||||||
PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
|
PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.sshd.common.config.keys.loader.pem;
|
package org.apache.sshd.common.config.keys.loader.pem;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
|
@ -67,15 +68,19 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<KeyPair> extractKeyPairs(
|
public Collection<KeyPair> extractKeyPairs(SessionContext session,
|
||||||
SessionContext session, NamedResource resourceKey,
|
NamedResource resourceKey,
|
||||||
String beginMarker, String endMarker,
|
String beginMarker,
|
||||||
|
String endMarker,
|
||||||
FilePasswordProvider passwordProvider,
|
FilePasswordProvider passwordProvider,
|
||||||
InputStream stream, Map<String, String> headers)
|
byte[] rawKey,
|
||||||
|
Map<String, String> headers)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
|
||||||
KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
|
KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
|
||||||
return Collections.singletonList(kp);
|
return Collections.singletonList(kp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -157,97 +157,53 @@ public enum BuiltinSignatures implements SignatureFactory {
|
||||||
return new SignatureECDSA.SignatureECDSA256();
|
return new SignatureECDSA.SignatureECDSA256();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
nistp256_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT) {
|
nistp256_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return new SignatureECDSA.SignatureECDSA256();
|
return new SignatureECDSA.SignatureECDSA256();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
nistp384(KeyPairProvider.ECDSA_SHA2_NISTP384) {
|
nistp384(KeyPairProvider.ECDSA_SHA2_NISTP384) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return new SignatureECDSA.SignatureECDSA384();
|
return new SignatureECDSA.SignatureECDSA384();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
nistp384_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT) {
|
nistp384_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return new SignatureECDSA.SignatureECDSA384();
|
return new SignatureECDSA.SignatureECDSA384();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
nistp521(KeyPairProvider.ECDSA_SHA2_NISTP521) {
|
nistp521(KeyPairProvider.ECDSA_SHA2_NISTP521) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return new SignatureECDSA.SignatureECDSA521();
|
return new SignatureECDSA.SignatureECDSA521();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
nistp521_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT) {
|
nistp521_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return new SignatureECDSA.SignatureECDSA521();
|
return new SignatureECDSA.SignatureECDSA521();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
sk_ecdsa_sha2_nistp256(SkECDSAPublicKeyEntryDecoder.KEY_TYPE) {
|
sk_ecdsa_sha2_nistp256(SkECDSAPublicKeyEntryDecoder.KEY_TYPE) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return new SignatureSkECDSA();
|
return new SignatureSkECDSA();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isECCSupported();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ed25519(KeyPairProvider.SSH_ED25519) {
|
ed25519(KeyPairProvider.SSH_ED25519) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return SecurityUtils.getEDDSASigner();
|
return new SignatureEd25519();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isEDDSACurveSupported();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ed25519_cert(KeyPairProvider.SSH_ED25519_CERT) {
|
ed25519_cert(KeyPairProvider.SSH_ED25519_CERT) {
|
||||||
@Override
|
@Override
|
||||||
public Signature create() {
|
public Signature create() {
|
||||||
return SecurityUtils.getEDDSASigner();
|
return new SignatureEd25519();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SecurityUtils.isEDDSACurveSupported();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package org.apache.sshd.common.util.security.edec;
|
package org.apache.sshd.common.signature;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.sshd.common.session.SessionContext;
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
import org.apache.sshd.common.signature.AbstractSignature;
|
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
import org.apache.sshd.common.util.ValidateUtils;
|
||||||
|
|
||||||
public class SignatureEd25519 extends AbstractSignature {
|
public class SignatureEd25519 extends AbstractSignature {
|
||||||
|
|
||||||
public SignatureEd25519() {
|
public SignatureEd25519() {
|
||||||
super("NONEwithEdDSA");
|
super("EdDSA");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,12 +30,9 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.interfaces.DSAParams;
|
import java.security.interfaces.DSAParams;
|
||||||
import java.security.interfaces.DSAPrivateKey;
|
|
||||||
import java.security.interfaces.DSAPublicKey;
|
import java.security.interfaces.DSAPublicKey;
|
||||||
import java.security.interfaces.ECPrivateKey;
|
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.security.interfaces.EdECPublicKey;
|
import java.security.interfaces.EdECPublicKey;
|
||||||
import java.security.interfaces.RSAPrivateCrtKey;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.security.spec.DSAPrivateKeySpec;
|
import java.security.spec.DSAPrivateKeySpec;
|
||||||
import java.security.spec.DSAPublicKeySpec;
|
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.KeyUtils;
|
||||||
import org.apache.sshd.common.config.keys.OpenSshCertificate;
|
import org.apache.sshd.common.config.keys.OpenSshCertificate;
|
||||||
import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
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.GenericUtils;
|
||||||
import org.apache.sshd.common.util.NumberUtils;
|
import org.apache.sshd.common.util.NumberUtils;
|
||||||
import org.apache.sshd.common.util.Readable;
|
import org.apache.sshd.common.util.Readable;
|
||||||
|
@ -73,6 +71,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
|
||||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||||
*/
|
*/
|
||||||
public abstract class Buffer implements Readable {
|
public abstract class Buffer implements Readable {
|
||||||
|
|
||||||
protected final byte[] workBuf = new byte[Long.BYTES];
|
protected final byte[] workBuf = new byte[Long.BYTES];
|
||||||
|
|
||||||
protected Buffer() {
|
protected Buffer() {
|
||||||
|
@ -514,7 +513,7 @@ public abstract class Buffer implements Readable {
|
||||||
pub = keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
|
pub = keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
|
||||||
prv = keyFactory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
|
prv = keyFactory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
|
||||||
} else if (KeyPairProvider.SSH_ED25519.equals(keyAlg)) {
|
} else if (KeyPairProvider.SSH_ED25519.equals(keyAlg)) {
|
||||||
return SecurityUtils.extractEDDSAKeyPair(this, keyAlg);
|
throw new UnsupportedOperationException();
|
||||||
} else {
|
} else {
|
||||||
ECCurves curve = ECCurves.fromKeyType(keyAlg);
|
ECCurves curve = ECCurves.fromKeyType(keyAlg);
|
||||||
if (curve == null) {
|
if (curve == null) {
|
||||||
|
@ -900,10 +899,6 @@ public abstract class Buffer implements Readable {
|
||||||
putString(curve.getName());
|
putString(curve.getName());
|
||||||
putBytes(ecPoint);
|
putBytes(ecPoint);
|
||||||
}
|
}
|
||||||
case EdECPublicKey edECPublicKey -> {
|
|
||||||
byte[] b = edECPublicKey.getEncoded();
|
|
||||||
putBytes(b);
|
|
||||||
}
|
|
||||||
case OpenSshCertificate cert -> {
|
case OpenSshCertificate cert -> {
|
||||||
putBytes(cert.getNonce());
|
putBytes(cert.getNonce());
|
||||||
putRawPublicKeyBytes(cert.getServerHostKey());
|
putRawPublicKeyBytes(cert.getServerHostKey());
|
||||||
|
@ -923,50 +918,15 @@ public abstract class Buffer implements Readable {
|
||||||
putBytes(tmpBuffer.getCompactData());
|
putBytes(tmpBuffer.getCompactData());
|
||||||
putBytes(cert.getSignature());
|
putBytes(cert.getSignature());
|
||||||
}
|
}
|
||||||
|
case EdECPublicKey edECPublicKey -> {
|
||||||
|
byte[] b = edECPublicKey.getPoint().getY().toByteArray();
|
||||||
|
ArrayUtil.reverse(b);
|
||||||
|
putBytes(b);
|
||||||
|
}
|
||||||
default -> throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm());
|
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");
|
|
||||||
}
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Buffer ensureCapacity(int capacity) {
|
public Buffer ensureCapacity(int capacity) {
|
||||||
return ensureCapacity(capacity, BufferUtils.DEFAULT_BUFFER_GROWTH_FACTOR);
|
return ensureCapacity(capacity, BufferUtils.DEFAULT_BUFFER_GROWTH_FACTOR);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,20 @@
|
||||||
|
|
||||||
package org.apache.sshd.common.util.buffer.keys;
|
package org.apache.sshd.common.util.buffer.keys;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PublicKey;
|
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.keyprovider.KeyPairProvider;
|
||||||
|
import org.apache.sshd.common.util.ArrayUtil;
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
import org.apache.sshd.common.util.ValidateUtils;
|
||||||
import org.apache.sshd.common.util.buffer.Buffer;
|
import org.apache.sshd.common.util.buffer.Buffer;
|
||||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO complete this when SSHD-440 is done
|
* 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 {
|
public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
|
||||||
ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
|
ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
|
||||||
byte[] seed = buffer.getBytes();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,11 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -48,8 +43,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.KeyAgreement;
|
import javax.crypto.KeyAgreement;
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
|
@ -58,20 +51,14 @@ import javax.crypto.spec.DHParameterSpec;
|
||||||
import org.apache.sshd.common.NamedResource;
|
import org.apache.sshd.common.NamedResource;
|
||||||
import org.apache.sshd.common.PropertyResolverUtils;
|
import org.apache.sshd.common.PropertyResolverUtils;
|
||||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
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.KeyPairResourceParser;
|
||||||
import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser;
|
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.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.JceRandomFactory;
|
||||||
import org.apache.sshd.common.random.RandomFactory;
|
import org.apache.sshd.common.random.RandomFactory;
|
||||||
import org.apache.sshd.common.session.SessionContext;
|
import org.apache.sshd.common.session.SessionContext;
|
||||||
import org.apache.sshd.common.util.GenericUtils;
|
import org.apache.sshd.common.util.GenericUtils;
|
||||||
import org.apache.sshd.common.util.ValidateUtils;
|
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;
|
import org.apache.sshd.common.util.threads.ThreadUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,15 +72,6 @@ public final class SecurityUtils {
|
||||||
*/
|
*/
|
||||||
public static final String BOUNCY_CASTLE = "BC";
|
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
|
* 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
|
* 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 String SECURITY_PROVIDER_REGISTRARS = "org.apache.sshd.security.registrars";
|
||||||
|
|
||||||
public static final List<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS =
|
public static final List<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS = List.of();
|
||||||
List.of("org.apache.sshd.common.util.security.edec.EdECSecurityProviderRegistrar");
|
|
||||||
//Collections.unmodifiableList(Arrays.asList("org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar"));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System property used to control whether Elliptic Curves are supported or not. If not set then the support is
|
* 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
|
* @return {@code true} if Diffie-Hellman Group Exchange is supported
|
||||||
* @see #getMinDHGroupExchangeKeySize()
|
* @see #getMinDHGroupExchangeKeySize()
|
||||||
|
@ -351,25 +305,6 @@ public final class SecurityUtils {
|
||||||
DEFAULT_PROVIDER_HOLDER.set(choice);
|
DEFAULT_PROVIDER_HOLDER.set(choice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A <U>copy</U> of the currently registered security providers
|
|
||||||
*/
|
|
||||||
public static Set<String> 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) {
|
public static SecurityProviderRegistrar getRegisteredProvider(String provider) {
|
||||||
ValidateUtils.checkNotNullAndNotEmpty(provider, "No provider name specified");
|
ValidateUtils.checkNotNullAndNotEmpty(provider, "No provider name specified");
|
||||||
synchronized (REGISTERED_PROVIDERS) {
|
synchronized (REGISTERED_PROVIDERS) {
|
||||||
|
@ -377,10 +312,6 @@ public final class SecurityUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isRegistrationCompleted() {
|
|
||||||
return REGISTRATION_STATE_HOLDER.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void register() {
|
private static void register() {
|
||||||
synchronized (REGISTRATION_STATE_HOLDER) {
|
synchronized (REGISTRATION_STATE_HOLDER) {
|
||||||
if (REGISTRATION_STATE_HOLDER.get()) {
|
if (REGISTRATION_STATE_HOLDER.get()) {
|
||||||
|
@ -491,107 +422,6 @@ public final class SecurityUtils {
|
||||||
return JceRandomFactory.INSTANCE;
|
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<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
|
|
||||||
if (!isEDDSACurveSupported()) {
|
|
||||||
throw new UnsupportedOperationException(EDDSA + " provider N/A");
|
|
||||||
}
|
|
||||||
return EdDSASecurityProviderUtils.getEDDSAPublicKeyEntryDecoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> 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<? extends PublicKey> getEDDSAPublicKeyType() {
|
|
||||||
return isEDDSACurveSupported() ? EdDSASecurityProviderUtils.getEDDSAPublicKeyType() : PublicKey.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Class<? extends PrivateKey> 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 extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
|
|
||||||
if (!isEDDSACurveSupported()) {
|
|
||||||
throw new UnsupportedOperationException(EDDSA + " provider not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
return EdDSASecurityProviderUtils.putRawEDDSAPublicKey(buffer, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <B extends Buffer> B putEDDSAKeyPair(B buffer, KeyPair kp) {
|
|
||||||
return putEDDSAKeyPair(buffer, Objects.requireNonNull(kp, "No key pair").getPublic(), kp.getPrivate());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <B extends Buffer> 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() {
|
public static KeyPairResourceParser getKeyPairResourceParser() {
|
||||||
KeyPairResourceParser parser;
|
KeyPairResourceParser parser;
|
||||||
synchronized (KEYPAIRS_PARSER_HODLER) {
|
synchronized (KEYPAIRS_PARSER_HODLER) {
|
||||||
|
|
|
@ -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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
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<String> BEGINNERS = Collections.singletonList(BEGIN_MARKER);
|
|
||||||
|
|
||||||
public static final String END_MARKER = "END EDDSA PRIVATE KEY";
|
|
||||||
public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see <A HREF="https://tools.ietf.org/html/rfc8410#section-3">RFC8412 section 3</A>
|
|
||||||
*/
|
|
||||||
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<KeyPair> extractKeyPairs(
|
|
||||||
SessionContext session, NamedResource resourceKey, String beginMarker,
|
|
||||||
String endMarker, FilePasswordProvider passwordProvider,
|
|
||||||
InputStream stream, Map<String, String> 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<Integer> 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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
|
|
||||||
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<String, String> 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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
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<Boolean> 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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
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<? extends PublicKey> getEDDSAPublicKeyType() {
|
|
||||||
return EdDSAPublicKey.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Class<? extends PrivateKey> 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<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
|
|
||||||
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
|
|
||||||
return Ed25519PublicKeyDecoder.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> 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 extends Buffer> 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 extends Buffer> 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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
||||||
*/
|
|
||||||
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<String, byte[]> 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<EdECPublicKey, EdECPrivateKey> {
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,3 +1,3 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = files
|
name = files
|
||||||
version = 4.6.0
|
version = 4.7.0
|
||||||
|
|
|
@ -22,7 +22,8 @@ tasks.withType(JavaCompile).configureEach {
|
||||||
options.forkOptions.jvmArgs += ['-Duser.language=en', '-Duser.country=US']
|
options.forkOptions.jvmArgs += ['-Duser.language=en', '-Duser.country=US']
|
||||||
options.encoding = 'UTF-8'
|
options.encoding = 'UTF-8'
|
||||||
options.compilerArgs.add('-Xlint:all')
|
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("--module-path")
|
||||||
options.compilerArgs.add(classpath.asPath)
|
options.compilerArgs.add(classpath.asPath)
|
||||||
classpath = files()
|
classpath = files()
|
||||||
|
|
|
@ -16,8 +16,6 @@ dependencyResolutionManagement {
|
||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
libs {
|
libs {
|
||||||
version('gradle', '8.7')
|
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')
|
library('maverick-synergy-client', 'com.sshtools', 'maverick-synergy-client').version('3.1.1')
|
||||||
}
|
}
|
||||||
testLibs {
|
testLibs {
|
||||||
|
|
Loading…
Reference in a new issue