Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/main/java/com/trilead/ssh2/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -380,6 +381,25 @@ public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair)

return authenticated;
}

/**
* Offer a public key without signing. Acceptance only indicates that the
* server is willing to authenticate with this key; it does not authenticate
* the connection. Rejected offers may count toward the server's attempt limit.
*
* @param user username to authenticate
* @param key public key to offer
* @return true for a matching SSH_MSG_USERAUTH_PK_OK, false for rejection
* @throws IOException on transport or protocol error
*/
public synchronized boolean probePublicKey(String user, PublicKey key) throws IOException
{
if (key == null)
throw new IllegalArgumentException("Public key argument is null");
checkRequirements(user);
return am.probePublicKey(user, key);
}

/**
* A convenience wrapper function which reads in a private key (PEM format,
* either DSA, EC, or RSA) and then calls
Expand Down
272 changes: 108 additions & 164 deletions src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package com.trilead.ssh2.auth;

import com.trilead.ssh2.crypto.PublicKeyUtils;
import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
import com.trilead.ssh2.signature.RSASHA256Verify;
import com.trilead.ssh2.signature.RSASHA512Verify;
import java.io.IOException;
Expand All @@ -14,6 +13,7 @@
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

Expand All @@ -32,6 +32,7 @@
import com.trilead.ssh2.packets.PacketUserauthRequestPublicKey;
import com.trilead.ssh2.packets.Packets;
import com.trilead.ssh2.packets.TypesWriter;
import com.trilead.ssh2.packets.TypesReader;
import com.trilead.ssh2.signature.DSASHA1Verify;
import com.trilead.ssh2.signature.ECDSASHA2Verify;
import com.trilead.ssh2.signature.Ed25519Verify;
Expand Down Expand Up @@ -238,194 +239,137 @@
{
initialize(user);

if (!methodPossible("publickey"))

Check failure on line 242 in src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "publickey" 3 times.

See more on https://sonarcloud.io/project/issues?id=connectbot_sshlib&issues=AaEfebPtchGL3MC0rUTW&open=AaEfebPtchGL3MC0rUTW&pullRequest=558
throw new IOException("Authentication method publickey not supported by the server at this stage.");

if (publicKey instanceof DSAPublicKey)
{
SSHSignature s = DSASHA1Verify.get();
byte[] pk_enc = s.encodePublicKey(publicKey);

byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, DSASHA1Verify.ID_SSH_DSS, pk_enc);

byte[] ds_enc;
if (signatureProxy != null)
{
ds_enc = signatureProxy.sign(msg, SignatureProxy.SHA1);
}
else
{
ds_enc = s.generateSignature(msg, privateKey, rnd);
}

PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
DSASHA1Verify.ID_SSH_DSS, pk_enc, ds_enc);
tm.sendMessage(ua.getPayload());
}
else if (publicKey instanceof RSAPublicKey)
{
byte[] pk_enc = RSASHA1Verify.get().encodePublicKey(publicKey);
String pk_algorithm;

PublicKeyOffer offer = publicKeyOffer(publicKey);
byte[] msg = generatePublicKeyUserAuthenticationRequest(user, offer.algorithm, offer.blob);
byte[] signature;
if (signatureProxy != null)
signature = signatureProxy.sign(msg, offer.hashAlgorithm);
else if (offer.verifier != null)
signature = offer.verifier.generateSignature(msg, privateKey, rnd);
else
throw new IOException("SK key authentication requires a SignatureProxy for signing.");
if (signature == null)
throw new IOException("Signing returned no signature.");
tm.sendMessage(new PacketUserauthRequestPublicKey("ssh-connection", user,
offer.algorithm, offer.blob, signature).getPayload());

// Servers support different hash algorithms for RSA keys
// https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2-12
Set<String> algsAccepted = tm.getExtensionInfo().getSignatureAlgorithmsAccepted();
final byte[] rsa_sig_enc;
byte[] ar = getNextMessage();

if (algsAccepted.contains(RSASHA512Verify.get().getKeyFormat()))
{
SSHSignature s = RSASHA512Verify.get();
pk_algorithm = s.getKeyFormat();
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
if (signatureProxy != null)
{
rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA512);
}
else
{
rsa_sig_enc = s.generateSignature(msg, privateKey, rnd);
}
}
else if (algsAccepted.contains(RSASHA256Verify.ID_RSA_SHA_2_256))
{
pk_algorithm = RSASHA256Verify.ID_RSA_SHA_2_256;
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
return isAuthenticationSuccessful(ar);
}
catch (IOException e)
{
e.printStackTrace();
tm.close(e, false);
throw new IOException("Publickey authentication failed.", e);
}
}

if (signatureProxy != null)
{
rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA256);
}
else
{
rsa_sig_enc = RSASHA256Verify.get().generateSignature(msg, privateKey, rnd);
}
}
else
{
pk_algorithm = "ssh-rsa";
byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
if (signatureProxy != null)
{
rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA1);
}
else
{
// Server always accepts RSA with SHA1
rsa_sig_enc = RSASHA1Verify.get().generateSignature(msg, privateKey, rnd);
}
}
public boolean probePublicKey(String user, PublicKey key) throws IOException
{
try
{
initialize(user);

PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
pk_algorithm, pk_enc, rsa_sig_enc);
if (!methodPossible("publickey"))
throw new IOException("Authentication method publickey not supported by the server at this stage.");

tm.sendMessage(ua.getPayload());
}
else if (publicKey instanceof ECPublicKey)
{
ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
PublicKeyOffer offer = publicKeyOffer(key);
tm.sendMessage(new PacketUserauthRequestPublicKey("ssh-connection", user,
offer.algorithm, offer.blob, null).getPayload());

ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForKey(ecPublicKey);
byte[] reply = getNextMessage();

final String algo = verifier.getKeyFormat();
if (reply[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
return isAuthenticationSuccessful(reply);

byte[] pk_enc = verifier.encodePublicKey(ecPublicKey);
TypesReader reader = new TypesReader(reply);
if (reader.readByte() != Packets.SSH_MSG_USERAUTH_PK_OK)
throw new IOException("Unexpected reply to public key offer.");

byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);
String algorithm = reader.readString();
byte[] blob = reader.readByteString();

byte[] ec_sig_enc;
if (signatureProxy != null)
{
ec_sig_enc = signatureProxy.sign(msg, ECDSASHA2Verify.getDigestAlgorithmForParams(ecPublicKey));
}
else
{
ec_sig_enc = verifier.generateSignature(msg, privateKey, rnd);
}
if (reader.remain() != 0 || !offer.algorithm.equals(algorithm) || !Arrays.equals(offer.blob, blob))
throw new IOException("Public key acceptance does not match the offered key.");

PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
algo, pk_enc, ec_sig_enc);
isPartialSuccess = false;
return true;
}
catch (IOException e)
{
tm.close(e, false);
throw new IOException("Public key offer failed.", e);
}
}

tm.sendMessage(ua.getPayload());
}
else if (publicKey instanceof SkPublicKey)
private PublicKeyOffer publicKeyOffer(PublicKey key) throws IOException
{
SSHSignature verifier;
String hashAlgorithm;
if (key instanceof DSAPublicKey)
{
verifier = DSASHA1Verify.get();
hashAlgorithm = SignatureProxy.SHA1;
}
else if (key instanceof RSAPublicKey)
{
Set<String> algorithms = tm.getExtensionInfo().getSignatureAlgorithmsAccepted();
if (algorithms.contains(RSASHA512Verify.get().getKeyFormat()))
{
// FIDO2 Security Key (SK) authentication
// This check must come before the Ed25519 check because
// SK Ed25519 keys match isEd25519Key() but require SK-specific encoding.
if (signatureProxy == null)
{
throw new IOException("SK key authentication requires a SignatureProxy for signing.");
}

SkPublicKey skPublicKey = (SkPublicKey) publicKey;
final String algo = skPublicKey.getSshKeyType();

// Get the encoded public key (includes key type, key data, and application)
byte[] pk_enc = skPublicKey.getEncoded();

byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);

// Determine the hash algorithm based on key type
// sk-ssh-ed25519@openssh.com uses SHA512 (same as Ed25519)
// sk-ecdsa-sha2-nistp256@openssh.com uses SHA256
String hashAlgorithm;
if (algo.contains("ed25519"))
{
hashAlgorithm = SignatureProxy.SHA512;
}
else
{
hashAlgorithm = SignatureProxy.SHA256;
}

// The SignatureProxy.sign() for SK keys must return the complete
// signature blob including flags and counter, not just the raw signature
byte[] sk_sig_enc = signatureProxy.sign(msg, hashAlgorithm);

PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
algo, pk_enc, sk_sig_enc);

tm.sendMessage(ua.getPayload());
verifier = RSASHA512Verify.get();
hashAlgorithm = SignatureProxy.SHA512;
}
else if (PublicKeyUtils.isEd25519Key(publicKey))
else if (algorithms.contains(RSASHA256Verify.ID_RSA_SHA_2_256))
{
final String algo = Ed25519Verify.ED25519_ID;

byte[] pk_enc = Ed25519Verify.get().encodePublicKey(publicKey);

byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);

byte[] ed_sig_enc;
if (signatureProxy != null)
{
ed_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA512);
}
else
{
Ed25519PrivateKey pk = Ed25519Verify.convertPrivateKey(privateKey);
ed_sig_enc = Ed25519Verify.get().generateSignature(msg, pk, rnd);
}

PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
algo, pk_enc, ed_sig_enc);

tm.sendMessage(ua.getPayload());
verifier = RSASHA256Verify.get();
hashAlgorithm = SignatureProxy.SHA256;
}
else
{
throw new IOException("Unknown public key type.");
verifier = RSASHA1Verify.get();
hashAlgorithm = SignatureProxy.SHA1;
}
}
else if (key instanceof ECPublicKey)
{
ECPublicKey ecKey = (ECPublicKey) key;
verifier = ECDSASHA2Verify.getVerifierForKey(ecKey);
hashAlgorithm = ECDSASHA2Verify.getDigestAlgorithmForParams(ecKey);
}
else if (key instanceof SkPublicKey)
{
SkPublicKey skKey = (SkPublicKey) key;
String algorithm = skKey.getSshKeyType();
return new PublicKeyOffer(algorithm, skKey.getEncoded(), null,
algorithm.contains("ed25519") ? SignatureProxy.SHA512 : SignatureProxy.SHA256);
}
else if (PublicKeyUtils.isEd25519Key(key))
{
verifier = Ed25519Verify.get();
hashAlgorithm = SignatureProxy.SHA512;
}
else
throw new IOException("Unknown public key type.");
return new PublicKeyOffer(verifier.getKeyFormat(), verifier.encodePublicKey(key), verifier, hashAlgorithm);
}

byte[] ar = getNextMessage();
private static class PublicKeyOffer
{
final String algorithm;
final byte[] blob;
final SSHSignature verifier;
final String hashAlgorithm;

return isAuthenticationSuccessful(ar);
}
catch (IOException e)
PublicKeyOffer(String algorithm, byte[] blob, SSHSignature verifier, String hashAlgorithm)
{
e.printStackTrace();
tm.close(e, false);
throw new IOException("Publickey authentication failed.", e);
this.algorithm = algorithm;
this.blob = blob;
this.verifier = verifier;
this.hashAlgorithm = hashAlgorithm;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ public byte[] getPayload() throws UnsupportedEncodingException {
tw.writeString(userName, "UTF-8");
tw.writeString(serviceName);
tw.writeString("publickey");
tw.writeBoolean(true);
tw.writeBoolean(sig != null);
tw.writeString(pkAlgoName);
tw.writeString(pk, 0, pk.length);
tw.writeString(sig, 0, sig.length);
if (sig != null)
tw.writeString(sig, 0, sig.length);
payload = tw.getBytes();
}
return payload;
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/trilead/ssh2/packets/Packets.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class Packets
public static final int SSH_MSG_USERAUTH_FAILURE = 51;
public static final int SSH_MSG_USERAUTH_SUCCESS = 52;
public static final int SSH_MSG_USERAUTH_BANNER = 53;
public static final int SSH_MSG_USERAUTH_PK_OK = 60;
public static final int SSH_MSG_USERAUTH_INFO_REQUEST = 60;
public static final int SSH_MSG_USERAUTH_INFO_RESPONSE = 61;

Expand Down
15 changes: 15 additions & 0 deletions src/test/java/com/trilead/ssh2/DropbearCompatibilityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ private void canConnectWithPubkey(String keyFilename) throws Exception {
}
}

@Test
public void publicKeyProbeThenSign() throws Exception {
char[] chars = IOUtils.toCharArray(getClass().getResourceAsStream("crypto/ecdsa-nistp256-openssh2-private-key.txt"), "UTF-8");
java.security.KeyPair key = com.trilead.ssh2.crypto.PEMDecoder.decode(chars, "");
try (Connection connection = withServer(server)) {
connection.connect();
java.security.KeyPairGenerator generator = java.security.KeyPairGenerator.getInstance("EC");
generator.initialize(256);
assertThat(connection.probePublicKey(USERNAME, generator.generateKeyPair().getPublic()), is(false));
assertThat(connection.probePublicKey(USERNAME, key.getPublic()), is(true));
assertThat(connection.isAuthenticationComplete(), is(false));
assertThat(connection.authenticateWithPublicKey(USERNAME, key), is(true));
}
}

@Test
public void canConnectWithEd25519() throws Exception {
canConnectWithPubkey("ed25519-openssh2-private-key.txt");
Expand Down
Loading
Loading