EC alawy enabled, EdEC always anabled, make EdEC JDK-based keys work, drop eddsa, xbib net, bouncycastle dependency

This commit is contained in:
Jörg Prante 2024-05-29 11:17:51 +02:00
parent 9af09a4176
commit bac77bbd0f
46 changed files with 563 additions and 1914 deletions

View file

@ -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);
} }

View file

@ -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
} }

View file

@ -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;
} }

View file

@ -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()));

View file

@ -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();
}
}
} }

View file

@ -1,3 +0,0 @@
dependencies {
implementation libs.net.security
}

View file

@ -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;
} }

View file

@ -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);

View file

@ -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;
{ {

View file

@ -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;
} }
} }

View file

@ -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);
} }

View file

@ -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() {

View file

@ -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));

View file

@ -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);
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
if (SecurityUtils.isECCSupported()) { registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE); registerPublicKeyEntryDecoder(Ed25519PublicKeyEntryDecoder.INSTANCE);
registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.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());
}
} }

View file

@ -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);
} }

View file

@ -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");
}
} }
} }

View file

@ -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));
}
} }

View file

@ -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,

View file

@ -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,
FilePasswordProvider passwordProvider, String endMarker,
List<String> lines, Map<String, String> headers) FilePasswordProvider passwordProvider,
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,
FilePasswordProvider passwordProvider, String endMarker,
byte[] bytes, Map<String, String> headers) FilePasswordProvider passwordProvider,
throws IOException, GeneralSecurityException { byte[] bytes,
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;
} }

View file

@ -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");
}
} }
} }

View file

@ -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;
}
}

View file

@ -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);
registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
if (SecurityUtils.isECCSupported()) { registerPrivateKeyEntryDecoder(OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE);
registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.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, * @param session The {@link SessionContext} for invoking this load command - may be {@code null}
FilePasswordProvider passwordProvider, * if not invoked within a session context (e.g., offline tool or session unknown).
InputStream stream, Map<String, String> headers) * @param resourceKey A hint as to the origin of the text lines
throws IOException, GeneralSecurityException { * @param beginMarker The line containing the begin marker
* @param endMarker The line containing the end marker
stream = validateStreamMagicMarker(session, resourceKey, stream); * @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be
* {@code null} if no encrypted
String cipher = KeyEntryResolver.decodeString(stream, MAX_CIPHER_NAME_LENGTH); * @param rawKey The bytes from the lines containing the data
OpenSSHKdfOptions kdfOptions = resolveKdfOptions(session, resourceKey, beginMarker, endMarker, stream, headers); * @param headers Any headers that may have been available when data was read
OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfOptions); * @return
int numKeys = KeyEntryResolver.decodeInt(stream); * @throws IOException
if (numKeys <= 0) { * @throws GeneralSecurityException
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( @Override
SessionContext session, NamedResource resourceKey, public Collection<KeyPair> extractKeyPairs(SessionContext session,
OpenSSHParserContext context, Collection<? extends PublicKey> publicKeys, NamedResource resourceKey,
FilePasswordProvider passwordProvider, InputStream stream) String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
if (GenericUtils.isEmpty(publicKeys)) { OpenSsh openSsh = decodeFromOpenSsh(rawKey);
return Collections.emptyList(); NamedParameterSpec params = NamedParameterSpec.ED25519;
} EdECPrivateKeySpec edECPrivateKeySpec = new EdECPrivateKeySpec(params, openSsh.privatekey);
byte[] arr = openSsh.publickey;
int check1 = KeyEntryResolver.decodeInt(stream); byte msb = arr[arr.length - 1];
int check2 = KeyEntryResolver.decodeInt(stream); boolean xOdd = (msb & 0x80) != 0;
arr[arr.length - 1] &= (byte) 0x7F;
/* ArrayUtil.reverse(arr);
* According to the documentation: BigInteger y = new BigInteger(1, arr);
* EdECPoint edECPoint = new EdECPoint(xOdd, y);
* Before the key is encrypted, a random integer is assigned to both checkint fields so successful decryption EdECPublicKeySpec edECPublicKeySpec = new EdECPublicKeySpec(params, edECPoint);
* can be quickly checked by verifying that both checkint fields hold the same value. KeyFactory keyFactory = KeyFactory.getInstance("EdDSA");
*/ KeyPair keyPair = new KeyPair(keyFactory.generatePublic(edECPublicKeySpec), keyFactory.generatePrivate(edECPrivateKeySpec));
if (check1 != check2) { return List.of(keyPair);
throw new StreamCorruptedException(
"Mismatched private key check values ("
+ Integer.toHexString(check1) + "/" + Integer.toHexString(check2) + ") in "
+ resourceKey);
}
List<KeyPair> keyPairs = new ArrayList<>(publicKeys.size());
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( private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException {
SessionContext session, NamedResource resourceKey, OpenSsh openSsh = new OpenSsh();
OpenSSHParserContext context, String keyType, openSsh.verify = Arrays.copyOfRange(rawKey, 0, OPENSSH_KEY_V1.length());
FilePasswordProvider passwordProvider, InputStream stream) if (!new String(openSsh.verify).equals(OPENSSH_KEY_V1)) {
throws IOException, GeneralSecurityException { throw new InvalidKeySpecException("invalid OpenSSH key file");
String prvType = KeyEntryResolver.decodeString(stream, MAX_KEY_TYPE_NAME_LENGTH);
if (!Objects.equals(keyType, prvType)) {
throw new StreamCorruptedException(
"Mismatched private key type: "
+ ", expected=" + keyType + ", actual=" + prvType
+ " in " + resourceKey);
} }
boolean occurred = false;
PrivateKeyEntryDecoder<?, ?> decoder = getPrivateKeyEntryDecoder(prvType); int index = 0;
if (decoder == null) { for (int i = 0; i < rawKey.length; i++) {
throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey); 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);
PrivateKey prvKey = decoder.decodePrivateKey(session, prvType, passwordProvider, stream); index += 32;
if (prvKey == null) { for (int i = index; i < rawKey.length; i++) {
throw new InvalidKeyException("Cannot parse key type (" + prvType + ") in " + resourceKey); 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);
String comment = KeyEntryResolver.decodeString(stream, MAX_KEY_COMMENT_LENGTH); return openSsh;
return new SimpleImmutableEntry<>(prvKey, comment);
} }
protected <S extends InputStream> S validateStreamMagicMarker( private static final String OPENSSH_KEY_V1 = "openssh-key-v1";
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(); private static class OpenSsh {
if (eos == -1) { byte[] verify;
throw new EOFException(resourceKey + ": Premature EOF after magic marker value"); byte[] publickey;
} byte[] privatekey;
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;
} }
} }

View file

@ -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)) {

View file

@ -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.

View file

@ -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,14 +67,18 @@ 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,
FilePasswordProvider passwordProvider, String endMarker,
InputStream stream, Map<String, String> headers) FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false); try (InputStream stream = new ByteArrayInputStream(rawKey)) {
return Collections.singletonList(kp); KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
return Collections.singletonList(kp);
}
} }
/** /**

View file

@ -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,15 +72,18 @@ 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,
FilePasswordProvider passwordProvider, String endMarker,
InputStream stream, Map<String, String> headers) FilePasswordProvider passwordProvider,
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(
@ -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());

View file

@ -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,10 +53,13 @@ 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)
KeyPair kp = parseEd25519KeyPair(stream, false); throws IOException, GeneralSecurityException {
return Collections.singletonList(kp); try (InputStream stream = new ByteArrayInputStream(rawKey)) {
KeyPair kp = parseEd25519KeyPair(stream, false);
return Collections.singletonList(kp);
}
} }
public static KeyPair parseEd25519KeyPair(InputStream inputStream, public static KeyPair parseEd25519KeyPair(InputStream inputStream,
@ -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);
} }

View file

@ -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),

View file

@ -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,14 +68,18 @@ 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,
FilePasswordProvider passwordProvider, String endMarker,
InputStream stream, Map<String, String> headers) FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false); try (InputStream stream = new ByteArrayInputStream(rawKey)) {
return Collections.singletonList(kp); KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
return Collections.singletonList(kp);
}
} }
/** /**

View file

@ -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();
} }
}; };

View file

@ -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

View file

@ -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;
}
}

View file

@ -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,47 +918,12 @@ public abstract class Buffer implements Readable {
putBytes(tmpBuffer.getCompactData()); putBytes(tmpBuffer.getCompactData());
putBytes(cert.getSignature()); putBytes(cert.getSignature());
} }
default -> throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm()); case EdECPublicKey edECPublicKey -> {
} byte[] b = edECPublicKey.getPoint().getY().toByteArray();
} ArrayUtil.reverse(b);
putBytes(b);
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); default -> throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm());
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());
} }
} }

View file

@ -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);
} }
} }

View file

@ -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) {

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -1,3 +1,3 @@
group = org.xbib group = org.xbib
name = files name = files
version = 4.6.0 version = 4.7.0

View file

@ -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()

View file

@ -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 {