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
public void testDionysos() throws Exception {
public void testAlkmene() throws Exception {
Map<String, String> env = new HashMap<>();
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");
PrivateKeyReader privateKeyReader = new PrivateKeyReader();
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.DERParser;
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>
@ -109,9 +109,9 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar
kp = ECDSAPEMResourceKeyPairParser.parseECKeyPair(curve, parser);
}
} else if (SecurityUtils.isEDDSACurveSupported()
&& Ed25519PEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
&& EdECPEMResourceKeyParser.ED25519_OID.endsWith(oid)) {
ASN1Object privateKeyBytes = pkcs8Info.getPrivateKeyBytes();
kp = Ed25519PEMResourceKeyParser.decodeEd25519KeyPair(privateKeyBytes.getPureValueBytes());
kp = EdECPEMResourceKeyParser.decodeKeyPair(privateKeyBytes.getPureValueBytes());
} else {
PrivateKey prvKey = decodePEMPrivateKeyPKCS8(oidAlgorithm, encBytes);
PublicKey pubKey = ValidateUtils.checkNotNull(KeyUtils.recoverPublicKey(prvKey),

View file

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

View file

@ -34,9 +34,7 @@ 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;
@ -50,6 +48,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;

View file

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

View file

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

View file

@ -1,7 +1,6 @@
package org.apache.sshd.common.util.security.edec;
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;
@ -15,10 +14,11 @@ public class SignatureEd25519 extends AbstractSignature {
@Override
public boolean verify(SessionContext session, byte[] sig) throws Exception {
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) {
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();
}
return doVerify(data);

View file

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