Skip to content

EdDSA: update handling to allow other providers #345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package com.trilead.ssh2.auth;

import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
import com.trilead.ssh2.signature.RSASHA256Verify;
import com.trilead.ssh2.signature.RSASHA512Verify;
import java.io.IOException;
Expand Down Expand Up @@ -316,7 +315,7 @@ else if (publicKey instanceof ECPublicKey)

tm.sendMessage(ua.getPayload());
}
else if (publicKey instanceof Ed25519PublicKey)
else if ("EdDSA".equals(publicKey.getAlgorithm()))
{
final String algo = Ed25519Verify.ED25519_ID;

Expand Down
25 changes: 23 additions & 2 deletions src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,28 @@ protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec) thro
}

@Override
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
throw new InvalidKeyException("No other EdDSA key providers known");
public Key engineTranslateKey(Key key) throws InvalidKeyException {
if (key instanceof Ed25519PublicKey || key instanceof Ed25519PrivateKey) {
return key;
}
if (key instanceof PublicKey && key.getFormat().equals("X.509")) {
byte[] encoded = key.getEncoded();
try {
return new Ed25519PublicKey(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
}

if (key instanceof PrivateKey && key.getFormat().equals("PKCS#8")) {
byte[] encoded = key.getEncoded();
try {
return new Ed25519PrivateKey(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
}

throw new InvalidKeyException("Could not convert EdDSA key from key class " + key.getClass());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import java.security.Security;

public class Ed25519Provider extends Provider {
public static final String NAME = "ConnectBot Ed25519 Provider";
public static final String KEY_ALGORITHM = "Ed25519";
private static final Object sInitLock = new Object();
private static boolean sInitialized = false;

public Ed25519Provider() {
super("ConnectBot Ed25519 Provider", 1.0, "Not for use elsewhere");
super(NAME, 1.0, "Not for use elsewhere");
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
setup();
return null;
Expand Down
28 changes: 24 additions & 4 deletions src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
package com.trilead.ssh2.signature;

import com.google.crypto.tink.subtle.Ed25519Sign;
import com.trilead.ssh2.crypto.keys.Ed25519KeyFactory;
import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
import com.trilead.ssh2.log.Logger;
Expand All @@ -38,6 +39,7 @@

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
Expand Down Expand Up @@ -66,8 +68,8 @@ public static Ed25519Verify get() {
}

@Override
public byte[] encodePublicKey(PublicKey publicKey) {
Ed25519PublicKey ed25519PublicKey = (Ed25519PublicKey) publicKey;
public byte[] encodePublicKey(PublicKey publicKey) throws IOException {
Ed25519PublicKey ed25519PublicKey = getEd25519PublicKey(publicKey);

TypesWriter tw = new TypesWriter();

Expand Down Expand Up @@ -102,17 +104,35 @@ public PublicKey decodePublicKey(byte[] encoded) throws IOException {

@Override
public byte[] generateSignature(byte[] msg, PrivateKey privateKey, SecureRandom secureRandom) throws IOException {
Ed25519PrivateKey ed25519PrivateKey = (Ed25519PrivateKey) privateKey;
Ed25519PrivateKey ed25519PrivateKey = getEd25519PrivateKey(privateKey);
try {
return encodeSSHEd25519Signature(new Ed25519Sign(ed25519PrivateKey.getSeed()).sign(msg));
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
}

private static Ed25519PublicKey getEd25519PublicKey(PublicKey publicKey) throws IOException {
Ed25519KeyFactory kf = new Ed25519KeyFactory();
try {
return (Ed25519PublicKey) kf.engineTranslateKey(publicKey);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}

private static Ed25519PrivateKey getEd25519PrivateKey(PrivateKey privateKey) throws IOException {
Ed25519KeyFactory kf = new Ed25519KeyFactory();
try {
return (Ed25519PrivateKey) kf.engineTranslateKey(privateKey);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}

@Override
public boolean verifySignature(byte[] message, byte[] sshSig, PublicKey publicKey) throws IOException {
Ed25519PublicKey ed25519PublicKey = (Ed25519PublicKey) publicKey;
Ed25519PublicKey ed25519PublicKey = getEd25519PublicKey(publicKey);
byte[] javaSig = decodeSSHEd25519Signature(sshSig);
try {
new com.google.crypto.tink.subtle.Ed25519Verify(ed25519PublicKey.getAbyte()).verify(javaSig, message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import org.junit.Test;

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertArrayEquals;

public class Ed25519KeyFactoryTest {
private static final byte[] PRIVATE = toByteArray("302e020100300506032b657004220420f72a0a036e3479e15edb74da5f2a5418e66db450ad50687cad90247eeab6440c");
Expand Down Expand Up @@ -41,4 +47,38 @@ public void generatesPublicKey() throws Exception {
Ed25519PublicKey pub = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(PUBLIC));
assertThat(pub.getAbyte(), is(KAT_ED25519_PUB));
}

@Test
public void translatesNativeJDKKeys() throws Exception {
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EdDSA");
} catch (NoSuchAlgorithmException e) {
// Skip test if EdDSA is not supported by the JDK
System.err.println("Skipping translatesNativeJDKKeys test: EdDSA not supported by this JDK");
return;
}

KeyPair keyPair = kpg.generateKeyPair();
PublicKey nativePubKey = keyPair.getPublic();
PrivateKey nativePrivKey = keyPair.getPrivate();

Ed25519KeyFactory keyFactorySpi = new Ed25519KeyFactory();

// Translate Public Key
PublicKey translatedPubKey = (PublicKey) keyFactorySpi.engineTranslateKey(nativePubKey);
assertThat(translatedPubKey.getAlgorithm(), is("EdDSA"));
assertThat(translatedPubKey.getFormat(), is("X.509"));
assertArrayEquals(nativePubKey.getEncoded(), translatedPubKey.getEncoded());
assertThat(((Ed25519PublicKey) translatedPubKey).getAbyte(), is(((Ed25519PublicKey) keyFactorySpi
.engineGeneratePublic(new X509EncodedKeySpec(nativePubKey.getEncoded()))).getAbyte()));

// Translate Private Key
PrivateKey translatedPrivKey = (PrivateKey) keyFactorySpi.engineTranslateKey(nativePrivKey);
assertThat(translatedPrivKey.getAlgorithm(), is("EdDSA"));
assertThat(translatedPrivKey.getFormat(), is("PKCS#8"));
assertArrayEquals(nativePrivKey.getEncoded(), translatedPrivKey.getEncoded());
assertThat(((Ed25519PrivateKey) translatedPrivKey).getSeed(), is(((Ed25519PrivateKey) keyFactorySpi
.engineGeneratePrivate(new PKCS8EncodedKeySpec(nativePrivKey.getEncoded()))).getSeed()));
}
}
30 changes: 30 additions & 0 deletions src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
Expand Down Expand Up @@ -190,4 +194,30 @@ public void encodesAndDecodesJavaFormat() throws Exception {
Ed25519PublicKey pubKey2 = new Ed25519PublicKey(new X509EncodedKeySpec(pubKeyEncoded));
assertThat(pubKey.getAbyte(), is(pubKey2.getAbyte()));
}

@Test
public void nativeJDKConversion() throws Exception {
java.security.KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EdDSA");
} catch (NoSuchAlgorithmException e) {
// Skip test if EdDSA is not supported by the JDK
System.err.println("Skipping nativeJDKConversion test: EdDSA not supported by this JDK");
return;
}

KeyPair keyPair = kpg.generateKeyPair();

// Convert and check Public Key
PublicKey nativePubKey = keyPair.getPublic();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(nativePubKey.getEncoded());
Ed25519PublicKey convertedPubKey = new Ed25519PublicKey(pubKeySpec);
assertArrayEquals(nativePubKey.getEncoded(), convertedPubKey.getEncoded());

// Convert and check Private Key
PrivateKey nativePrivKey = keyPair.getPrivate();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(nativePrivKey.getEncoded());
Ed25519PrivateKey convertedPrivKey = new Ed25519PrivateKey(privKeySpec);
assertArrayEquals(nativePrivKey.getEncoded(), convertedPrivKey.getEncoded());
}
}