diff --git a/src/main/java/com/trilead/ssh2/crypto/PublicKeyUtils.java b/src/main/java/com/trilead/ssh2/crypto/PublicKeyUtils.java index 9a72f304..8db5ea76 100644 --- a/src/main/java/com/trilead/ssh2/crypto/PublicKeyUtils.java +++ b/src/main/java/com/trilead/ssh2/crypto/PublicKeyUtils.java @@ -42,31 +42,32 @@ public static String toAuthorizedKeysFormat(PublicKey publicKey, String comment) comment = ""; } + String keyType; + byte[] encoded; + if (publicKey instanceof RSAPublicKey) { RSAPublicKey rsaKey = (RSAPublicKey) publicKey; - byte[] encoded = RSASHA1Verify.get().encodePublicKey(rsaKey); - String data = "ssh-rsa " + new String(Base64.encode(encoded)); - return comment.isEmpty() ? data : data + " " + comment; + keyType = "ssh-rsa"; + encoded = RSASHA1Verify.get().encodePublicKey(rsaKey); } else if (publicKey instanceof DSAPublicKey) { DSAPublicKey dsaKey = (DSAPublicKey) publicKey; - byte[] encoded = DSASHA1Verify.get().encodePublicKey(dsaKey); - String data = "ssh-dss " + new String(Base64.encode(encoded)); - return comment.isEmpty() ? data : data + " " + comment; + keyType = "ssh-dss"; + encoded = DSASHA1Verify.get().encodePublicKey(dsaKey); } else if (publicKey instanceof ECPublicKey) { ECPublicKey ecKey = (ECPublicKey) publicKey; - String keyType = ECDSASHA2Verify.getSshKeyType(ecKey); + keyType = ECDSASHA2Verify.getSshKeyType(ecKey); SSHSignature verifier = ECDSASHA2Verify.getVerifierForKey(ecKey); - byte[] encoded = verifier.encodePublicKey(ecKey); - String data = keyType + " " + new String(Base64.encode(encoded)); - return comment.isEmpty() ? data : data + " " + comment; - } else if (publicKey instanceof Ed25519PublicKey) { - Ed25519PublicKey ed25519Key = (Ed25519PublicKey) publicKey; - byte[] encoded = Ed25519Verify.get().encodePublicKey(ed25519Key); - String data = Ed25519Verify.ED25519_ID + " " + new String(Base64.encode(encoded)); - return comment.isEmpty() ? data : data + " " + comment; + encoded = verifier.encodePublicKey(ecKey); + } else if ("EdDSA".equals(publicKey.getAlgorithm()) || "Ed25519".equals(publicKey.getAlgorithm())) { + Ed25519PublicKey ed25519Key = Ed25519Verify.convertPublicKey(publicKey); + keyType = Ed25519Verify.ED25519_ID; + encoded = Ed25519Verify.get().encodePublicKey(ed25519Key); } else { throw new InvalidKeyException("Unknown key type: " + publicKey.getClass().getName()); } + + String data = keyType + " " + new String(Base64.encode(encoded)); + return comment.isEmpty() ? data : data + " " + comment; } /** @@ -87,8 +88,9 @@ public static byte[] extractPublicKeyBlob(PublicKey publicKey) ECPublicKey ecKey = (ECPublicKey) publicKey; SSHSignature verifier = ECDSASHA2Verify.getVerifierForKey(ecKey); return verifier.encodePublicKey(ecKey); - } else if (publicKey instanceof Ed25519PublicKey) { - return Ed25519Verify.get().encodePublicKey((Ed25519PublicKey) publicKey); + } else if ("EdDSA".equals(publicKey.getAlgorithm()) || "Ed25519".equals(publicKey.getAlgorithm())) { + Ed25519PublicKey ed25519Key = Ed25519Verify.convertPublicKey(publicKey); + return Ed25519Verify.get().encodePublicKey(ed25519Key); } else { throw new InvalidKeyException("Unknown key type: " + publicKey.getClass().getName()); } diff --git a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java index c9226511..9e4f1d94 100644 --- a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java +++ b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java @@ -1,9 +1,10 @@ package com.trilead.ssh2.crypto.keys; -import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.crypto.SimpleDERReader; import com.trilead.ssh2.packets.TypesWriter; import java.io.IOException; +import java.math.BigInteger; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; @@ -102,31 +103,42 @@ public byte[] getEncoded() { private static byte[] decode(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException { byte[] encoded = keySpec.getEncoded(); - if (encoded.length != ENCODED_SIZE) { - throw new InvalidKeySpecException("Key spec is of invalid size"); + + // Handle legacy RAW format (32 bytes) from commits f01a8b9 to 91bf5d0 (May-July 2020) + // Before commit 91bf5d0, getEncoded() returned just the raw 32-byte seed with format "RAW" + if (encoded.length == KEY_BYTES_LENGTH) { + return encoded; } + + // Handle standard PKCS#8 format (48+ bytes) from commit 91bf5d0 onwards try { - TypesReader tr = new TypesReader(keySpec.getEncoded()); - if (tr.readByte() != 0x30 || // ASN.1 sequence - tr.readByte() != ENCODED_SIZE - 2 || // Expected size - tr.readByte() != 0x02 || // ASN.1 Integer - tr.readByte() != 1 || // length - tr.readByte() != 0 || // v1 - tr.readByte() != 0x30 || // ASN.1 Sequence - tr.readByte() != ED25519_OID.length + 2 || // OID length - tr.readByte() != 0x06 || // ASN.1 OID - tr.readByte() != ED25519_OID.length) { - throw new InvalidKeySpecException("Key was not encoded correctly"); + SimpleDERReader reader = new SimpleDERReader(encoded); + + byte[] sequenceData = reader.readSequenceAsByteArray(); + SimpleDERReader sequenceReader = new SimpleDERReader(sequenceData); + + BigInteger version = sequenceReader.readInt(); + if (!version.equals(BigInteger.ZERO)) { + throw new InvalidKeySpecException("Unsupported PKCS#8 version: " + version); + } + + int algType = sequenceReader.readConstructedType(); + SimpleDERReader algReader = sequenceReader.readConstructed(); + + String oid = algReader.readOid(); + if (!"1.3.101.112".equals(oid)) { + throw new InvalidKeySpecException("Expected Ed25519 OID (1.3.101.112), got: " + oid); } - byte[] oid = tr.readBytes(ED25519_OID.length); - if (!Arrays.equals(ED25519_OID, oid) || - tr.readByte() != 0x04 || // ASN.1 octet string - tr.readByte() != KEY_BYTES_LENGTH + 2 || // length - tr.readByte() != 0x04 || // ASN.1 octet string - tr.readByte() != KEY_BYTES_LENGTH) { - throw new InvalidKeySpecException("Key was not encoded correctly"); + + byte[] privateKeyOctetString = sequenceReader.readOctetString(); + SimpleDERReader privateKeyReader = new SimpleDERReader(privateKeyOctetString); + byte[] seed = privateKeyReader.readOctetString(); + + if (seed.length != KEY_BYTES_LENGTH) { + throw new InvalidKeySpecException("Expected " + KEY_BYTES_LENGTH + " byte seed, got " + seed.length); } - return tr.readBytes(KEY_BYTES_LENGTH); + + return seed; } catch (IOException e) { throw new InvalidKeySpecException("Key was not encoded correctly", e); } diff --git a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PublicKey.java b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PublicKey.java index 740ce8d5..c5f6d259 100644 --- a/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PublicKey.java +++ b/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PublicKey.java @@ -1,6 +1,6 @@ package com.trilead.ssh2.crypto.keys; -import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.crypto.SimpleDERReader; import com.trilead.ssh2.packets.TypesWriter; import java.io.IOException; @@ -81,30 +81,40 @@ public boolean equals(Object o) { } private static byte[] decode(byte[] input) throws InvalidKeySpecException { - if (input.length != ENCODED_SIZE) { - throw new InvalidKeySpecException("Key is not of correct size"); + // Handle legacy RAW format (32 bytes) from commits f01a8b9 to 91bf5d0 (May-July 2020) + // Before commit 91bf5d0, getEncoded() returned just the raw 32-byte public key with format "RAW" + if (input.length == KEY_BYTES_LENGTH) { + return input; } + // Handle standard X.509 format (44 bytes) from commit 91bf5d0 onwards try { - TypesReader tr = new TypesReader(input); - if (tr.readByte() != 0x30 || - tr.readByte() != 7 + ED25519_OID.length + KEY_BYTES_LENGTH || - tr.readByte() != 0x30 || - tr.readByte() != 2 + ED25519_OID.length || - tr.readByte() != 0x06 || - tr.readByte() != ED25519_OID.length) { - throw new InvalidKeySpecException("Key was not encoded correctly"); + SimpleDERReader reader = new SimpleDERReader(input); + + byte[] sequenceData = reader.readSequenceAsByteArray(); + SimpleDERReader sequenceReader = new SimpleDERReader(sequenceData); + + int algType = sequenceReader.readConstructedType(); + SimpleDERReader algReader = sequenceReader.readConstructed(); + + String oid = algReader.readOid(); + if (!"1.3.101.112".equals(oid)) { + throw new InvalidKeySpecException("Expected Ed25519 OID (1.3.101.112), got: " + oid); } - byte[] oid = tr.readBytes(ED25519_OID.length); - if (!Arrays.equals(oid, ED25519_OID) || - tr.readByte() != 0x03 || - tr.readByte() != KEY_BYTES_LENGTH + 1 || - tr.readByte() != 0) { - throw new InvalidKeySpecException("Key was not encoded correctly"); + + byte[] publicKeyBitString = sequenceReader.readOctetString(); + + if (publicKeyBitString.length == KEY_BYTES_LENGTH + 1 && publicKeyBitString[0] == 0) { + byte[] result = new byte[KEY_BYTES_LENGTH]; + System.arraycopy(publicKeyBitString, 1, result, 0, KEY_BYTES_LENGTH); + return result; + } else if (publicKeyBitString.length == KEY_BYTES_LENGTH) { + return publicKeyBitString; + } else { + throw new InvalidKeySpecException("Expected " + KEY_BYTES_LENGTH + " byte public key, got " + publicKeyBitString.length); } - return tr.readBytes(KEY_BYTES_LENGTH); } catch (IOException e) { - throw new InvalidKeySpecException("Key was not encoded correctly"); + throw new InvalidKeySpecException("Key was not encoded correctly", e); } } diff --git a/src/test/java/com/trilead/ssh2/crypto/PublicKeyUtilsTest.java b/src/test/java/com/trilead/ssh2/crypto/PublicKeyUtilsTest.java index d99d6c55..c3cd1d85 100644 --- a/src/test/java/com/trilead/ssh2/crypto/PublicKeyUtilsTest.java +++ b/src/test/java/com/trilead/ssh2/crypto/PublicKeyUtilsTest.java @@ -6,6 +6,9 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -200,6 +203,45 @@ void testRoundTripExtractAndFormat() throws Exception { assertTrue(formatted.endsWith(" test")); } + @Test + void testExtractPublicKeyBlobWithNativeJDKEdDSAKey() throws Exception { + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("EdDSA"); + } catch (NoSuchAlgorithmException e) { + System.err.println("Skipping test: EdDSA not supported by this JDK"); + return; + } + + KeyPair keyPair = kpg.generateKeyPair(); + PublicKey nativePublicKey = keyPair.getPublic(); + + byte[] blob = PublicKeyUtils.extractPublicKeyBlob(nativePublicKey); + + assertNotNull(blob); + assertTrue(blob.length > 0); + } + + @Test + void testToAuthorizedKeysFormatWithNativeJDKEdDSAKey() throws Exception { + KeyPairGenerator kpg; + try { + kpg = KeyPairGenerator.getInstance("EdDSA"); + } catch (NoSuchAlgorithmException e) { + System.err.println("Skipping test: EdDSA not supported by this JDK"); + return; + } + + KeyPair keyPair = kpg.generateKeyPair(); + PublicKey nativePublicKey = keyPair.getPublic(); + + String result = PublicKeyUtils.toAuthorizedKeysFormat(nativePublicKey, "native-eddsa-key"); + + assertNotNull(result); + assertTrue(result.startsWith("ssh-ed25519 ")); + assertTrue(result.endsWith(" native-eddsa-key")); + } + private KeyPair loadKeyPair(String path) throws Exception { byte[] keyData = Files.readAllBytes(Paths.get(path)); String keyString = new String(keyData, "UTF-8"); 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 17f88b7d..cf2face6 100644 --- a/src/test/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactoryTest.java +++ b/src/test/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactoryTest.java @@ -21,6 +21,23 @@ public class Ed25519KeyFactoryTest { private static final byte[] KAT_ED25519_PRIV = toByteArray("f72a0a036e3479e15edb74da5f2a5418e66db450ad50687cad90247eeab6440c"); private static final byte[] KAT_ED25519_PUB = toByteArray("5386ea463b45fe14b4216f3f02a0a3f073b57724db10b86b65b2037e17b48c19"); + private static final byte[] OPENSSL_PRIVATE = toByteArray("302e020100300506032b657004220420afd211ae1c8a61e212ddfc5cc59949f6c37ef2f683772c14088a2f8e7b54baf7"); + private static final byte[] OPENSSL_PUBLIC = toByteArray("302a300506032b657003210006e59c37d4b0567863eb56397f7a2cfb78ae26a53dbd2206d83fb2c9cf1cbaea"); + private static final byte[] OPENSSL_ED25519_PRIV = toByteArray("afd211ae1c8a61e212ddfc5cc59949f6c37ef2f683772c14088a2f8e7b54baf7"); + private static final byte[] OPENSSL_ED25519_PUB = toByteArray("06e59c37d4b0567863eb56397f7a2cfb78ae26a53dbd2206d83fb2c9cf1cbaea"); + + // Generated with library version before commit 55e3ec98 (commit ce1cc53) + private static final byte[] OLD_LIB_PRIVATE = toByteArray("302e020100300506032b6570042204208fbad2fd15d27ca0a7b13c877d31b48e4a53ea55d9afc795f49696a50c389a77"); + private static final byte[] OLD_LIB_PUBLIC = toByteArray("302a300506032b6570032100b018a2d34b081be789c9f1af3896dcc14f7c963d3ce9dd38f7d805865f09ca88"); + private static final byte[] OLD_LIB_ED25519_PRIV = toByteArray("8fbad2fd15d27ca0a7b13c877d31b48e4a53ea55d9afc795f49696a50c389a77"); + private static final byte[] OLD_LIB_ED25519_PUB = toByteArray("b018a2d34b081be789c9f1af3896dcc14f7c963d3ce9dd38f7d805865f09ca88"); + + // Legacy RAW format from commits f01a8b9 to 91bf5d0 (May-July 2020) + // Before 91bf5d0, getEncoded() returned just the raw 32-byte seed with format "RAW" + // This tests backward compatibility for keys stored during that 2-month period + private static final byte[] LEGACY_RAW_PRIVATE = toByteArray("afd211ae1c8a61e212ddfc5cc59949f6c37ef2f683772c14088a2f8e7b54baf7"); + private static final byte[] LEGACY_RAW_PUBLIC = toByteArray("06e59c37d4b0567863eb56397f7a2cfb78ae26a53dbd2206d83fb2c9cf1cbaea"); + private static byte[] toByteArray(String s) { byte[] b = new byte[s.length() / 2]; for (int i = 0; i < b.length; i++) { @@ -81,4 +98,105 @@ public void translatesNativeJDKKeys() throws Exception { assertThat(((Ed25519PrivateKey) translatedPrivKey).getSeed(), is(((Ed25519PrivateKey) keyFactorySpi .engineGeneratePrivate(new PKCS8EncodedKeySpec(nativePrivKey.getEncoded()))).getSeed())); } + + @Test + public void decodesOpenSSLGeneratedPrivateKey() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + Ed25519PrivateKey pk = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(OPENSSL_PRIVATE)); + assertThat(pk.getSeed(), is(OPENSSL_ED25519_PRIV)); + } + + @Test + public void decodesOpenSSLGeneratedPublicKey() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + Ed25519PublicKey pub = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(OPENSSL_PUBLIC)); + assertThat(pub.getAbyte(), is(OPENSSL_ED25519_PUB)); + } + + @Test + public void openSSLKeyRoundTrip() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + + Ed25519PrivateKey privateKey = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(OPENSSL_PRIVATE)); + Ed25519PublicKey publicKey = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(OPENSSL_PUBLIC)); + + byte[] reEncodedPrivate = privateKey.getEncoded(); + byte[] reEncodedPublic = publicKey.getEncoded(); + + assertArrayEquals(OPENSSL_PRIVATE, reEncodedPrivate); + assertArrayEquals(OPENSSL_PUBLIC, reEncodedPublic); + + Ed25519PrivateKey privateKey2 = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(reEncodedPrivate)); + Ed25519PublicKey publicKey2 = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(reEncodedPublic)); + + assertThat(privateKey2.getSeed(), is(OPENSSL_ED25519_PRIV)); + assertThat(publicKey2.getAbyte(), is(OPENSSL_ED25519_PUB)); + } + + @Test + public void decodesOldLibraryGeneratedPrivateKey() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + Ed25519PrivateKey pk = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(OLD_LIB_PRIVATE)); + assertThat(pk.getSeed(), is(OLD_LIB_ED25519_PRIV)); + } + + @Test + public void decodesOldLibraryGeneratedPublicKey() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + Ed25519PublicKey pub = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(OLD_LIB_PUBLIC)); + assertThat(pub.getAbyte(), is(OLD_LIB_ED25519_PUB)); + } + + @Test + public void oldLibraryKeyRoundTrip() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + + Ed25519PrivateKey privateKey = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(OLD_LIB_PRIVATE)); + Ed25519PublicKey publicKey = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(OLD_LIB_PUBLIC)); + + byte[] reEncodedPrivate = privateKey.getEncoded(); + byte[] reEncodedPublic = publicKey.getEncoded(); + + assertArrayEquals(OLD_LIB_PRIVATE, reEncodedPrivate); + assertArrayEquals(OLD_LIB_PUBLIC, reEncodedPublic); + + Ed25519PrivateKey privateKey2 = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(reEncodedPrivate)); + Ed25519PublicKey publicKey2 = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(reEncodedPublic)); + + assertThat(privateKey2.getSeed(), is(OLD_LIB_ED25519_PRIV)); + assertThat(publicKey2.getAbyte(), is(OLD_LIB_ED25519_PUB)); + } + + @Test + public void decodesLegacyRawFormatPrivateKey() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + + Ed25519PrivateKey pk = (Ed25519PrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(LEGACY_RAW_PRIVATE)); + + assertThat(pk.getSeed(), is(LEGACY_RAW_PRIVATE)); + + byte[] reEncoded = pk.getEncoded(); + assertThat(reEncoded.length, is(48)); + } + + @Test + public void decodesLegacyRawFormatPublicKey() throws Exception { + Ed25519Provider p = new Ed25519Provider(); + KeyFactory kf = KeyFactory.getInstance("Ed25519", p); + + Ed25519PublicKey pub = (Ed25519PublicKey) kf.generatePublic(new X509EncodedKeySpec(LEGACY_RAW_PUBLIC)); + + assertThat(pub.getAbyte(), is(LEGACY_RAW_PUBLIC)); + + byte[] reEncoded = pub.getEncoded(); + assertThat(reEncoded.length, is(44)); + } + } diff --git a/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java b/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java index d5974ba8..9bc28e7e 100644 --- a/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java +++ b/src/test/java/com/trilead/ssh2/signature/Ed25519VerifyTest.java @@ -223,4 +223,44 @@ public void nativeJDKConversion() throws Exception { Ed25519PrivateKey convertedPrivKey = new Ed25519PrivateKey(privKeySpec); assertArrayEquals(nativePrivKey.getEncoded(), convertedPrivKey.getEncoded()); } + + @Test + public void opensslGeneratedKeySignAndVerify() throws Exception { + byte[] opensslPrivate = toByteArray("302e020100300506032b657004220420afd211ae1c8a61e212ddfc5cc59949f6c37ef2f683772c14088a2f8e7b54baf7"); + byte[] opensslPublic = toByteArray("302a300506032b657003210006e59c37d4b0567863eb56397f7a2cfb78ae26a53dbd2206d83fb2c9cf1cbaea"); + + Ed25519PrivateKey privateKey = new Ed25519PrivateKey(new PKCS8EncodedKeySpec(opensslPrivate)); + Ed25519PublicKey publicKey = new Ed25519PublicKey(new X509EncodedKeySpec(opensslPublic)); + + byte[] message = "Test message for OpenSSL-generated Ed25519 key".getBytes(); + + Ed25519Verify verifier = Ed25519Verify.get(); + byte[] signature = verifier.generateSignature(message, privateKey, new SecureRandom()); + + assertTrue(verifier.verifySignature(message, signature, publicKey)); + + byte[] tampered = Arrays.copyOf(message, message.length); + tampered[0] ^= 1; + assertFalse(verifier.verifySignature(tampered, signature, publicKey)); + } + + @Test + public void oldLibraryGeneratedKeySignAndVerify() throws Exception { + byte[] oldLibPrivate = toByteArray("302e020100300506032b6570042204208fbad2fd15d27ca0a7b13c877d31b48e4a53ea55d9afc795f49696a50c389a77"); + byte[] oldLibPublic = toByteArray("302a300506032b6570032100b018a2d34b081be789c9f1af3896dcc14f7c963d3ce9dd38f7d805865f09ca88"); + + Ed25519PrivateKey privateKey = new Ed25519PrivateKey(new PKCS8EncodedKeySpec(oldLibPrivate)); + Ed25519PublicKey publicKey = new Ed25519PublicKey(new X509EncodedKeySpec(oldLibPublic)); + + byte[] message = "Test message for old library-generated Ed25519 key".getBytes(); + + Ed25519Verify verifier = Ed25519Verify.get(); + byte[] signature = verifier.generateSignature(message, privateKey, new SecureRandom()); + + assertTrue(verifier.verifySignature(message, signature, publicKey)); + + byte[] tampered = Arrays.copyOf(message, message.length); + tampered[0] ^= 1; + assertFalse(verifier.verifySignature(tampered, signature, publicKey)); + } }