diff --git a/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java b/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java index 47460ca3..265117d0 100644 --- a/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java +++ b/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java @@ -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; @@ -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; diff --git a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java index 585331a7..8550566f 100644 --- a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java +++ b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java @@ -33,7 +33,28 @@ protected T engineGetKeySpec(Key key, Class 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()); } } diff --git a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java index 369ca72b..26f8aa28 100644 --- a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java +++ b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java @@ -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) () -> { setup(); return null; diff --git a/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java b/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java index 638fc961..0e7431ca 100644 --- a/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java +++ b/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java @@ -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; @@ -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; @@ -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(); @@ -102,7 +104,7 @@ 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) { @@ -110,9 +112,27 @@ public byte[] generateSignature(byte[] msg, PrivateKey privateKey, SecureRandom } } + 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); diff --git a/src/test/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactoryTest.java b/src/test/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactoryTest.java index 587fe2d6..e15c67e4 100644 --- a/src/test/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactoryTest.java +++ b/src/test/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactoryTest.java @@ -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"); @@ -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())); + } } diff --git a/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java b/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java index b731a204..4f308101 100644 --- a/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java +++ b/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java @@ -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; @@ -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()); + } }