working on ssh-ed25519 with jdk-based classes

This commit is contained in:
Jörg Prante 2024-05-28 09:17:26 +02:00
parent 5cf088bd12
commit 9af09a4176
9 changed files with 22 additions and 68 deletions

View file

@ -49,10 +49,10 @@ public class SftpWithPrivateKeyReaderTest {
} }
@Test @Test
public void testDionysos() throws Exception { public void testAlkmene() throws Exception {
Map<String, String> env = new HashMap<>(); Map<String, String> env = new HashMap<>();
env.put("username", "joerg"); env.put("username", "joerg");
URI uri = URI.create("sftp://dionysos"); URI uri = URI.create("sftp://alkmene");
Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519"); Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_ed25519");
PrivateKeyReader privateKeyReader = new PrivateKeyReader(); PrivateKeyReader privateKeyReader = new PrivateKeyReader();
try (InputStream inputStream = Files.newInputStream(privateKeyPath); try (InputStream inputStream = Files.newInputStream(privateKeyPath);

View file

@ -45,7 +45,7 @@ import org.apache.sshd.common.util.io.der.ASN1Object;
import org.apache.sshd.common.util.io.der.ASN1Type; import org.apache.sshd.common.util.io.der.ASN1Type;
import org.apache.sshd.common.util.io.der.DERParser; import org.apache.sshd.common.util.io.der.DERParser;
import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.common.util.security.eddsa.Ed25519PEMResourceKeyParser; import org.apache.sshd.common.util.security.edec.EdECPEMResourceKeyParser;
/** /**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@ -109,9 +109,9 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser); kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
} }
} else if (SecurityUtils.isEDDSACurveSupported() } else if (SecurityUtils.isEDDSACurveSupported()
&& Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) { && EdECPEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes(); ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes()); kp = EdECPEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes());
} else { } else {
PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes); PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey), PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),

View file

@ -20,7 +20,6 @@
package org.apache.sshd.common.signature; package org.apache.sshd.common.signature;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey; import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
@ -207,48 +206,4 @@ public interface SignatureFactory extends BuiltinFactory<Signature> {
return NamedResource.findFirstMatchByName(aliases, String.CASE_INSENSITIVE_ORDER, factories); 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;
}
} }

View file

@ -34,9 +34,7 @@ import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -50,6 +48,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyAgreement; import javax.crypto.KeyAgreement;
import javax.crypto.Mac; import javax.crypto.Mac;

View file

@ -60,7 +60,7 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
return Collections.singletonList(kp); return Collections.singletonList(kp);
} }
private static KeyPair parseEd25519KeyPair(InputStream inputStream, public static KeyPair parseEd25519KeyPair(InputStream inputStream,
boolean okToClose) throws IOException, GeneralSecurityException { boolean okToClose) throws IOException, GeneralSecurityException {
try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) { try (DERParser parser = new DERParser(NoCloseInputStream.resolveInputStream(inputStream, okToClose))) {
return parseKeyPair(parser); return parseKeyPair(parser);
@ -118,9 +118,9 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
return decodeKeyPair(obj.getValue()); return decodeKeyPair(obj.getValue());
} }
private static KeyPair decodeKeyPair(byte[] keyData) throws IOException, GeneralSecurityException { public static KeyPair decodeKeyPair(byte[] keyData) throws IOException, GeneralSecurityException {
EdECPrivateKey privateKey = getPrivateKey(keyData); EdECPrivateKey privateKey = getPrivateKey(keyData);
EdECPublicKey publicKey = getPublicKey(privateKey); EdECPublicKey publicKey = getPublicKey(keyData);
return new KeyPair(publicKey, privateKey); return new KeyPair(publicKey, privateKey);
} }
@ -142,14 +142,11 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
NamedParameterSpec spec = NamedParameterSpec.ED25519; NamedParameterSpec spec = NamedParameterSpec.ED25519;
EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed); EdECPrivateKeySpec keySpec = new EdECPrivateKeySpec(spec, seed);
KeyFactory factory = KeyFactory.getInstance("EdDSA"); KeyFactory factory = KeyFactory.getInstance("EdDSA");
return EdECPrivateKey.class.cast(factory.generatePrivate(keySpec)); return (EdECPrivateKey) factory.generatePrivate(keySpec);
} }
private static EdECPublicKey getPublicKey(PrivateKey key) throws GeneralSecurityException { private static EdECPublicKey getPublicKey(byte[] keyData) throws GeneralSecurityException {
if (!(key instanceof EdECPrivateKey)) { byte[] pk = keyData;
throw new InvalidKeyException("Private key is not EdEC private key");
}
byte[] pk = key.getEncoded();
boolean xisodd = false; boolean xisodd = false;
int lastbyteInt = pk[pk.length - 1]; int lastbyteInt = pk[pk.length - 1];
if ((lastbyteInt & 255) >> 7 == 1) { if ((lastbyteInt & 255) >> 7 == 1) {
@ -161,6 +158,6 @@ public class EdECPEMResourceKeyParser extends AbstractPEMResourceKeyPairParser {
EdECPoint ep = new EdECPoint(xisodd, y); EdECPoint ep = new EdECPoint(xisodd, y);
EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep); EdECPublicKeySpec publicKeySpec = new EdECPublicKeySpec(paramSpec, ep);
KeyFactory factory = KeyFactory.getInstance("EdDSA"); KeyFactory factory = KeyFactory.getInstance("EdDSA");
return EdECPublicKey.class.cast(factory.generatePublic(publicKeySpec)); return (EdECPublicKey) factory.generatePublic(publicKeySpec);
} }
} }

View file

@ -11,12 +11,12 @@ import org.apache.sshd.common.util.security.AbstractSecurityProviderRegistrar;
public class EdECSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar { public class EdECSecurityProviderRegistrar extends AbstractSecurityProviderRegistrar {
public EdECSecurityProviderRegistrar() { public EdECSecurityProviderRegistrar() {
super("Ed25519"); super("EdDSA");
} }
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return super.isEnabled(); return true;
} }
@Override @Override

View file

@ -92,6 +92,7 @@ public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntr
EdECPrivateKey key, EdECPrivateKey key,
EdECPublicKey pubKey) throws IOException { EdECPublicKey pubKey) throws IOException {
Objects.requireNonNull(key, "No private key provided"); Objects.requireNonNull(key, "No private key provided");
// TODO
return "ssh-ed25519"; return "ssh-ed25519";
} }
@ -102,6 +103,7 @@ public class OpenSSHEd25519PrivateKeyEntryDecoder extends AbstractPrivateKeyEntr
@Override @Override
public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException { public EdECPublicKey recoverPublicKey(EdECPrivateKey key) throws GeneralSecurityException {
// TODO
byte[] pk = key.getEncoded(); byte[] pk = key.getEncoded();
boolean xisodd = false; boolean xisodd = false;
int lastbyteInt = pk[pk.length - 1]; int lastbyteInt = pk[pk.length - 1];

View file

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

View file

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