Skip to content

Commit 5c8b534

Browse files
committed
Implement kex-strict from OpenSSH
Implement's OpenSSH's mitigation for the Terrapin attack.
1 parent b0b43bb commit 5c8b534

File tree

4 files changed

+88
-35
lines changed

4 files changed

+88
-35
lines changed

src/main/java/com/trilead/ssh2/transport/KexManager.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ public class KexManager
103103
/** RFC 8308 Section 2 */
104104
private static final String EXT_INFO_C = "ext-info-c";
105105

106+
private static final String KEX_STRICT_C_OPENSSH = "[email protected]";
107+
private static final String KEX_STRICT_S_OPENSSH = "[email protected]";
108+
106109
private KexState kxs;
107110
private int kexCount = 0;
108111
private KeyMaterial km;
@@ -193,6 +196,19 @@ private boolean compareFirstOfNameList(String[] a, String[] b)
193196
return (a[0].equals(b[0]));
194197
}
195198

199+
private boolean containsAlgo(String[] algos, String targetAlgo)
200+
{
201+
if (algos == null || targetAlgo == null)
202+
return false;
203+
204+
for (String algo : algos) {
205+
if (targetAlgo.equals(algo))
206+
return true;
207+
}
208+
209+
return false;
210+
}
211+
196212
private boolean isGuessOK(KexParameters cpar, KexParameters spar)
197213
{
198214
if (cpar == null || spar == null)
@@ -214,6 +230,8 @@ private NegotiatedParameters mergeKexParameters(KexParameters client, KexParamet
214230
{
215231
np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms);
216232

233+
np.isStrictKex = containsAlgo(server.kex_algorithms, KEX_STRICT_S_OPENSSH);
234+
217235
log.log(20, "kex_algo=" + np.kex_algo);
218236

219237
np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms,
@@ -304,13 +322,14 @@ public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex)
304322
*/
305323
private static void addExtraKexAlgorithms(CryptoWishList cwl) {
306324
String[] oldKexAlgorithms = cwl.kexAlgorithms;
307-
List<String> kexAlgorithms = new ArrayList<>(oldKexAlgorithms.length + 1);
325+
List<String> kexAlgorithms = new ArrayList<>(oldKexAlgorithms.length + 2);
308326
for (String algo : oldKexAlgorithms)
309327
{
310-
if (!algo.equals(EXT_INFO_C))
328+
if (!algo.equals(EXT_INFO_C) && !algo.equals(KEX_STRICT_C_OPENSSH))
311329
kexAlgorithms.add(algo);
312330
}
313331
kexAlgorithms.add(EXT_INFO_C);
332+
kexAlgorithms.add(KEX_STRICT_C_OPENSSH);
314333
cwl.kexAlgorithms = kexAlgorithms.toArray(new String[0]);
315334
}
316335

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

747766
throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")");
748767
}
768+
769+
public boolean isStrictKex() {
770+
return kxs.np.isStrictKex;
771+
}
749772
}

src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
public class NegotiatedParameters
1010
{
1111
public boolean guessOK;
12+
public boolean isStrictKex;
1213
public String kex_algo;
1314
public String server_host_key_algo;
1415
public String enc_algo_client_to_server;

src/main/java/com/trilead/ssh2/transport/TransportConnection.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,18 @@ public void startCompression() {
361361
can_recv_compress = true;
362362
can_send_compress = true;
363363
}
364+
365+
/**
366+
* Resets the send sequence number for MAC calculation.
367+
*/
368+
public void resetSendSequenceNumber() {
369+
send_seq_number = 0;
370+
}
371+
372+
/**
373+
* Resets the receive sequence number for MAC calculation.
374+
*/
375+
public void resetReceiveSequenceNumber() {
376+
recv_seq_number = 0;
377+
}
364378
}

src/main/java/com/trilead/ssh2/transport/TransportManager.java

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,11 @@ public void run()
122122
int port;
123123
Socket sock;
124124

125-
Object connectionSemaphore = new Object();
125+
private final Object connectionSemaphore = new Object();
126126

127127
boolean flagKexOngoing = false;
128128
boolean connectionClosed = false;
129+
boolean firstKexFinished = false;
129130

130131
Throwable reasonClosedCause = null;
131132

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

406407
public void kexFinished() {
408+
firstKexFinished = true;
409+
407410
synchronized (connectionSemaphore)
408411
{
409412
flagKexOngoing = false;
@@ -419,11 +422,15 @@ public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws I
419422
public void changeRecvCipher(BlockCipher bc, MAC mac)
420423
{
421424
tc.changeRecvCipher(bc, mac);
425+
if (km.isStrictKex())
426+
tc.resetReceiveSequenceNumber();
422427
}
423428

424429
public void changeSendCipher(BlockCipher bc, MAC mac)
425430
{
426431
tc.changeSendCipher(bc, mac);
432+
if (km.isStrictKex())
433+
tc.resetSendSequenceNumber();
427434
}
428435

429436
/**
@@ -531,38 +538,6 @@ public void receiveLoop() throws IOException
531538

532539
int type = msg[0] & 0xff;
533540

534-
if (type == Packets.SSH_MSG_IGNORE)
535-
continue;
536-
537-
if (type == Packets.SSH_MSG_DEBUG)
538-
{
539-
if (log.isEnabled())
540-
{
541-
TypesReader tr = new TypesReader(msg, 0, msglen);
542-
tr.readByte();
543-
tr.readBoolean();
544-
StringBuffer debugMessageBuffer = new StringBuffer();
545-
debugMessageBuffer.append(tr.readString("UTF-8"));
546-
547-
for (int i = 0; i < debugMessageBuffer.length(); i++)
548-
{
549-
char c = debugMessageBuffer.charAt(i);
550-
551-
if ((c >= 32) && (c <= 126))
552-
continue;
553-
debugMessageBuffer.setCharAt(i, '\uFFFD');
554-
}
555-
556-
log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
557-
}
558-
continue;
559-
}
560-
561-
if (type == Packets.SSH_MSG_UNIMPLEMENTED)
562-
{
563-
throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
564-
}
565-
566541
if (type == Packets.SSH_MSG_DISCONNECT)
567542
{
568543
TypesReader tr = new TypesReader(msg, 0, msglen);
@@ -615,6 +590,46 @@ public void receiveLoop() throws IOException
615590
continue;
616591
}
617592

593+
/*
594+
* Any other packet should not be used when kex-strict is enabled.
595+
*/
596+
if (!firstKexFinished && km.isStrictKex())
597+
{
598+
throw new IOException("Unexpected packet received when kex-strict enabled");
599+
}
600+
601+
if (type == Packets.SSH_MSG_IGNORE)
602+
continue;
603+
604+
if (type == Packets.SSH_MSG_DEBUG)
605+
{
606+
if (log.isEnabled())
607+
{
608+
TypesReader tr = new TypesReader(msg, 0, msglen);
609+
tr.readByte();
610+
tr.readBoolean();
611+
StringBuffer debugMessageBuffer = new StringBuffer();
612+
debugMessageBuffer.append(tr.readString("UTF-8"));
613+
614+
for (int i = 0; i < debugMessageBuffer.length(); i++)
615+
{
616+
char c = debugMessageBuffer.charAt(i);
617+
618+
if ((c >= 32) && (c <= 126))
619+
continue;
620+
debugMessageBuffer.setCharAt(i, '\uFFFD');
621+
}
622+
623+
log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
624+
}
625+
continue;
626+
}
627+
628+
if (type == Packets.SSH_MSG_UNIMPLEMENTED)
629+
{
630+
throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
631+
}
632+
618633
if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) {
619634
tc.startCompression();
620635
}

0 commit comments

Comments
 (0)