Skip to content
Open
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
24 changes: 24 additions & 0 deletions src/main/java/com/jcraft/jsch/Buffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@
public class Buffer {
final byte[] tmp = new byte[4];
byte[] buffer;

// write position
// Tracks the current writing position in the buffer and points to where the next
// write operation will occur and increments as data is written
int index;

// read position - Tracks the current reading position in the buffer and points to where the next
// read operation will
// occur and increments as data is read
int s;

public Buffer(int size) {
Expand Down Expand Up @@ -290,6 +298,22 @@ static Buffer fromBytes(byte[][] args) {
return buf;
}

/**
* Advances the read position by the specified number of bytes.
*
* If the requested number of bytes would move the position beyond the end of the available data,
* the position is advanced to the very end instead.
*
* @param bytesToSkip the number of bytes to skip.
*/
void readSkip(int bytesToSkip) {
if (bytesToSkip > getLength()) {
s += getLength();
return;
}
s += bytesToSkip;
}

/*
* static String[] chars={ "0","1","2","3","4","5","6","7","8","9", "a","b","c","d","e","f" };
* static void dump_buffer(){ int foo; for(int i=0; i<tmp_buffer_index; i++){
Expand Down
122 changes: 122 additions & 0 deletions src/main/java/com/jcraft/jsch/HostKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,128 @@ boolean isMatched(String _host) {
return isIncluded(_host);
}

/**
* Checks if the given hostname matches any of the host patterns in this HostKey, supporting
* OpenSSH-style wildcards.
* <p>
* This method supports wildcard patterns similar to OpenSSH's known_hosts file:
* </p>
* <ul>
* <li>{@code *} - Matches zero or more characters</li>
* <li>{@code ?} - Matches exactly one character</li>
* </ul>
* <p>
* The host field can contain multiple comma-separated patterns. The method returns {@code true}
* if the hostname matches ANY of the patterns.
* </p>
* <p>
* Examples:
* </p>
* <ul>
* <li>{@code *.example.com} matches {@code host.example.com}, {@code sub.example.com}</li>
* <li>{@code host?.example.com} matches {@code host1.example.com}, {@code hosta.example.com}</li>
* <li>{@code 192.168.1.*} matches {@code 192.168.1.1}, {@code 192.168.1.100}</li>
* <li>{@code host1.com,*.host2.com} matches {@code host1.com} or any subdomain of
* {@code host2.com}</li>
* </ul>
*
* @param _host the hostname to test against the patterns, must not be {@code null}
* @return {@code true} if the hostname matches any of the patterns (with wildcard support);
* {@code false} otherwise
* @see #isMatched(String)
*/
boolean isWildcardMatched(String _host) {
if (_host == null) {
return false;
}

String hosts = this.host;
if (hosts == null || hosts.isEmpty()) {
return false;
}

// Split by comma and check each pattern
int i = 0;
int hostslen = hosts.length();
while (i < hostslen) {
int j = hosts.indexOf(',', i);
String pattern;
if (j == -1) {
pattern = hosts.substring(i).trim();
if (matchesWildcardPattern(pattern, _host)) {
return true;
}
break;
} else {
pattern = hosts.substring(i, j).trim();
if (matchesWildcardPattern(pattern, _host)) {
return true;
}
i = j + 1;
}
}
return false;
}

/**
* Tests if a hostname matches a single wildcard pattern.
* <p>
* This method implements wildcard matching similar to OpenSSH, supporting:
* </p>
* <ul>
* <li>{@code *} - Matches zero or more characters</li>
* <li>{@code ?} - Matches exactly one character</li>
* </ul>
* <p>
* The matching is case-sensitive to maintain consistency with OpenSSH behavior.
* </p>
*
* @param pattern the wildcard pattern to match against (e.g., {@code *.example.com})
* @param hostname the hostname to test (e.g., {@code host.example.com})
* @return {@code true} if the hostname matches the pattern; {@code false} otherwise
*/
private boolean matchesWildcardPattern(String pattern, String hostname) {
if (pattern == null || hostname == null) {
return false;
}

int pLen = pattern.length();
int hLen = hostname.length();
int p = 0; // pattern index
int h = 0; // hostname index
int starIdx = -1; // last '*' position in pattern
int matchIdx = 0; // position in hostname after last '*' match

while (h < hLen) {
if (p < pLen && (pattern.charAt(p) == '?' || pattern.charAt(p) == hostname.charAt(h))) {
// Match single character or '?'
p++;
h++;
} else if (p < pLen && pattern.charAt(p) == '*') {
// Found '*', record position and try to match rest
starIdx = p;
matchIdx = h;
p++;
} else if (starIdx != -1) {
// No match, but we have a previous '*', backtrack
p = starIdx + 1;
matchIdx++;
h = matchIdx;
} else {
// No match and no '*' to backtrack to
return false;
}
}

// Process remaining pattern characters (should be all '*')
while (p < pLen && pattern.charAt(p) == '*') {
p++;
}

// Match if we've consumed entire pattern
return p == pLen;
}

private boolean isIncluded(String _host) {
int i = 0;
String hosts = this.host;
Expand Down
88 changes: 71 additions & 17 deletions src/main/java/com/jcraft/jsch/JSch.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

package com.jcraft.jsch;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Enumeration;
Expand All @@ -38,12 +40,18 @@ public class JSch {
/** The version number. */
public static final String VERSION = Version.getVersion();

private static final String CERTIFICATE_FILENAME_SUFFIX = "-cert.pub";

static Hashtable<String, String> config = new Hashtable<>();

static {
config.put("kex", Util.getSystemProperty("jsch.kex",
"mlkem768x25519-sha256,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256"));
config.put("server_host_key", Util.getSystemProperty("jsch.server_host_key",
"ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256"));
// CASignatureAlgorithms: specifies which algorithms are allowed for signing of certificates
// by certificate authorities (CAs). Default matches OpenSSH 8.2+ (excludes ssh-rsa/SHA-1).
config.put("ca_signature_algorithms", Util.getSystemProperty("jsch.ca_signature_algorithms",
"ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256"));
config.put("prefer_known_host_key_types",
Util.getSystemProperty("jsch.prefer_known_host_key_types", "yes"));
Expand Down Expand Up @@ -239,7 +247,7 @@ public class JSch {
config.put("PreferredAuthentications", Util.getSystemProperty("jsch.preferred_authentications",
"gssapi-with-mic,publickey,keyboard-interactive,password"));
config.put("PubkeyAcceptedAlgorithms", Util.getSystemProperty("jsch.client_pubkey",
"ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256"));
"ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256"));
config.put("enable_pubkey_auth_query",
Util.getSystemProperty("jsch.enable_pubkey_auth_query", "yes"));
config.put("try_additional_pubkey_algorithms",
Expand All @@ -259,6 +267,15 @@ public class JSch {

config.put("MaxAuthTries", Util.getSystemProperty("jsch.max_auth_tries", "6"));
config.put("ClearAllForwardings", "no");
/*
* host_certificate_to_key_fallback: Controls behavior when host certificate validation fails. -
* "yes" (default): Fall back to standard public key verification using the certificate's
* embedded public key. This matches OpenSSH behavior, which always performs this fallback. -
* "no": Reject connection if certificate validation fails (more secure, but may break existing
* setups when upgrading to a JSch version with certificate support).
*/
config.put("host_certificate_to_key_fallback",
Util.getSystemProperty("jsch.host_certificate_to_key_fallback", "yes"));
}

final InstanceLogger instLogger = new InstanceLogger();
Expand Down Expand Up @@ -320,11 +337,11 @@ public JSch() {}
* "user.name" will be referred.
*
* @param host hostname
* @throws JSchException if <code>username</code> or <code>host</code> are invalid.
* @return the instance of <code>Session</code> class.
* @throws JSchException if <code>username</code> or <code>host</code> are invalid.
* @see #getSession(String username, String host, int port)
* @see com.jcraft.jsch.Session
* @see com.jcraft.jsch.ConfigRepository
* @see Session
* @see ConfigRepository
*/
public Session getSession(String host) throws JSchException {
return getSession(null, host, 22);
Expand All @@ -337,10 +354,10 @@ public Session getSession(String host) throws JSchException {
*
* @param username user name
* @param host hostname
* @throws JSchException if <code>username</code> or <code>host</code> are invalid.
* @return the instance of <code>Session</code> class.
* @throws JSchException if <code>username</code> or <code>host</code> are invalid.
* @see #getSession(String username, String host, int port)
* @see com.jcraft.jsch.Session
* @see Session
*/
public Session getSession(String username, String host) throws JSchException {
return getSession(username, host, 22);
Expand All @@ -354,10 +371,10 @@ public Session getSession(String username, String host) throws JSchException {
* @param username user name
* @param host hostname
* @param port port number
* @throws JSchException if <code>username</code> or <code>host</code> are invalid.
* @return the instance of <code>Session</code> class.
* @throws JSchException if <code>username</code> or <code>host</code> are invalid.
* @see #getSession(String username, String host, int port)
* @see com.jcraft.jsch.Session
* @see Session
*/
public Session getSession(String username, String host, int port) throws JSchException {
if (host == null) {
Expand All @@ -383,8 +400,8 @@ protected boolean removeSession(Session session) {
* Sets the hostkey repository.
*
* @param hkrepo
* @see com.jcraft.jsch.HostKeyRepository
* @see com.jcraft.jsch.KnownHosts
* @see HostKeyRepository
* @see KnownHosts
*/
public void setHostKeyRepository(HostKeyRepository hkrepo) {
known_hosts = hkrepo;
Expand All @@ -395,7 +412,7 @@ public void setHostKeyRepository(HostKeyRepository hkrepo) {
*
* @param filename filename of known_hosts file.
* @throws JSchException if the given filename is invalid.
* @see com.jcraft.jsch.KnownHosts
* @see KnownHosts
*/
public void setKnownHosts(String filename) throws JSchException {
if (known_hosts == null)
Expand All @@ -412,7 +429,7 @@ public void setKnownHosts(String filename) throws JSchException {
*
* @param stream the instance of InputStream from known_hosts file.
* @throws JSchException if an I/O error occurs.
* @see com.jcraft.jsch.KnownHosts
* @see KnownHosts
*/
public void setKnownHosts(InputStream stream) throws JSchException {
if (known_hosts == null)
Expand All @@ -429,8 +446,8 @@ public void setKnownHosts(InputStream stream) throws JSchException {
* <code>KnownHosts</code>.
*
* @return current hostkey repository.
* @see com.jcraft.jsch.HostKeyRepository
* @see com.jcraft.jsch.KnownHosts
* @see HostKeyRepository
* @see KnownHosts
*/
public HostKeyRepository getHostKeyRepository() {
if (known_hosts == null)
Expand Down Expand Up @@ -492,7 +509,39 @@ public void addIdentity(String prvkey, byte[] passphrase) throws JSchException {
* @throws JSchException if <code>passphrase</code> is not right.
*/
public void addIdentity(String prvkey, String pubkey, byte[] passphrase) throws JSchException {
Identity identity = IdentityFile.newInstance(prvkey, pubkey, instLogger);
byte[] pubkeyFileContent = null;
String pubkeyFile = pubkey;
Identity identity;

// If pubkey is null, try to auto-discover certificate file (prvkey + "-cert.pub")
// This mimics KeyPair.load() behavior which tries prvkey + ".pub"
if (pubkeyFile == null) {
String certFile = prvkey + CERTIFICATE_FILENAME_SUFFIX;
if (new File(certFile).exists()) {
pubkeyFile = certFile;
}
}

if (pubkeyFile != null) {
try {
pubkeyFileContent = Util.fromFile(pubkeyFile);
} catch (IOException e) {
// Only throw if pubkey was explicitly provided (not auto-discovered)
// This matches KeyPair.load() behavior
if (pubkey != null) {
throw new JSchException(e.toString(), e);
}
// Otherwise, silently ignore and fall through to IdentityFile
}
}

if (pubkeyFileContent != null
&& OpenSshCertificateAwareIdentityFile.isOpenSshCertificateFile(pubkeyFileContent)) {
identity = OpenSshCertificateAwareIdentityFile.newInstance(prvkey, pubkeyFile, instLogger);
} else {
identity = IdentityFile.newInstance(prvkey, pubkey, instLogger);
}

addIdentity(identity, passphrase);
}

Expand All @@ -507,7 +556,12 @@ public void addIdentity(String prvkey, String pubkey, byte[] passphrase) throws
*/
public void addIdentity(String name, byte[] prvkey, byte[] pubkey, byte[] passphrase)
throws JSchException {
Identity identity = IdentityFile.newInstance(name, prvkey, pubkey, instLogger);
Identity identity;
if (OpenSshCertificateAwareIdentityFile.isOpenSshCertificateFile(pubkey)) {
identity = OpenSshCertificateAwareIdentityFile.newInstance(name, prvkey, pubkey, instLogger);
} else {
identity = IdentityFile.newInstance(name, prvkey, pubkey, instLogger);
}
addIdentity(identity, passphrase);
}

Expand Down Expand Up @@ -665,7 +719,7 @@ public static Map<String, String> getConfig() {
* Sets the logger
*
* @param logger logger or <code>null</code> if no logging should take place
* @see com.jcraft.jsch.Logger
* @see Logger
*/
public static void setLogger(Logger logger) {
if (logger == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jcraft.jsch;

public class JSchInvalidHostCertificateException extends JSchHostKeyException {

private static final long serialVersionUID = -1L;

public JSchInvalidHostCertificateException(String s) {
super(s);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/jcraft/jsch/JSchUnknownCAKeyException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jcraft.jsch;

public class JSchUnknownCAKeyException extends JSchHostKeyException {

private static final long serialVersionUID = -1L;

JSchUnknownCAKeyException(String s) {
super(s);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jcraft.jsch;

public class JSchUnknownPublicKeyAlgorithmException extends JSchHostKeyException {

private static final long serialVersionUID = -1L;

JSchUnknownPublicKeyAlgorithmException(String s) {
super(s);
}
}
Loading