Compare commits

...

5 Commits
stable ... main

@ -1,5 +1,6 @@
package org.xbib.files.jadaptive.sftp.test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.IOException;
@ -18,10 +19,11 @@ public class SftpFileSystemTest {
public SftpFileSystemTest() {
}
@Disabled("missing connection object")
@Test
public void testFileSystem() throws IOException {
Map<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()) {
logger.log(Level.INFO, "root dir = " + path);
}

@ -1,5 +1,4 @@
dependencies {
api project(':files-api')
api project(':files-sftp')
testImplementation libs.net.security
}

@ -4,7 +4,6 @@ module org.xbib.files.sftp.fs.test {
requires org.xbib.files;
requires org.xbib.files.sftp;
requires org.xbib.files.sftp.fs;
requires org.xbib.net.security;
exports org.apache.sshd.fs.test;
opens org.apache.sshd.fs.test to org.junit.platform.commons;
}

@ -13,7 +13,7 @@ public class FileServiceProviderTest {
@Test
public void testSFTP() throws Exception {
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!
try (Stream<Path> list = fs.list(".")) {
list.forEach(p -> Logger.getAnonymousLogger().info(p.toString()));

@ -1,40 +0,0 @@
package org.apache.sshd.fs.test;
import java.nio.file.Files;
import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.fs.SftpFileSystem;
import org.apache.sshd.fs.SftpFileSystemProvider;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.xbib.net.security.PrivateKeyReader;
public class SFTPFileSystemTest {
private static final Logger logger = Logger.getLogger(SFTPFileSystemTest.class.getName());
@Test
public void testXbib() throws Exception {
Map<String, String> env = new HashMap<>();
env.put("username", "joerg");
URI uri = URI.create("sftp://xbib.org");
SshClient sshClient = ClientBuilder.builder().build();
Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519");
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
KeyPair keyPair = privateKeyReader.generateFrom(Files.newInputStream(privateKeyPath), null);
sshClient.addPublicKeyIdentity(keyPair);
sshClient.setNioWorkers(1);
sshClient.start();
SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env);
fileSystem.close();
// ...
sshClient.stop();
sshClient.close();
}
}

@ -0,0 +1,41 @@
package org.apache.sshd.fs.test;
import java.nio.file.Files;
import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.fs.SftpFileSystem;
import org.apache.sshd.fs.SftpFileSystemProvider;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SftpWithPrivateKeyReaderTest {
private static final Logger logger = Logger.getLogger(SftpWithPrivateKeyReaderTest.class.getName());
@Test
public void testXbib() throws Exception {
Map<String, String> env = new HashMap<>();
env.put("username", "joerg");
URI uri = URI.create("sftp://alkmene");
try (SshClient sshClient = ClientBuilder.builder()
.build()) {
sshClient.setNioWorkers(1);
sshClient.start();
SftpFileSystem fileSystem = new SftpFileSystemProvider(sshClient).newFileSystem(uri, env);
assertTrue(Files.exists(fileSystem.getDefaultDir()));
try (Stream<Path> stream = Files.list(fileSystem.getDefaultDir())) {
stream.forEach(p -> logger.log(Level.INFO, "p = " + p));
}
fileSystem.close();
sshClient.stop();
}
}
}

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

@ -1,4 +1,5 @@
module org.xbib.files.sftp {
requires java.logging;
exports org.apache.sshd.client;
exports org.apache.sshd.client.auth;
exports org.apache.sshd.client.auth.password;
@ -77,9 +78,6 @@ module org.xbib.files.sftp {
exports org.apache.sshd.common.util.io.resource;
exports org.apache.sshd.common.util.net;
exports org.apache.sshd.common.util.security;
exports org.apache.sshd.common.util.security.eddsa;
exports org.apache.sshd.common.util.threads;
requires org.xbib.net.security;
requires java.logging;
uses org.apache.sshd.common.io.IoServiceFactoryFactory;
}

@ -42,7 +42,6 @@ import org.apache.sshd.common.signature.SignatureFactory;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
/**
@ -51,6 +50,7 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFactoriesManager {
public static final String NAME = UserAuthPublicKeyFactory.NAME;
protected Iterator<PublicKeyIdentity> keys;
@ -95,25 +95,19 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
} catch (Error e) {
throw new RuntimeSshException(e);
}
PublicKeyAuthenticationReporter reporter = session.getPublicKeyAuthenticationReporter();
if (current == null) {
if (reporter != null) {
reporter.signalAuthenticationExhausted(session, service);
}
return false;
}
KeyPair keyPair;
try {
keyPair = current.getKeyIdentity();
} catch (Error e) {
throw new RuntimeSshException(e);
}
PublicKey pubKey = keyPair.getPublic();
String keyType = KeyUtils.getKeyType(pubKey);
NamedFactory<? extends Signature> factory;
@ -124,14 +118,11 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
} else {
factory = SignatureFactory.resolveSignatureFactory(keyType, getSignatureFactories());
}
String algo = (factory == null) ? keyType : factory.getName();
String name = getName();
if (reporter != null) {
reporter.signalAuthenticationAttempt(session, service, keyPair, algo);
}
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
buffer.putString(session.getUsername());
buffer.putString(service);
@ -147,17 +138,14 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
if ((keys != null) && keys.hasNext()) {
return keys.next();
}
UserInteraction ui = session.getUserInteraction();
if ((ui == null) || (!ui.isInteractionAllowed(session))) {
return null;
}
KeyPair kp = ui.resolveAuthPublicKeyIdentityAttempt(session);
if (kp == null) {
return null;
}
return new KeyPairIdentity(this, session, kp);
}
@ -263,8 +251,6 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
} catch (Error e) {
throw new RuntimeSshException(e);
}
bs.clear();
bs.putString(algo);
bs.putBytes(sig);

@ -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>
*/
public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel {
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 {
super(session);
try {
Collection<Iterable<? extends PublicKeyIdentity>> identities = new ArrayList<>(2);
Collection<Iterable<? extends PublicKeyIdentity>> identities = new ArrayList<>();
Iterable<? extends PublicKeyIdentity> sessionIds = initializeSessionIdentities(session, signatureFactories);
if (sessionIds != null) {
identities.add(sessionIds);
}
if (identities.isEmpty()) {
current = Collections.emptyIterator();
} else {
@ -66,10 +67,9 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKey
}
}
@SuppressWarnings("checkstyle:anoninnerlength")
protected Iterable<KeyPairIdentity> initializeSessionIdentities(
ClientSession session, SignatureFactoriesManager signatureFactories) {
return new Iterable<KeyPairIdentity>() {
return new Iterable<>() {
private final String sessionId = session.toString();
private final AtomicReference<Iterable<KeyPair>> keysHolder = new AtomicReference<>();
@ -81,14 +81,11 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKey
KeyIdentityProvider sessionKeysProvider = ClientSession.providerOf(session);
keysHolder.set(sessionKeysProvider.loadKeys(session));
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(
"Unexpected " + e.getClass().getSimpleName() + ")"
+ " keys loading exception: " + e.getMessage(),
e);
throw new RuntimeException("Unexpected " + e.getClass().getSimpleName() + ")" + " keys loading exception: " + e.getMessage(), e);
}
}
return new Iterator<KeyPairIdentity>() {
return new Iterator<>() {
private final Iterator<KeyPair> keys;
{

@ -27,11 +27,12 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.BuiltinIdentities;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.FilePasswordProviderHolder;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
@ -41,13 +42,19 @@ import org.apache.sshd.common.util.GenericUtils;
public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher {
private final boolean supportedOnly;
public BuiltinClientIdentitiesWatcher(Path keysFolder, boolean supportedOnly,
ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
public BuiltinClientIdentitiesWatcher(Path keysFolder,
boolean supportedOnly,
ClientIdentityLoader loader,
FilePasswordProvider provider,
boolean strict) {
this(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES), supportedOnly, loader, provider, strict);
}
public BuiltinClientIdentitiesWatcher(Path keysFolder, Collection<String> ids, boolean supportedOnly,
ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
public BuiltinClientIdentitiesWatcher(Path keysFolder,
Collection<String> ids,
boolean supportedOnly,
ClientIdentityLoader loader,
FilePasswordProvider provider, boolean strict) {
this(keysFolder, ids, supportedOnly,
ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")),
FilePasswordProviderHolder.providerHolderOf(Objects.requireNonNull(provider, "No password provider")),
@ -90,18 +97,17 @@ public class BuiltinClientIdentitiesWatcher extends ClientIdentitiesWatcher {
return getBuiltinIdentitiesPaths(keysFolder, NamedResource.getNameList(BuiltinIdentities.VALUES));
}
public static List<Path> getBuiltinIdentitiesPaths(Path keysFolder, Collection<String> ids) {
public static List<Path> getBuiltinIdentitiesPaths(Path keysFolder,
Collection<String> ids) {
Objects.requireNonNull(keysFolder, "No keys folder");
if (GenericUtils.isEmpty(ids)) {
return Collections.emptyList();
}
List<Path> paths = new ArrayList<>(ids.size());
for (String id : ids) {
String fileName = ClientIdentity.getIdentityFileName(id);
paths.add(keysFolder.resolve(fileName));
}
return paths;
}
}

@ -31,16 +31,21 @@ import org.apache.sshd.common.config.keys.PublicKeyEntry;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatcher {
public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider) {
public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader,
FilePasswordProvider provider) {
this(loader, provider, true);
}
public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader, FilePasswordProvider provider, boolean strict) {
public DefaultClientIdentitiesWatcher(ClientIdentityLoader loader,
FilePasswordProvider provider,
boolean strict) {
this(true, loader, provider, strict);
}
public DefaultClientIdentitiesWatcher(
boolean supportedOnly, ClientIdentityLoader loader, FilePasswordProvider provider,
public DefaultClientIdentitiesWatcher(boolean supportedOnly,
ClientIdentityLoader loader,
FilePasswordProvider provider,
boolean strict) {
this(supportedOnly,
ClientIdentityLoaderHolder.loaderHolderOf(Objects.requireNonNull(loader, "No client identity loader")),
@ -48,17 +53,20 @@ public class DefaultClientIdentitiesWatcher extends BuiltinClientIdentitiesWatch
strict);
}
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider) {
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader,
FilePasswordProviderHolder provider) {
this(loader, provider, true);
}
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider,
public DefaultClientIdentitiesWatcher(ClientIdentityLoaderHolder loader,
FilePasswordProviderHolder provider,
boolean strict) {
this(true, loader, provider, strict);
}
public DefaultClientIdentitiesWatcher(boolean supportedOnly,
ClientIdentityLoaderHolder loader, FilePasswordProviderHolder provider,
ClientIdentityLoaderHolder loader,
FilePasswordProviderHolder provider,
boolean strict) {
super(PublicKeyEntry.getDefaultKeysFolderPath(), supportedOnly, loader, provider, strict);
}

@ -198,7 +198,7 @@ public enum ECCurves implements KeyTypeIndicator, KeySizeIndicator, NamedResourc
@Override
public final boolean isSupported() {
return SecurityUtils.isECCSupported() && digestFactory.isSupported();
return digestFactory.isSupported();
}
public final ECParameterSpec getParameters() {

@ -27,6 +27,8 @@ import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.EdECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collection;
@ -43,29 +45,20 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyTypeIndicator;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public enum BuiltinIdentities implements Identity {
RSA(Constants.RSA, RSAPublicKey.class, RSAPrivateKey.class, KeyPairProvider.SSH_RSA),
DSA(Constants.DSA, DSAPublicKey.class, DSAPrivateKey.class, KeyPairProvider.SSH_DSS),
RSA(Constants.RSA, RSAPublicKey.class, RSAPrivateKey.class, KeyPairProvider.SSH_RSA),
ECDSA(Constants.ECDSA, KeyUtils.EC_ALGORITHM, ECPublicKey.class, ECPrivateKey.class,
ECCurves.VALUES.stream().map(KeyTypeIndicator::getKeyType).collect(Collectors.toList())) {
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
ED25119(Constants.ED25519, SecurityUtils.EDDSA,
SecurityUtils.getEDDSAPublicKeyType(),
SecurityUtils.getEDDSAPrivateKeyType(),
KeyPairProvider.SSH_ED25519) {
@Override
public boolean isSupported() {
return SecurityUtils.isEDDSACurveSupported();
}
ED25119(Constants.ED25519, "EdDSA",
EdECPublicKey.class,
EdECPrivateKey.class,
"ssh-ed25519") {
};
public static final Set<BuiltinIdentities> VALUES = Collections.unmodifiableSet(EnumSet.allOf(BuiltinIdentities.class));

@ -39,6 +39,9 @@ import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.EdECKey;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.EdECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
@ -68,8 +71,9 @@ import org.apache.sshd.common.Factory;
import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.Ed25519PublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder;
import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder;
import org.apache.sshd.common.config.keys.impl.RSAPublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest;
@ -156,16 +160,11 @@ public final class KeyUtils {
static {
registerPublicKeyEntryDecoder(OpenSSHCertificateDecoder.INSTANCE);
registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
registerPublicKeyEntryDecoder(RSAPublicKeyEntryDecoder.INSTANCE);
registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
if (SecurityUtils.isECCSupported()) {
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
}
if (SecurityUtils.isEDDSACurveSupported()) {
registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder());
}
registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
registerPublicKeyEntryDecoder(SkECDSAPublicKeyEntryDecoder.INSTANCE);
registerPublicKeyEntryDecoder(Ed25519PublicKeyEntryDecoder.INSTANCE);
}
private KeyUtils() {
@ -810,7 +809,7 @@ public final class KeyUtils {
} else {
return curve.getKeyType();
}
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
} else if (key instanceof EdECKey) {
return KeyPairProvider.SSH_ED25519;
} else if (key instanceof OpenSshCertificate) {
return ((OpenSshCertificate) key).getKeyType();
@ -946,10 +945,9 @@ public final class KeyUtils {
if (curve != null) {
return curve.getKeySize();
}
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
return SecurityUtils.getEDDSAKeySize(key);
} else if (key instanceof EdECKey) {
return 256;
}
return -1;
}
@ -1005,9 +1003,8 @@ public final class KeyUtils {
return compareECKeys(ECPublicKey.class.cast(k1), ECPublicKey.class.cast(k2));
} else if ((k1 instanceof SkEcdsaPublicKey) && (k2 instanceof SkEcdsaPublicKey)) {
return compareSkEcdsaKeys(SkEcdsaPublicKey.class.cast(k1), SkEcdsaPublicKey.class.cast(k2));
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
} else if ((k1 instanceof EdECPublicKey) && (k2 instanceof EdECPublicKey)) {
return compareEDDSAPPublicKeys(k1, k2);
} else {
return false; // either key is null or not of same class
}
@ -1030,9 +1027,8 @@ public final class KeyUtils {
return compareDSAKeys(DSAPrivateKey.class.cast(k1), DSAPrivateKey.class.cast(k2));
} else if ((k1 instanceof ECPrivateKey) && (k2 instanceof ECPrivateKey)) {
return compareECKeys(ECPrivateKey.class.cast(k1), ECPrivateKey.class.cast(k2));
} else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
&& (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
return SecurityUtils.compareEDDSAPrivateKeys(k1, k2);
} else if ((k1 instanceof EdECPrivateKey) && (k2 instanceof EdECPrivateKey)) {
return compareEDDSAPrivateKeys(k1, k2);
} else {
return false; // either key is null or not of same class
}
@ -1177,4 +1173,12 @@ public final class KeyUtils {
&& compareECKeys(k1.getDelegatePublicKey(), k2.getDelegatePublicKey());
}
}
public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
return Arrays.equals(k1.getEncoded(), k2.getEncoded());
}
public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
return Arrays.equals(k1.getEncoded(), k2.getEncoded());
}
}

@ -46,15 +46,13 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
extends KeyEntryResolver<PUB, PRV>, PrivateKeyEntryResolver {
@Override
default PrivateKey resolve(
SessionContext session, String keyType, byte[] keyData)
default PrivateKey resolve(SessionContext session, String keyType, byte[] keyData)
throws IOException, GeneralSecurityException {
ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
Collection<String> supported = getSupportedKeyTypes();
if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) {
return decodePrivateKey(session, FilePasswordProvider.EMPTY, keyData);
}
throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported);
}
@ -87,20 +85,19 @@ public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends Priva
}
}
default PRV decodePrivateKey(
SessionContext session, FilePasswordProvider passwordProvider, InputStream keyData)
default PRV decodePrivateKey(SessionContext session,
FilePasswordProvider passwordProvider,
InputStream keyData)
throws IOException, GeneralSecurityException {
// the actual data is preceded by a string that repeats the key type
String type = KeyEntryResolver.decodeString(keyData, KeyPairResourceLoader.MAX_KEY_TYPE_NAME_LENGTH);
if (GenericUtils.isEmpty(type)) {
throw new StreamCorruptedException("Missing key type string");
}
Collection<String> supported = getSupportedKeyTypes();
if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) {
throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported);
}
return decodePrivateKey(session, type, passwordProvider, keyData);
}

@ -27,7 +27,6 @@ import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
@ -77,10 +76,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
}
ECPublicKey decodePublicKey(ECCurves curve, InputStream keyData) throws IOException, GeneralSecurityException {
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
String keyCurveName = curve.getName();
// see rfc5656 section 3.1
String encCurveName = KeyEntryResolver.decodeString(keyData, MAX_CURVE_NAME_LENGTH);
@ -109,10 +104,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
@Override
public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
if (key == null) {
return null;
}
@ -127,10 +118,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
@Override
public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
if (key == null) {
return null;
}
@ -164,11 +151,7 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
@Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
if (SecurityUtils.isECCSupported()) {
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
} else {
throw new NoSuchProviderException("ECC not supported");
}
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
}
@Override
@ -185,10 +168,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC
@Override
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
if (SecurityUtils.isECCSupported()) {
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
} else {
throw new NoSuchProviderException("ECC not supported");
}
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
}
}

@ -0,0 +1,82 @@
package org.apache.sshd.common.config.keys.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.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.Collections;
import java.util.Map;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.ArrayUtil;
public final class Ed25519PublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
public static final Ed25519PublicKeyEntryDecoder INSTANCE = new Ed25519PublicKeyEntryDecoder();
private Ed25519PublicKeyEntryDecoder() {
super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
}
@Override
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
return KeyPairGenerator.getInstance("Ed25519");
}
@Override
public String encodePublicKey(OutputStream s, EdECPublicKey key) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
return KeyFactory.getInstance("EdDSA");
}
@Override
public EdECPublicKey decodePublicKey(SessionContext session,
String keyType,
InputStream keyData,
Map<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
public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
EdECPoint edECPoint = key.getPoint();
return generatePublicKey(new EdECPublicKeySpec(namedParameterSpec, edECPoint));
}
}
@Override
public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
return generatePrivateKey(new EdECPrivateKeySpec(namedParameterSpec, key.getEncoded()));
}
}
}

@ -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>
*/
public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder();
public class RSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey, RSAPrivateKey> {
public static final RSAPublicKeyEntryDecoder INSTANCE = new RSAPublicKeyEntryDecoder();
public RSAPublicKeyDecoder() {
public RSAPublicKeyEntryDecoder() {
super(RSAPublicKey.class, RSAPrivateKey.class,
Collections.unmodifiableList(
Arrays.asList(KeyPairProvider.SSH_RSA,

@ -19,9 +19,7 @@
package org.apache.sshd.common.config.keys.loader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
@ -44,6 +42,7 @@ import org.apache.sshd.common.util.ValidateUtils;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public abstract class AbstractKeyPairResourceParser implements KeyPairResourceParser {
private final List<String> beginners;
private final List<String> enders;
private final List<List<String>> endingMarkers;
@ -86,10 +85,12 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
}
@Override
public Collection<KeyPair> loadKeyPairs(
SessionContext session, NamedResource resourceKey, FilePasswordProvider passwordProvider, List<String> lines)
public Collection<KeyPair> loadKeyPairs(SessionContext session,
NamedResource resourceKey,
FilePasswordProvider passwordProvider,
List<String> lines)
throws IOException, GeneralSecurityException {
Collection<KeyPair> keyPairs = Collections.emptyList();
Collection<KeyPair> keyPairs = new LinkedList<>();
List<String> beginMarkers = getBeginners();
List<List<String>> endMarkers = getEndingMarkers();
for (Map.Entry<Integer, Integer> markerPos = KeyPairResourceParser.findMarkerLine(lines, beginMarkers);
@ -97,22 +98,19 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
int startIndex = markerPos.getKey();
String startLine = lines.get(startIndex);
startIndex++;
int markerIndex = markerPos.getValue();
List<String> ender = endMarkers.get(markerIndex);
markerPos = KeyPairResourceParser.findMarkerLine(lines, startIndex, ender);
if (markerPos == null) {
throw new StreamCorruptedException("Missing end marker (" + ender + ") after line #" + startIndex);
}
int endIndex = markerPos.getKey();
String endLine = lines.get(endIndex);
Map.Entry<? extends Map<String, String>, ? extends List<String>> result = separateDataLinesFromHeaders(
session, resourceKey, startLine, endLine, lines.subList(startIndex, endIndex));
Map<String, String> headers = result.getKey();
List<String> dataLines = result.getValue();
Collection<KeyPair> kps = extractKeyPairs(
session, resourceKey, startLine, endLine, passwordProvider,
Collection<KeyPair> kps = extractKeyPairs(session, resourceKey, startLine, endLine, passwordProvider,
(dataLines == null) ? Collections.emptyList() : dataLines,
(headers == null) ? Collections.emptyMap() : headers);
if (GenericUtils.isNotEmpty(kps)) {
@ -122,11 +120,8 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
keyPairs.addAll(kps);
}
}
// see if there are more
markerPos = KeyPairResourceParser.findMarkerLine(lines, endIndex + 1, beginMarkers);
}
return keyPairs;
}
@ -153,11 +148,13 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
* @throws IOException If failed to parse the data
* @throws GeneralSecurityException If failed to generate the keys
*/
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
List<String> lines, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
List<String> lines,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
byte[] dataBytes = KeyPairResourceParser.extractDataBytes(lines);
try {
@ -175,42 +172,18 @@ public abstract class AbstractKeyPairResourceParser implements KeyPairResourcePa
* @param endMarker The line containing the end marker
* @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be
* {@code null} if no encrypted
* @param bytes The decoded bytes from the lines containing the data
* @param headers Any headers that may have been available when data was read
* @return The extracted {@link KeyPair}s - may be {@code null}/empty if none.
* @throws IOException If failed to parse the data
* @throws GeneralSecurityException If failed to generate the keys
*/
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
byte[] bytes, Map<String, String> headers)
throws IOException, GeneralSecurityException {
try (InputStream bais = new ByteArrayInputStream(bytes)) {
return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers);
}
}
/**
* @param session The {@link SessionContext} for invoking this load command - may be {@code null}
* if not invoked within a session context (e.g., offline tool or session unknown).
* @param resourceKey A hint as to the origin of the text lines
* @param beginMarker The line containing the begin marker
* @param endMarker The line containing the end marker
* @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be
* {@code null} if no encrypted
* @param stream The decoded data {@link InputStream}
* @param bytes The bytes from the lines containing the data
* @param headers Any headers that may have been available when data was read
* @return The extracted {@link KeyPair}s - may be {@code null}/empty if none.
* @throws IOException If failed to parse the data
* @throws GeneralSecurityException If failed to generate the keys
*/
public abstract Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
public abstract Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
byte[] bytes,
Map<String, String> headers)
throws IOException, GeneralSecurityException;
}

@ -64,11 +64,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
if (curve == null) {
throw new InvalidKeySpecException("Not an EC curve name: " + keyType);
}
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
String keyCurveName = curve.getName();
// see rfc5656 section 3.1
String encCurveName = KeyEntryResolver.decodeString(keyData, ECDSAPublicKeyEntryDecoder.MAX_CURVE_NAME_LENGTH);
@ -117,10 +112,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
@Override
public ECPublicKey clonePublicKey(ECPublicKey key) throws GeneralSecurityException {
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
if (key == null) {
return null;
}
@ -135,10 +126,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
@Override
public ECPrivateKey clonePrivateKey(ECPrivateKey key) throws GeneralSecurityException {
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
if (key == null) {
return null;
}
@ -153,11 +140,7 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
@Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
if (SecurityUtils.isECCSupported()) {
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
} else {
throw new NoSuchProviderException("ECC not supported");
}
return SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
}
@Override
@ -174,10 +157,6 @@ public class OpenSSHECDSAPrivateKeyEntryDecoder extends AbstractPrivateKeyEntryD
@Override
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
if (SecurityUtils.isECCSupported()) {
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
} else {
throw new NoSuchProviderException("ECC not supported");
}
return SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
}
}

@ -0,0 +1,164 @@
package org.apache.sshd.common.config.keys.loader.openssh;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.EdECPublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPrivateKeySpec;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import java.util.Arrays;
import java.util.Collections;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
import org.apache.sshd.common.util.io.der.ASN1Object;
import org.apache.sshd.common.util.io.der.ASN1Type;
import org.apache.sshd.common.util.io.der.DERParser;
public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<EdECPublicKey, EdECPrivateKey> {
public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder();
public OpenSSHEd25519PrivateKeyEntryDecoder() {
super(EdECPublicKey.class, EdECPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_ED25519));
}
@Override
public EdECPrivateKey decodePrivateKey(SessionContext session,
String keyType,
FilePasswordProvider passwordProvider,
InputStream keyData)
throws IOException, GeneralSecurityException {
if (!"ssh-ed25519".equals(keyType)) {
throw new InvalidKeyException("Unsupported key type: " + keyType);
}
byte[] b = keyData.readAllBytes();
OpenSsh openSsh = decodeFromOpenSsh(b);
NamedParameterSpec params = NamedParameterSpec.ED25519;
EdECPrivateKeySpec spec = new EdECPrivateKeySpec(params, openSsh.privatekey);
KeyFactory factory = KeyFactory.getInstance("EdDSA");
return (EdECPrivateKey) factory.generatePrivate(spec);
}
@Override
public String encodePrivateKey(SecureByteArrayOutputStream s,
EdECPrivateKey key,
EdECPublicKey pubKey) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException {
throw new UnsupportedOperationException();
}
@Override
public EdECPublicKey clonePublicKey(EdECPublicKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
EdECPoint edECPoint = key.getPoint();
EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint);
return generatePublicKey(spec);
}
}
@Override
public EdECPrivateKey clonePrivateKey(EdECPrivateKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
try (DERParser parser = new DERParser(key.getEncoded())) {
ASN1Object obj = parser.readObject();
if (obj == null) {
throw new StreamCorruptedException("Missing key data container");
}
ASN1Type objType = obj.getObjType();
if (objType != ASN1Type.OCTET_STRING) {
throw new StreamCorruptedException("Mismatched key data container type: " + objType);
}
NamedParameterSpec spec = NamedParameterSpec.ED25519;
EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, obj.getValue());
KeyFactory factory = KeyFactory.getInstance("EdDSA");
return (EdECPrivateKey) factory.generatePrivate(keySpec);
} catch (IOException e) {
throw new GeneralSecurityException(e);
}
}
}
@Override
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
return KeyPairGenerator.getInstance("Ed25519");
}
@Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
return KeyFactory.getInstance("EdDSA");
}
private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException {
OpenSsh openSsh = new OpenSsh();
openSsh.verify = Arrays.copyOfRange(rawKey, 0, OPENSSH_KEY_V1.length());
if (!new String(openSsh.verify).equals(OPENSSH_KEY_V1)) {
throw new InvalidKeySpecException("invalid OpenSSH key file");
}
boolean occurred = false;
int index = 0;
for (int i = 0; i < rawKey.length; i++) {
if (rawKey[i] == 's'
&& rawKey[i + 1] == 's'
&& rawKey[i + 2] == 'h'
&& rawKey[i + 3] == '-'
&& rawKey[i + 4] == 'e'
&& rawKey[i + 5] == 'd'
&& rawKey[i + 6] == '2'
&& rawKey[i + 7] == '5'
&& rawKey[i + 8] == '5'
&& rawKey[i + 9] == '1'
&& rawKey[i + 10] == '9'
&& rawKey[i + 11] == 0x00
&& rawKey[i + 12] == 0x00
&& rawKey[i + 13] == 0x00
&& rawKey[i + 14] == ' ') {
index = i + 15;
if (occurred) {
break;
}
occurred = true;
}
}
openSsh.publickey = Arrays.copyOfRange(rawKey, index, index + 32);
index += 32;
for (int i = index; i < rawKey.length; i++) {
if (rawKey[i] == 0x00
&& rawKey[i + 1] == 0x00
&& rawKey[i + 2] == 0x00
&& rawKey[i + 3] == '@') {
index = i + 4;
break;
}
}
openSsh.privatekey = Arrays.copyOfRange(rawKey, index, index + 32);
return openSsh;
}
private static final String OPENSSH_KEY_V1 = "openssh-key-v1";
private static class OpenSsh {
byte[] verify;
byte[] publickey;
byte[] privatekey;
}
}

@ -18,22 +18,17 @@
*/
package org.apache.sshd.common.config.keys.loader.openssh;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.net.ProtocolException;
import java.nio.charset.StandardCharsets;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPrivateKeySpec;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -43,24 +38,14 @@ import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import javax.security.auth.login.FailedLoginException;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult;
import org.apache.sshd.common.config.keys.KeyEntryResolver;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser;
import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
import org.apache.sshd.common.config.keys.loader.openssh.kdf.RawKdfOptions;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.ArrayUtil;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* Basic support for <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>
*/
public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser {
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 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 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
= new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private static final Map<Class<?>, PrivateKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP = new HashMap<>();
static {
registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyDecoder.INSTANCE);
registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyEntryDecoder.INSTANCE);
registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE);
if (SecurityUtils.isECCSupported()) {
registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
}
if (SecurityUtils.isEDDSACurveSupported()) {
registerPrivateKeyEntryDecoder(SecurityUtils.getOpenSSHEDDSAPrivateKeyEntryDecoder());
}
registerPrivateKeyEntryDecoder(OpenSSHECDSAPrivateKeyEntryDecoder.INSTANCE);
registerPrivateKeyEntryDecoder(OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE);
}
public OpenSSHKeyPairResourceParser() {
super(BEGINNERS, ENDERS);
}
/**
* Ed25519 only. No password.
*
* @param session The {@link SessionContext} for invoking this load command - may be {@code null}
* if not invoked within a session context (e.g., offline tool or session unknown).
* @param resourceKey A hint as to the origin of the text lines
* @param beginMarker The line containing the begin marker
* @param endMarker The line containing the end marker
* @param passwordProvider The {@link FilePasswordProvider} to use in case the data is encrypted - may be
* {@code null} if no encrypted
* @param rawKey The bytes from the lines containing the data
* @param headers Any headers that may have been available when data was read
* @return
* @throws IOException
* @throws GeneralSecurityException
*/
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
OpenSsh openSsh = decodeFromOpenSsh(rawKey);
NamedParameterSpec params = NamedParameterSpec.ED25519;
EdECPrivateKeySpec edECPrivateKeySpec = new EdECPrivateKeySpec(params, openSsh.privatekey);
byte[] arr = openSsh.publickey;
byte msb = arr[arr.length - 1];
boolean xOdd = (msb & 0x80) != 0;
arr[arr.length - 1] &= (byte) 0x7F;
ArrayUtil.reverse(arr);
BigInteger y = new BigInteger(1, arr);
EdECPoint edECPoint = new EdECPoint(xOdd, y);
EdECPublicKeySpec edECPublicKeySpec = new EdECPublicKeySpec(params, edECPoint);
KeyFactory keyFactory = KeyFactory.getInstance("EdDSA");
KeyPair keyPair = new KeyPair(keyFactory.generatePublic(edECPublicKeySpec), keyFactory.generatePrivate(edECPrivateKeySpec));
return List.of(keyPair);
}
stream = validateStreamMagicMarker(session, resourceKey, stream);
String cipher = KeyEntryResolver.decodeString(stream, MAX_CIPHER_NAME_LENGTH);
OpenSSHKdfOptions kdfOptions = resolveKdfOptions(session, resourceKey, beginMarker, endMarker, stream, headers);
OpenSSHParserContext context = new OpenSSHParserContext(cipher, kdfOptions);
int numKeys = KeyEntryResolver.decodeInt(stream);
if (numKeys <= 0) {
return Collections.emptyList();
}
List<PublicKey> publicKeys = new ArrayList<>(numKeys);
for (int index = 1; index <= numKeys; index++) {
PublicKey pubKey = readPublicKey(session, resourceKey, context, stream, headers);
ValidateUtils.checkNotNull(pubKey, "Empty public key #%d in %s", index, resourceKey);
publicKeys.add(pubKey);
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");
}
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);
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;
}
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);
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;
}
return decoder.decodePublicKey(session, keyType, bais, headers);
}
openSsh.privatekey = Arrays.copyOfRange(rawKey, index, index + 32);
return openSsh;
}
/*
* NOTE: called AFTER decrypting the original bytes, however we still propagate the password provider - just in case
* some "sub-encryption" is detected
*/
protected List<KeyPair> readPrivateKeys(
SessionContext session, NamedResource resourceKey,
OpenSSHParserContext context, Collection<? extends PublicKey> publicKeys,
FilePasswordProvider passwordProvider, InputStream stream)
throws IOException, GeneralSecurityException {
if (GenericUtils.isEmpty(publicKeys)) {
return Collections.emptyList();
}
int check1 = KeyEntryResolver.decodeInt(stream);
int check2 = KeyEntryResolver.decodeInt(stream);
/*
* According to the documentation:
*
* Before the key is encrypted, a random integer is assigned to both checkint fields so successful decryption
* can be quickly checked by verifying that both checkint fields hold the same value.
*/
if (check1 != check2) {
throw new StreamCorruptedException(
"Mismatched private key check values ("
+ Integer.toHexString(check1) + "/" + Integer.toHexString(check2) + ") in "
+ resourceKey);
}
List<KeyPair> keyPairs = new ArrayList<>(publicKeys.size());
for (PublicKey pubKey : publicKeys) {
String pubType = KeyUtils.getKeyType(pubKey);
int keyIndex = keyPairs.size() + 1;
private static final String OPENSSH_KEY_V1 = "openssh-key-v1";
Map.Entry<PrivateKey, String> prvData
= readPrivateKey(session, resourceKey, context, pubType, passwordProvider, stream);
PrivateKey prvKey = (prvData == null) ? null : prvData.getKey();
ValidateUtils.checkNotNull(prvKey, "Empty private key #%d in %s", keyIndex, resourceKey);
String prvType = KeyUtils.getKeyType(prvKey);
ValidateUtils.checkTrue(Objects.equals(pubType, prvType),
"Mismatched public (%s) vs. private (%s) key type #%d in %s",
pubType, prvType, keyIndex, resourceKey);
keyPairs.add(new KeyPair(pubKey, prvKey));
}
return keyPairs;
}
protected Map.Entry<PrivateKey, String> readPrivateKey(
SessionContext session, NamedResource resourceKey,
OpenSSHParserContext context, String keyType,
FilePasswordProvider passwordProvider, InputStream stream)
throws IOException, GeneralSecurityException {
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);
}
PrivateKeyEntryDecoder<?, ?> decoder = getPrivateKeyEntryDecoder(prvType);
if (decoder == null) {
throw new NoSuchAlgorithmException("Unsupported key type (" + prvType + ") in " + resourceKey);
}
PrivateKey prvKey = decoder.decodePrivateKey(session, prvType, passwordProvider, stream);
if (prvKey == null) {
throw new InvalidKeyException("Cannot parse key type (" + prvType + ") in " + resourceKey);
}
String comment = KeyEntryResolver.decodeString(stream, MAX_KEY_COMMENT_LENGTH);
return new SimpleImmutableEntry<>(prvKey, comment);
}
protected <S extends InputStream> S validateStreamMagicMarker(
SessionContext session, NamedResource resourceKey, S stream)
throws IOException {
byte[] actual = new byte[AUTH_MAGIC_BYTES.length];
IoUtils.readFully(stream, actual);
if (!Arrays.equals(AUTH_MAGIC_BYTES, actual)) {
throw new StreamCorruptedException(
resourceKey + ": Mismatched magic marker value: " + BufferUtils.toHex(':', actual));
}
int eos = stream.read();
if (eos == -1) {
throw new EOFException(resourceKey + ": Premature EOF after magic marker value");
}
if (eos != 0) {
throw new StreamCorruptedException(
resourceKey + ": Missing EOS after magic marker value: 0x" + Integer.toHexString(eos));
}
return stream;
private static class OpenSsh {
byte[] verify;
byte[] publickey;
byte[] privatekey;
}
/**
@ -341,7 +198,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
for (String n : names) {
PrivateKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
if (prev != null) {
// noinspection UnnecessaryContinue
continue; // debug breakpoint
}
}
@ -404,7 +260,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
return null;
}
synchronized (BY_KEY_TYPE_DECODERS_MAP) {
PrivateKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
if (decoder != null) {
@ -420,7 +275,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
}
}
}
return null;
}
}

@ -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>
*/
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() {
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
public RSAPrivateKey decodePrivateKey(
SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
public RSAPrivateKey decodePrivateKey(SessionContext session,
String keyType,
FilePasswordProvider passwordProvider,
InputStream keyData)
throws IOException, GeneralSecurityException {
if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
throw new InvalidKeySpecException("Unexpected key type: " + keyType);
}
BigInteger n = KeyEntryResolver.decodeBigInt(keyData);
BigInteger e = KeyEntryResolver.decodeBigInt(keyData);
if (!Objects.equals(e, DEFAULT_PUBLIC_EXPONENT)) {

@ -18,9 +18,7 @@
*/
package org.apache.sshd.common.config.keys.loader.pem;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.net.ProtocolException;
import java.security.GeneralSecurityException;
@ -59,8 +57,10 @@ public abstract class AbstractPEMResourceKeyPairParser
private final String algo;
private final String algId;
protected AbstractPEMResourceKeyPairParser(
String algo, String algId, List<String> beginners, List<String> enders) {
protected AbstractPEMResourceKeyPairParser(String algo,
String algId,
List<String> beginners,
List<String> enders) {
super(beginners, enders);
this.algo = ValidateUtils.checkNotNullAndNotEmpty(algo, "No encryption algorithm provided");
this.algId = ValidateUtils.checkNotNullAndNotEmpty(algId, "No algorithm identifier provided");
@ -166,10 +166,8 @@ public abstract class AbstractPEMResourceKeyPairParser
try {
encryptedData = KeyPairResourceParser.extractDataBytes(dataLines);
decodedData = applyPrivateKeyCipher(encryptedData, encContext, false);
try (InputStream bais = new ByteArrayInputStream(decodedData)) {
keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais,
keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, decodedData,
headers);
}
} finally {
Arrays.fill(encryptedData, (byte) 0); // get rid of sensitive data a.s.a.p.
Arrays.fill(decodedData, (byte) 0); // get rid of sensitive data a.s.a.p.

@ -19,6 +19,7 @@
package org.apache.sshd.common.config.keys.loader.pem;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
@ -66,14 +67,18 @@ public class DSSPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
}
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
return Collections.singletonList(kp);
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
KeyPair kp = decodeDSSKeyPair(SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM), stream, false);
return Collections.singletonList(kp);
}
}
/**

@ -19,6 +19,7 @@
package org.apache.sshd.common.config.keys.loader.pem;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
@ -26,7 +27,6 @@ import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchProviderException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
@ -55,10 +55,10 @@ import org.apache.sshd.common.util.security.SecurityUtils;
*/
public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
public static final String BEGIN_MARKER = "BEGIN EC PRIVATE KEY";
public static final List<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 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>
@ -72,15 +72,18 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
}
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
KeyPair kp = parseECKeyPair(stream, false);
return Collections.singletonList(kp);
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
KeyPair kp = parseECKeyPair(stream, false);
return Collections.singletonList(kp);
}
}
public static KeyPair parseECKeyPair(
@ -104,10 +107,6 @@ public class ECDSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
throws IOException, GeneralSecurityException {
ASN1Object sequence = parser.readObject();
Map.Entry<ECPublicKeySpec, ECPrivateKeySpec> spec = decodeECPrivateKeySpec(curve, sequence);
if (!SecurityUtils.isECCSupported()) {
throw new NoSuchProviderException("ECC not supported");
}
KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM);
ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(spec.getKey());
ECPrivateKey prvKey = (ECPrivateKey) kf.generatePrivate(spec.getValue());

@ -1,24 +1,6 @@
/*
* 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;
package org.apache.sshd.common.config.keys.loader.pem;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
@ -26,38 +8,33 @@ import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
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.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.ArrayUtil;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.io.NoCloseInputStream;
import org.apache.sshd.common.util.io.der.ASN1Object;
import org.apache.sshd.common.util.io.der.ASN1Type;
import org.apache.sshd.common.util.io.der.DERParser;
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 String BEGIN_MARKER = "BEGIN OPENSSH 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 String END_MARKER = "END OPENSSH PRIVATE KEY";
public static final List<String> ENDERS = Collections.singletonList(END_MARKER);
/**
@ -68,24 +45,27 @@ public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParse
public static final Ed25519PEMResourceKeyParser INSTANCE = new Ed25519PEMResourceKeyParser();
public Ed25519PEMResourceKeyParser() {
super(EdDSAKey.KEY_ALGORITHM, ED25519_OID, BEGINNERS, ENDERS);
super("Ed25519", 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)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
KeyPair kp = parseEd25519KeyPair(stream, false);
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, boolean okToClose)
throws IOException, GeneralSecurityException {
public static KeyPair parseEd25519KeyPair(InputStream inputStream,
boolean okToClose) throws IOException, GeneralSecurityException {
try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
return parseEd25519KeyPair(parser);
return parseKeyPair(parser);
}
}
@ -103,80 +83,80 @@ public class Ed25519PEMResourceKeyParser extends AbstractPEMResourceKeyPairParse
* 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 {
private static KeyPair parseKeyPair(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());
return decodeKeyPair(obj.getValue());
}
public static KeyPair decodeEd25519KeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
EdDSAPrivateKey privateKey = decodeEdDSAPrivateKey(keyData);
EdDSAPublicKey publicKey = EdDSASecurityProviderUtils.recoverEDDSAPublicKey(privateKey);
public static KeyPair decodeKeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
EdECPrivateKey privateKey = getPrivateKey(keyData);
EdECPublicKey publicKey = getPublicKey(keyData);
return new KeyPair(publicKey, privateKey);
}
public static EdDSAPrivateKey decodeEdDSAPrivateKey(byte[] keyData) throws IOException, GeneralSecurityException {
private static EdECPrivateKey getPrivateKey(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());
return generatePrivateKey(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));
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) factory.generatePrivate(keySpec);
}
private static EdECPublicKey getPublicKey(byte[] arr) throws GeneralSecurityException {
byte msb = arr[arr.length - 1];
boolean xOdd = (msb & 0x80) != 0;
arr[arr.length - 1] &= (byte) 0x7F;
ArrayUtil.reverse(arr);
BigInteger y = new BigInteger(1, arr);
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
EdECPoint edECPoint = new EdECPoint(xOdd, y);
EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(namedParameterSpec, edECPoint);
KeyFactory factory = KeyFactory.getInstance("EdDSA");
return (EdECPublicKey) factory.generatePublic(publicKeySpec);
}
}

@ -20,7 +20,6 @@
package org.apache.sshd.common.config.keys.loader.pem;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
@ -40,12 +39,10 @@ import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.io.der.ASN1Object;
import org.apache.sshd.common.util.io.der.ASN1Type;
import org.apache.sshd.common.util.io.der.DERParser;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.common.util.security.eddsa.Ed25519PEMResourceKeyParser;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@ -54,10 +51,10 @@ import org.apache.sshd.common.util.security.eddsa.Ed25519PEMResourceKeyParser;
public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParser {
// Not exactly according to standard but good enough
public static final String BEGIN_MARKER = "BEGIN PRIVATE KEY";
public static final List<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 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";
@ -68,31 +65,31 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
}
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
byte[] encBytes,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
// Save the data before getting the algorithm OID since we will need it
byte[] encBytes = IoUtils.toByteArray(stream);
PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes);
return extractKeyPairs(
session, resourceKey, beginMarker, endMarker,
return extractKeyPairs(session, resourceKey, beginMarker, endMarker,
passwordProvider, encBytes, pkcs8Info, headers);
}
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider, byte[] encBytes,
PKCS8PrivateKeyInfo pkcs8Info, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
byte[] encBytes,
PKCS8PrivateKeyInfo pkcs8Info,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
List<Integer> oidAlgorithm = pkcs8Info.getAlgorithmIdentifier();
String oid = GenericUtils.join(oidAlgorithm, '.');
KeyPair kp;
if (SecurityUtils.isECCSupported()
&& ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
if (ECDSAPEMResourceKeyPairParser.ECDSA_OID.equals(oid)) {
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
ASN1Object extraInfo = pkcs8Info.getAlgorithmParameter();
ASN1Type objType = (extraInfo == null) ? ASN1Type.NULL : extraInfo.getObjType();
@ -104,14 +101,12 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
throw new NoSuchAlgorithmException("Cannot match EC curve OID=" + oidCurve);
}
}
try (DERParser parser = privateKeyBytes.createParser()) {
kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
}
} else if (SecurityUtils.isEDDSACurveSupported()
&& Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
} else if (Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes());
kp = Ed25519PEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes());
} else {
PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),

@ -19,6 +19,7 @@
package org.apache.sshd.common.config.keys.loader.pem;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
@ -67,14 +68,18 @@ public class RSAPEMResourceKeyPairParser extends AbstractPEMResourceKeyPairParse
}
@Override
public Collection<KeyPair> extractKeyPairs(
SessionContext session, NamedResource resourceKey,
String beginMarker, String endMarker,
FilePasswordProvider passwordProvider,
InputStream stream, Map<String, String> headers)
public Collection<KeyPair> extractKeyPairs(SessionContext session,
NamedResource resourceKey,
String beginMarker,
String endMarker,
FilePasswordProvider passwordProvider,
byte[] rawKey,
Map<String, String> headers)
throws IOException, GeneralSecurityException {
KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
return Collections.singletonList(kp);
try (InputStream stream = new ByteArrayInputStream(rawKey)) {
KeyPair kp = decodeRSAKeyPair(SecurityUtils.getKeyFactory(KeyUtils.RSA_ALGORITHM), stream, false);
return Collections.singletonList(kp);
}
}
/**

@ -157,97 +157,53 @@ public enum BuiltinSignatures implements SignatureFactory {
return new SignatureECDSA.SignatureECDSA256();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
nistp256_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT) {
@Override
public Signature create() {
return new SignatureECDSA.SignatureECDSA256();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
nistp384(KeyPairProvider.ECDSA_SHA2_NISTP384) {
@Override
public Signature create() {
return new SignatureECDSA.SignatureECDSA384();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
nistp384_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT) {
@Override
public Signature create() {
return new SignatureECDSA.SignatureECDSA384();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
nistp521(KeyPairProvider.ECDSA_SHA2_NISTP521) {
@Override
public Signature create() {
return new SignatureECDSA.SignatureECDSA521();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
nistp521_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT) {
@Override
public Signature create() {
return new SignatureECDSA.SignatureECDSA521();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
sk_ecdsa_sha2_nistp256(SkECDSAPublicKeyEntryDecoder.KEY_TYPE) {
@Override
public Signature create() {
return new SignatureSkECDSA();
}
@Override
public boolean isSupported() {
return SecurityUtils.isECCSupported();
}
},
ed25519(KeyPairProvider.SSH_ED25519) {
@Override
public Signature create() {
return SecurityUtils.getEDDSASigner();
}
@Override
public boolean isSupported() {
return SecurityUtils.isEDDSACurveSupported();
return new SignatureEd25519();
}
},
ed25519_cert(KeyPairProvider.SSH_ED25519_CERT) {
@Override
public Signature create() {
return SecurityUtils.getEDDSASigner();
}
@Override
public boolean isSupported() {
return SecurityUtils.isEDDSACurveSupported();
return new SignatureEd25519();
}
};

@ -0,0 +1,25 @@
package org.apache.sshd.common.signature;
import java.util.Map;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.ValidateUtils;
public class SignatureEd25519 extends AbstractSignature {
public SignatureEd25519() {
super("EdDSA");
}
@Override
public boolean verify(SessionContext session, byte[] sig) throws Exception {
byte[] data = sig;
Map.Entry<String, byte[]> encoding = extractEncodedSignature(data,
s -> s.equalsIgnoreCase("ssh-ed25519"));
if (encoding != null) {
String keyType = encoding.getKey();
ValidateUtils.checkTrue("ssh-ed25519".equals(keyType), "Mismatched key type: %s", keyType);
data = encoding.getValue();
}
return doVerify(data);
}
}

@ -20,7 +20,6 @@
package org.apache.sshd.common.signature;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
@ -207,48 +206,4 @@ public interface SignatureFactory extends BuiltinFactory<Signature> {
return NamedResource.findFirstMatchByName(aliases, String.CASE_INSENSITIVE_ORDER, factories);
}
}
/**
* @param pubKey The intended {@link PublicKey} - ignored if {@code null}
* @param algo The intended signature algorithm - if {@code null}/empty and multiple signatures
* available for the key type then a default will be used. Otherwise, it is
* validated to make sure it matches the public key type
* @return The {@link Signature} factory or {@code null} if no match found
* @throws InvalidKeySpecException If specified algorithm does not match the selected public key
*/
static NamedFactory<Signature> resolveSignatureFactoryByPublicKey(PublicKey pubKey, String algo)
throws InvalidKeySpecException {
if (pubKey == null) {
return null;
}
NamedFactory<Signature> factory = null;
if (pubKey instanceof ECPublicKey) {
ECPublicKey ecKey = (ECPublicKey) pubKey;
factory = BuiltinSignatures.getFactoryByCurveSize(ecKey.getParams());
} else if (pubKey instanceof RSAPublicKey) {
// SSHD-1104 take into account key aliases
if (GenericUtils.isEmpty(algo)) {
factory = BuiltinSignatures.rsa;
} else if (algo.contains("rsa")) {
factory = BuiltinSignatures.fromFactoryName(algo);
}
} else if (SecurityUtils.EDDSA.equalsIgnoreCase(pubKey.getAlgorithm())) {
factory = BuiltinSignatures.ed25519;
}
if (GenericUtils.isEmpty(algo) || (factory == null)) {
return factory;
}
String name = factory.getName();
if (!algo.equalsIgnoreCase(name)) {
throw new InvalidKeySpecException(
"Mismatched factory name (" + name + ")"
+ " for algorithm=" + algo + " when using key type"
+ KeyUtils.getKeyType(pubKey));
}
return factory;
}
}

@ -0,0 +1,31 @@
package org.apache.sshd.common.util;
public class ArrayUtil {
private ArrayUtil() {
}
public static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(Character.forDigit((b & 240) >> 4, 16)).append(Character.forDigit((b & 15), 16));
}
return sb.toString();
}
public static void reverse(byte[] arr) {
int i = 0;
int j = arr.length - 1;
while (i < j) {
swap(arr, i, j);
i++;
j--;
}
}
private static void swap(byte[] arr, int i, int j) {
byte tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}

@ -30,11 +30,9 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.EdECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
@ -60,6 +58,7 @@ import org.apache.sshd.common.cipher.ECCurves;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.OpenSshCertificate;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.ArrayUtil;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.Readable;
@ -72,6 +71,7 @@ import org.apache.sshd.common.util.security.SecurityUtils;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public abstract class Buffer implements Readable {
protected final byte[] workBuf = new byte[Long.BYTES];
protected Buffer() {
@ -513,7 +513,7 @@ public abstract class Buffer implements Readable {
pub = keyFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
prv = keyFactory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g));
} else if (KeyPairProvider.SSH_ED25519.equals(keyAlg)) {
return SecurityUtils.extractEDDSAKeyPair(this, keyAlg);
throw new UnsupportedOperationException();
} else {
ECCurves curve = ECCurves.fromKeyType(keyAlg);
if (curve == null) {
@ -877,104 +877,56 @@ public abstract class Buffer implements Readable {
public void putRawPublicKeyBytes(PublicKey key) {
Objects.requireNonNull(key, "No key");
if (key instanceof RSAPublicKey) {
RSAPublicKey rsaPub = (RSAPublicKey) key;
putMPInt(rsaPub.getPublicExponent());
putMPInt(rsaPub.getModulus());
} else if (key instanceof DSAPublicKey) {
DSAPublicKey dsaPub = (DSAPublicKey) key;
DSAParams dsaParams = dsaPub.getParams();
putMPInt(dsaParams.getP());
putMPInt(dsaParams.getQ());
putMPInt(dsaParams.getG());
putMPInt(dsaPub.getY());
} else if (key instanceof ECPublicKey) {
ECPublicKey ecKey = (ECPublicKey) key;
ECParameterSpec ecParams = ecKey.getParams();
ECCurves curve = ECCurves.fromCurveParameters(ecParams);
if (curve == null) {
throw new BufferException("Unsupported EC curve parameters");
switch (key) {
case RSAPublicKey rsaPub -> {
putMPInt(rsaPub.getPublicExponent());
putMPInt(rsaPub.getModulus());
}
byte[] ecPoint = ECCurves.encodeECPoint(ecKey.getW(), ecParams);
putString(curve.getName());
putBytes(ecPoint);
} else if (SecurityUtils.EDDSA.equals(key.getAlgorithm())) {
SecurityUtils.putRawEDDSAPublicKey(this, key);
} else if (key instanceof OpenSshCertificate) {
OpenSshCertificate cert = (OpenSshCertificate) key;
putBytes(cert.getNonce());
putRawPublicKeyBytes(cert.getServerHostKey());
putLong(cert.getSerial());
putInt(cert.getType());
putString(cert.getId());
ByteArrayBuffer tmpBuffer = new ByteArrayBuffer();
tmpBuffer.putStringList(cert.getPrincipals(), false);
putBytes(tmpBuffer.getCompactData());
putLong(cert.getValidAfter());
putLong(cert.getValidBefore());
putNameList(cert.getCriticalOptions());
putNameList(cert.getExtensions());
putString(cert.getReserved());
tmpBuffer = new ByteArrayBuffer(); // TODO tmpBuffer.clear() instead of allocate new buffer
tmpBuffer.putRawPublicKey(cert.getCaPubKey());
putBytes(tmpBuffer.getCompactData());
putBytes(cert.getSignature());
} else {
throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm());
}
}
public void putKeyPair(KeyPair kp) {
PublicKey pubKey = kp.getPublic();
PrivateKey prvKey = kp.getPrivate();
if (prvKey instanceof RSAPrivateCrtKey) {
RSAPublicKey rsaPub = (RSAPublicKey) pubKey;
RSAPrivateCrtKey rsaPrv = (RSAPrivateCrtKey) prvKey;
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) {
DSAPublicKey dsaPub = (DSAPublicKey) pubKey;
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) {
ECPublicKey ecPub = (ECPublicKey) pubKey;
ECPrivateKey ecPriv = (ECPrivateKey) prvKey;
ECParameterSpec ecParams = ecPub.getParams();
ECCurves curve = ECCurves.fromCurveParameters(ecParams);
if (curve == null) {
throw new BufferException("Unsupported EC curve parameters");
case DSAPublicKey dsaPub -> {
DSAParams dsaParams = dsaPub.getParams();
putMPInt(dsaParams.getP());
putMPInt(dsaParams.getQ());
putMPInt(dsaParams.getG());
putMPInt(dsaPub.getY());
}
byte[] ecPoint = ECCurves.encodeECPoint(ecPub.getW(), ecParams);
putString(curve.getKeyType());
putString(curve.getName());
putBytes(ecPoint);
putMPInt(ecPriv.getS());
} else if (SecurityUtils.EDDSA.equals(pubKey.getAlgorithm())) {
SecurityUtils.putEDDSAKeyPair(this, pubKey, prvKey);
} else {
throw new BufferException("Unsupported key pair algorithm: " + pubKey.getAlgorithm());
case ECPublicKey ecKey -> {
ECParameterSpec ecParams = ecKey.getParams();
ECCurves curve = ECCurves.fromCurveParameters(ecParams);
if (curve == null) {
throw new BufferException("Unsupported EC curve parameters");
}
byte[] ecPoint = ECCurves.encodeECPoint(ecKey.getW(), ecParams);
putString(curve.getName());
putBytes(ecPoint);
}
case OpenSshCertificate cert -> {
putBytes(cert.getNonce());
putRawPublicKeyBytes(cert.getServerHostKey());
putLong(cert.getSerial());
putInt(cert.getType());
putString(cert.getId());
ByteArrayBuffer tmpBuffer = new ByteArrayBuffer();
tmpBuffer.putStringList(cert.getPrincipals(), false);
putBytes(tmpBuffer.getCompactData());
putLong(cert.getValidAfter());
putLong(cert.getValidBefore());
putNameList(cert.getCriticalOptions());
putNameList(cert.getExtensions());
putString(cert.getReserved());
tmpBuffer = new ByteArrayBuffer(); // TODO tmpBuffer.clear() instead of allocate new buffer
tmpBuffer.putRawPublicKey(cert.getCaPubKey());
putBytes(tmpBuffer.getCompactData());
putBytes(cert.getSignature());
}
case EdECPublicKey edECPublicKey -> {
byte[] arr = edECPublicKey.getPoint().getY().toByteArray();
ArrayUtil.reverse(arr);
if (edECPublicKey.getPoint().isXOdd()) {
arr[arr.length - 1] |= (byte) 0x80;
}
putBytes(arr);
}
default -> throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm());
}
}

@ -19,13 +19,20 @@
package org.apache.sshd.common.util.buffer.keys;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.ArrayUtil;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.SecurityUtils;
/**
* TODO complete this when SSHD-440 is done
@ -43,6 +50,20 @@ public class ED25519BufferPublicKeyParser extends AbstractBufferPublicKeyParser<
public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws GeneralSecurityException {
ValidateUtils.checkTrue(isKeyTypeSupported(keyType), "Unsupported key type: %s", keyType);
byte[] seed = buffer.getBytes();
return SecurityUtils.generateEDDSAPublicKey(keyType, seed);
return generateEDDSAPublicKey(keyType, seed);
}
private static PublicKey generateEDDSAPublicKey(String keyType, byte[] arr)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte msb = arr[arr.length - 1];
boolean xOdd = (msb & 0x80) != 0;
arr[arr.length - 1] &= (byte) 0x7F;
ArrayUtil.reverse(arr);
BigInteger y = new BigInteger(1, arr);
EdECPoint edECPoint = new EdECPoint(xOdd, y);
NamedParameterSpec namedParameterSpec = NamedParameterSpec.ED25519;
EdECPublicKeySpec spec = new EdECPublicKeySpec(namedParameterSpec, edECPoint);
KeyFactory keyFactory = KeyFactory.getInstance("EdDSA");
return keyFactory.generatePublic(spec);
}
}

@ -22,21 +22,14 @@ import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@ -58,20 +51,14 @@ import javax.crypto.spec.DHParameterSpec;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
import org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser;
import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.random.JceRandomFactory;
import org.apache.sshd.common.random.RandomFactory;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderUtils;
import org.apache.sshd.common.util.threads.ThreadUtils;
/**
@ -85,15 +72,6 @@ public final class SecurityUtils {
*/
public static final String BOUNCY_CASTLE = "BC";
/**
* EDDSA support - should match {@code EdDSAKey.KEY_ALGORITHM}
*/
public static final String EDDSA = "EdDSA";
// A copy-paste from the original, but we don't want to drag the classes into the classpath
// See EdDSAEngine.SIGNATURE_ALGORITHM
public static final String CURVE_ED25519_SHA512 = "NONEwithEdDSA";
/**
* System property used to configure the value for the minimum supported Diffie-Hellman Group Exchange key size. If
* not set, then an internal auto-discovery mechanism is employed. If set to negative value then Diffie-Hellman
@ -123,8 +101,8 @@ public final class SecurityUtils {
* Comma separated list of fully qualified {@link SecurityProviderRegistrar}s to automatically register
*/
public static final String SECURITY_PROVIDER_REGISTRARS = "org.apache.sshd.security.registrars";
public static final List<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS = Collections.unmodifiableList(
Arrays.asList("org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar"));
public static final List<String> DEFAULT_SECURITY_PROVIDER_REGISTRARS = List.of();
/**
* System property used to control whether Elliptic Curves are supported or not. If not set then the support is
@ -199,28 +177,6 @@ public final class SecurityUtils {
}
}
/**
* @return {@code true} if Elliptic Curve Cryptography is supported
* @see #ECC_SUPPORTED_PROP
*/
public static boolean isECCSupported() {
if (hasEcc == null) {
String propValue = System.getProperty(ECC_SUPPORTED_PROP);
if (GenericUtils.isEmpty(propValue)) {
try {
getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
hasEcc = Boolean.TRUE;
} catch (Throwable t) {
hasEcc = Boolean.FALSE;
}
} else {
hasEcc = Boolean.valueOf(propValue);
}
}
return hasEcc;
}
/**
* @return {@code true} if Diffie-Hellman Group Exchange is supported
* @see #getMinDHGroupExchangeKeySize()
@ -349,25 +305,6 @@ public final class SecurityUtils {
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) {
ValidateUtils.checkNotNullAndNotEmpty(provider, "No provider name specified");
synchronized (REGISTERED_PROVIDERS) {
@ -375,10 +312,6 @@ public final class SecurityUtils {
}
}
public static boolean isRegistrationCompleted() {
return REGISTRATION_STATE_HOLDER.get();
}
private static void register() {
synchronized (REGISTRATION_STATE_HOLDER) {
if (REGISTRATION_STATE_HOLDER.get()) {
@ -489,107 +422,6 @@ public final class SecurityUtils {
return JceRandomFactory.INSTANCE;
}
///////////////////////////// ED25519 support ///////////////////////////////
/**
* @return {@code true} if EDDSA curves (e.g., {@code ed25519}) are supported
*/
public static boolean isEDDSACurveSupported() {
register();
SecurityProviderRegistrar r = getRegisteredProvider(EDDSA);
return (r != null) && r.isEnabled() && r.isSupported();
}
/* -------------------------------------------------------------------- */
public static PublicKeyEntryDecoder<? 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() {
KeyPairResourceParser parser;
synchronized (KEYPAIRS_PARSER_HODLER) {

@ -1,105 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.eddsa;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import org.apache.sshd.common.config.keys.KeyEntryResolver;
import org.apache.sshd.common.config.keys.impl.AbstractPublicKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class Ed25519PublicKeyDecoder extends AbstractPublicKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
public static final int MAX_ALLOWED_SEED_LEN = 1024; // in reality it is much less than this
public static final Ed25519PublicKeyDecoder INSTANCE = new Ed25519PublicKeyDecoder();
private Ed25519PublicKeyDecoder() {
super(EdDSAPublicKey.class, EdDSAPrivateKey.class,
Collections.unmodifiableList(
Collections.singletonList(
KeyPairProvider.SSH_ED25519)));
}
@Override
public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams()));
}
}
@Override
public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams()));
}
}
@Override
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
}
@Override
public String encodePublicKey(OutputStream s, EdDSAPublicKey key) throws IOException {
Objects.requireNonNull(key, "No public key provided");
KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_ED25519);
byte[] seed = getSeedValue(key);
KeyEntryResolver.writeRLEBytes(s, seed);
return KeyPairProvider.SSH_ED25519;
}
@Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
}
@Override
public EdDSAPublicKey decodePublicKey(
SessionContext session, String keyType, InputStream keyData, Map<String, String> headers)
throws IOException, GeneralSecurityException {
byte[] seed = KeyEntryResolver.readRLEBytes(keyData, MAX_ALLOWED_SEED_LEN);
return EdDSAPublicKey.class.cast(SecurityUtils.generateEDDSAPublicKey(keyType, seed));
}
public static byte[] getSeedValue(EdDSAPublicKey key) {
// a bit of reverse-engineering on the EdDSAPublicKeySpec
return (key == null) ? null : key.getAbyte();
}
}

@ -1,97 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.eddsa;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.Provider;
import java.security.Signature;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ReflectionUtils;
import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.common.util.threads.ThreadUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class EdDSASecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
public static final String PROVIDER_CLASS = "org.xbib.net.security.eddsa.EdDSASecurityProvider";
// Do not define a static registrar instance to minimize class loading issues
private final AtomicReference<Boolean> supportHolder = new AtomicReference<>(null);
public EdDSASecurityProviderRegistrar() {
super(SecurityUtils.EDDSA);
}
@Override
public boolean isEnabled() {
return super.isEnabled();
}
@Override
public Provider getSecurityProvider() {
try {
return getOrCreateProvider(PROVIDER_CLASS);
} catch (ReflectiveOperationException t) {
Throwable e = GenericUtils.peelException(t);
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
}
@Override
public boolean isSecurityEntitySupported(Class<?> entityType, String name) {
if (!isSupported()) {
return false;
}
if (KeyPairGenerator.class.isAssignableFrom(entityType)
|| KeyFactory.class.isAssignableFrom(entityType)) {
return Objects.compare(name, getName(), String.CASE_INSENSITIVE_ORDER) == 0;
} else if (Signature.class.isAssignableFrom(entityType)) {
return Objects.compare(SecurityUtils.CURVE_ED25519_SHA512, name, String.CASE_INSENSITIVE_ORDER) == 0;
} else {
return false;
}
}
@Override
public boolean isSupported() {
Boolean supported;
synchronized (supportHolder) {
supported = supportHolder.get();
if (supported != null) {
return supported.booleanValue();
}
ClassLoader cl = ThreadUtils.resolveDefaultClassLoader(getClass());
supported = ReflectionUtils.isClassAvailable(cl, "org.xbib.net.security.eddsa.EdDSAKey");
supportHolder.set(supported);
}
return supported.booleanValue();
}
}

@ -1,203 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.eddsa;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Objects;
import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.net.security.eddsa.EdDSAEngine;
import org.xbib.net.security.eddsa.EdDSAKey;
import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class EdDSASecurityProviderUtils {
// See EdDSANamedCurveTable
public static final String CURVE_ED25519_SHA512 = "Ed25519";
public static final int KEY_SIZE = 256;
private EdDSASecurityProviderUtils() {
throw new UnsupportedOperationException("No instance");
}
public static Class<? extends PublicKey> getEDDSAPublicKeyType() {
return EdDSAPublicKey.class;
}
public static Class<? extends PrivateKey> getEDDSAPrivateKeyType() {
return EdDSAPrivateKey.class;
}
public static boolean isEDDSAKey(Key key) {
return getEDDSAKeySize(key) == KEY_SIZE;
}
public static int getEDDSAKeySize(Key key) {
return (SecurityUtils.isEDDSACurveSupported() && (key instanceof EdDSAKey)) ? KEY_SIZE : -1;
}
public static boolean compareEDDSAPPublicKeys(PublicKey k1, PublicKey k2) {
if (!SecurityUtils.isEDDSACurveSupported()) {
return false;
}
if ((k1 instanceof EdDSAPublicKey) && (k2 instanceof EdDSAPublicKey)) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
}
EdDSAPublicKey ed1 = (EdDSAPublicKey) k1;
EdDSAPublicKey ed2 = (EdDSAPublicKey) k2;
return Arrays.equals(ed1.getAbyte(), ed2.getAbyte())
&& compareEDDSAKeyParams(ed1.getParams(), ed2.getParams());
}
return false;
}
public static boolean isEDDSASignatureAlgorithm(String algorithm) {
return EdDSAEngine.SIGNATURE_ALGORITHM.equalsIgnoreCase(algorithm);
}
public static EdDSAPublicKey recoverEDDSAPublicKey(PrivateKey key) throws GeneralSecurityException {
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
if (!(key instanceof EdDSAPrivateKey)) {
throw new InvalidKeyException("Private key is not " + SecurityUtils.EDDSA);
}
EdDSAPrivateKey prvKey = (EdDSAPrivateKey) key;
EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(prvKey.getAbyte(), prvKey.getParams());
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
return EdDSAPublicKey.class.cast(factory.generatePublic(keySpec));
}
public static org.apache.sshd.common.signature.Signature getEDDSASignature() {
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
return new SignatureEd25519();
}
public static boolean isEDDSAKeyFactoryAlgorithm(String algorithm) {
return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
}
public static boolean isEDDSAKeyPairGeneratorAlgorithm(String algorithm) {
return SecurityUtils.EDDSA.equalsIgnoreCase(algorithm);
}
public static PublicKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getEDDSAPublicKeyEntryDecoder() {
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
return Ed25519PublicKeyDecoder.INSTANCE;
}
public static PrivateKeyEntryDecoder<? extends PublicKey, ? extends PrivateKey> getOpenSSHEDDSAPrivateKeyEntryDecoder() {
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
return OpenSSHEd25519PrivateKeyEntryDecoder.INSTANCE;
}
public static boolean compareEDDSAPrivateKeys(PrivateKey k1, PrivateKey k2) {
if (!SecurityUtils.isEDDSACurveSupported()) {
return false;
}
if ((k1 instanceof EdDSAPrivateKey) && (k2 instanceof EdDSAPrivateKey)) {
if (Objects.equals(k1, k2)) {
return true;
} else if (k1 == null || k2 == null) {
return false; // both null is covered by Objects#equals
}
EdDSAPrivateKey ed1 = (EdDSAPrivateKey) k1;
EdDSAPrivateKey ed2 = (EdDSAPrivateKey) k2;
return Arrays.equals(ed1.getSeed(), ed2.getSeed())
&& compareEDDSAKeyParams(ed1.getParams(), ed2.getParams());
}
return false;
}
public static boolean compareEDDSAKeyParams(EdDSAParameterSpec s1, EdDSAParameterSpec s2) {
if (Objects.equals(s1, s2)) {
return true;
} else if (s1 == null || s2 == null) {
return false; // both null is covered by Objects#equals
} else {
return Objects.equals(s1.getHashAlgorithm(), s2.getHashAlgorithm())
&& Objects.equals(s1.getCurve(), s2.getCurve())
&& Objects.equals(s1.getB(), s2.getB());
}
}
public static PublicKey generateEDDSAPublicKey(byte[] seed) throws GeneralSecurityException {
if (!SecurityUtils.isEDDSACurveSupported()) {
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported");
}
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
EdDSAPublicKeySpec keySpec = new EdDSAPublicKeySpec(seed, params);
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
return factory.generatePublic(keySpec);
}
public static PrivateKey generateEDDSAPrivateKey(byte[] seed) throws GeneralSecurityException {
if (!SecurityUtils.isEDDSACurveSupported()) {
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " not supported");
}
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512);
EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seed, params);
KeyFactory factory = SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
return factory.generatePrivate(keySpec);
}
public static <B extends Buffer> B putRawEDDSAPublicKey(B buffer, PublicKey key) {
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, EdDSAPublicKey.class, "Not an EDDSA public key: %s", key);
byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(edKey);
ValidateUtils.checkNotNull(seed, "No seed extracted from key: %s", edKey.getA());
buffer.putBytes(seed);
return buffer;
}
public static <B extends Buffer> B putEDDSAKeyPair(B buffer, PublicKey pubKey, PrivateKey prvKey) {
ValidateUtils.checkTrue(SecurityUtils.isEDDSACurveSupported(), SecurityUtils.EDDSA + " not supported");
ValidateUtils.checkInstanceOf(pubKey, EdDSAPublicKey.class, "Not an EDDSA public key: %s", pubKey);
ValidateUtils.checkInstanceOf(prvKey, EdDSAPrivateKey.class, "Not an EDDSA private key: %s", prvKey);
throw new UnsupportedOperationException("Full SSHD-440 implementation N/A");
}
}

@ -1,186 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.eddsa;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Objects;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyEntryResolver;
import org.apache.sshd.common.config.keys.impl.AbstractPrivateKeyEntryDecoder;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.io.SecureByteArrayOutputStream;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.xbib.net.security.eddsa.EdDSAPrivateKey;
import org.xbib.net.security.eddsa.EdDSAPublicKey;
import org.xbib.net.security.eddsa.spec.EdDSANamedCurveTable;
import org.xbib.net.security.eddsa.spec.EdDSAParameterSpec;
import org.xbib.net.security.eddsa.spec.EdDSAPrivateKeySpec;
import org.xbib.net.security.eddsa.spec.EdDSAPublicKeySpec;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntryDecoder<EdDSAPublicKey, EdDSAPrivateKey> {
public static final OpenSSHEd25519PrivateKeyEntryDecoder INSTANCE = new OpenSSHEd25519PrivateKeyEntryDecoder();
private static final int PK_SIZE = 32;
private static final int SK_SIZE = 32;
private static final int KEYPAIR_SIZE = PK_SIZE + SK_SIZE;
public OpenSSHEd25519PrivateKeyEntryDecoder() {
super(EdDSAPublicKey.class, EdDSAPrivateKey.class,
Collections.unmodifiableList(
Collections.singletonList(
KeyPairProvider.SSH_ED25519)));
}
@Override
public EdDSAPrivateKey decodePrivateKey(
SessionContext session, String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
throws IOException, GeneralSecurityException {
if (!KeyPairProvider.SSH_ED25519.equals(keyType)) {
throw new InvalidKeyException("Unsupported key type: " + keyType);
}
if (!SecurityUtils.isEDDSACurveSupported()) {
throw new NoSuchAlgorithmException(SecurityUtils.EDDSA + " provider not supported");
}
// ed25519 bernstein naming: pk .. public key, sk .. secret key
// we expect to find two byte arrays with the following structure (type:size):
// [pk:32], [sk:32,pk:32]
byte[] pk = GenericUtils.EMPTY_BYTE_ARRAY;
byte[] keypair = GenericUtils.EMPTY_BYTE_ARRAY;
try {
pk = KeyEntryResolver.readRLEBytes(keyData, PK_SIZE * 2);
keypair = KeyEntryResolver.readRLEBytes(keyData, KEYPAIR_SIZE * 2);
if (pk.length != PK_SIZE) {
throw new InvalidKeyException(
String.format(Locale.ENGLISH, "Unexpected pk size: %s (expected %s)", pk.length, PK_SIZE));
}
if (keypair.length != KEYPAIR_SIZE) {
throw new InvalidKeyException(
String.format(Locale.ENGLISH, "Unexpected keypair size: %s (expected %s)", keypair.length,
KEYPAIR_SIZE));
}
// verify that the keypair contains the expected pk
// yes, it's stored redundant, this seems to mimic the output structure of the keypair generation interface
if (!Arrays.equals(pk, Arrays.copyOfRange(keypair, SK_SIZE, KEYPAIR_SIZE))) {
throw new InvalidKeyException("Keypair did not contain the public key.");
}
byte[] sk = Arrays.copyOf(keypair, SK_SIZE);
EdDSAParameterSpec params = EdDSANamedCurveTable.getByName(EdDSASecurityProviderUtils.CURVE_ED25519_SHA512);
EdDSAPrivateKey privateKey = generatePrivateKey(new EdDSAPrivateKeySpec(sk, params));
// the private key class contains the calculated public key (Abyte)
// pointers to the corresponding code:
// EdDSAPrivateKeySpec.EdDSAPrivateKeySpec(byte[], EdDSAParameterSpec): A = spec.getB().scalarMultiply(a);
// EdDSAPrivateKey.EdDSAPrivateKey(EdDSAPrivateKeySpec): this.Abyte = this.A.toByteArray();
// we can now verify the generated pk matches the one we read
if (!Arrays.equals(privateKey.getAbyte(), pk)) {
throw new InvalidKeyException("The provided pk does NOT match the computed pk for the given sk.");
}
return privateKey;
} finally {
// get rid of sensitive data a.s.a.p
Arrays.fill(pk, (byte) 0);
Arrays.fill(keypair, (byte) 0);
}
}
@Override
public String encodePrivateKey(SecureByteArrayOutputStream s, EdDSAPrivateKey key, EdDSAPublicKey pubKey)
throws IOException {
Objects.requireNonNull(key, "No private key provided");
// ed25519 bernstein naming: pk .. public key, sk .. secret key
// we are expected to write the following arrays (type:size):
// [pk:32], [sk:32,pk:32]
byte[] sk = key.getSeed();
byte[] pk = key.getAbyte();
Objects.requireNonNull(sk, "No seed");
byte[] keypair = new byte[KEYPAIR_SIZE];
System.arraycopy(sk, 0, keypair, 0, SK_SIZE);
System.arraycopy(pk, 0, keypair, SK_SIZE, PK_SIZE);
KeyEntryResolver.writeRLEBytes(s, pk);
KeyEntryResolver.writeRLEBytes(s, keypair);
return KeyPairProvider.SSH_ED25519;
}
@Override
public boolean isPublicKeyRecoverySupported() {
return true;
}
@Override
public EdDSAPublicKey recoverPublicKey(EdDSAPrivateKey prvKey) throws GeneralSecurityException {
return EdDSASecurityProviderUtils.recoverEDDSAPublicKey(prvKey);
}
@Override
public EdDSAPublicKey clonePublicKey(EdDSAPublicKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
return generatePublicKey(new EdDSAPublicKeySpec(key.getA(), key.getParams()));
}
}
@Override
public EdDSAPrivateKey clonePrivateKey(EdDSAPrivateKey key) throws GeneralSecurityException {
if (key == null) {
return null;
} else {
return generatePrivateKey(new EdDSAPrivateKeySpec(key.getSeed(), key.getParams()));
}
}
@Override
public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
return SecurityUtils.getKeyPairGenerator(SecurityUtils.EDDSA);
}
@Override
public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
return SecurityUtils.getKeyFactory(SecurityUtils.EDDSA);
}
}

@ -1,48 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.security.eddsa;
import java.util.Map;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.signature.AbstractSignature;
import org.apache.sshd.common.util.ValidateUtils;
import org.xbib.net.security.eddsa.EdDSAEngine;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SignatureEd25519 extends AbstractSignature {
public SignatureEd25519() {
super(EdDSAEngine.SIGNATURE_ALGORITHM);
}
@Override
public boolean verify(SessionContext session, byte[] sig) throws Exception {
byte[] data = sig;
Map.Entry<String, byte[]> encoding = extractEncodedSignature(data, KeyPairProvider.SSH_ED25519::equalsIgnoreCase);
if (encoding != null) {
String keyType = encoding.getKey();
ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(keyType), "Mismatched key type: %s", keyType);
data = encoding.getValue();
}
return doVerify(data);
}
}

@ -1,62 +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.xbib;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
import org.apache.sshd.common.session.SessionContext;
import org.xbib.net.security.PrivateKeyReader;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class XbibKeyPairResourceParser implements KeyPairResourceParser {
public static final XbibKeyPairResourceParser INSTANCE = new XbibKeyPairResourceParser();
public XbibKeyPairResourceParser() {
}
@Override
public boolean canExtractKeyPairs(NamedResource resourceKey, List<String> lines) {
return true;
}
@Override
public Collection<KeyPair> loadKeyPairs(SessionContext session,
NamedResource resourceKey,
FilePasswordProvider passwordProvider,
List<String> lines) throws IOException, GeneralSecurityException {
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
InputStream inputStream = new ByteArrayInputStream(String.join("\n", lines).getBytes(StandardCharsets.US_ASCII));
String password = passwordProvider.getPassword(session, resourceKey, 0);
return Collections.singleton(privateKeyReader.generateFrom(inputStream, password));
}
}

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

@ -22,7 +22,8 @@ tasks.withType(JavaCompile).configureEach {
options.forkOptions.jvmArgs += ['-Duser.language=en', '-Duser.country=US']
options.encoding = 'UTF-8'
options.compilerArgs.add('-Xlint:all')
// enforce presence of module-info.java
options.compilerArgs.add("--module-version")
options.compilerArgs.add(project.version as String)
options.compilerArgs.add("--module-path")
options.compilerArgs.add(classpath.asPath)
classpath = files()

@ -16,8 +16,6 @@ dependencyResolutionManagement {
versionCatalogs {
libs {
version('gradle', '8.7')
version('net', '4.4.0')
library('net-security', 'org.xbib', 'net-security').versionRef('net')
library('maverick-synergy-client', 'com.sshtools', 'maverick-synergy-client').version('3.1.1')
}
testLibs {

Loading…
Cancel
Save