ssh2 packet format
authormarkus <markus@openbsd.org>
Mon, 3 Apr 2000 20:12:55 +0000 (20:12 +0000)
committermarkus <markus@openbsd.org>
Mon, 3 Apr 2000 20:12:55 +0000 (20:12 +0000)
usr.bin/ssh/packet.c
usr.bin/ssh/packet.h

index 901b2ed..9925f75 100644 (file)
  * 
  * This file contains code implementing the packet protocol and communication
  * with the other side.  This same code is used both on client and server side.
- * 
+ *
+ * SSH2 packet format added by Markus Friedl.
+ *
  */
 
 #include "includes.h"
-RCSID("$Id: packet.c,v 1.24 2000/04/03 07:07:15 markus Exp $");
+RCSID("$Id: packet.c,v 1.25 2000/04/03 20:12:55 markus Exp $");
 
 #include "xmalloc.h"
 #include "buffer.h"
@@ -30,6 +32,22 @@ RCSID("$Id: packet.c,v 1.24 2000/04/03 07:07:15 markus Exp $");
 #include "deattack.h"
 #include "channels.h"
 
+#include "compat.h"
+#include "ssh2.h"
+
+#include <ssl/bn.h>
+#include <ssl/dh.h>
+#include <ssl/hmac.h>
+#include "buffer.h"
+#include "kex.h"
+#include "hmac.h"
+
+#ifdef PACKET_DEBUG
+#define DBG(x) x
+#else
+#define DBG(x)
+#endif
+
 /*
  * This variable contains the file descriptors used for communicating with
  * the other side.  connection_in is used for reading; connection_out for
@@ -81,11 +99,45 @@ static int initialized = 0;
 /* Set to true if the connection is interactive. */
 static int interactive_mode = 0;
 
+/* True if SSH2 packet format is used */
+int use_ssh2_packet_format = 0;
+
+/* Session key information for Encryption and MAC */
+Kex    *kex = NULL;
+
+void
+packet_set_kex(Kex *k)
+{
+       if( k->mac[MODE_IN ].key == NULL ||
+           k->enc[MODE_IN ].key == NULL ||
+           k->enc[MODE_IN ].iv  == NULL ||
+           k->mac[MODE_OUT].key == NULL ||
+           k->enc[MODE_OUT].key == NULL ||
+           k->enc[MODE_OUT].iv  == NULL)
+               fatal("bad KEX");
+       kex = k;
+}
+void
+clear_enc_keys(Enc *enc, int len)
+{
+       memset(enc->iv,  0, len);
+       memset(enc->key, 0, len);
+       xfree(enc->iv);
+       xfree(enc->key);
+       enc->iv = NULL;
+       enc->key = NULL;
+}
+void
+packet_set_ssh2_format(void)
+{
+       debug("use_ssh2_packet_format");
+       use_ssh2_packet_format = 1;
+}
+
 /*
  * Sets the descriptors used for communication.  Disables encryption until
  * packet_set_encryption_key is called.
  */
-
 void
 packet_set_connection(int fd_in, int fd_out)
 {
@@ -225,6 +277,7 @@ packet_get_protocol_flags()
  * Level is compression level 1 (fastest) - 9 (slow, best) as in gzip.
  */
 
+/*** XXXXX todo: kex means re-init */
 void
 packet_start_compression(int level)
 {
@@ -242,7 +295,7 @@ packet_start_compression(int level)
 
 void
 packet_encrypt(CipherContext * cc, void *dest, void *src,
-              unsigned int bytes)
+    unsigned int bytes)
 {
        cipher_encrypt(cc, dest, src, bytes);
 }
@@ -254,7 +307,7 @@ packet_encrypt(CipherContext * cc, void *dest, void *src,
 
 void
 packet_decrypt(CipherContext * cc, void *dest, void *src,
-              unsigned int bytes)
+    unsigned int bytes)
 {
        int i;
 
@@ -266,15 +319,11 @@ packet_decrypt(CipherContext * cc, void *dest, void *src,
         * (C)1998 CORE-SDI, Buenos Aires Argentina Ariel Futoransky(futo@core-sdi.com)
         */
 
-       switch (cc->type) {
-       case SSH_CIPHER_NONE:
+       if (cc->type == SSH_CIPHER_NONE || compat20) {
                i = DEATTACK_OK;
-               break;
-       default:
+       } else {
                i = detect_attack(src, bytes, NULL);
-               break;
        }
-
        if (i == DEATTACK_DETECTED)
                packet_disconnect("crc32 compensation attack: network attack detected");
 
@@ -289,8 +338,11 @@ packet_decrypt(CipherContext * cc, void *dest, void *src,
 
 void
 packet_set_encryption_key(const unsigned char *key, unsigned int keylen,
-                         int cipher)
+    int cipher)
 {
+       if (keylen < 20)
+               fatal("keylen too small: %d", keylen);
+
        /* All other ciphers use the same key in both directions for now. */
        cipher_set_key(&receive_context, cipher, key, keylen, 0);
        cipher_set_key(&send_context, cipher, key, keylen, 1);
@@ -299,7 +351,7 @@ packet_set_encryption_key(const unsigned char *key, unsigned int keylen,
 /* Starts constructing a packet to send. */
 
 void
-packet_start(int type)
+packet_start1(int type)
 {
        char buf[9];
 
@@ -309,6 +361,29 @@ packet_start(int type)
        buffer_append(&outgoing_packet, buf, 9);
 }
 
+void
+packet_start2(int type)
+{
+       char buf[4+1+1];
+
+       buffer_clear(&outgoing_packet);
+       memset(buf, 0, sizeof buf);
+       /* buf[0..3] = payload_len; */
+       /* buf[4] =    pad_len; */
+       buf[5] = type & 0xff;
+       buffer_append(&outgoing_packet, buf, sizeof buf);
+}
+
+void
+packet_start(int type)
+{
+       DBG(debug("packet_start[%d]",type));
+       if (use_ssh2_packet_format)
+               packet_start2(type);
+       else
+               packet_start1(type);
+}
+
 /* Appends a character to the packet data. */
 
 void
@@ -365,7 +440,7 @@ packet_put_bignum2(BIGNUM * value)
  */
 
 void
-packet_send()
+packet_send1()
 {
        char buf[8], *cp;
        int i, padding, len;
@@ -435,6 +510,139 @@ packet_send()
         */
 }
 
+/*
+ * Finalize packet in SSH2 format (compress, mac, encrypt, enqueue)
+ */
+void
+packet_send2()
+{
+       unsigned char *macbuf = NULL;
+       char *cp;
+       unsigned int packet_length = 0;
+       unsigned int i, padlen, len;
+       u_int32_t rand = 0;
+        static unsigned int seqnr = 0;
+       int type;
+       Enc *enc   = NULL;
+       Mac *mac   = NULL;
+       Comp *comp = NULL;
+       int block_size;
+
+       if (kex != NULL) {
+               enc  = &kex->enc[MODE_OUT];
+               mac  = &kex->mac[MODE_OUT];
+               comp = &kex->comp[MODE_OUT];
+       }
+       block_size = enc ? enc->block_size : 8;
+
+       cp = buffer_ptr(&outgoing_packet);
+       type = cp[5] & 0xff;
+
+#ifdef PACKET_DEBUG
+       fprintf(stderr, "plain:     ");
+       buffer_dump(&outgoing_packet);
+#endif
+
+       if (comp && comp->enabled) {
+               len = buffer_len(&outgoing_packet);
+               /* skip header, compress only payload */
+               buffer_consume(&outgoing_packet, 5);
+               buffer_clear(&compression_buffer);
+               buffer_compress(&outgoing_packet, &compression_buffer);
+               buffer_clear(&outgoing_packet);
+               buffer_append(&outgoing_packet, "\0\0\0\0\0", 5);
+               buffer_append(&outgoing_packet, buffer_ptr(&compression_buffer),
+                   buffer_len(&compression_buffer));
+               DBG(debug("compression: raw %d compressed %d", len,
+                   buffer_len(&outgoing_packet)));
+       }
+
+       /* sizeof (packet_len + pad_len + payload) */
+       len = buffer_len(&outgoing_packet);
+
+       /*
+        * calc size of padding, alloc space, get random data,
+        * minimum padding is 4 bytes
+        */
+       padlen = block_size - (len % block_size);
+       if (padlen < 4)
+               padlen += block_size;
+       buffer_append_space(&outgoing_packet, &cp, padlen);
+       if (enc && enc->type != SSH_CIPHER_NONE) {
+               for (i = 0; i < padlen; i++) {
+                       if (i % 4 == 0)
+                               rand = arc4random();
+                       cp[i] = rand & 0xff;
+                       rand <<= 8;
+               }
+       }
+       /* packet_length includes payload, padding and padding length field */
+       packet_length = buffer_len(&outgoing_packet) - 4;
+       cp = buffer_ptr(&outgoing_packet);
+       PUT_32BIT(cp, packet_length);
+       cp[4] = padlen & 0xff;
+       DBG(debug("send: len %d (includes padlen %d)", packet_length+4, padlen));
+
+       /* compute MAC over seqnr and packet(length fields, payload, padding) */
+       if (mac && mac->enabled) {
+               macbuf = hmac( mac->md, seqnr,
+                   (unsigned char *) buffer_ptr(&outgoing_packet),
+                   buffer_len(&outgoing_packet),
+                   mac->key, mac->key_len
+               );
+               DBG(debug("done calc HMAC out #%d", seqnr));
+       }
+       /* encrypt packet and append to output buffer. */
+       buffer_append_space(&output, &cp, buffer_len(&outgoing_packet));
+       packet_encrypt(&send_context, cp, buffer_ptr(&outgoing_packet),
+           buffer_len(&outgoing_packet));
+       /* append unencrypted MAC */
+       if (mac && mac->enabled)
+               buffer_append(&output, (char *)macbuf, mac->mac_len);
+#ifdef PACKET_DEBUG
+       fprintf(stderr, "encrypted: ");
+       buffer_dump(&output);
+#endif
+        /* increment sequence number for outgoing packets */
+        if (++seqnr == 0)
+                log("outgoing seqnr wraps around");
+       buffer_clear(&outgoing_packet);
+
+       if (type == SSH2_MSG_NEWKEYS) {
+               if (kex==NULL || mac==NULL || enc==NULL || comp==NULL)
+                       fatal("packet_send2: no KEX");
+               if (mac->md != NULL)
+                       mac->enabled = 1;
+               debug("cipher_set_key_iv send_context");
+               cipher_set_key_iv(&send_context, enc->type,
+                   enc->key, enc->key_len,
+                   enc->iv, enc->iv_len);
+               clear_enc_keys(enc, kex->we_need);
+               if (comp->type != 0 && comp->enabled == 0) {
+                       comp->enabled = 1;
+                       if (! packet_compression)
+                               packet_start_compression(6);
+               }
+       }
+}
+
+void
+packet_send()
+{
+       if (use_ssh2_packet_format)
+               packet_send2();
+       else
+               packet_send1();
+       DBG(debug("packet_send done"));
+}
+
+void
+packet_send_and_wait()
+{
+       packet_send();
+       packet_write_wait();
+}
+
 /*
  * Waits until a packet has been received, and returns its type.  Note that
  * no other data is processed until this returns, so this function should not
@@ -447,6 +655,7 @@ packet_read(int *payload_len_ptr)
        int type, len;
        fd_set set;
        char buf[8192];
+       DBG(debug("packet_read()"));
 
        /* Since we are blocking, ensure that all written packets have been sent. */
        packet_write_wait();
@@ -500,7 +709,7 @@ packet_read_expect(int *payload_len_ptr, int expected_type)
        type = packet_read(payload_len_ptr);
        if (type != expected_type)
                packet_disconnect("Protocol error: expected packet type %d, got %d",
-                                 expected_type, type);
+                   expected_type, type);
 }
 
 /* Checks if a full packet is available in the data received so far via
@@ -519,15 +728,13 @@ packet_read_expect(int *payload_len_ptr, int expected_type)
  */
 
 int
-packet_read_poll(int *payload_len_ptr)
+packet_read_poll1(int *payload_len_ptr)
 {
        unsigned int len, padded_len;
        unsigned char *ucp;
-       char buf[8], *cp, *msg;
+       char buf[8], *cp;
        unsigned int checksum, stored_checksum;
 
-restart:
-
        /* Check if input size is less than minimum packet size. */
        if (buffer_len(&input) < 4 + 8)
                return SSH_MSG_NONE;
@@ -560,7 +767,7 @@ restart:
 
        /* Compute packet checksum. */
        checksum = crc32((unsigned char *) buffer_ptr(&incoming_packet),
-                        buffer_len(&incoming_packet) - 4);
+           buffer_len(&incoming_packet) - 4);
 
        /* Skip padding. */
        buffer_consume(&incoming_packet, 8 - len % 8);
@@ -569,7 +776,7 @@ restart:
 
        if (len != buffer_len(&incoming_packet))
                packet_disconnect("packet_read_poll: len %d != buffer_len %d.",
-                                 len, buffer_len(&incoming_packet));
+                   len, buffer_len(&incoming_packet));
 
        ucp = (unsigned char *) buffer_ptr(&incoming_packet) + len - 4;
        stored_checksum = GET_32BIT(ucp);
@@ -583,7 +790,7 @@ restart:
                buffer_uncompress(&incoming_packet, &compression_buffer);
                buffer_clear(&incoming_packet);
                buffer_append(&incoming_packet, buffer_ptr(&compression_buffer),
-                             buffer_len(&compression_buffer));
+                   buffer_len(&compression_buffer));
        }
        /* Get packet type. */
        buffer_get(&incoming_packet, &buf[0], 1);
@@ -591,29 +798,208 @@ restart:
        /* Return length of payload (without type field). */
        *payload_len_ptr = buffer_len(&incoming_packet);
 
-       /* Handle disconnect message. */
-       if ((unsigned char) buf[0] == SSH_MSG_DISCONNECT) {
-               msg = packet_get_string(NULL);
-               log("Received disconnect: %.900s", msg);
-               xfree(msg);
-               fatal_cleanup();
-       }       
-
-       /* Ignore ignore messages. */
-       if ((unsigned char) buf[0] == SSH_MSG_IGNORE)
-               goto restart;
-
-       /* Send debug messages as debugging output. */
-       if ((unsigned char) buf[0] == SSH_MSG_DEBUG) {
-               msg = packet_get_string(NULL);
-               debug("Remote: %.900s", msg);
-               xfree(msg);
-               goto restart;
-       }
        /* Return type. */
        return (unsigned char) buf[0];
 }
 
+int
+packet_read_poll2(int *payload_len_ptr)
+{
+       unsigned int padlen, need;
+       unsigned char buf[8], *macbuf;
+       unsigned char *ucp;
+       char *cp;
+       static unsigned int packet_length = 0;
+       static unsigned int seqnr = 0;
+       int type;
+       int maclen, block_size;
+       Enc *enc   = NULL;
+       Mac *mac   = NULL;
+       Comp *comp = NULL;
+
+       if (kex != NULL) {
+               enc  = &kex->enc[MODE_IN];
+               mac  = &kex->mac[MODE_IN];
+               comp = &kex->comp[MODE_IN];
+       }
+       maclen = mac && mac->enabled ? mac->mac_len : 0;
+       block_size = enc ? enc->block_size : 8;
+
+       if (packet_length == 0) {
+               /*
+                * check if input size is less than the cipher block size,
+                * decrypt first block and extract length of incoming packet
+                */
+               if (buffer_len(&input) < block_size)
+                       return SSH_MSG_NONE;
+               buffer_clear(&incoming_packet);
+               buffer_append_space(&incoming_packet, &cp, block_size);
+               packet_decrypt(&receive_context, cp, buffer_ptr(&input),
+                   block_size);
+               ucp = (unsigned char *) buffer_ptr(&incoming_packet);
+               packet_length = GET_32BIT(ucp);
+               if (packet_length < 1 + 4 || packet_length > 256 * 1024) {
+                       buffer_dump(&incoming_packet);
+                       packet_disconnect("Bad packet length %d.", packet_length);
+               }
+               DBG(debug("input: packet len %d", packet_length+4));
+               buffer_consume(&input, block_size);
+       }
+       /* we have a partial packet of block_size bytes */
+       need = 4 + packet_length - block_size;
+       DBG(debug("partial packet %d, need %d, maclen %d", block_size,
+           need, maclen));
+       if (need % block_size != 0)
+               fatal("padding error: need %d block %d mod %d",
+                   need, block_size, need % block_size);
+       /*
+        * check if the entire packet has been received and
+        * decrypt into incoming_packet
+        */
+       if (buffer_len(&input) < need + maclen)
+               return SSH_MSG_NONE;
+#ifdef PACKET_DEBUG
+       fprintf(stderr, "read_poll enc/full: ");
+       buffer_dump(&input);
+#endif
+       buffer_append_space(&incoming_packet, &cp, need);
+       packet_decrypt(&receive_context, cp, buffer_ptr(&input), need);
+       buffer_consume(&input, need);
+       /*
+        * compute MAC over seqnr and packet,
+        * increment sequence number for incoming packet
+        */
+        if (mac && mac->enabled) {
+               macbuf = hmac( mac->md, seqnr,
+                   (unsigned char *) buffer_ptr(&incoming_packet),
+                   buffer_len(&incoming_packet),
+                   mac->key, mac->key_len
+               );
+               if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0)
+                       packet_disconnect("Corrupted HMAC on input.");
+               DBG(debug("HMAC #%d ok", seqnr));
+               buffer_consume(&input, mac->mac_len);
+       }
+        if (++seqnr == 0)
+                log("incoming seqnr wraps around");
+
+       /* get padlen */
+       cp = buffer_ptr(&incoming_packet) + 4;
+       padlen = *cp & 0xff;
+       DBG(debug("input: padlen %d", padlen));
+       if (padlen < 4)
+               packet_disconnect("Corrupted padlen %d on input.", padlen);
+
+       /* skip packet size + padlen, discard padding */
+       buffer_consume(&incoming_packet, 4 + 1);
+       buffer_consume_end(&incoming_packet, padlen);
+
+       DBG(debug("input: len before de-compress %d", buffer_len(&incoming_packet)));
+       if (comp && comp->enabled) {
+               buffer_clear(&compression_buffer);
+               buffer_uncompress(&incoming_packet, &compression_buffer);
+               buffer_clear(&incoming_packet);
+               buffer_append(&incoming_packet, buffer_ptr(&compression_buffer),
+                   buffer_len(&compression_buffer));
+               DBG(debug("input: len after de-compress %d", buffer_len(&incoming_packet)));
+       }
+       /*
+        * get packet type, implies consume.
+        * return length of payload (without type field)
+        */
+       buffer_get(&incoming_packet, (char *)&buf[0], 1);
+       *payload_len_ptr = buffer_len(&incoming_packet);
+
+       /* reset for next packet */
+       packet_length = 0;
+
+       /* extract packet type */
+       type = (unsigned char)buf[0];
+
+       if (type == SSH2_MSG_NEWKEYS) {
+               if (kex==NULL || mac==NULL || enc==NULL || comp==NULL)
+                       fatal("packet_read_poll2: no KEX");
+               if (mac->md != NULL)
+                       mac->enabled = 1;
+               debug("cipher_set_key_iv receive_context");
+               cipher_set_key_iv(&receive_context, enc->type,
+                   enc->key, enc->key_len,
+                   enc->iv, enc->iv_len);
+               clear_enc_keys(enc, kex->we_need);
+               if (comp->type != 0 && comp->enabled == 0) {
+                       comp->enabled = 1;
+                       if (! packet_compression)
+                               packet_start_compression(6);
+               }
+       }
+
+#ifdef PACKET_DEBUG
+       fprintf(stderr, "read/plain[%d]:\r\n",type);
+       buffer_dump(&incoming_packet);
+#endif
+       return (unsigned char)type;
+}
+
+int
+packet_read_poll(int *payload_len_ptr)
+{
+       char *msg;
+       for (;;) {
+               int type = use_ssh2_packet_format ?
+                   packet_read_poll2(payload_len_ptr):
+                   packet_read_poll1(payload_len_ptr);
+
+               if(compat20) {
+                       int reason;
+                       if (type != 0)
+                               DBG(debug("received packet type %d", type));
+                       switch(type) {
+                       case SSH2_MSG_IGNORE:
+                               break;
+                       case SSH2_MSG_DEBUG:
+                               packet_get_char();
+                               msg = packet_get_string(NULL);
+                               debug("Remote: %.900s", msg);
+                               xfree(msg);
+                               msg = packet_get_string(NULL);
+                               xfree(msg);
+                               break;
+                       case SSH2_MSG_DISCONNECT:
+                               reason = packet_get_int();
+                               msg = packet_get_string(NULL);
+                               log("Received disconnect: %d: %.900s", reason, msg);
+                               xfree(msg);
+                               fatal_cleanup();
+                               break;
+                       default:
+                               return type;
+                               break;
+                       }       
+               } else {
+                       switch(type) {
+                       case SSH_MSG_IGNORE:
+                               break;
+                       case SSH_MSG_DEBUG:
+                               msg = packet_get_string(NULL);
+                               debug("Remote: %.900s", msg);
+                               xfree(msg);
+                               break;
+                       case SSH_MSG_DISCONNECT:
+                               msg = packet_get_string(NULL);
+                               log("Received disconnect: %.900s", msg);
+                               fatal_cleanup();
+                               xfree(msg);
+                               break;
+                       default:
+                               if (type != 0)
+                                       DBG(debug("received packet type %d", type));
+                               return type;
+                               break;
+                       }       
+               }
+       }
+}
+
 /*
  * Buffers the given amount of input characters.  This is intended to be used
  * together with packet_read_poll.
@@ -733,8 +1119,15 @@ packet_disconnect(const char *fmt,...)
        va_end(args);
 
        /* Send the disconnect message to the other side, and wait for it to get sent. */
-       packet_start(SSH_MSG_DISCONNECT);
-       packet_put_string(buf, strlen(buf));
+       if (compat20) {
+               packet_start(SSH2_MSG_DISCONNECT);
+               packet_put_int(SSH2_DISCONNECT_PROTOCOL_ERROR);
+               packet_put_cstring(buf);
+               packet_put_cstring("");
+       } else {
+               packet_start(SSH_MSG_DISCONNECT);
+               packet_put_string(buf, strlen(buf));
+       }
        packet_send();
        packet_write_wait();
 
@@ -865,7 +1258,8 @@ packet_set_maxsize(int s)
 {
        static int called = 0;
        if (called) {
-               log("packet_set_maxsize: called twice: old %d new %d", max_packet_size, s);
+               log("packet_set_maxsize: called twice: old %d new %d",
+                   max_packet_size, s);
                return -1;
        }
        if (s < 4 * 1024 || s > 1024 * 1024) {
index 4539091..6d936a7 100644 (file)
@@ -13,7 +13,7 @@
  * 
  */
 
-/* RCSID("$Id: packet.h,v 1.11 2000/04/03 07:07:15 markus Exp $"); */
+/* RCSID("$Id: packet.h,v 1.12 2000/04/03 20:12:55 markus Exp $"); */
 
 #ifndef PACKET_H
 #define PACKET_H
@@ -132,7 +132,7 @@ unsigned int packet_get_int(void);
  * must have been initialized before this call.
  */
 void    packet_get_bignum(BIGNUM * value, int *length_ptr);
-void   packet_get_bignum2(BIGNUM * value, int *length_ptr);
+void    packet_get_bignum2(BIGNUM * value, int *length_ptr);
 char   *packet_get_raw(int *length_ptr);
 
 /*
@@ -200,4 +200,7 @@ do { \
 int    packet_connection_is_on_socket(void);
 int    packet_connection_is_ipv4(void);
 
+/* enable SSH2 packet format */
+void   packet_set_ssh2_format(void);
+
 #endif                         /* PACKET_H */