]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
New 'contrib' tool: a script for faking initial KEX.
authorSimon Tatham <anakin@pobox.com>
Sat, 28 Feb 2015 07:58:29 +0000 (07:58 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 20 Jun 2015 08:31:54 +0000 (09:31 +0100)
encodelib.py is a Python library which implements some handy SSH-2
encoding primitives; samplekex.py uses that to fabricate the start of
an SSH connection, up to the point where key exchange totally fails
its crypto.

The idea is that you adapt samplekex.py to construct initial-kex
sequences with particular properties, in order to test robustness and
security fixes that affect the initial-kex sequence. For example, I
used an adaptation of this to test the Diffie-Hellman range check
that's just gone into 0.64.

(cherry picked from commit 12d5b00d62240d1875be4ac0a6c5d29240696c89)

.gitignore
contrib/encodelib.py [new file with mode: 0644]
contrib/samplekex.py [new file with mode: 0755]

index 92699284978d830734781d078e94f2b71cec65dd..bea01d86130ffce1d57dcdfc452e12abebee61ee 100644 (file)
@@ -1,4 +1,5 @@
 *.o
+*.pyc
 .dirstamp
 .deps
 /*.pdb
diff --git a/contrib/encodelib.py b/contrib/encodelib.py
new file mode 100644 (file)
index 0000000..196c467
--- /dev/null
@@ -0,0 +1,115 @@
+# Python module to make it easy to manually encode SSH packets, by
+# supporting the various uint32, string, mpint primitives.
+#
+# The idea of this is that you can use it to manually construct key
+# exchange sequences of interesting kinds, for testing purposes.
+
+import struct, random
+
+def boolean(b):
+    return "\1" if b else "\0"
+
+def byte(b):
+    assert 0 <= b < 0x100
+    return chr(b)
+
+def uint32(u):
+    assert 0 <= u < 0x100000000
+    return struct.pack(">I", u)
+
+def uint64(u):
+    assert 0 <= u < 0x10000000000000000
+    return struct.pack(">L", u)
+
+def string(s):
+    return uint32(len(s)) + s
+
+def mpint(m):
+    s = ""
+    lastbyte = 0
+    while m > 0:
+        lastbyte = m & 0xFF
+        s = chr(lastbyte) + s
+        m >>= 8
+    if lastbyte & 0x80:
+        s = "\0" + s
+    return string(s)
+
+def name_list(ns):
+    s = ""
+    for n in ns:
+        assert "," not in n
+        if s != "":
+            s += ","
+        s += n
+    return string(s)
+
+def ssh_rsa_key_blob(modulus, exponent):
+    return string(string("ssh-rsa") + mpint(modulus) + mpint(exponent))
+
+def ssh_rsa_signature_blob(signature):
+    return string(string("ssh-rsa") + mpint(signature))
+
+def greeting(string):
+    # Greeting at the start of an SSH connection.
+    return string + "\r\n"
+
+# Packet types.
+SSH2_MSG_DISCONNECT = 1
+SSH2_MSG_IGNORE = 2
+SSH2_MSG_UNIMPLEMENTED = 3
+SSH2_MSG_DEBUG = 4
+SSH2_MSG_SERVICE_REQUEST = 5
+SSH2_MSG_SERVICE_ACCEPT = 6
+SSH2_MSG_KEXINIT = 20
+SSH2_MSG_NEWKEYS = 21
+SSH2_MSG_KEXDH_INIT = 30
+SSH2_MSG_KEXDH_REPLY = 31
+SSH2_MSG_KEX_DH_GEX_REQUEST = 30
+SSH2_MSG_KEX_DH_GEX_GROUP = 31
+SSH2_MSG_KEX_DH_GEX_INIT = 32
+SSH2_MSG_KEX_DH_GEX_REPLY = 33
+SSH2_MSG_KEXRSA_PUBKEY = 30
+SSH2_MSG_KEXRSA_SECRET = 31
+SSH2_MSG_KEXRSA_DONE = 32
+SSH2_MSG_USERAUTH_REQUEST = 50
+SSH2_MSG_USERAUTH_FAILURE = 51
+SSH2_MSG_USERAUTH_SUCCESS = 52
+SSH2_MSG_USERAUTH_BANNER = 53
+SSH2_MSG_USERAUTH_PK_OK = 60
+SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ = 60
+SSH2_MSG_USERAUTH_INFO_REQUEST = 60
+SSH2_MSG_USERAUTH_INFO_RESPONSE = 61
+SSH2_MSG_GLOBAL_REQUEST = 80
+SSH2_MSG_REQUEST_SUCCESS = 81
+SSH2_MSG_REQUEST_FAILURE = 82
+SSH2_MSG_CHANNEL_OPEN = 90
+SSH2_MSG_CHANNEL_OPEN_CONFIRMATION = 91
+SSH2_MSG_CHANNEL_OPEN_FAILURE = 92
+SSH2_MSG_CHANNEL_WINDOW_ADJUST = 93
+SSH2_MSG_CHANNEL_DATA = 94
+SSH2_MSG_CHANNEL_EXTENDED_DATA = 95
+SSH2_MSG_CHANNEL_EOF = 96
+SSH2_MSG_CHANNEL_CLOSE = 97
+SSH2_MSG_CHANNEL_REQUEST = 98
+SSH2_MSG_CHANNEL_SUCCESS = 99
+SSH2_MSG_CHANNEL_FAILURE = 100
+SSH2_MSG_USERAUTH_GSSAPI_RESPONSE = 60
+SSH2_MSG_USERAUTH_GSSAPI_TOKEN = 61
+SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE = 63
+SSH2_MSG_USERAUTH_GSSAPI_ERROR = 64
+SSH2_MSG_USERAUTH_GSSAPI_ERRTOK = 65
+SSH2_MSG_USERAUTH_GSSAPI_MIC = 66
+
+def clearpkt(msgtype, *stuff):
+    # SSH-2 binary packet, in the cleartext format used for initial
+    # setup and kex.
+    s = byte(msgtype)
+    for thing in stuff:
+        s += thing
+    padlen = 0
+    while padlen < 4 or len(s) % 8 != 3:
+        padlen += 1
+        s += byte(random.randint(0,255))
+    s = byte(padlen) + s
+    return string(s)
diff --git a/contrib/samplekex.py b/contrib/samplekex.py
new file mode 100755 (executable)
index 0000000..4705983
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+# Example Python script to synthesise the server end of an SSH key exchange.
+
+# This is an output-only script; you run it by means of saying
+# something like
+#
+#   samplekex.py | nc -l 2222 | hex dump utility of your choice
+#
+# and then connecting PuTTY to port 2222. Being output-only, of
+# course, it cannot possibly get the key exchange _right_, so PuTTY
+# will terminate with an error when the signature in the final message
+# doesn't validate. But everything until then should be processed as
+# if it was a normal SSH-2 connection, which means you can use this
+# script as a starting point for constructing interestingly malformed
+# key exchanges to test bug fixes.
+
+import sys, random
+from encodelib import *
+
+# A random Diffie-Hellman group, taken from an SSH server I made a
+# test connection to.
+groupgen = 5
+group = 0xf5d3849d2092fd427b4ebd838ea4830397a55f80b644626320dbbe51e8f63ed88148d787c94e7e67e4f393f26c565e1992b0cff8a47a953439462a4d0ffa5763ef60ff908f8ee6c4f6ef9f32b9ba50f01ad56fe7ebe90876a5cf61813a4ad4ba7ec0704303c9bf887d36abbd6c2aa9545fc2263232927e731060f5c701c96dc34016636df438ce30973715f121d767cfb98b5d09ae7b86fa36a051ad3c2941a295a68e2f583a56bc69913ec9d25abef4fdf1e31ede827a02620db058b9f041da051c8c0f13b132c17ceb893fa7c4cd8d8feebd82c5f9120cb221b8e88c5fe4dc17ca020a535484c92c7d4bee69c7703e1fa9a652d444c80065342c6ec0fac23c24de246e3dee72ca8bc8beccdade2b36771efcc350558268f5352ae53f2f71db62249ad9ac4fabdd6dfb099c6cff8c05bdea894390f9860f011cca046dfeb2f6ef81094e7980be526742706d1f3db920db107409291bb4c11f9a7dcbfaf26d808e6f9fe636b26b939de419129e86b1e632c60ec23b65c815723c5d861af068fd0ac8b37f4c06ecbd5cb2ef069ca8daac5cbd67c6182a65fed656d0dfbbb8a430b1dbac7bd6303bec8de078fe69f443a7bc8131a284d25dc2844f096240bfc61b62e91a87802987659b884c094c68741d29aa5ca19b9457e1f9df61c7dbbb13a61a79e4670b086027f20da2af4f5b020725f8828726379f429178926a1f0ea03f
+
+# An RSA key, generated specially for this script.
+rsaexp = 0x10001
+rsamod = 0xB98FE0C0BEE1E05B35FDDF5517B3E29D8A9A6A7834378B6783A19536968968F755E341B5822CAE15B465DECB80EE4116CF8D22DB5A6C85444A68D0D45D9D42008329619BE3CAC3B192EF83DD2B75C4BB6B567E11B841073BACE92108DA7E97E543ED7F032F454F7AC3C6D3F27DB34BC9974A85C7963C546662AE300A61CBABEE274481FD041C41D0145704F5FA9C77A5A442CD7A64827BB0F21FB56FDE388B596A20D7A7D1C5F22DA96C6C2171D90A673DABC66596CD99499D75AD82FEFDBE04DEC2CC7E1414A95388F668591B3F4D58249F80489646ED2C318E77D4F4E37EE8A588E79F2960620E6D28BF53653F1C974C91845F0BABFE5D167E1CA7044EE20D
+
+# 16 bytes of random data for the start of KEXINIT.
+cookie = "".join([chr(random.randint(0,255)) for i in range(16)])
+
+sys.stdout.write(greeting("SSH-2.0-Example KEX synthesis"))
+
+# Expect client to send KEXINIT
+
+sys.stdout.write(
+    clearpkt(SSH2_MSG_KEXINIT,
+             cookie,
+             name_list(("diffie-hellman-group-exchange-sha256",)), # kex
+             name_list(("ssh-rsa",)), # host keys
+             name_list(("aes128-ctr",)), # client->server ciphers
+             name_list(("aes128-ctr",)), # server->client ciphers
+             name_list(("hmac-sha2-256",)), # client->server MACs
+             name_list(("hmac-sha2-256",)), # server->client MACs
+             name_list(("none",)), # client->server compression
+             name_list(("none",)), # server->client compression
+             name_list(()), # client->server languages
+             name_list(()), # server->client languages
+             boolean(False), # first kex packet does not follow
+             uint32(0)))
+
+# Expect client to send SSH2_MSG_KEX_DH_GEX_REQUEST(0x1000)
+
+sys.stdout.write(
+    clearpkt(SSH2_MSG_KEX_DH_GEX_GROUP,
+             mpint(group),
+             mpint(groupgen)))
+
+# Expect client to send SSH2_MSG_KEX_DH_GEX_INIT
+
+sys.stdout.write(
+    clearpkt(SSH2_MSG_KEX_DH_GEX_REPLY,
+             ssh_rsa_key_blob(rsaexp, rsamod),
+             mpint(random.randint(2, group-2)),
+             ssh_rsa_signature_blob(random.randint(2, rsamod-2))))