This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit bca964c7265c27e415d949e6a7764899012647f4
Author: Thomas Wolf <[email protected]>
AuthorDate: Mon Jul 14 21:32:17 2025 +0200

    GH-767: Make SkED25519PublicKey work with arbitrary ed25519 keys
    
    Remove the hard dependency on net.i2p.
---
 CHANGES.md                                         |  6 +-
 .../keys/impl/SkED25519PublicKeyEntryDecoder.java  | 20 +++--
 .../common/config/keys/u2f/SkED25519PublicKey.java | 17 +++--
 .../keys/SkED25519BufferPublicKeyParser.java       |  3 +-
 .../util/security/eddsa/generic/EdDSAUtils.java    | 89 ++++++++++++++++++++++
 5 files changed, 118 insertions(+), 17 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index a99f71630..99efaf1e0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -39,6 +39,8 @@
 * [GH-727](https://github.com/apache/mina-sshd/issues/727) Supply default port 
22 for proxy jump hosts for which there is no `HostConfigEntry`
 * [GH-733](https://github.com/apache/mina-sshd/issues/733) Fix 
`SftpRemotePathChannel.transferTo()` (avoid NPE)
 * [GH-751](https://github.com/apache/mina-sshd/issues/751) Fix SFTP v3 "long 
name" if SFTP server uses an `SftpFileSystem` to another server
+* [GH-767](https://github.com/apache/mina-sshd/issues/767) Remove dependency 
on net.i2p.crypto in `SkED25519PublicKey`
+* [GH-771](https://github.com/apache/mina-sshd/issues/771) Remove dependency 
on net.i2p.crypto in `EdDSAPuttyKeyDecoder`
 * [GH-774](https://github.com/apache/mina-sshd/issues/774) Fix 
`WritePendingException` in SFTP file copy
 
 
@@ -55,8 +57,8 @@
 
 ## Potential Compatibility Issues
 
-Client-side KEX: we've changed the default of the setting 
`CoreModuleProperties.ABORT_ON_INVALID_CERTIFICATE` from `false` to `true`.
-A client will newly abort an SSH connection if the server presents an invalid 
OpenSSH host certificate as host key.
+* Client-side KEX: we've changed the default of the setting 
`CoreModuleProperties.ABORT_ON_INVALID_CERTIFICATE` from `false` to `true`. A 
client will newly abort an SSH connection if the server presents an invalid 
OpenSSH host certificate as host key.
+* [GH-767](https://github.com/apache/mina-sshd/issues/767) and 
[GH-771](https://github.com/apache/mina-sshd/issues/771) cause API changes in 
classes `SkED25519PublicKey` and `EdDSAPuttyKeyDecoder`. Both changes are 
unlikely to be noticed in user code since user code normally doesn't need to 
use either class.
 
 ## Major Code Re-factoring
 
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
index 550e1b925..1380a2b41 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/SkED25519PublicKeyEntryDecoder.java
@@ -23,21 +23,23 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
+import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.config.keys.KeyEntryResolver;
 import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.session.SessionContext;
-import org.apache.sshd.common.util.security.eddsa.Ed25519PublicKeyDecoder;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.security.eddsa.generic.EdDSAUtils;
 
 /**
  * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
@@ -63,10 +65,10 @@ public class SkED25519PublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecode
         }
 
         boolean noTouchRequired = parseBooleanHeader(headers, 
NO_TOUCH_REQUIRED_HEADER, false);
-        EdDSAPublicKey edDSAPublicKey
-                = Ed25519PublicKeyDecoder.INSTANCE.decodePublicKey(session, 
KeyPairProvider.SSH_ED25519, keyData, headers);
+        PublicKey pk = 
SecurityUtils.getEDDSAPublicKeyEntryDecoder().decodePublicKey(session, 
KeyPairProvider.SSH_ED25519,
+                keyData, headers);
         String appName = KeyEntryResolver.decodeString(keyData, 
MAX_APP_NAME_LENGTH);
-        return new SkED25519PublicKey(appName, noTouchRequired, 
edDSAPublicKey);
+        return new SkED25519PublicKey(appName, noTouchRequired, pk);
     }
 
     @Override
@@ -82,8 +84,12 @@ public class SkED25519PublicKeyEntryDecoder extends 
AbstractPublicKeyEntryDecode
     public String encodePublicKey(OutputStream s, SkED25519PublicKey key) 
throws IOException {
         Objects.requireNonNull(key, "No public key provided");
         KeyEntryResolver.encodeString(s, KEY_TYPE);
-        byte[] seed = 
Ed25519PublicKeyDecoder.getSeedValue(key.getDelegatePublicKey());
-        KeyEntryResolver.writeRLEBytes(s, seed);
+        try {
+            byte[] keyData = EdDSAUtils.getBytes(key.getDelegatePublicKey());
+            KeyEntryResolver.writeRLEBytes(s, keyData);
+        } catch (InvalidKeyException e) {
+            throw new IOException(e.getMessage(), e);
+        }
         KeyEntryResolver.encodeString(s, key.getAppName());
         return KEY_TYPE;
     }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
index ebac6a583..a6f525e66 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/u2f/SkED25519PublicKey.java
@@ -18,24 +18,29 @@
  */
 package org.apache.sshd.common.config.keys.u2f;
 
+import java.security.PublicKey;
 import java.util.Objects;
 
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.ValidateUtils;
 
-public class SkED25519PublicKey implements 
SecurityKeyPublicKey<EdDSAPublicKey> {
+public class SkED25519PublicKey implements SecurityKeyPublicKey<PublicKey> {
 
     public static final String ALGORITHM = "ED25519-SK";
 
-    private static final long serialVersionUID = 4587115316266869640L;
+    private static final long serialVersionUID = -3947776805731312115L;
 
     private final String appName;
     private final boolean noTouchRequired;
-    private final EdDSAPublicKey delegatePublicKey;
+    private final PublicKey delegatePublicKey;
 
-    public SkED25519PublicKey(String appName, boolean noTouchRequired, 
EdDSAPublicKey delegatePublicKey) {
+    public SkED25519PublicKey(String appName, boolean noTouchRequired, 
PublicKey delegatePublicKey) {
         this.appName = appName;
         this.noTouchRequired = noTouchRequired;
+        
ValidateUtils.checkTrue(KeyPairProvider.SSH_ED25519.equals(KeyUtils.getKeyType(delegatePublicKey)),
+                "Key is not an ed25519 key");
         this.delegatePublicKey = delegatePublicKey;
     }
 
@@ -70,7 +75,7 @@ public class SkED25519PublicKey implements 
SecurityKeyPublicKey<EdDSAPublicKey>
     }
 
     @Override
-    public EdDSAPublicKey getDelegatePublicKey() {
+    public PublicKey getDelegatePublicKey() {
         return delegatePublicKey;
     }
 
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
index c905e7727..217258de8 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/SkED25519BufferPublicKeyParser.java
@@ -22,7 +22,6 @@ package org.apache.sshd.common.util.buffer.keys;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
 
-import net.i2p.crypto.eddsa.EdDSAPublicKey;
 import org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder;
 import org.apache.sshd.common.config.keys.u2f.SkED25519PublicKey;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
@@ -44,6 +43,6 @@ public class SkED25519BufferPublicKeyParser extends 
AbstractBufferPublicKeyParse
         // the end
         PublicKey publicKey = 
ED25519BufferPublicKeyParser.INSTANCE.getRawPublicKey(KeyPairProvider.SSH_ED25519,
 buffer);
         String appName = buffer.getString();
-        return new SkED25519PublicKey(appName, false, (EdDSAPublicKey) 
publicKey);
+        return new SkED25519PublicKey(appName, false, publicKey);
     }
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
new file mode 100644
index 000000000..713b49e37
--- /dev/null
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/generic/EdDSAUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.util.security.eddsa.generic;
+
+import java.security.InvalidKeyException;
+import java.security.PublicKey;
+import java.util.Arrays;
+
+/**
+ * Utilities to extract the raw key bytes from ed25519 or ed448 public keys, 
in a manner that is independent of the
+ * actual concrete key implementation classes.
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public final class EdDSAUtils {
+
+    private static final int ED25519_LENGTH = 32; // bytes
+
+    private static final int ED448_LENGTH = 57; // bytes
+
+    // These are the constant prefixes of X.509 encodings of ed25519 and ed448 
keys. Appending the actual 32
+    // or 57 key bytes yields valid encodings.
+
+    // Sequence, length 42, Sequence, length 5, OID, length 3, O, I, D, Bit 
String, length 33, zero unused bits
+    private static final byte[] ED25519_X509_PREFIX = {
+            0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 
0x00 };
+    // Sequence, length 67, Sequence, length 5, OID, length 3, O, I, D, Bit 
String, length 58, zero unused bits
+    private static final byte[] ED448_X509_PREFIX = {
+            0x30, 0x43, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x71, 0x03, 0x3a, 
0x00 };
+
+    private EdDSAUtils() {
+        throw new IllegalStateException("No instantiation");
+    }
+
+    private static boolean startsWith(byte[] data, byte[] prefix) {
+        if (data == null || prefix == null || prefix.length == 0 || 
data.length < prefix.length) {
+            return false;
+        }
+        int unequal = 0;
+        int length = prefix.length;
+        for (int i = 0; i < length; i++) {
+            unequal |= data[i] ^ prefix[i];
+        }
+        return unequal == 0;
+    }
+
+    /**
+     * Retrieves the raw key bytes from an ed25519 or ed448 {@link PublicKey}.
+     *
+     * @param  key                 {@link PublicKey} to get the bytes of
+     * @return                     the raw key bytes
+     * @throws InvalidKeyException if the key is not an ed25519 or ed448 key, 
or if it doesn't use X.509 encoding
+     */
+    public static byte[] getBytes(PublicKey key) throws InvalidKeyException {
+        // Extract the public key bytes from the X.509 encoding (last n bytes, 
depending on the OID).
+        if (!"X.509".equalsIgnoreCase(key.getFormat())) {
+            throw new InvalidKeyException("Cannot extract public key bytes 
from a non-X.509 encoding");
+        }
+        byte[] encoded = key.getEncoded();
+        if (encoded == null) {
+            throw new InvalidKeyException("Public key " + 
key.getClass().getCanonicalName() + " does not support encoding");
+        }
+        int n;
+        if (encoded.length == ED25519_LENGTH + ED25519_X509_PREFIX.length && 
startsWith(encoded, ED25519_X509_PREFIX)) {
+            n = ED25519_LENGTH;
+        } else if (encoded.length == ED448_LENGTH + ED448_X509_PREFIX.length 
&& startsWith(encoded, ED448_X509_PREFIX)) {
+            n = ED448_LENGTH;
+        } else {
+            throw new InvalidKeyException("Public key is neither ed25519 nor 
ed448");
+        }
+        return Arrays.copyOfRange(encoded, encoded.length - n, encoded.length);
+    }
+}

Reply via email to