Skip to content
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
186 changes: 93 additions & 93 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 18 additions & 2 deletions src/main/java/com/trilead/ssh2/crypto/PublicKeyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Locale;

/**
* Utilities for working with SSH public keys.
Expand Down Expand Up @@ -58,7 +59,7 @@ public static String toAuthorizedKeysFormat(PublicKey publicKey, String comment)
keyType = ECDSASHA2Verify.getSshKeyType(ecKey);
SSHSignature verifier = ECDSASHA2Verify.getVerifierForKey(ecKey);
encoded = verifier.encodePublicKey(ecKey);
} else if ("EdDSA".equals(publicKey.getAlgorithm()) || "Ed25519".equals(publicKey.getAlgorithm())) {
} else if (isEd25519Key(publicKey)) {
Ed25519PublicKey ed25519Key = Ed25519Verify.convertPublicKey(publicKey);
keyType = Ed25519Verify.ED25519_ID;
encoded = Ed25519Verify.get().encodePublicKey(ed25519Key);
Expand Down Expand Up @@ -88,14 +89,29 @@ public static byte[] extractPublicKeyBlob(PublicKey publicKey)
ECPublicKey ecKey = (ECPublicKey) publicKey;
SSHSignature verifier = ECDSASHA2Verify.getVerifierForKey(ecKey);
return verifier.encodePublicKey(ecKey);
} else if ("EdDSA".equals(publicKey.getAlgorithm()) || "Ed25519".equals(publicKey.getAlgorithm())) {
} else if (isEd25519Key(publicKey)) {
Ed25519PublicKey ed25519Key = Ed25519Verify.convertPublicKey(publicKey);
return Ed25519Verify.get().encodePublicKey(ed25519Key);
} else {
throw new InvalidKeyException("Unknown key type: " + publicKey.getClass().getName());
}
}

/**
* Checks whether the given key is an Ed25519 key. This handles keys from different providers
* (e.g., JDK, Conscrypt/Google Play Services) which may use different algorithm names or
* class names for Ed25519 keys.
*/
static boolean isEd25519Key(PublicKey publicKey) {
String algorithm = publicKey.getAlgorithm();
if ("EdDSA".equals(algorithm) || "Ed25519".equals(algorithm) || "1.3.101.112".equals(algorithm)) {
return true;
}

String className = publicKey.getClass().getName().toLowerCase(Locale.ROOT);
return className.contains("ed25519") || className.contains("eddsa");
}

/**
* Detect the key type from OpenSSH format private key data without requiring password.
* This reads the unencrypted public key section of the OpenSSH format.
Expand Down
24 changes: 14 additions & 10 deletions src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ public Key engineTranslateKey(Key key) throws InvalidKeyException {
return key;
}

if (key instanceof PublicKey && key.getFormat().equals("X.509")) {
if (key instanceof PublicKey) {
byte[] encoded = key.getEncoded();
try {
return new Ed25519PublicKey(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
if (encoded != null) {
try {
return new Ed25519PublicKey(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
}
}

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

Expand Down
71 changes: 71 additions & 0 deletions src/test/java/com/trilead/ssh2/crypto/PublicKeyUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand Down Expand Up @@ -242,6 +243,76 @@ void testToAuthorizedKeysFormatWithNativeJDKEdDSAKey() throws Exception {
assertTrue(result.endsWith(" native-eddsa-key"));
}

@Test
void testExtractPublicKeyBlobWithOidAlgorithmName() throws Exception {
String keyPath = TEST_RESOURCES + "openssh_ed25519";
KeyPair keyPair = loadKeyPair(keyPath);
PublicKey originalKey = keyPair.getPublic();

PublicKey wrappedKey = new PublicKey() {
@Override
public String getAlgorithm() {
return "1.3.101.112";
}

@Override
public String getFormat() {
return originalKey.getFormat();
}

@Override
public byte[] getEncoded() {
return originalKey.getEncoded();
}
};

byte[] blob = assertDoesNotThrow(() -> PublicKeyUtils.extractPublicKeyBlob(wrappedKey));
assertNotNull(blob);
assertTrue(blob.length > 0);

byte[] expectedBlob = PublicKeyUtils.extractPublicKeyBlob(originalKey);
assertEquals(expectedBlob.length, blob.length);
for (int i = 0; i < expectedBlob.length; i++) {
assertEquals(expectedBlob[i], blob[i]);
}
}

@Test
void testExtractPublicKeyBlobWithEdDsaClassName() throws Exception {
String keyPath = TEST_RESOURCES + "openssh_ed25519";
KeyPair keyPair = loadKeyPair(keyPath);
PublicKey originalKey = keyPair.getPublic();

PublicKey wrappedKey = new OpenSslEdDsaPublicKeyStub(originalKey);

byte[] blob = assertDoesNotThrow(() -> PublicKeyUtils.extractPublicKeyBlob(wrappedKey));
assertNotNull(blob);
assertTrue(blob.length > 0);
}

private static class OpenSslEdDsaPublicKeyStub implements PublicKey {
private final PublicKey delegate;

OpenSslEdDsaPublicKeyStub(PublicKey delegate) {
this.delegate = delegate;
}

@Override
public String getAlgorithm() {
return "UnknownAlgorithm";
}

@Override
public String getFormat() {
return delegate.getFormat();
}

@Override
public byte[] getEncoded() {
return delegate.getEncoded();
}
}

private KeyPair loadKeyPair(String path) throws Exception {
byte[] keyData = Files.readAllBytes(Paths.get(path));
String keyString = new String(keyData, "UTF-8");
Expand Down
Loading