working on ssh-ed25519 with jdk-based classes
This commit is contained in:
parent
5cf088bd12
commit
9af09a4176
9 changed files with 22 additions and 68 deletions
|
@ -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);
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue