Skip to content

Commit

Permalink
Attempt to authenticate using other signature algorithms supported by…
Browse files Browse the repository at this point in the history
… the same public key.

Some servers incorrectly respond with SSH_MSG_USERAUTH_PK_OK to the
intial auth query, but then fail the full SSH_MSG_USERAUTH_REQUEST for
RSA keys (which can support multiple signautre algorithms).

Allow the new behavior to potentially try other algorithms to be
disabled via a new config option `try_additional_pubkey_algorithms`.

Additionally, add a new config option `enable_pubkey_auth_query` to
allow skipping auth queries and proceed directly to attempting full
SSH_MSG_USERAUTH_REQUEST's.
  • Loading branch information
norrisjeremy committed Jan 25, 2023
1 parent c4c06c5 commit 408547e
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 143 deletions.
2 changes: 2 additions & 0 deletions src/main/java/com/jcraft/jsch/JSch.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ 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"));
config.put("enable_pubkey_auth_query", Util.getSystemProperty("jsch.enable_pubkey_auth_query", "yes"));
config.put("try_additional_pubkey_algorithms", Util.getSystemProperty("jsch.try_additional_pubkey_algorithms", "yes"));

config.put("CheckCiphers", Util.getSystemProperty("jsch.check_ciphers", "chacha20-poly1305@openssh.com"));
config.put("CheckMacs", Util.getSystemProperty("jsch.check_macs", ""));
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/jcraft/jsch/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -3113,6 +3113,8 @@ private void applyConfig() throws JSchException {
checkConfig(config, "kex");
checkConfig(config, "server_host_key");
checkConfig(config, "prefer_known_host_key_types");
checkConfig(config, "enable_pubkey_auth_query");
checkConfig(config, "try_additional_pubkey_algorithms");

checkConfig(config, "cipher.c2s");
checkConfig(config, "cipher.s2c");
Expand Down
308 changes: 165 additions & 143 deletions src/main/java/com/jcraft/jsch/UserAuthPublicKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ private boolean _start(Session session, List<Identity> identities, List<String>
return false;
}

boolean use_pk_auth_query=session.getConfig("enable_pubkey_auth_query").equals("yes");
boolean try_other_pkmethods=session.getConfig("try_additional_pubkey_algorithms").equals("yes");

List<String> rsamethods=new ArrayList<>();
List<String> nonrsamethods=new ArrayList<>();
for(String pkmethod : pkmethods){
Expand Down Expand Up @@ -156,71 +159,181 @@ private boolean _start(Session session, List<Identity> identities, List<String>
String _ipkmethod=identity.getAlgName();
List<String> ipkmethods=null;
if(_ipkmethod.equals("ssh-rsa")){
ipkmethods=rsamethods;
ipkmethods=new ArrayList<>(rsamethods);
}
else if(nonrsamethods.contains(_ipkmethod)){
ipkmethods=Collections.singletonList(_ipkmethod);
ipkmethods=new ArrayList<>(1);
ipkmethods.add(_ipkmethod);
}
if(ipkmethods==null) {
if(ipkmethods==null || ipkmethods.isEmpty()) {
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
_ipkmethod+" cannot be used as public key type for identity "+identity.getName());
}
continue;
continue iloop;
}

byte[] pubkeyblob=identity.getPublicKeyBlob();
List<String> pkmethodsuccesses=null;
ipkmethodloop:
while(!ipkmethods.isEmpty() && session.auth_failures < session.max_auth_tries){
byte[] pubkeyblob=identity.getPublicKeyBlob();
List<String> pkmethodsuccesses=null;

if(pubkeyblob!=null && use_pk_auth_query){
command=SSH_MSG_USERAUTH_FAILURE;
Iterator<String> it=ipkmethods.iterator();
loop3:
while(it.hasNext()){
String ipkmethod=it.next();
it.remove();
if(not_available_pks.contains(ipkmethod) && !(identity instanceof AgentIdentity)){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod+" not available for identity "+identity.getName());
}
continue loop3;
}

// send
// byte SSH_MSG_USERAUTH_REQUEST(50)
// string user name
// string service name ("ssh-connection")
// string "publickey"
// boolen FALSE
// string public key algorithm name
// string public key blob
packet.reset();
buf.putByte((byte)SSH_MSG_USERAUTH_REQUEST);
buf.putString(_username);
buf.putString(Util.str2byte("ssh-connection"));
buf.putString(Util.str2byte("publickey"));
buf.putByte((byte)0);
buf.putString(Util.str2byte(ipkmethod));
buf.putString(pubkeyblob);
session.write(packet);

loop1:
while(true){
buf=session.read(buf);
command=buf.getCommand()&0xff;

if(command==SSH_MSG_USERAUTH_PK_OK){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod + " preauth success");
}
pkmethodsuccesses=new ArrayList<>(1);
pkmethodsuccesses.add(ipkmethod);
break loop3;
}
else if(command==SSH_MSG_USERAUTH_FAILURE){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod + " preauth failure");
}
continue loop3;
}
else if(command==SSH_MSG_USERAUTH_BANNER){
buf.getInt(); buf.getByte(); buf.getByte();
byte[] _message=buf.getString();
byte[] lang=buf.getString();
String message=Util.byte2str(_message);
if(userinfo!=null){
userinfo.showMessage(message);
}
continue loop1;
}
else{
//System.err.println("USERAUTH fail ("+command+")");
//throw new JSchException("USERAUTH fail ("+command+")");
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod + " preauth failure command (" + command + ")");
}
continue loop3;
}
}
}

if(command!=SSH_MSG_USERAUTH_PK_OK){
continue iloop;
}
}

if(identity.isEncrypted()) continue iloop;
if(pubkeyblob==null) pubkeyblob=identity.getPublicKeyBlob();

//System.err.println("UserAuthPublicKey: pubkeyblob="+pubkeyblob);

if(pubkeyblob!=null){
command=SSH_MSG_USERAUTH_FAILURE;
loop3:
for(String ipkmethod : ipkmethods){
if(not_available_pks.contains(ipkmethod) && !(identity instanceof AgentIdentity)){
if(pubkeyblob==null) continue iloop;
if(pkmethodsuccesses==null) pkmethodsuccesses=ipkmethods;
if(pkmethodsuccesses.isEmpty()) continue iloop;

Iterator<String> it=pkmethodsuccesses.iterator();
loop4:
while(it.hasNext() && session.auth_failures < session.max_auth_tries){
String pkmethodsuccess=it.next();
it.remove();
if(not_available_pks.contains(pkmethodsuccess) && !(identity instanceof AgentIdentity)){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod+" not available for identity "+identity.getName());
pkmethodsuccess+" not available for identity "+identity.getName());
}
continue loop3;
continue loop4;
}

// send
// byte SSH_MSG_USERAUTH_REQUEST(50)
// string user name
// string service name ("ssh-connection")
// string "publickey"
// boolen FALSE
// boolen TRUE
// string public key algorithm name
// string public key blob
// string signature
packet.reset();
buf.putByte((byte)SSH_MSG_USERAUTH_REQUEST);
buf.putString(_username);
buf.putString(Util.str2byte("ssh-connection"));
buf.putString(Util.str2byte("publickey"));
buf.putByte((byte)0);
buf.putString(Util.str2byte(ipkmethod));
buf.putByte((byte)1);
buf.putString(Util.str2byte(pkmethodsuccess));
buf.putString(pubkeyblob);

//byte[] tmp=new byte[buf.index-5];
//System.arraycopy(buf.buffer, 5, tmp, 0, tmp.length);
//buf.putString(signature);

byte[] sid=session.getSessionId();
int sidlen=sid.length;
byte[] tmp=new byte[4+sidlen+buf.index-5];
tmp[0]=(byte)(sidlen>>>24);
tmp[1]=(byte)(sidlen>>>16);
tmp[2]=(byte)(sidlen>>>8);
tmp[3]=(byte)(sidlen);
System.arraycopy(sid, 0, tmp, 4, sidlen);
System.arraycopy(buf.buffer, 5, tmp, 4+sidlen, buf.index-5);
byte[] signature=identity.getSignature(tmp, pkmethodsuccess);
if(signature==null){ // for example, too long key length.
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
pkmethodsuccess + " signature failure");
}
continue loop4;
}
buf.putString(signature);
session.write(packet);

loop1:
loop2:
while(true){
buf=session.read(buf);
command=buf.getCommand()&0xff;

if(command==SSH_MSG_USERAUTH_PK_OK){
if(command==SSH_MSG_USERAUTH_SUCCESS){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod + " preauth success");
pkmethodsuccess + " auth success");
}
pkmethodsuccesses=Collections.singletonList(ipkmethod);
break loop3;
}
else if(command==SSH_MSG_USERAUTH_FAILURE){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod + " preauth failure");
}
continue loop3;
return true;
}
else if(command==SSH_MSG_USERAUTH_BANNER){
buf.getInt(); buf.getByte(); buf.getByte();
Expand All @@ -230,132 +343,41 @@ else if(command==SSH_MSG_USERAUTH_BANNER){
if(userinfo!=null){
userinfo.showMessage(message);
}
continue loop1;
continue loop2;
}
else{
//System.err.println("USERAUTH fail ("+command+")");
//throw new JSchException("USERAUTH fail ("+command+")");
else if(command==SSH_MSG_USERAUTH_FAILURE){
buf.getInt(); buf.getByte(); buf.getByte();
byte[] foo=buf.getString();
int partial_success=buf.getByte();
//System.err.println(new String(foo)+
// " partial_success:"+(partial_success!=0));
if(partial_success!=0){
throw new JSchPartialAuthException(Util.byte2str(foo));
}
session.auth_failures++;
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
ipkmethod + " preauth failure command (" + command + ")");
pkmethodsuccess + " auth failure");
}
continue loop3;
}
}
}

if(command!=SSH_MSG_USERAUTH_PK_OK){
continue iloop;
}
}



if(identity.isEncrypted()) continue;
if(pubkeyblob==null) pubkeyblob=identity.getPublicKeyBlob();

//System.err.println("UserAuthPublicKey: pubkeyblob="+pubkeyblob);

if(pubkeyblob==null) continue;
if(pkmethodsuccesses==null) pkmethodsuccesses=ipkmethods;

loop4:
for(String pkmethodsuccess : pkmethodsuccesses){
if(not_available_pks.contains(pkmethodsuccess) && !(identity instanceof AgentIdentity)){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
pkmethodsuccess+" not available for identity "+identity.getName());
}
continue loop4;
}

// send
// byte SSH_MSG_USERAUTH_REQUEST(50)
// string user name
// string service name ("ssh-connection")
// string "publickey"
// boolen TRUE
// string public key algorithm name
// string public key blob
// string signature
packet.reset();
buf.putByte((byte)SSH_MSG_USERAUTH_REQUEST);
buf.putString(_username);
buf.putString(Util.str2byte("ssh-connection"));
buf.putString(Util.str2byte("publickey"));
buf.putByte((byte)1);
buf.putString(Util.str2byte(pkmethodsuccess));
buf.putString(pubkeyblob);

// byte[] tmp=new byte[buf.index-5];
// System.arraycopy(buf.buffer, 5, tmp, 0, tmp.length);
// buf.putString(signature);

byte[] sid=session.getSessionId();
int sidlen=sid.length;
byte[] tmp=new byte[4+sidlen+buf.index-5];
tmp[0]=(byte)(sidlen>>>24);
tmp[1]=(byte)(sidlen>>>16);
tmp[2]=(byte)(sidlen>>>8);
tmp[3]=(byte)(sidlen);
System.arraycopy(sid, 0, tmp, 4, sidlen);
System.arraycopy(buf.buffer, 5, tmp, 4+sidlen, buf.index-5);
byte[] signature=identity.getSignature(tmp, pkmethodsuccess);
if(signature==null){ // for example, too long key length.
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
pkmethodsuccess + " signature failure");
}
continue loop4;
}
buf.putString(signature);
session.write(packet);

loop2:
while(true){
buf=session.read(buf);
command=buf.getCommand()&0xff;

if(command==SSH_MSG_USERAUTH_SUCCESS){
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
pkmethodsuccess + " auth success");
}
return true;
}
else if(command==SSH_MSG_USERAUTH_BANNER){
buf.getInt(); buf.getByte(); buf.getByte();
byte[] _message=buf.getString();
byte[] lang=buf.getString();
String message=Util.byte2str(_message);
if(userinfo!=null){
userinfo.showMessage(message);
}
continue loop2;
}
else if(command==SSH_MSG_USERAUTH_FAILURE){
buf.getInt(); buf.getByte(); buf.getByte();
byte[] foo=buf.getString();
int partial_success=buf.getByte();
//System.err.println(new String(foo)+
// " partial_success:"+(partial_success!=0));
if(partial_success!=0){
throw new JSchPartialAuthException(Util.byte2str(foo));
if(session.auth_failures >= session.max_auth_tries){
return false;
}
else if(try_other_pkmethods){
break loop2;
}
else{
continue iloop;
}
}
session.auth_failures++;
//System.err.println("USERAUTH fail ("+command+")");
//throw new JSchException("USERAUTH fail ("+command+")");
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
pkmethodsuccess + " auth failure");
pkmethodsuccess + " auth failure command (" + command +")");
}
break loop2;
}
//System.err.println("USERAUTH fail ("+command+")");
//throw new JSchException("USERAUTH fail ("+command+")");
if(session.getLogger().isEnabled(Logger.DEBUG)){
session.getLogger().log(Logger.DEBUG,
pkmethodsuccess + " auth failure command (" + command +")");
}
break loop2;
}
}
}
Expand Down

0 comments on commit 408547e

Please sign in to comment.