You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tw...@apache.org on 2025/04/24 20:33:18 UTC

(mina-sshd) branch dev_3.0 updated (1209dcc84 -> d04cc4584)

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

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


    from 1209dcc84 [site] Improve links in japicmp reports
     new 138bcfbe8 Simplify SshClient
     new ba8f228ed [test] Add a test for HttpParser
     new ccb211d4c Session creation: lazy initialize ClientUserAuthService
     new 07ebc0998 Fix typo in comment
     new a998735f1 Minor simplification in server-side SocksProxy class
     new d04cc4584 An OpenSshCertificate is not a PrivateKey!

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/sshd/common/config/keys/KeyUtils.java   | 128 ++++-----------------
 .../common/config/keys/OpenSshCertificate.java     |   3 +-
 .../keys/impl/OpenSSHCertificateDecoder.java       |  30 ++---
 .../openssh/OpenSSHKeyPairResourceParser.java      |  50 +-------
 .../java/org/apache/sshd/client/SshClient.java     | 111 ++++++++++--------
 .../sshd/client/auth/pubkey/UserAuthPublicKey.java |  14 ---
 .../sshd/client/session/ClientUserAuthService.java |  41 +++----
 .../client/session/proxy/GssApiMechanisms.java     |   4 +-
 .../org/apache/sshd/common/forward/SocksProxy.java |  46 +++-----
 .../sshd/client/session/proxy/HttpParserTest.java  | 110 ++++++++++++++++++
 10 files changed, 254 insertions(+), 283 deletions(-)
 create mode 100644 sshd-core/src/test/java/org/apache/sshd/client/session/proxy/HttpParserTest.java


(mina-sshd) 03/06: Session creation: lazy initialize ClientUserAuthService

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ccb211d4cc71adf763c031ae9563bbb8bff78f41
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Apr 24 00:08:04 2025 +0200

    Session creation: lazy initialize ClientUserAuthService
    
    This service gets created when the session is created. At that time the
    properties may not be set yet on the session, so the constructor cannot
    read them. Read them later when the service is started.
    
    Arguably creating the service when the session is created is too early,
    it would be sufficient to create it once the SERVICE_REQUEST message
    arrives. But that would be a more risky change.
---
 .../java/org/apache/sshd/client/SshClient.java     | 25 ++++++++++---
 .../sshd/client/session/ClientUserAuthService.java | 41 +++++++++++-----------
 2 files changed, 42 insertions(+), 24 deletions(-)

diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 54d85a853..5778bd426 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -23,12 +23,14 @@ import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.SocketTimeoutException;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.channels.UnsupportedAddressTypeException;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
+import java.text.MessageFormat;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -82,6 +84,7 @@ import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.channel.ChannelFactory;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.FilePasswordProviderManager;
@@ -629,7 +632,12 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                                 }
                             } else {
                                 proxySession.close(true);
-                                connectFuture.setException(f3.getException());
+                                // Record at which proxy we could not log in
+                                SshException ex = new SshException(SshConstants.SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+                                        MessageFormat.format("Cannot log in at {0}:{1}", jump.getHost(),
+                                                Integer.toString(jump.getPort())),
+                                        f3.getException());
+                                connectFuture.setException(ex);
                             }
                         });
                     } catch (IOException e) {
@@ -722,17 +730,26 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         if (GenericUtils.isEmpty(proxyJump)) {
             return jumps;
         }
-        String[] hops = GenericUtils.split(proxyJump, ',');
+        String trimmed = proxyJump.trim();
+        if ("none".equals(trimmed)) {
+            return jumps;
+        }
+        String[] hops = GenericUtils.split(trimmed, ',');
         for (String hop : hops) {
             String h = hop.trim();
             if (h.isEmpty()) {
                 throw new IllegalArgumentException("Empty proxy jump in list: " + proxyJump);
             }
             URI uri = URI.create(h.contains("://") ? h : "ssh://" + h);
-            if (GenericUtils.isNotEmpty(uri.getScheme()) && !"ssh".equals(uri.getScheme())) {
-                throw new IllegalArgumentException("Unsupported scheme for proxy jump: " + hop);
+            if (!"ssh".equals(uri.getScheme())) {
+                throw new IllegalArgumentException("Invalid proxy jump: " + hop,
+                        new URISyntaxException(hop, "Scheme must be 'ssh', was '" + uri.getScheme() + '\''));
             }
             String host = uri.getHost();
+            if (GenericUtils.isEmpty(host) || !GenericUtils.isEmpty(uri.getPath())) {
+                throw new IllegalArgumentException("Invalid proxy jump: " + hop,
+                        new URISyntaxException(hop, "Must have a host and no path"));
+            }
             int port = uri.getPort();
             String userInfo = uri.getUserInfo();
             HostConfigEntry entry = resolveHost(userInfo, host, port, context, null);
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
index ffcfe5b30..140a6fbac 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientUserAuthService.java
@@ -82,6 +82,27 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
                 clientSession.getUserAuthFactories(), "No user auth factories for %s", s);
         clientMethods = new ArrayList<>();
 
+        clientSession.resetAuthTimeout();
+    }
+
+    @Override
+    public ClientSession getSession() {
+        return getClientSession();
+    }
+
+    @Override
+    public ClientSession getClientSession() {
+        return clientSession;
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public void start() {
+        ClientSession s = getClientSession();
         String prefs = CoreModuleProperties.PREFERRED_AUTHS.getOrNull(s);
         boolean debugEnabled = log.isDebugEnabled();
         if (GenericUtils.isEmpty(prefs)) {
@@ -109,26 +130,6 @@ public class ClientUserAuthService extends AbstractCloseable implements Service,
             log.debug("ClientUserAuthService({}) client methods: {}", s, clientMethods);
         }
 
-        clientSession.resetAuthTimeout();
-    }
-
-    @Override
-    public ClientSession getSession() {
-        return getClientSession();
-    }
-
-    @Override
-    public ClientSession getClientSession() {
-        return clientSession;
-    }
-
-    @Override
-    public Map<String, Object> getProperties() {
-        return properties;
-    }
-
-    @Override
-    public void start() {
         Runnable initial;
         synchronized (initLock) {
             started = true;


(mina-sshd) 06/06: An OpenSshCertificate is not a PrivateKey!

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d04cc45845c301bca9f60487c71f53479e19068d
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Apr 24 21:00:32 2025 +0200

    An OpenSshCertificate is not a PrivateKey!
    
    Remove the interface. Handle this like the SK keys: specify the private
    key type in the decoder as PrivateKey.
    
    Remove the decoder-by-key-class methods and maps; they are not needed
    and, as the now removed comment pointed out, fragile.
---
 .../apache/sshd/common/config/keys/KeyUtils.java   | 128 ++++-----------------
 .../common/config/keys/OpenSshCertificate.java     |   3 +-
 .../keys/impl/OpenSSHCertificateDecoder.java       |  30 ++---
 .../openssh/OpenSSHKeyPairResourceParser.java      |  50 +-------
 4 files changed, 42 insertions(+), 169 deletions(-)

diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
index b53a3ea78..357635eeb 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -54,7 +54,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
@@ -147,10 +146,6 @@ public final class KeyUtils {
     private static final Map<String, PublicKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP
             = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
 
-    // order matters here, when, for example, SkED25519PublicKeyEntryDecoder is used, it registers the PrivateKey
-    // type as java.security.PrivateKey, and all PrivateKey types are assignable to this, so it must be consulted last
-    private static final Map<Class<?>, PublicKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP = new LinkedHashMap<>();
-
     private static final Map<String, String> KEY_TYPE_ALIASES
             = NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER)
                     .put(RSA_SHA256_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA)
@@ -355,7 +350,7 @@ public final class KeyUtils {
      * @return                          A {@link KeyPair} of the specified type and size
      * @throws GeneralSecurityException If failed to generate the key pair
      * @see                             #getPublicKeyEntryDecoder(String)
-     * @see                             PublicKeyEntryDecoder#generateKeyPair(int)
+     * @see                             PublicKeyEntryDecoder<?, ?>#generateKeyPair(int)
      */
     public static KeyPair generateKeyPair(String keyType, int keySize) throws GeneralSecurityException {
         PublicKeyEntryDecoder<?, ?> decoder = getPublicKeyEntryDecoder(keyType);
@@ -385,88 +380,33 @@ public final class KeyUtils {
     }
 
     /**
-     * @param  decoder                  The decoder to register
-     * @throws IllegalArgumentException if no decoder or not key type or no supported names for the decoder
-     * @see                             PublicKeyEntryDecoder#getPublicKeyType()
-     * @see                             PublicKeyEntryDecoder#getSupportedKeyTypes()
-     */
-    public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
-        Objects.requireNonNull(decoder, "No decoder specified");
-
-        Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
-        Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
-        synchronized (BY_KEY_CLASS_DECODERS_MAP) {
-            BY_KEY_CLASS_DECODERS_MAP.put(pubType, decoder);
-            BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
-        }
-
-        registerPublicKeyEntryDecoderKeyTypes(decoder);
-    }
-
-    /**
-     * Registers the specified decoder for all the types it {@link PublicKeyEntryDecoder#getSupportedKeyTypes()
+     * Registers the specified decoder for all the types it {@link PublicKeyEntryDecoder<?, ?>#getSupportedKeyTypes()
      * supports}
      *
-     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
-     * @see           #registerPublicKeyEntryDecoderForKeyType(String, PublicKeyEntryDecoder)
+     * @param decoder The (never {@code null}) {@link PublicKeyEntryDecoder<?, ?> decoder} to register
+     * @see           #registerPublicKeyEntryDecoderForKeyType(String, PublicKeyEntryDecoder<?, ?>)
      */
-    public static void registerPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder<?, ?> decoder) {
+    public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
         Objects.requireNonNull(decoder, "No decoder specified");
 
         Collection<String> names
                 = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedKeyTypes(), "No supported key types");
-        for (String n : names) {
-            PublicKeyEntryDecoder<?, ?> prev = registerPublicKeyEntryDecoderForKeyType(n, decoder);
-            if (prev != null) {
-                // noinspection UnnecessaryContinue
-                continue; // debug breakpoint
-            }
-        }
-    }
-
-    /**
-     * @param  keyType The key (never {@code null}/empty) key type
-     * @param  decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to register
-     * @return         The previously registered decoder for this key type - {@code null} if none
-     */
-    public static PublicKeyEntryDecoder<?, ?> registerPublicKeyEntryDecoderForKeyType(
-            String keyType, PublicKeyEntryDecoder<?, ?> decoder) {
-        keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
-        Objects.requireNonNull(decoder, "No decoder specified");
-
         synchronized (BY_KEY_TYPE_DECODERS_MAP) {
-            return BY_KEY_TYPE_DECODERS_MAP.put(keyType, decoder);
-        }
-    }
-
-    /**
-     * @param  decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister
-     * @return         The case <U>insensitive</U> {@link NavigableSet} of all the effectively un-registered key types
-     *                 out of all the {@link PublicKeyEntryDecoder#getSupportedKeyTypes() supported} ones.
-     * @see            #unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder)
-     */
-    public static NavigableSet<String> unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
-        Objects.requireNonNull(decoder, "No decoder specified");
-
-        Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
-        Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
-        synchronized (BY_KEY_CLASS_DECODERS_MAP) {
-            BY_KEY_CLASS_DECODERS_MAP.remove(pubType);
-            BY_KEY_CLASS_DECODERS_MAP.remove(prvType);
+            for (String keyType : names) {
+                BY_KEY_TYPE_DECODERS_MAP.put(keyType, decoder);
+            }
         }
-
-        return unregisterPublicKeyEntryDecoderKeyTypes(decoder);
     }
 
     /**
      * Unregisters the specified decoder for all the types it supports
      *
-     * @param  decoder The (never {@code null}) {@link PublicKeyEntryDecoder decoder} to unregister
+     * @param  decoder The (never {@code null}) {@link PublicKeyEntryDecoder<?, ?> decoder} to unregister
      * @return         The case <U>insensitive</U> {@link NavigableSet} of all the effectively un-registered key types
-     *                 out of all the {@link PublicKeyEntryDecoder#getSupportedKeyTypes() supported} ones.
+     *                 out of all the {@link PublicKeyEntryDecoder<?, ?>#getSupportedKeyTypes() supported} ones.
      * @see            #unregisterPublicKeyEntryDecoderForKeyType(String)
      */
-    public static NavigableSet<String> unregisterPublicKeyEntryDecoderKeyTypes(PublicKeyEntryDecoder<?, ?> decoder) {
+    public static NavigableSet<String> unregisterPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
         Objects.requireNonNull(decoder, "No decoder specified");
 
         Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(
@@ -495,8 +435,8 @@ public final class KeyUtils {
      * Unregister the decoder registered for the specified key type
      *
      * @param  keyType The key (never {@code null}/empty) key type
-     * @return         The unregistered {@link PublicKeyEntryDecoder} - {@code null} if none registered for this key
-     *                 type
+     * @return         The unregistered {@link PublicKeyEntryDecoder<?, ?>} - {@code null} if none registered for this
+     *                 key type
      */
     public static PublicKeyEntryDecoder<?, ?> unregisterPublicKeyEntryDecoderForKeyType(String keyType) {
         keyType = ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type specified");
@@ -509,7 +449,7 @@ public final class KeyUtils {
     /**
      * @param  keyType The {@code OpenSSH} key type string - e.g., {@code ssh-rsa, ssh-dss} - ignored if
      *                 {@code null}/empty
-     * @return         The registered {@link PublicKeyEntryDecoder} or {code null} if not found
+     * @return         The registered {@link PublicKeyEntryDecoder<?, ?>} or {code null} if not found
      */
     public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(String keyType) {
         if (GenericUtils.isEmpty(keyType)) {
@@ -523,8 +463,8 @@ public final class KeyUtils {
 
     /**
      * @param  kp The {@link KeyPair} to examine - ignored if {@code null}
-     * @return    The matching {@link PublicKeyEntryDecoder} provided <U>both</U> the public and private keys have the
-     *            same decoder - {@code null} if no match found
+     * @return    The matching {@link PublicKeyEntryDecoder<?, ?>} provided <U>both</U> the public and private keys have
+     *            the same decoder - {@code null} if no match found
      * @see       #getPublicKeyEntryDecoder(Key)
      */
     public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(KeyPair kp) {
@@ -543,45 +483,17 @@ public final class KeyUtils {
 
     /**
      * @param  key The {@link Key} (public or private) - ignored if {@code null}
-     * @return     The registered {@link PublicKeyEntryDecoder} for this key or {code null} if no match found
+     * @return     The registered {@link PublicKeyEntryDecoder<?, ?>} for this key or {code null} if no match found
      * @see        #getPublicKeyEntryDecoder(Class)
      */
     public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(Key key) {
         if (key == null) {
             return null;
         } else {
-            return getPublicKeyEntryDecoder(key.getClass());
+            return getPublicKeyEntryDecoder(KeyUtils.getKeyType(key));
         }
     }
 
-    /**
-     * @param  keyType The key {@link Class} - ignored if {@code null} or not a {@link Key} compatible type
-     * @return         The registered {@link PublicKeyEntryDecoder} or {code null} if no match found
-     */
-    public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(Class<?> keyType) {
-        if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
-            return null;
-        }
-
-        synchronized (BY_KEY_TYPE_DECODERS_MAP) {
-            PublicKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
-            if (decoder != null) {
-                return decoder;
-            }
-
-            // in case it is a derived class
-            for (PublicKeyEntryDecoder<?, ?> dec : BY_KEY_CLASS_DECODERS_MAP.values()) {
-                Class<?> pubType = dec.getPublicKeyType();
-                Class<?> prvType = dec.getPrivateKeyType();
-                if (pubType.isAssignableFrom(keyType) || prvType.isAssignableFrom(keyType)) {
-                    return dec;
-                }
-            }
-        }
-
-        return null;
-    }
-
     /**
      * @return The default {@link DigestFactory} by the {@link #getFingerPrint(PublicKey)} and
      *         {@link #getFingerPrint(String)} methods
@@ -837,7 +749,7 @@ public final class KeyUtils {
     /**
      * @param  kp a key pair - ignored if {@code null}. If the private key is non-{@code null} then it is used to
      *            determine the type, otherwise the public one is used.
-     * @return    the key type or {@code null} if cannot determine it
+     * @return    the SSH key type or {@code null} if cannot determine it
      * @see       #getKeyType(Key)
      */
     public static String getKeyType(KeyPair kp) {
@@ -854,7 +766,7 @@ public final class KeyUtils {
 
     /**
      * @param  key a public or private key
-     * @return     the key type or {@code null} if cannot determine it
+     * @return     the SSH key type or {@code null} if cannot determine it
      */
     public static String getKeyType(Key key) {
         if (key == null) {
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java
index f9f0cef30..ab3e230a1 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sshd.common.config.keys;
 
-import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.time.Instant;
 import java.util.Arrays;
@@ -37,7 +36,7 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  * @see    <a href= "https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD">PROTOCOL.certkeys</a>
  */
-public interface OpenSshCertificate extends SshPublicKey, PrivateKey {
+public interface OpenSshCertificate extends SshPublicKey {
 
     /**
      * {@link OpenSshCertificate}s have a type indicating whether the certificate if for a host key (certifying a host
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
index b6fa7a9f5..68822e7ce 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
@@ -26,7 +26,9 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
+import java.security.KeyPair;
 import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
@@ -43,11 +45,11 @@ import org.apache.sshd.common.util.io.IoUtils;
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<OpenSshCertificate, OpenSshCertificate> {
+public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<OpenSshCertificate, PrivateKey> {
     public static final OpenSSHCertificateDecoder INSTANCE = new OpenSSHCertificateDecoder();
 
     public OpenSSHCertificateDecoder() {
-        super(OpenSshCertificate.class, OpenSshCertificate.class,
+        super(OpenSshCertificate.class, PrivateKey.class,
               Collections.unmodifiableList(Arrays.asList(
                       KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS,
                       KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS,
@@ -72,9 +74,7 @@ public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<Ope
         buffer.putString(keyType);
         buffer.putRawBytes(IoUtils.toByteArray(keyData));
         buffer.getString(); // Skip the key type just prepended
-        OpenSshCertificate cert = OpenSSHCertPublicKeyParser.INSTANCE.getRawPublicKey(keyType, buffer);
-
-        return cert;
+        return OpenSSHCertPublicKeyParser.INSTANCE.getRawPublicKey(keyType, buffer);
     }
 
     @Override
@@ -82,8 +82,7 @@ public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<Ope
         Objects.requireNonNull(key, "No public key provided");
 
         ByteArrayBuffer buffer = new ByteArrayBuffer();
-        // use Buffer.putRawPublicKey so the certificate type is prepended
-        buffer.putRawPublicKey(key);
+        buffer.putRawPublicKey(key); // prepends the certificate type
         s.write(buffer.getCompactData());
 
         return key.getKeyType();
@@ -102,17 +101,22 @@ public class OpenSSHCertificateDecoder extends AbstractPublicKeyEntryDecoder<Ope
     }
 
     @Override
-    public OpenSshCertificate clonePrivateKey(OpenSshCertificate key) throws GeneralSecurityException {
-        return clonePublicKey(key);
+    public PrivateKey clonePrivateKey(PrivateKey key) {
+        throw new UnsupportedOperationException("Private key operations are not supported for certificates.");
+    }
+
+    @Override
+    public KeyFactory getKeyFactoryInstance() {
+        throw new UnsupportedOperationException("Private key operations are not supported for certificates.");
     }
 
     @Override
-    public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
-        return null;
+    public KeyPair generateKeyPair(int keySize) {
+        throw new UnsupportedOperationException("Private key operations are not supported for certificates.");
     }
 
     @Override
-    public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
-        return null;
+    public KeyPairGenerator getKeyPairGenerator() {
+        throw new UnsupportedOperationException("Private key operations are not supported for certificates.");
     }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
index 3fd1c1d5a..a28989375 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java
@@ -36,7 +36,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -83,8 +82,6 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
     private static final Map<String, PrivateKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP
             = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
 
-    private static final Map<Class<?>, PrivateKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP = new HashMap<>();
-
     static {
         registerPrivateKeyEntryDecoder(OpenSSHRSAPrivateKeyDecoder.INSTANCE);
         registerPrivateKeyEntryDecoder(OpenSSHDSSPrivateKeyEntryDecoder.INSTANCE);
@@ -279,7 +276,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
 
             if (traceEnabled) {
                 log.trace("extractKeyPairs({}) add private key #{}: {} {}",
-                        resourceKey, keyIndex, prvType, prvData.getValue());
+                        resourceKey, keyIndex, prvType, prvData == null ? "" : prvData.getValue());
             }
             keyPairs.add(new KeyPair(pubKey, prvKey));
         }
@@ -345,22 +342,11 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
     public static void registerPrivateKeyEntryDecoder(PrivateKeyEntryDecoder<?, ?> decoder) {
         Objects.requireNonNull(decoder, "No decoder specified");
 
-        Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
-        Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
-        synchronized (BY_KEY_CLASS_DECODERS_MAP) {
-            BY_KEY_CLASS_DECODERS_MAP.put(pubType, decoder);
-            BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
-        }
-
         Collection<String> names
                 = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedKeyTypes(), "No supported key type");
         synchronized (BY_KEY_TYPE_DECODERS_MAP) {
-            for (String n : names) {
-                PrivateKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
-                if (prev != null) {
-                    // noinspection UnnecessaryContinue
-                    continue; // debug breakpoint
-                }
+            for (String keyType : names) {
+                BY_KEY_TYPE_DECODERS_MAP.put(keyType, decoder);
             }
         }
     }
@@ -409,35 +395,7 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser
         if (key == null) {
             return null;
         } else {
-            return getPrivateKeyEntryDecoder(key.getClass());
-        }
-    }
-
-    /**
-     * @param  keyType The key {@link Class} - ignored if {@code null} or not a {@link Key} compatible type
-     * @return         The registered {@link PrivateKeyEntryDecoder} or {code null} if no match found
-     */
-    public static PrivateKeyEntryDecoder<?, ?> getPrivateKeyEntryDecoder(Class<?> keyType) {
-        if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
-            return null;
-        }
-
-        synchronized (BY_KEY_TYPE_DECODERS_MAP) {
-            PrivateKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
-            if (decoder != null) {
-                return decoder;
-            }
-
-            // in case it is a derived class
-            for (PrivateKeyEntryDecoder<?, ?> dec : BY_KEY_CLASS_DECODERS_MAP.values()) {
-                Class<?> pubType = dec.getPublicKeyType();
-                Class<?> prvType = dec.getPrivateKeyType();
-                if (pubType.isAssignableFrom(keyType) || prvType.isAssignableFrom(keyType)) {
-                    return dec;
-                }
-            }
+            return getPrivateKeyEntryDecoder(KeyUtils.getKeyType(key));
         }
-
-        return null;
     }
 }


(mina-sshd) 05/06: Minor simplification in server-side SocksProxy class

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a998735f17c91c3a49d2f30db95938be46bc56d4
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Apr 24 18:42:17 2025 +0200

    Minor simplification in server-side SocksProxy class
    
    Use the buffer methods directly.
---
 .../org/apache/sshd/common/forward/SocksProxy.java | 46 +++++++---------------
 1 file changed, 15 insertions(+), 31 deletions(-)

diff --git a/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java b/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
index 2534db1a6..4736fe924 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
@@ -115,14 +115,6 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
             }
         }
 
-        protected int getUByte(Buffer buffer) {
-            return buffer.getUByte();
-        }
-
-        protected int getUShort(Buffer buffer) {
-            return buffer.getUShort();
-        }
-
         protected void sendReply(Buffer message, boolean success, LocalWindow window, long windowSize) {
             try {
                 session.writeBuffer(message);
@@ -166,11 +158,9 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
                 if (cmd != SocksConstants.Socks4.CMD_CONNECT) {
                     throw new IllegalStateException("Unsupported socks command: " + cmd);
                 }
-                int port = getUShort(buffer);
-                String host = Integer.toString(getUByte(buffer)) + "."
-                              + Integer.toString(getUByte(buffer)) + "."
-                              + Integer.toString(getUByte(buffer)) + "."
-                              + Integer.toString(getUByte(buffer));
+                int port = buffer.getUShort();
+                String host = Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte()) + "."
+                              + Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte());
                 String userId = getNTString(buffer);
                 // Socks4a
                 if (host.startsWith("0.0.0.")) {
@@ -219,7 +209,7 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
 
         protected String getNTString(Buffer buffer) {
             StringBuilder sb = new StringBuilder();
-            for (char c = (char) getUByte(buffer); c != '\0'; c = (char) getUByte(buffer)) {
+            for (char c = (char) buffer.getUByte(); c != '\0'; c = (char) buffer.getUByte()) {
                 sb.append(c);
             }
             return sb.toString();
@@ -242,7 +232,7 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
         protected void onMessage(Buffer buffer) throws IOException {
             boolean debugEnabled = log.isDebugEnabled();
             if (authMethods == null) {
-                int nbAuthMethods = getUByte(buffer);
+                int nbAuthMethods = buffer.getUByte();
                 authMethods = new byte[nbAuthMethods];
                 buffer.getRawBytes(authMethods);
                 boolean foundNoAuth = false;
@@ -259,7 +249,7 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
                     log.debug("Received socks5 greeting");
                 }
             } else if (channel == null) {
-                int version = getUByte(buffer);
+                int version = buffer.getUByte();
                 if (version != SocksConstants.Socks5.VERSION) {
                     throw new IllegalStateException("Unexpected version: " + version);
                 }
@@ -275,25 +265,19 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
                 int type = buffer.getUByte();
                 String host;
                 if (type == SocksConstants.Socks5.ADDRESS_IPV4) {
-                    host = Integer.toString(getUByte(buffer)) + "."
-                           + Integer.toString(getUByte(buffer)) + "."
-                           + Integer.toString(getUByte(buffer)) + "."
-                           + Integer.toString(getUByte(buffer));
+                    host = Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte()) + "."
+                           + Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte());
                 } else if (type == SocksConstants.Socks5.ADDRESS_FQDN) {
                     host = getBLString(buffer);
                 } else if (type == SocksConstants.Socks5.ADDRESS_IPV6) {
-                    host = Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer)) + ":"
-                           + Integer.toHexString(getUShort(buffer));
+                    host = Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort()) + ":"
+                           + Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort()) + ":"
+                           + Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort()) + ":"
+                           + Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort());
                 } else {
                     throw new IllegalStateException("Unsupported address type: " + type);
                 }
-                int port = getUShort(buffer);
+                int port = buffer.getUShort();
                 if (debugEnabled) {
                     log.debug("Received socks5 connection request to {}:{}", host, port);
                 }
@@ -348,10 +332,10 @@ public class SocksProxy extends AbstractCloseable implements IoHandler {
         }
 
         protected String getBLString(Buffer buffer) {
-            int length = getUByte(buffer);
+            int length = buffer.getUByte();
             StringBuilder sb = new StringBuilder(length);
             for (int i = 0; i < length; i++) {
-                sb.append((char) getUByte(buffer));
+                sb.append((char) buffer.getUByte());
             }
             return sb.toString();
         }


(mina-sshd) 01/06: Simplify SshClient

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 138bcfbe8d05cde28f6880230024fc5b3d1c0121
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Wed Apr 23 20:08:13 2025 +0200

    Simplify SshClient
    
    With the HostConfigEntry available on the session from the moment it is
    created, we don't need to pass along so many data through the internal
    methods of SshClient. We get a single point once the IoConnectFuture is
    done and has created the session where we can read data from the
    HostConfigEntry and configure the session.
    
    Split the actual session configuration in several overrideable methods
    so that user code can customize it easily.
    
    Remove no longer needed AttributeKeys.
---
 .../java/org/apache/sshd/client/SshClient.java     | 86 +++++++++++-----------
 .../sshd/client/auth/pubkey/UserAuthPublicKey.java | 14 ----
 2 files changed, 44 insertions(+), 56 deletions(-)

diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 9687237f6..54d85a853 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -56,7 +56,6 @@ import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
 import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
 import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
 import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
-import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
 import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
 import org.apache.sshd.client.config.hosts.HostConfigEntry;
 import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
@@ -561,15 +560,9 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
             AttributeRepository context, SocketAddress localAddress)
             throws IOException {
         Objects.requireNonNull(hostConfig, "No host configuration");
-        String host = ValidateUtils.checkNotNullAndNotEmpty(hostConfig.getHostName(), "No target host");
+        ValidateUtils.checkNotNullAndNotEmpty(hostConfig.getHostName(), "No target host");
         int port = hostConfig.getPort();
         ValidateUtils.checkTrue(port > 0, "Invalid port: %d", port);
-        Collection<String> hostIds = hostConfig.getIdentities();
-        Collection<PathResource> idFiles = GenericUtils.stream(hostIds)
-                .map(Paths::get)
-                .map(PathResource::new)
-                .collect(Collectors.toCollection(() -> new ArrayList<>(hostIds.size())));
-        KeyIdentityProvider keys = preloadClientIdentities(idFiles);
         String username = hostConfig.getUsername();
         InetSocketAddress targetAddress = new InetSocketAddress(hostConfig.getHostName(), hostConfig.getPort());
         if (GenericUtils.isNotEmpty(jumps)) {
@@ -613,7 +606,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                                             .createLocalPortForwardingTracker(SshdSocketAddress.LOCALHOST_ADDRESS, address);
                                     SshdSocketAddress bound = tracker.getBoundAddress();
                                     ConnectFuture f4 = doConnect(hostConfig.getUsername(), bound.toInetSocketAddress(), null,
-                                            context, localAddress, keys, hostConfig);
+                                            context, localAddress, hostConfig);
                                     toCancel.set(f4);
                                     if (connectFuture.isCanceled()) {
                                         f4.cancel();
@@ -654,14 +647,13 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
             ProxyDataFactory factory = getProxyDataFactory();
             ProxyData proxy = factory == null ? null : factory.get(targetAddress);
             return doConnect(hostConfig.getUsername(), targetAddress, proxy,
-                    context, localAddress, keys, hostConfig);
+                    context, localAddress, hostConfig);
         }
     }
 
     protected ConnectFuture doConnect(
             String username, InetSocketAddress targetAddress, ProxyData proxy,
-            AttributeRepository context, SocketAddress localAddress,
-            KeyIdentityProvider identities, HostConfigEntry hostConfig)
+            AttributeRepository context, SocketAddress localAddress, HostConfigEntry hostConfig)
             throws IOException {
         if (connector == null) {
             throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server");
@@ -682,8 +674,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
             sessionContext = new ShadowingAttributeRepository(sessionContext, context);
         }
         ConnectFuture connectFuture = new DefaultConnectFuture(username + "@" + targetAddress, null);
-        SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener(
-                connectFuture, username, targetAddress, identities, hostConfig);
+        SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener(connectFuture, username, targetAddress);
         IoConnectFuture connectingFuture = connector.connect(connectAddress, sessionContext, localAddress);
         connectFuture.addListener(c -> {
             if (c.isCanceled()) {
@@ -779,20 +770,8 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         return entry;
     }
 
-    protected KeyIdentityProvider preloadClientIdentities(
-            Collection<? extends NamedResource> locations)
-            throws IOException {
-        return GenericUtils.isEmpty(locations)
-                ? KeyIdentityProvider.EMPTY_KEYS_PROVIDER
-                : ClientIdentityLoader.asKeyIdentityProvider(
-                        Objects.requireNonNull(getClientIdentityLoader(), "No ClientIdentityLoader"),
-                        locations, getFilePasswordProvider(),
-                        CoreModuleProperties.IGNORE_INVALID_IDENTITIES.getRequired(this));
-    }
-
     protected SshFutureListener<IoConnectFuture> createConnectCompletionListener(
-            ConnectFuture connectFuture, String username, SocketAddress address,
-            KeyIdentityProvider identities, HostConfigEntry hostConfig) {
+            ConnectFuture connectFuture, String username, SocketAddress address) {
         return new SshFutureListener<IoConnectFuture>() {
             @Override
             @SuppressWarnings("synthetic-access")
@@ -815,7 +794,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
                 } else {
                     IoSession ioSession = future.getSession();
                     try {
-                        onConnectOperationComplete(ioSession, connectFuture, username, address, identities, hostConfig);
+                        onConnectOperationComplete(ioSession, connectFuture, username, address);
                     } catch (IOException | GeneralSecurityException | RuntimeException e) {
                         warn("operationComplete({}@{}) failed ({}) to signal completion of session={}: {}",
                                 username, address, e.getClass().getSimpleName(), ioSession, e.getMessage(), e);
@@ -834,24 +813,13 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     }
 
     protected void onConnectOperationComplete(
-            IoSession ioSession, ConnectFuture connectFuture, String username,
-            SocketAddress address, KeyIdentityProvider identities, HostConfigEntry hostConfig)
+            IoSession ioSession, ConnectFuture connectFuture, String username, SocketAddress address)
             throws IOException, GeneralSecurityException {
         AbstractClientSession session = (AbstractClientSession) AbstractSession.getSession(ioSession);
         session.setUsername(username);
         session.setConnectAddress(address);
-        boolean useDefaultIdentities = !hostConfig.isIdentitiesOnly();
-        session.setAttribute(UserAuthPublicKey.USE_DEFAULT_IDENTITIES, Boolean.valueOf(useDefaultIdentities));
-        String identityAgent = hostConfig.getProperty(HostConfigEntry.IDENTITY_AGENT);
-        session.setAttribute(UserAuthPublicKey.IDENTITY_AGENT, identityAgent == null ? "" : identityAgent);
-
-        if (useDefaultIdentities) {
-            setupDefaultSessionIdentities(session, identities);
-        } else if (identities == null) {
-            session.setKeyIdentityProvider(KeyIdentityProvider.EMPTY_KEYS_PROVIDER);
-        } else {
-            session.setKeyIdentityProvider(ensureFilePasswordProvider(identities));
-        }
+
+        configure(session);
 
         connectFuture.setSession(session);
         if (session != connectFuture.getSession()) {
@@ -867,6 +835,40 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
         }
     }
 
+    protected void configure(ClientSession session) throws IOException, GeneralSecurityException {
+        setSessionKeyIdentities(session);
+    }
+
+    protected void setSessionKeyIdentities(ClientSession session)
+            throws IOException, GeneralSecurityException {
+        HostConfigEntry hostConfig = session.getHostConfigEntry();
+        KeyIdentityProvider fromHostConfig = getKeyIdentities(hostConfig);
+        if (!hostConfig.isIdentitiesOnly()) {
+            setupDefaultSessionIdentities(session, fromHostConfig);
+        } else if (fromHostConfig == null) {
+            session.setKeyIdentityProvider(KeyIdentityProvider.EMPTY_KEYS_PROVIDER);
+        } else {
+            session.setKeyIdentityProvider(ensureFilePasswordProvider(fromHostConfig));
+        }
+    }
+
+    protected KeyIdentityProvider getKeyIdentities(HostConfigEntry hostConfig) {
+        Collection<String> hostIds = hostConfig.getIdentities();
+        Collection<PathResource> idFiles = GenericUtils.stream(hostIds)
+                .map(Paths::get)
+                .map(PathResource::new)
+                .collect(Collectors.toCollection(() -> new ArrayList<>(hostIds.size())));
+        return preloadClientIdentities(idFiles);
+    }
+
+    protected KeyIdentityProvider preloadClientIdentities(Collection<? extends NamedResource> locations) {
+        return GenericUtils.isEmpty(locations)
+                ? KeyIdentityProvider.EMPTY_KEYS_PROVIDER
+                : ClientIdentityLoader.asKeyIdentityProvider(
+                        Objects.requireNonNull(getClientIdentityLoader(), "No ClientIdentityLoader"), locations,
+                        getFilePasswordProvider(), CoreModuleProperties.IGNORE_INVALID_IDENTITIES.getRequired(this));
+    }
+
     /**
      * Sets this client's {@link FilePasswordProvider} on the {@link KeyIdentityProvider} if it is an
      * {@link AbstractResourceKeyPairProvider} or implements {@link FilePasswordProviderManager} and doesn't have one
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java
index 6bd9424a5..aaff791dc 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java
@@ -35,7 +35,6 @@ import java.util.TreeSet;
 import org.apache.sshd.client.auth.AbstractUserAuth;
 import org.apache.sshd.client.auth.keyboard.UserInteraction;
 import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.AttributeRepository.AttributeKey;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.RuntimeSshException;
 import org.apache.sshd.common.SshConstants;
@@ -60,19 +59,6 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFactoriesManager {
     public static final String NAME = UserAuthPublicKeyFactory.NAME;
 
-    /**
-     * Is set on a {@link ClientSession} when it is created; if {@link Boolean#FALSE}, no default identities shall be
-     * used.
-     */
-    public static final AttributeKey<Boolean> USE_DEFAULT_IDENTITIES = new AttributeKey<>();
-
-    /**
-     * Is set on a {@link ClientSession} when it is created; contains the value of the {@code IdentityAgent} SSH config
-     * setting. May be the empty string if not specified in the
-     * {@link org.apache.sshd.client.config.hosts.HostConfigEntry#IDENTITY_AGENT HostConfigEntry}.
-     */
-    public static final AttributeKey<String> IDENTITY_AGENT = new AttributeKey<>();
-
     protected final Deque<String> currentAlgorithms = new LinkedList<>();
 
     protected Iterator<PublicKeyIdentity> keys;


(mina-sshd) 02/06: [test] Add a test for HttpParser

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ba8f228ed30d9bf87aef456aa576e3f27921dd97
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Wed Apr 23 20:52:10 2025 +0200

    [test] Add a test for HttpParser
---
 .../sshd/client/session/proxy/HttpParserTest.java  | 110 +++++++++++++++++++++
 1 file changed, 110 insertions(+)

diff --git a/sshd-core/src/test/java/org/apache/sshd/client/session/proxy/HttpParserTest.java b/sshd-core/src/test/java/org/apache/sshd/client/session/proxy/HttpParserTest.java
new file mode 100644
index 000000000..84305ec3a
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/session/proxy/HttpParserTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.client.session.proxy;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Basic tests for {@link HttpParser}.
+ */
+class HttpParserTest extends JUnitTestSupport {
+
+    private static final String STATUS_LINE = "HTTP/1.1. 407 Authentication required";
+
+    @Test
+    void testEmpty() throws Exception {
+        String[] lines = { STATUS_LINE };
+        List<AuthenticationChallenge> challenges = HttpParser.getAuthenticationHeaders(Arrays.asList(lines),
+                "WWW-Authenticate:");
+        assertTrue(challenges.isEmpty(), "No challenges expected");
+    }
+
+    @Test
+    void testRFC7235Example() throws Exception {
+        // The example from RFC 7235, sec. 4.1, slightly modified ("kind" argument with whitespace around '=')
+        String[] lines = {
+                STATUS_LINE, //
+                "WWW-Authenticate: Newauth realm=\"apps\", type=1  , kind = \t2 ",
+                "   \t  title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"" };
+        List<AuthenticationChallenge> challenges = HttpParser.getAuthenticationHeaders(Arrays.asList(lines),
+                "WWW-Authenticate:");
+        assertEquals(2, challenges.size(), "Unexpected number of challenges");
+        assertNull(challenges.get(0).getToken(), "No token expected");
+        assertNull(challenges.get(1).getToken(), "No token expected");
+        assertEquals("Newauth", challenges.get(0).getMechanism(), "Unexpected mechanism");
+        assertEquals("Basic", challenges.get(1).getMechanism(), "Unexpected mechanism");
+        Map<String, String> expectedArguments = new LinkedHashMap<>();
+        expectedArguments.put("realm", "apps");
+        expectedArguments.put("type", "1");
+        expectedArguments.put("kind", "2");
+        expectedArguments.put("title", "Login to \"apps\"");
+        assertEquals(expectedArguments, challenges.get(0).getArguments(), "Unexpected arguments");
+        expectedArguments.clear();
+        expectedArguments.put("realm", "simple");
+        assertEquals(expectedArguments, challenges.get(1).getArguments(), "Unexpected arguments");
+    }
+
+    @Test
+    void testMultipleHeaders() {
+        String[] lines = {
+                STATUS_LINE, //
+                "Server: Apache", //
+                "WWW-Authenticate: Newauth realm=\"apps\", type=1  , kind = \t2 ",
+                "   \t  title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"", //
+                "Content-Type: text/plain", //
+                "WWW-Authenticate: Other 0123456789===  , YetAnother, ", //
+                "WWW-Authenticate: Negotiate   ",
+                "WWW-Authenticate: Negotiate a87421000492aa874209af8bc028" };
+        List<AuthenticationChallenge> challenges = HttpParser.getAuthenticationHeaders(Arrays.asList(lines),
+                "WWW-Authenticate:");
+        assertEquals(6, challenges.size(), "Unexpected number of challenges");
+        assertEquals("Other", challenges.get(2).getMechanism(), "Mismatched challenge");
+        assertEquals("0123456789===", challenges.get(2).getToken(), "Token expected");
+        assertEquals("YetAnother", challenges.get(3).getMechanism(), "Mismatched challenge");
+        assertNull(challenges.get(3).getToken(), "No token expected");
+        assertTrue(challenges.get(3).getArguments().isEmpty(), "No arguments expected");
+        assertEquals("Negotiate", challenges.get(4).getMechanism(), "Mismatched challenge");
+        assertNull(challenges.get(4).getToken(), "No token expected");
+        assertEquals("Negotiate", challenges.get(5).getMechanism(), "Mismatched challenge");
+        assertEquals("a87421000492aa874209af8bc028", challenges.get(5).getToken(), "Token expected");
+    }
+
+    @Test
+    void testStopOnEmptyLine() {
+        String[] lines = {
+                STATUS_LINE, //
+                "Server: Apache", //
+                "WWW-Authenticate: Newauth realm=\"apps\", type=1  , kind = \t2 ",
+                "   \t  title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"", //
+                "Content-Type: text/plain",
+                "WWW-Authenticate: Other 0123456789===", "",
+                // Not headers anymore; this would be the body
+                "WWW-Authenticate: Negotiate   ", //
+                "WWW-Authenticate: Negotiate a87421000492aa874209af8bc028" };
+        List<AuthenticationChallenge> challenges = HttpParser.getAuthenticationHeaders(Arrays.asList(lines),
+                "WWW-Authenticate:");
+        assertEquals(3, challenges.size(), "Unexpected number of challenges");
+    }
+}


(mina-sshd) 04/06: Fix typo in comment

Posted by tw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 07ebc09987d9c63d86f2c4f68a405f7051373830
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Thu Apr 24 00:36:55 2025 +0200

    Fix typo in comment
---
 .../java/org/apache/sshd/client/session/proxy/GssApiMechanisms.java   | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/proxy/GssApiMechanisms.java b/sshd-core/src/main/java/org/apache/sshd/client/session/proxy/GssApiMechanisms.java
index 9d966d245..0a7d4a0c7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/proxy/GssApiMechanisms.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/proxy/GssApiMechanisms.java
@@ -25,7 +25,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
@@ -51,8 +50,7 @@ public final class GssApiMechanisms {
     private static final Object LOCK = new Object();
 
     /**
-     * The {@link AtomicBoolean} is set to {@code true} when the mechanism could be initialized successfully at least
-     * once.
+     * The {@link Boolean} is set to {@code true} when the mechanism could be initialized successfully at least once.
      */
     private static Map<Oid, Boolean> supportedMechanisms;