Skip to content

Commit

Permalink
Implement kex-strict from OpenSSH
Browse files Browse the repository at this point in the history
Implement's OpenSSH's mitigation for the Terrapin attack.
  • Loading branch information
kruton committed Dec 19, 2023
1 parent b0b43bb commit 5c8b534
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 35 deletions.
27 changes: 25 additions & 2 deletions src/main/java/com/trilead/ssh2/transport/KexManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public class KexManager
/** RFC 8308 Section 2 */
private static final String EXT_INFO_C = "ext-info-c";

private static final String KEX_STRICT_C_OPENSSH = "kex-strict-c-v00@openssh.com";
private static final String KEX_STRICT_S_OPENSSH = "kex-strict-s-v00@openssh.com";

private KexState kxs;
private int kexCount = 0;
private KeyMaterial km;
Expand Down Expand Up @@ -193,6 +196,19 @@ private boolean compareFirstOfNameList(String[] a, String[] b)
return (a[0].equals(b[0]));
}

private boolean containsAlgo(String[] algos, String targetAlgo)
{
if (algos == null || targetAlgo == null)
return false;

for (String algo : algos) {
if (targetAlgo.equals(algo))
return true;
}

return false;
}

private boolean isGuessOK(KexParameters cpar, KexParameters spar)
{
if (cpar == null || spar == null)
Expand All @@ -214,6 +230,8 @@ private NegotiatedParameters mergeKexParameters(KexParameters client, KexParamet
{
np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms);

np.isStrictKex = containsAlgo(server.kex_algorithms, KEX_STRICT_S_OPENSSH);

log.log(20, "kex_algo=" + np.kex_algo);

np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms,
Expand Down Expand Up @@ -304,13 +322,14 @@ public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex)
*/
private static void addExtraKexAlgorithms(CryptoWishList cwl) {
String[] oldKexAlgorithms = cwl.kexAlgorithms;
List<String> kexAlgorithms = new ArrayList<>(oldKexAlgorithms.length + 1);
List<String> kexAlgorithms = new ArrayList<>(oldKexAlgorithms.length + 2);
for (String algo : oldKexAlgorithms)
{
if (!algo.equals(EXT_INFO_C))
if (!algo.equals(EXT_INFO_C) && !algo.equals(KEX_STRICT_C_OPENSSH))
kexAlgorithms.add(algo);
}
kexAlgorithms.add(EXT_INFO_C);
kexAlgorithms.add(KEX_STRICT_C_OPENSSH);
cwl.kexAlgorithms = kexAlgorithms.toArray(new String[0]);
}

Expand Down Expand Up @@ -746,4 +765,8 @@ public synchronized void handleMessage(byte[] msg, int msglen) throws IOExceptio

throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")");
}

public boolean isStrictKex() {
return kxs.np.isStrictKex;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
public class NegotiatedParameters
{
public boolean guessOK;
public boolean isStrictKex;
public String kex_algo;
public String server_host_key_algo;
public String enc_algo_client_to_server;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/trilead/ssh2/transport/TransportConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,18 @@ public void startCompression() {
can_recv_compress = true;
can_send_compress = true;
}

/**
* Resets the send sequence number for MAC calculation.
*/
public void resetSendSequenceNumber() {
send_seq_number = 0;
}

/**
* Resets the receive sequence number for MAC calculation.
*/
public void resetReceiveSequenceNumber() {
recv_seq_number = 0;
}
}
81 changes: 48 additions & 33 deletions src/main/java/com/trilead/ssh2/transport/TransportManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,11 @@ public void run()
int port;
Socket sock;

Object connectionSemaphore = new Object();
private final Object connectionSemaphore = new Object();

boolean flagKexOngoing = false;
boolean connectionClosed = false;
boolean firstKexFinished = false;

Throwable reasonClosedCause = null;

Expand Down Expand Up @@ -404,6 +405,8 @@ public void sendKexMessage(byte[] msg) throws IOException
}

public void kexFinished() {
firstKexFinished = true;

synchronized (connectionSemaphore)
{
flagKexOngoing = false;
Expand All @@ -419,11 +422,15 @@ public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws I
public void changeRecvCipher(BlockCipher bc, MAC mac)
{
tc.changeRecvCipher(bc, mac);
if (km.isStrictKex())
tc.resetReceiveSequenceNumber();
}

public void changeSendCipher(BlockCipher bc, MAC mac)
{
tc.changeSendCipher(bc, mac);
if (km.isStrictKex())
tc.resetSendSequenceNumber();
}

/**
Expand Down Expand Up @@ -531,38 +538,6 @@ public void receiveLoop() throws IOException

int type = msg[0] & 0xff;

if (type == Packets.SSH_MSG_IGNORE)
continue;

if (type == Packets.SSH_MSG_DEBUG)
{
if (log.isEnabled())
{
TypesReader tr = new TypesReader(msg, 0, msglen);
tr.readByte();
tr.readBoolean();
StringBuffer debugMessageBuffer = new StringBuffer();
debugMessageBuffer.append(tr.readString("UTF-8"));

for (int i = 0; i < debugMessageBuffer.length(); i++)
{
char c = debugMessageBuffer.charAt(i);

if ((c >= 32) && (c <= 126))
continue;
debugMessageBuffer.setCharAt(i, '\uFFFD');
}

log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
}
continue;
}

if (type == Packets.SSH_MSG_UNIMPLEMENTED)
{
throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
}

if (type == Packets.SSH_MSG_DISCONNECT)
{
TypesReader tr = new TypesReader(msg, 0, msglen);
Expand Down Expand Up @@ -615,6 +590,46 @@ public void receiveLoop() throws IOException
continue;
}

/*
* Any other packet should not be used when kex-strict is enabled.
*/
if (!firstKexFinished && km.isStrictKex())
{
throw new IOException("Unexpected packet received when kex-strict enabled");
}

if (type == Packets.SSH_MSG_IGNORE)
continue;

if (type == Packets.SSH_MSG_DEBUG)
{
if (log.isEnabled())
{
TypesReader tr = new TypesReader(msg, 0, msglen);
tr.readByte();
tr.readBoolean();
StringBuffer debugMessageBuffer = new StringBuffer();
debugMessageBuffer.append(tr.readString("UTF-8"));

for (int i = 0; i < debugMessageBuffer.length(); i++)
{
char c = debugMessageBuffer.charAt(i);

if ((c >= 32) && (c <= 126))
continue;
debugMessageBuffer.setCharAt(i, '\uFFFD');
}

log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
}
continue;
}

if (type == Packets.SSH_MSG_UNIMPLEMENTED)
{
throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
}

if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) {
tc.startCompression();
}
Expand Down

0 comments on commit 5c8b534

Please sign in to comment.