# include "phantasmal/ssh.h" # include "phantasmal/ssh_asn1.h" # define DEBUG SSH_DEBUG inherit glue SSH_GLUE; private inherit ASN1_UTILS; private inherit SSH_UTILS; private int receive_packet(string str); static int userauth(string str); /* supplied by connection layer */ /* ========================================================================= * * Section 1: packet layer * * ========================================================================= */ private string obuffer; /* send buffer */ private int cts; /* clear to send? */ private string ibuffer; /* receive buffer */ private string header; /* first 8 characters of packet */ private int length; /* length of packet to receive */ private int recv_seqno, send_seqno; /* send and receive sequence numbers */ private string dkey1, dkey2, dkey3; /* decryption keys */ private string ekey1, ekey2, ekey3; /* encryption keys */ private string dstate, estate; /* en/decryption state */ private string client_mac; /* client MAC key */ private string server_mac; /* server MAC key */ /* * NAME: make_packet() * DESCRIPTION: build a packet (without MAC) */ private string make_packet(string str) { int length, padding; /* minimum padding is 4 bytes, round up to multiple of 8 bytes */ length = strlen(str); padding = 12 - (length + 1) % 8; length += padding + 1; str = "\0\0..." + str + random_string(padding); str[2] = length >> 8; str[3] = length; str[4] = padding; return str; } /* * NAME: encrypt_packet() * DESCRIPTION: encrypt a packet */ private string encrypt_packet(string str) { int i, n, length; string *encrypted; length = strlen(str); encrypted = allocate(length / 8); for (i = n = 0; i < length; i += 8, n++) { estate = encrypt("DES", ekey3, decrypt("DES", ekey2, encrypt("DES", ekey1, asn_xor(str[i .. i + 7], estate)))), encrypted[n] = estate; } return implode(encrypted, ""); } /* * NAME: decrypt_string() * DESCRIPTION: decrypt a string */ private string decrypt_string(string str) { int i, n, length; string chunk, *decrypted; length = strlen(str); decrypted = allocate(length / 8); for (i = n = 0; i < length; i += 8, n++) { chunk = str[i .. i + 7]; decrypted[n] = asn_xor(decrypt("DES", dkey1, encrypt("DES", dkey2, decrypt("DES", dkey3, chunk))), dstate); dstate = chunk; } return implode(decrypted, ""); } /* * NAME: hmac() * DESCRIPTION: compute HMAC */ private string hmac(string key, string str) { string ipad, opad; ipad = "\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36" + "\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36" + "\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36" + "\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36\x36"; opad = "\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c" + "\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c" + "\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c" + "\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c"; return hash_sha1(asn_xor(key, opad), hash_sha1(asn_xor(key, ipad), str)); } /* * NAME: __send_packet() * DESCRIPTION: send a packet to the other side */ private atomic int __send_packet(string str) { DEBUG(2, "__send_packet: " + dump_ssh_msg_type(str[0])); str = make_packet(str); if (server_mac) { str = encrypt_packet(str) + hmac(server_mac, make_int(send_seqno) + str); } send_seqno++; if (!cts) { if (obuffer) { obuffer += str; } else { obuffer = str; } return FALSE; } return cts = ::message(str); } /* * NAME: message_done() * DESCRIPTION: ready for another message */ int message_done() { if (SSH_GLUE_CALL) { string str; if (obuffer) { str = obuffer; obuffer = nil; cts = ::message(str); } else { cts = TRUE; return ::message_done(); } } return MODE_NOCHANGE; } /* * NAME: process_message() * DESCRIPTION: process a message */ static int process_message(string str) { if (client_mac) { string mac; /* decrypt & verify MAC */ mac = str[length ..]; str = header + decrypt_string(str[.. length - 1]); if (mac != hmac(client_mac, make_int(recv_seqno) + str)) { DEBUG(0, "bad MAC"); __send_packet(make_mesg(SSH_MSG_DISCONNECT) + make_int(SSH_DISCONNECT_MAC_ERROR) + make_string("bad MAC") + make_string("en")); return MODE_DISCONNECT; } } else { /* unencrypted */ str = header + str; } recv_seqno++; str = str[5 .. length + 7 - str[4]]; length = -1; return receive_packet(str); } /* * NAME: receive_message() * DESCRIPTION: receive a message */ int receive_message(string str) { int mode; if (SSH_GLUE_CALL || previous_program() == SSH_TRANSPORT) { if (!ibuffer) { ibuffer = str; return MODE_NOCHANGE; } ibuffer += str; while (this_object()) { if (length < 0) { /* * new packet */ if (strlen(ibuffer) < 8) { break; } header = ibuffer[.. 7]; ibuffer = ibuffer[8 ..]; if (client_mac) { header = decrypt_string(header); } length = get_int(header, 0); if (length <= 0 || length > 35000 - 4 || (length & 7) != 4) { DEBUG(0, "bad packet length " + length); catch { __send_packet(make_mesg(SSH_MSG_DISCONNECT) + make_int(SSH_DISCONNECT_PROTOCOL_ERROR) + make_string("bad packet length") + make_string("en")); } return MODE_DISCONNECT; } length -= 4; if (client_mac) { length += 20; } } if (strlen(ibuffer) < length) { break; } /* * full packet received */ str = ibuffer[.. length - 1]; ibuffer = ibuffer[length ..]; if (client_mac) { length -= 20; } SSH_GLUE_RLIMITS(mode, process_message, str); if (mode == MODE_DISCONNECT) { return MODE_DISCONNECT; } if (mode >= MODE_UNBLOCK) { set_mode(mode); } } } return MODE_RAW; } /* * NAME: recv_seqno() * DESCRIPTION: return the sequence number of the last message */ static int recv_seqno() { return recv_seqno - 1; } /* * NAME: create_packet() * DESCRIPTION: initialize packet layer functions */ private void create_packet() { cts = TRUE; length = -1; } /* ========================================================================= * * Section 2: transport layer * * ========================================================================= */ # define TRANSPORT_KEXINIT 0 # define TRANSPORT_SKIP 1 # define TRANSPORT_KEXDH 2 # define TRANSPORT_NEWKEYS 3 # define TRANSPORT_TRANSPORT 4 private int state; /* transport state */ private string *packet_buffer; /* output packet buffer while rekeying */ private string client_version; /* client protocol version string */ private string client_kexinit; /* client KEXINIT string */ private string server_kexinit; /* server KEXINIT string */ private string p, q; /* prime and group order */ private string y; /* intermediate result */ private string f, e; /* shared secrets */ private string K, H; /* crypto stuff */ private string session_id; /* ID for this entire session */ /* * NAME: send_packet() * DESCRIPTION: send a packet, unless rekeying */ private int send_packet(string str) { if (packet_buffer) { packet_buffer += ({ str }); return FALSE; } else { return __send_packet(str); } } /* * NAME: flush_packet_buffer() * DESCRIPTION: flush output packet buffer after rekeying */ private void flush_packet_buffer() { int i, sz; for (i = 0, sz = sizeof(packet_buffer); i < sz; i++) { __send_packet(packet_buffer[i]); } packet_buffer = nil; } /* * NAME: ssh_dss_sign() * DESCRIPTION: sign m with the host key */ private string ssh_dss_sign(string m, string host_key) { string p, q, g, x; string k, r, s; mixed *asn1; asn1 = parse_asn1(host_key, 0); if (!asn1) { error("Invalid Host Key"); } /* parse_asn1() returns ({ parsed-data, offset }) */ asn1 = asn1[0]; /* Assume it's a sequence of 6 integers. */ p = asn1[ASN_CONTENTS][1][ASN_CONTENTS]; q = asn1[ASN_CONTENTS][2][ASN_CONTENTS]; g = asn1[ASN_CONTENTS][3][ASN_CONTENTS]; x = asn1[ASN_CONTENTS][5][ASN_CONTENTS]; /* k = random 0 < k < q */ do { k = asn_mod("\1" + better_random_string(strlen(q)), q); } while (strlen(k) < strlen(q) - 1); /* r = (g ^ k mod p) mod q */ r = asn_mod(asn_pow(g, k, p), q); /* s = (k ^ -1 * (H(m) + x * r)) mod q */ s = asn_mult(asn_pow(k, asn_sub(q, "\2", q), q), asn_add("\0" + hash_sha1(m), asn_mult(x, r, q), q), q); r = ("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + r)[strlen(r) ..]; s = ("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + s)[strlen(s) ..]; return make_string("ssh-dss") + make_string(r + s); } /* * NAME: shift_key() * DESCRIPTION: shift out the lowest bit of all characters in a key string */ private string shift_key(string key) { int i, len; /* * Believe it or not, but the openssl crypto suite has the parity for * DES setkey in the <lowest> bit. */ for (i = 0, len = strlen(key); i < len; i++) { key[i] >>= 1; } return key; } /* * NAME: set_keys() * DESCRIPTION: create keys as negotiated */ private void set_keys() { string str, client_key, server_key; str = make_string(K) + H; dstate = hash_sha1(str, "A", session_id)[.. 7]; estate = hash_sha1(str, "B", session_id)[.. 7]; client_key = hash_sha1(str, "C", session_id); client_key += hash_sha1(str, client_key); server_key = hash_sha1(str, "D", session_id); server_key += hash_sha1(str, server_key); client_mac = hash_sha1(str, "E", session_id) + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; server_mac = hash_sha1(str, "F", session_id) + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; dkey1 = decrypt("DES key", shift_key(client_key[.. 7])); dkey2 = encrypt("DES key", shift_key(client_key[8 .. 15])); dkey3 = decrypt("DES key", shift_key(client_key[16 .. 23])); ekey1 = encrypt("DES key", shift_key(server_key[.. 7])); ekey2 = decrypt("DES key", shift_key(server_key[8 .. 15])); ekey3 = encrypt("DES key", shift_key(server_key[16 .. 23])); } /* * NAME: query_session_id() * DESCRIPTION: return the session ID for authentication purposes */ static string query_session_id() { return session_id; } /* * NAME: start_transport() * DESCRIPTION: start up the transport layer */ static void start_transport(string version) { DEBUG(1, "client version is " + version); state = TRANSPORT_KEXINIT; client_version = version; server_kexinit = make_mesg(SSH_MSG_KEXINIT) + better_random_string(16) + make_string("diffie-hellman-group1-sha1") + make_string("ssh-dss") + make_string("3des-cbc") + make_string("3des-cbc") + make_string("hmac-sha1") + make_string("hmac-sha1") + make_string("none") + make_string("none") + make_string("") + make_string("") + "\0" + "\0\0\0\0"; __send_packet(server_kexinit); receive_message(""); /* process message in buffer, if any */ } /* * NAME: receive_packet() * DESCRIPTION: receive a packet from connection */ private int receive_packet(string str) { int offset; DEBUG(2, "receive_packet: " + dump_ssh_msg_type(str[0])); if (state == TRANSPORT_SKIP) { state = TRANSPORT_KEXDH; return MODE_NOCHANGE; } switch (str[0]) { case SSH_MSG_DISCONNECT: return MODE_DISCONNECT; case SSH_MSG_IGNORE: break; case SSH_MSG_UNIMPLEMENTED: DEBUG(1, "received SSH_MSG_UNIMPLEMENTED for packet " + get_int(str, 1)); break; case SSH_MSG_DEBUG: DEBUG(0, "received SSH_MSG_DEBUG: " + implode(explode(get_string(str, 2), "\r\n"), "\n")); break; case SSH_MSG_KEXINIT: if (state == TRANSPORT_TRANSPORT) { /* * client wants to negotiate new keys */ server_kexinit = make_mesg(SSH_MSG_KEXINIT) + better_random_string(16) + make_string("diffie-hellman-group1-sha1") + make_string("ssh-dss") + make_string("3des-cbc") + make_string("3des-cbc") + make_string("hmac-sha1") + make_string("hmac-sha1") + make_string("none") + make_string("none") + make_string("") + make_string("") + "\0" + "\0\0\0\0"; __send_packet(server_kexinit); packet_buffer = ({ }); } else if (state != TRANSPORT_KEXINIT) { DEBUG(1, "Unexpected SSH_MSG_KEXINIT received"); break; } client_kexinit = str; /* generate random y (0 < y < q) */ do { y = asn_mod("\1" + better_random_string(strlen(q)), q); } while (strlen(y) < strlen(q) - 1); /* f = g ^ y mod p */ f = asn_pow("\2", y, p); offset = 17; /* type + random */ offset += 4 + get_int(str, offset); /* kex */ offset += 4 + get_int(str, offset); /* host key */ offset += 4 + get_int(str, offset); /* decrypt */ offset += 4 + get_int(str, offset); /* encrypt */ offset += 4 + get_int(str, offset); /* demac */ offset += 4 + get_int(str, offset); /* mac */ offset += 4 + get_int(str, offset); /* decompress */ offset += 4 + get_int(str, offset); /* compress */ offset += 4 + get_int(str, offset); /* de-lang */ offset += 4 + get_int(str, offset); /* lang */ if (str[offset]) { state = TRANSPORT_SKIP; } else { state = TRANSPORT_KEXDH; } break; case SSH_MSG_KEXDH_INIT: if (state == TRANSPORT_KEXDH) { e = get_string(str, 1); str = SSHD->query_pub_host_key(); /* K = e ^ y mod p */ K = asn_pow(e, y, p); /* H = shared secret */ H = hash_sha1(make_string(client_version), make_string(SSHD->query_version()), make_string(client_kexinit), make_string(server_kexinit), make_string(str), make_mpint(e), make_mpint(f), make_mpint(K)); if (!session_id) { session_id = H; } str = make_mesg(SSH_MSG_KEXDH_REPLY) + make_string(str) + make_mpint(f) + make_string(ssh_dss_sign(H, SSHD->query_host_key())); __send_packet(str); state = TRANSPORT_NEWKEYS; } else { DEBUG(1, "Unexpected SSH_MSG_KEXDH_INIT received"); } break; case SSH_MSG_NEWKEYS: if (state == TRANSPORT_NEWKEYS) { __send_packet(make_mesg(SSH_MSG_NEWKEYS)); set_keys(); state = TRANSPORT_TRANSPORT; if (packet_buffer) { flush_packet_buffer(); } } else { DEBUG(1, "Unexpected SSH_MSG_NEWKEYS received"); } break; default: if (state == TRANSPORT_TRANSPORT) { return userauth(str); } else { DEBUG(1, "Unexpected packet type " + str[0] + " received"); } break; } return MODE_NOCHANGE; } /* * NAME: message() * DESCRIPTION: send a message (well, a packet) to the connection */ static int message(string str) { return send_packet(str); } /* * NAME: create_transport() * DESCRIPTION: initialize transport layer */ static void create_transport() { glue::create_glue(); create_packet(); /* p = 2^1024 - 2^960 - 1 + 2^64 * floor( 2^894 Pi + 129093 ) */ p = "\0" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F\xDA\xA2\x21\x68\xC2\x34" + "\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1\x29\x02\x4E\x08\x8A\x67\xCC\x74" + "\x02\x0B\xBE\xA6\x3B\x13\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD" + "\xEF\x95\x19\xB3\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37" + "\x4F\xE1\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6" + "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06\xB7\xED" + "\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11\x7C\x4B\x1F\xE6" + "\x49\x28\x66\x51\xEC\xE6\x53\x81\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; /* q = (p - 1) / 2 */ q = asn_rshift(p, 1); }