From: Steinar H. Gunderson Date: Thu, 3 Feb 2005 22:46:49 +0000 (+0000) Subject: Even more imports. Link to OpenSSL. X-Git-Url: https://git.sesse.net/?p=rdpsrv;a=commitdiff_plain;h=b3ae47054997c61d01087d17a3ab001b0a54324b Even more imports. Link to OpenSSL. --- diff --git a/Makefile b/Makefile index fd811cf..f5da472 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ CC=gcc CFLAGS=-g -Wall +LIBS=-lssl all: rdpsrv -OBJS=rdpsrv.o iso.o tcp.o util.o +OBJS=rdpsrv.o iso.o tcp.o util.o mcs.o secure.o channels.o rdpsrv: $(OBJS) - $(CC) -o rdpsrv $(OBJS) + $(CC) -o rdpsrv $(OBJS) $(LIBS) clean: $(RM) $(OBJS) rdpsrv diff --git a/channels.c b/channels.c new file mode 100644 index 0000000..6fc0cde --- /dev/null +++ b/channels.c @@ -0,0 +1,178 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - Virtual channels + Copyright (C) Erik Forsberg 2003 + Copyright (C) Matthew Chapman 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +#define MAX_CHANNELS 4 +#define CHANNEL_CHUNK_LENGTH 1600 +#define CHANNEL_FLAG_FIRST 0x01 +#define CHANNEL_FLAG_LAST 0x02 +#define CHANNEL_FLAG_SHOW_PROTOCOL 0x10 + +extern BOOL g_use_rdp5; +extern BOOL g_encryption; + +VCHANNEL g_channels[MAX_CHANNELS]; +unsigned int g_num_channels; + +/* FIXME: We should use the information in TAG_SRV_CHANNELS to map RDP5 + channels to MCS channels. + + The format of TAG_SRV_CHANNELS seems to be + + global_channel_no (uint16le) + number_of_other_channels (uint16le) + ..followed by uint16les for the other channels. +*/ + +VCHANNEL * +channel_register(char *name, uint32 flags, void (*callback) (STREAM)) +{ + VCHANNEL *channel; + + if (g_num_channels >= MAX_CHANNELS) + { + error("Channel table full, increase MAX_CHANNELS\n"); + return NULL; + } + + channel = &g_channels[g_num_channels]; + channel->mcs_id = MCS_GLOBAL_CHANNEL + 1 + g_num_channels; + strncpy(channel->name, name, 8); + channel->flags = flags; + channel->process = callback; + g_num_channels++; + return channel; +} + +STREAM +channel_init(VCHANNEL * channel, uint32 length) +{ + STREAM s; + + s = sec_init(/*g_encryption ? SEC_ENCRYPT :*/ 0, length + 8); + s_push_layer(s, channel_hdr, 8); + return s; +} + +void +channel_send(STREAM s, VCHANNEL * channel) +{ + uint32 length, flags; + uint32 thislength, remaining; + uint8 *data; + + /* first fragment sent in-place */ + s_pop_layer(s, channel_hdr); + length = s->end - s->p - 8; + + DEBUG_CLIPBOARD(("channel_send, length = %d\n", length)); + + thislength = MIN(length, CHANNEL_CHUNK_LENGTH); +/* Note: In the original clipboard implementation, this number was + 1592, not 1600. However, I don't remember the reason and 1600 seems + to work so.. This applies only to *this* length, not the length of + continuation or ending packets. */ + remaining = length - thislength; + flags = (remaining == 0) ? CHANNEL_FLAG_FIRST | CHANNEL_FLAG_LAST : CHANNEL_FLAG_FIRST; + if (channel->flags & CHANNEL_OPTION_SHOW_PROTOCOL) + flags |= CHANNEL_FLAG_SHOW_PROTOCOL; + + out_uint32_le(s, length); + out_uint32_le(s, flags); + data = s->end = s->p + thislength; + DEBUG_CLIPBOARD(("Sending %d bytes with FLAG_FIRST\n", thislength)); + sec_send_to_channel(s, /*g_encryption ? SEC_ENCRYPT :*/ 0, channel->mcs_id); + + /* subsequent segments copied (otherwise would have to generate headers backwards) */ + while (remaining > 0) + { + thislength = MIN(remaining, CHANNEL_CHUNK_LENGTH); + remaining -= thislength; + flags = (remaining == 0) ? CHANNEL_FLAG_LAST : 0; + if (channel->flags & CHANNEL_OPTION_SHOW_PROTOCOL) + flags |= CHANNEL_FLAG_SHOW_PROTOCOL; + + DEBUG_CLIPBOARD(("Sending %d bytes with flags %d\n", thislength, flags)); + + s = sec_init(/*g_encryption ? SEC_ENCRYPT : */0, thislength + 8); + out_uint32_le(s, length); + out_uint32_le(s, flags); + out_uint8p(s, data, thislength); + s_mark_end(s); + sec_send_to_channel(s, /*g_encryption ? SEC_ENCRYPT : */0, channel->mcs_id); + + data += thislength; + } +} + +void +channel_process(STREAM s, uint16 mcs_channel) +{ + uint32 length, flags; + uint32 thislength; + VCHANNEL *channel = NULL; + unsigned int i; + STREAM in; + + for (i = 0; i < g_num_channels; i++) + { + channel = &g_channels[i]; + if (channel->mcs_id == mcs_channel) + break; + } + + if (i >= g_num_channels) + return; + + in_uint32_le(s, length); + in_uint32_le(s, flags); + if ((flags & CHANNEL_FLAG_FIRST) && (flags & CHANNEL_FLAG_LAST)) + { + /* single fragment - pass straight up */ + channel->process(s); + } + else + { + /* add fragment to defragmentation buffer */ + in = &channel->in; + if (flags & CHANNEL_FLAG_FIRST) + { + if (length > in->size) + { + in->data = xrealloc(in->data, length); + in->size = length; + } + in->p = in->data; + } + + thislength = MIN(s->end - s->p, in->data + in->size - in->p); + memcpy(in->p, s->p, thislength); + in->p += thislength; + + if (flags & CHANNEL_FLAG_LAST) + { + in->end = in->p; + in->p = in->data; + channel->process(in); + } + } +} diff --git a/mcs.c b/mcs.c new file mode 100644 index 0000000..ac8148b --- /dev/null +++ b/mcs.c @@ -0,0 +1,379 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - Multipoint Communications Service + Copyright (C) Matthew Chapman 1999-2002 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +uint16 g_mcs_userid; +extern VCHANNEL g_channels[]; +extern unsigned int g_num_channels; + +/* Parse an ASN.1 BER header */ +static BOOL +ber_parse_header(STREAM s, int tagval, int *length) +{ + int tag, len; + + if (tagval > 0xff) + { + in_uint16_be(s, tag); + } + else + { + in_uint8(s, tag)} + + if (tag != tagval) + { + error("expected tag %d, got %d\n", tagval, tag); + return False; + } + + in_uint8(s, len); + + if (len & 0x80) + { + len &= ~0x80; + *length = 0; + while (len--) + next_be(s, *length); + } + else + *length = len; + + return s_check(s); +} + +/* Output an ASN.1 BER header */ +static void +ber_out_header(STREAM s, int tagval, int length) +{ + if (tagval > 0xff) + { + out_uint16_be(s, tagval); + } + else + { + out_uint8(s, tagval); + } + + if (length >= 0x80) + { + out_uint8(s, 0x82); + out_uint16_be(s, length); + } + else + out_uint8(s, length); +} + +/* Output an ASN.1 BER integer */ +static void +ber_out_integer(STREAM s, int value) +{ + ber_out_header(s, BER_TAG_INTEGER, 2); + out_uint16_be(s, value); +} + +/* Output a DOMAIN_PARAMS structure (ASN.1 BER) */ +static void +mcs_out_domain_params(STREAM s, int max_channels, int max_users, int max_tokens, int max_pdusize) +{ + ber_out_header(s, MCS_TAG_DOMAIN_PARAMS, 32); + ber_out_integer(s, max_channels); + ber_out_integer(s, max_users); + ber_out_integer(s, max_tokens); + ber_out_integer(s, 1); /* num_priorities */ + ber_out_integer(s, 0); /* min_throughput */ + ber_out_integer(s, 1); /* max_height */ + ber_out_integer(s, max_pdusize); + ber_out_integer(s, 2); /* ver_protocol */ +} + +/* Parse a DOMAIN_PARAMS structure (ASN.1 BER) */ +static BOOL +mcs_parse_domain_params(STREAM s) +{ + int length; + + ber_parse_header(s, MCS_TAG_DOMAIN_PARAMS, &length); + in_uint8s(s, length); + + return s_check(s); +} + +/* Send an MCS_CONNECT_INITIAL message (ASN.1 BER) */ +static void +mcs_send_connect_initial(STREAM mcs_data) +{ + int datalen = mcs_data->end - mcs_data->data; + int length = 9 + 3 * 34 + 4 + datalen; + STREAM s; + + s = iso_init(length + 5); + + ber_out_header(s, MCS_CONNECT_INITIAL, length); + ber_out_header(s, BER_TAG_OCTET_STRING, 1); /* calling domain */ + out_uint8(s, 1); + ber_out_header(s, BER_TAG_OCTET_STRING, 1); /* called domain */ + out_uint8(s, 1); + + ber_out_header(s, BER_TAG_BOOLEAN, 1); + out_uint8(s, 0xff); /* upward flag */ + + mcs_out_domain_params(s, 34, 2, 0, 0xffff); /* target params */ + mcs_out_domain_params(s, 1, 1, 1, 0x420); /* min params */ + mcs_out_domain_params(s, 0xffff, 0xfc17, 0xffff, 0xffff); /* max params */ + + ber_out_header(s, BER_TAG_OCTET_STRING, datalen); + out_uint8p(s, mcs_data->data, datalen); + + s_mark_end(s); + iso_send(s); +} + +/* Expect a MCS_CONNECT_RESPONSE message (ASN.1 BER) */ +static BOOL +mcs_recv_connect_response(STREAM mcs_data) +{ + uint8 result; + int length; + STREAM s; + + s = iso_recv(); + if (s == NULL) + return False; + + ber_parse_header(s, MCS_CONNECT_RESPONSE, &length); + + ber_parse_header(s, BER_TAG_RESULT, &length); + in_uint8(s, result); + if (result != 0) + { + error("MCS connect: %d\n", result); + return False; + } + + ber_parse_header(s, BER_TAG_INTEGER, &length); + in_uint8s(s, length); /* connect id */ + mcs_parse_domain_params(s); + + ber_parse_header(s, BER_TAG_OCTET_STRING, &length); + + sec_process_mcs_data(s); + /* + if (length > mcs_data->size) + { + error("MCS data length %d, expected %d\n", length, + mcs_data->size); + length = mcs_data->size; + } + + in_uint8a(s, mcs_data->data, length); + mcs_data->p = mcs_data->data; + mcs_data->end = mcs_data->data + length; + */ + return s_check_end(s); +} + +/* Send an EDrq message (ASN.1 PER) */ +static void +mcs_send_edrq(void) +{ + STREAM s; + + s = iso_init(5); + + out_uint8(s, (MCS_EDRQ << 2)); + out_uint16_be(s, 1); /* height */ + out_uint16_be(s, 1); /* interval */ + + s_mark_end(s); + iso_send(s); +} + +/* Send an AUrq message (ASN.1 PER) */ +static void +mcs_send_aurq(void) +{ + STREAM s; + + s = iso_init(1); + + out_uint8(s, (MCS_AURQ << 2)); + + s_mark_end(s); + iso_send(s); +} + +/* Expect a AUcf message (ASN.1 PER) */ +static BOOL +mcs_recv_aucf(uint16 * mcs_userid) +{ + uint8 opcode, result; + STREAM s; + + s = iso_recv(); + if (s == NULL) + return False; + + in_uint8(s, opcode); + if ((opcode >> 2) != MCS_AUCF) + { + error("expected AUcf, got %d\n", opcode); + return False; + } + + in_uint8(s, result); + if (result != 0) + { + error("AUrq: %d\n", result); + return False; + } + + if (opcode & 2) + in_uint16_be(s, *mcs_userid); + + return s_check_end(s); +} + +/* Send a CJrq message (ASN.1 PER) */ +static void +mcs_send_cjrq(uint16 chanid) +{ + STREAM s; + + DEBUG_RDP5(("Sending CJRQ for channel #%d\n", chanid)); + + s = iso_init(5); + + out_uint8(s, (MCS_CJRQ << 2)); + out_uint16_be(s, g_mcs_userid); + out_uint16_be(s, chanid); + + s_mark_end(s); + iso_send(s); +} + +/* Expect a CJcf message (ASN.1 PER) */ +static BOOL +mcs_recv_cjcf(void) +{ + uint8 opcode, result; + STREAM s; + + s = iso_recv(); + if (s == NULL) + return False; + + in_uint8(s, opcode); + if ((opcode >> 2) != MCS_CJCF) + { + error("expected CJcf, got %d\n", opcode); + return False; + } + + in_uint8(s, result); + if (result != 0) + { + error("CJrq: %d\n", result); + return False; + } + + in_uint8s(s, 4); /* mcs_userid, req_chanid */ + if (opcode & 2) + in_uint8s(s, 2); /* join_chanid */ + + return s_check_end(s); +} + +/* Initialise an MCS transport data packet */ +STREAM +mcs_init(int length) +{ + STREAM s; + + s = iso_init(length + 8); + s_push_layer(s, mcs_hdr, 8); + + return s; +} + +/* Send an MCS transport data packet to a specific channel */ +void +mcs_send_to_channel(STREAM s, uint16 channel) +{ + uint16 length; + + s_pop_layer(s, mcs_hdr); + length = s->end - s->p - 8; + length |= 0x8000; + + out_uint8(s, (MCS_SDRQ << 2)); + out_uint16_be(s, g_mcs_userid); + out_uint16_be(s, channel); + out_uint8(s, 0x70); /* flags */ + out_uint16_be(s, length); + + iso_send(s); +} + +/* Send an MCS transport data packet to the global channel */ +void +mcs_send(STREAM s) +{ + mcs_send_to_channel(s, MCS_GLOBAL_CHANNEL); +} + +/* Receive an MCS transport data packet */ +STREAM +mcs_recv(uint16 * channel) +{ + uint8 opcode, appid, length; + STREAM s; + + s = iso_recv(); + if (s == NULL) + return NULL; + + in_uint8(s, opcode); + appid = opcode >> 2; + if (appid != MCS_SDIN) + { + if (appid != MCS_DPUM) + { + error("expected data, got %d\n", opcode); + } + return NULL; + } + + in_uint8s(s, 2); /* userid */ + in_uint16_be(s, *channel); + in_uint8s(s, 1); /* flags */ + in_uint8(s, length); + if (length & 0x80) + in_uint8s(s, 1); /* second byte of length */ + + return s; +} + +/* Disconnect from the MCS layer */ +void +mcs_disconnect(void) +{ + iso_disconnect(); +} diff --git a/proto.h b/proto.h index b08a977..b371330 100644 --- a/proto.h +++ b/proto.h @@ -48,7 +48,6 @@ void process_orders(STREAM s, uint16 num_orders); void reset_order_state(void); /* printer.c */ /* rdesktop.c */ -int main(int argc, char *argv[]); void generate_random(uint8 * random); void *xmalloc(int size); void *xrealloc(void *oldmem, int size); diff --git a/rdpsrv.c b/rdpsrv.c index 5b1603d..8355426 100644 --- a/rdpsrv.c +++ b/rdpsrv.c @@ -6,6 +6,8 @@ #include #include +#include "rdesktop.h" + const int tcp_port_rdp = 3389; int create_server_socket(); int serve_client(int sock); @@ -69,8 +71,9 @@ int serve_client(int sock) { for ( ;; ) { unsigned char buf[4096]; + short channel; /* receive ISO packets */ - + mcs_recv(&channel); } } diff --git a/secure.c b/secure.c new file mode 100644 index 0000000..fd96936 --- /dev/null +++ b/secure.c @@ -0,0 +1,758 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - RDP encryption and licensing + Copyright (C) Matthew Chapman 1999-2002 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "rdesktop.h" + +#include +#include +#include +#include +#include + +extern char hostname[16]; +extern int g_width; +extern int g_height; +extern int keylayout; +extern BOOL g_encryption; +extern BOOL g_licence_issued; +extern BOOL g_use_rdp5; +extern BOOL g_console_session; +extern int g_server_bpp; +extern uint16 mcs_userid; +extern VCHANNEL g_channels[]; +extern unsigned int g_num_channels; + +static int rc4_key_len; +static RC4_KEY rc4_decrypt_key; +static RC4_KEY rc4_encrypt_key; +static RSA *server_public_key; + +static uint8 sec_sign_key[16]; +static uint8 sec_decrypt_key[16]; +static uint8 sec_encrypt_key[16]; +static uint8 sec_decrypt_update_key[16]; +static uint8 sec_encrypt_update_key[16]; +static uint8 sec_crypted_random[SEC_MODULUS_SIZE]; + +uint16 g_server_rdp_version = 0; + +/* + * General purpose 48-byte transformation, using two 32-byte salts (generally, + * a client and server salt) and a global salt value used for padding. + * Both SHA1 and MD5 algorithms are used. + */ +void +sec_hash_48(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2, uint8 salt) +{ + uint8 shasig[20]; + uint8 pad[4]; + SHA_CTX sha; + MD5_CTX md5; + int i; + + for (i = 0; i < 3; i++) + { + memset(pad, salt + i, i + 1); + + SHA1_Init(&sha); + SHA1_Update(&sha, pad, i + 1); + SHA1_Update(&sha, in, 48); + SHA1_Update(&sha, salt1, 32); + SHA1_Update(&sha, salt2, 32); + SHA1_Final(shasig, &sha); + + MD5_Init(&md5); + MD5_Update(&md5, in, 48); + MD5_Update(&md5, shasig, 20); + MD5_Final(&out[i * 16], &md5); + } +} + +/* + * Weaker 16-byte transformation, also using two 32-byte salts, but + * only using a single round of MD5. + */ +void +sec_hash_16(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2) +{ + MD5_CTX md5; + + MD5_Init(&md5); + MD5_Update(&md5, in, 16); + MD5_Update(&md5, salt1, 32); + MD5_Update(&md5, salt2, 32); + MD5_Final(out, &md5); +} + +/* Reduce key entropy from 64 to 40 bits */ +static void +sec_make_40bit(uint8 * key) +{ + key[0] = 0xd1; + key[1] = 0x26; + key[2] = 0x9e; +} + +/* Generate a session key and RC4 keys, given client and server randoms */ +static void +sec_generate_keys(uint8 * client_key, uint8 * server_key, int rc4_key_size) +{ + uint8 session_key[48]; + uint8 temp_hash[48]; + uint8 input[48]; + + /* Construct input data to hash */ + memcpy(input, client_key, 24); + memcpy(input + 24, server_key, 24); + + /* Generate session key - two rounds of sec_hash_48 */ + sec_hash_48(temp_hash, input, client_key, server_key, 65); + sec_hash_48(session_key, temp_hash, client_key, server_key, 88); + + /* Store first 16 bytes of session key, for generating signatures */ + memcpy(sec_sign_key, session_key, 16); + + /* Generate RC4 keys */ + sec_hash_16(sec_decrypt_key, &session_key[16], client_key, server_key); + sec_hash_16(sec_encrypt_key, &session_key[32], client_key, server_key); + + if (rc4_key_size == 1) + { + DEBUG(("40-bit encryption enabled\n")); + sec_make_40bit(sec_sign_key); + sec_make_40bit(sec_decrypt_key); + sec_make_40bit(sec_encrypt_key); + rc4_key_len = 8; + } + else + { + DEBUG(("rc_4_key_size == %d, 128-bit encryption enabled\n", rc4_key_size)); + rc4_key_len = 16; + } + + /* Save initial RC4 keys as update keys */ + memcpy(sec_decrypt_update_key, sec_decrypt_key, 16); + memcpy(sec_encrypt_update_key, sec_encrypt_key, 16); + + /* Initialise RC4 state arrays */ + RC4_set_key(&rc4_decrypt_key, rc4_key_len, sec_decrypt_key); + RC4_set_key(&rc4_encrypt_key, rc4_key_len, sec_encrypt_key); +} + +static uint8 pad_54[40] = { + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 54, 54 +}; + +static uint8 pad_92[48] = { + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 92, 92, 92, 92, 92, 92, 92 +}; + +/* Output a uint32 into a buffer (little-endian) */ +void +buf_out_uint32(uint8 * buffer, uint32 value) +{ + buffer[0] = (value) & 0xff; + buffer[1] = (value >> 8) & 0xff; + buffer[2] = (value >> 16) & 0xff; + buffer[3] = (value >> 24) & 0xff; +} + +/* Generate a signature hash, using a combination of SHA1 and MD5 */ +void +sec_sign(uint8 * signature, int siglen, uint8 * session_key, int keylen, uint8 * data, int datalen) +{ + uint8 shasig[20]; + uint8 md5sig[16]; + uint8 lenhdr[4]; + SHA_CTX sha; + MD5_CTX md5; + + buf_out_uint32(lenhdr, datalen); + + SHA1_Init(&sha); + SHA1_Update(&sha, session_key, keylen); + SHA1_Update(&sha, pad_54, 40); + SHA1_Update(&sha, lenhdr, 4); + SHA1_Update(&sha, data, datalen); + SHA1_Final(shasig, &sha); + + MD5_Init(&md5); + MD5_Update(&md5, session_key, keylen); + MD5_Update(&md5, pad_92, 48); + MD5_Update(&md5, shasig, 20); + MD5_Final(md5sig, &md5); + + memcpy(signature, md5sig, siglen); +} + +/* Update an encryption key - similar to the signing process */ +static void +sec_update(uint8 * key, uint8 * update_key) +{ + uint8 shasig[20]; + SHA_CTX sha; + MD5_CTX md5; + RC4_KEY update; + + SHA1_Init(&sha); + SHA1_Update(&sha, update_key, rc4_key_len); + SHA1_Update(&sha, pad_54, 40); + SHA1_Update(&sha, key, rc4_key_len); + SHA1_Final(shasig, &sha); + + MD5_Init(&md5); + MD5_Update(&md5, update_key, rc4_key_len); + MD5_Update(&md5, pad_92, 48); + MD5_Update(&md5, shasig, 20); + MD5_Final(key, &md5); + + RC4_set_key(&update, rc4_key_len, key); + RC4(&update, rc4_key_len, key, key); + + if (rc4_key_len == 8) + sec_make_40bit(key); +} + +/* Encrypt data using RC4 */ +static void +sec_encrypt(uint8 * data, int length) +{ + static int use_count; + + if (use_count == 4096) + { + sec_update(sec_encrypt_key, sec_encrypt_update_key); + RC4_set_key(&rc4_encrypt_key, rc4_key_len, sec_encrypt_key); + use_count = 0; + } + + RC4(&rc4_encrypt_key, length, data, data); + use_count++; +} + +/* Decrypt data using RC4 */ +void +sec_decrypt(uint8 * data, int length) +{ + static int use_count; + + if (use_count == 4096) + { + sec_update(sec_decrypt_key, sec_decrypt_update_key); + RC4_set_key(&rc4_decrypt_key, rc4_key_len, sec_decrypt_key); + use_count = 0; + } + + RC4(&rc4_decrypt_key, length, data, data); + use_count++; +} + +static void +reverse(uint8 * p, int len) +{ + int i, j; + uint8 temp; + + for (i = 0, j = len - 1; i < j; i++, j--) + { + temp = p[i]; + p[i] = p[j]; + p[j] = temp; + } +} + +/* Perform an RSA public key encryption operation */ +static void +sec_rsa_encrypt(uint8 * out, uint8 * in, int len, uint8 * modulus, uint8 * exponent) +{ + BN_CTX *ctx; + BIGNUM mod, exp, x, y; + uint8 inr[SEC_MODULUS_SIZE]; + int outlen; + + reverse(modulus, SEC_MODULUS_SIZE); + reverse(exponent, SEC_EXPONENT_SIZE); + memcpy(inr, in, len); + reverse(inr, len); + + ctx = BN_CTX_new(); + BN_init(&mod); + BN_init(&exp); + BN_init(&x); + BN_init(&y); + + BN_bin2bn(modulus, SEC_MODULUS_SIZE, &mod); + BN_bin2bn(exponent, SEC_EXPONENT_SIZE, &exp); + BN_bin2bn(inr, len, &x); + BN_mod_exp(&y, &x, &exp, &mod, ctx); + outlen = BN_bn2bin(&y, out); + reverse(out, outlen); + if (outlen < SEC_MODULUS_SIZE) + memset(out + outlen, 0, SEC_MODULUS_SIZE - outlen); + + BN_free(&y); + BN_clear_free(&x); + BN_free(&exp); + BN_free(&mod); + BN_CTX_free(ctx); +} + +/* Initialise secure transport packet */ +STREAM +sec_init(uint32 flags, int maxlen) +{ + int hdrlen; + STREAM s; + + /*if (!g_licence_issued) */ + hdrlen = (flags & SEC_ENCRYPT) ? 12 : 4; +/* else + hdrlen = (flags & SEC_ENCRYPT) ? 12 : 0; */ + s = mcs_init(maxlen + hdrlen); + s_push_layer(s, sec_hdr, hdrlen); + + return s; +} + +/* Transmit secure transport packet over specified channel */ +void +sec_send_to_channel(STREAM s, uint32 flags, uint16 channel) +{ + int datalen; + + s_pop_layer(s, sec_hdr); + //if (!g_licence_issued || (flags & SEC_ENCRYPT)) + out_uint32_le(s, flags); + + if (flags & SEC_ENCRYPT) + { + flags &= ~SEC_ENCRYPT; + datalen = s->end - s->p - 8; + +#if WITH_DEBUG + DEBUG(("Sending encrypted packet:\n")); + hexdump(s->p + 8, datalen); +#endif + + sec_sign(s->p, 8, sec_sign_key, rc4_key_len, s->p + 8, datalen); + sec_encrypt(s->p + 8, datalen); + } + + mcs_send_to_channel(s, channel); +} + +/* Transmit secure transport packet */ + +void +sec_send(STREAM s, uint32 flags) +{ + sec_send_to_channel(s, flags, MCS_GLOBAL_CHANNEL); +} + + +/* Transfer the client random to the server */ +static void +sec_establish_key(void) +{ + uint32 length = SEC_MODULUS_SIZE + SEC_PADDING_SIZE; + uint32 flags = SEC_CLIENT_RANDOM; + STREAM s; + + s = sec_init(flags, 76); + + out_uint32_le(s, length); + out_uint8p(s, sec_crypted_random, SEC_MODULUS_SIZE); + out_uint8s(s, SEC_PADDING_SIZE); + + s_mark_end(s); + sec_send(s, flags); +} + +/* Parse a public key structure */ +static BOOL +sec_parse_public_key(STREAM s, uint8 ** modulus, uint8 ** exponent) +{ + uint32 magic, modulus_len; + + in_uint32_le(s, magic); + if (magic != SEC_RSA_MAGIC) + { + error("RSA magic 0x%x\n", magic); + return False; + } + + in_uint32_le(s, modulus_len); + if (modulus_len != SEC_MODULUS_SIZE + SEC_PADDING_SIZE) + { + error("modulus len 0x%x\n", modulus_len); + return False; + } + + in_uint8s(s, 8); /* modulus_bits, unknown */ + in_uint8p(s, *exponent, SEC_EXPONENT_SIZE); + in_uint8p(s, *modulus, SEC_MODULUS_SIZE); + in_uint8s(s, SEC_PADDING_SIZE); + + return s_check(s); +} + +static BOOL +sec_parse_x509_key(X509 * cert) +{ + EVP_PKEY *epk = NULL; + /* By some reason, Microsoft sets the OID of the Public RSA key to + the oid for "MD5 with RSA Encryption" instead of "RSA Encryption" + + Kudos to Richard Levitte for the following (. intiutive .) + lines of code that resets the OID and let's us extract the key. */ + if (OBJ_obj2nid(cert->cert_info->key->algor->algorithm) == NID_md5WithRSAEncryption) + { + DEBUG_RDP5(("Re-setting algorithm type to RSA in server certificate\n")); + cert->cert_info->key->algor->algorithm = OBJ_nid2obj(NID_rsaEncryption); + } + epk = X509_get_pubkey(cert); + if (NULL == epk) + { + error("Failed to extract public key from certificate\n"); + return False; + } + + server_public_key = (RSA *) epk->pkey.ptr; + + return True; +} + + +/* Parse a crypto information structure */ +static BOOL +sec_parse_crypt_info(STREAM s, uint32 * rc4_key_size, + uint8 ** server_random, uint8 ** modulus, uint8 ** exponent) +{ + uint32 crypt_level, random_len, rsa_info_len; + uint32 cacert_len, cert_len, flags; + X509 *cacert, *server_cert; + uint16 tag, length; + uint8 *next_tag, *end; + + in_uint32_le(s, *rc4_key_size); /* 1 = 40-bit, 2 = 128-bit */ + in_uint32_le(s, crypt_level); /* 1 = low, 2 = medium, 3 = high */ + if (crypt_level == 0) /* no encryption */ + return False; + in_uint32_le(s, random_len); + in_uint32_le(s, rsa_info_len); + + if (random_len != SEC_RANDOM_SIZE) + { + error("random len %d, expected %d\n", random_len, SEC_RANDOM_SIZE); + return False; + } + + in_uint8p(s, *server_random, random_len); + + /* RSA info */ + end = s->p + rsa_info_len; + if (end > s->end) + return False; + + in_uint32_le(s, flags); /* 1 = RDP4-style, 0x80000002 = X.509 */ + if (flags & 1) + { + DEBUG_RDP5(("We're going for the RDP4-style encryption\n")); + in_uint8s(s, 8); /* unknown */ + + while (s->p < end) + { + in_uint16_le(s, tag); + in_uint16_le(s, length); + + next_tag = s->p + length; + + switch (tag) + { + case SEC_TAG_PUBKEY: + if (!sec_parse_public_key(s, modulus, exponent)) + return False; + DEBUG_RDP5(("Got Public key, RDP4-style\n")); + + break; + + case SEC_TAG_KEYSIG: + /* Is this a Microsoft key that we just got? */ + /* Care factor: zero! */ + /* Actually, it would probably be a good idea to check if the public key is signed with this key, and then store this + key as a known key of the hostname. This would prevent some MITM-attacks. */ + break; + + default: + unimpl("crypt tag 0x%x\n", tag); + } + + s->p = next_tag; + } + } + else + { + uint32 certcount; + + DEBUG_RDP5(("We're going for the RDP5-style encryption\n")); + in_uint32_le(s, certcount); /* Number of certificates */ + + if(certcount < 2) + { + error("Server didn't send enough X509 certificates\n"); + return False; + } + + for(; certcount > 2; certcount--) + { /* ignore all the certificates between the root and the signing CA */ + uint32 ignorelen; + X509 *ignorecert; + + DEBUG_RDP5(("Ignored certs left: %d\n", certcount)); + + in_uint32_le(s, ignorelen); + DEBUG_RDP5(("Ignored Certificate length is %d\n", ignorelen)); + ignorecert = d2i_X509(NULL, &(s->p), ignorelen); + + if(ignorecert == NULL) + { /* XXX: error out? */ + DEBUG_RDP5(("got a bad cert: this will probably screw up the rest of the communication\n")); + } + +#ifdef WITH_DEBUG_RDP5 + DEBUG_RDP5(("cert #%d (ignored):\n",certcount)); + X509_print_fp(stdout, ignorecert); +#endif + } + + /* Do da funky X.509 stuffy + + "How did I find out about this? I looked up and saw a + bright light and when I came to I had a scar on my forehead + and knew about X.500" + - Peter Gutman in a early version of + http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt + */ + + in_uint32_le(s, cacert_len); + DEBUG_RDP5(("CA Certificate length is %d\n", cacert_len)); + cacert = d2i_X509(NULL, &(s->p), cacert_len); + /* Note: We don't need to move s->p here - d2i_X509 is + "kind" enough to do it for us */ + if (NULL == cacert) + { + error("Couldn't load CA Certificate from server\n"); + return False; + } + + /* Currently, we don't use the CA Certificate. + FIXME: + *) Verify the server certificate (server_cert) with the + CA certificate. + *) Store the CA Certificate with the hostname of the + server we are connecting to as key, and compare it + when we connect the next time, in order to prevent + MITM-attacks. + */ + + in_uint32_le(s, cert_len); + DEBUG_RDP5(("Certificate length is %d\n", cert_len)); + server_cert = d2i_X509(NULL, &(s->p), cert_len); + if (NULL == server_cert) + { + error("Couldn't load Certificate from server\n"); + return False; + } + + in_uint8s(s, 16); /* Padding */ + + /* Note: Verifying the server certificate must be done here, + before sec_parse_public_key since we'll have to apply + serious violence to the key after this */ + + if (!sec_parse_x509_key(server_cert)) + { + DEBUG_RDP5(("Didn't parse X509 correctly\n")); + return False; + } + return True; /* There's some garbage here we don't care about */ + } + return s_check_end(s); +} + +/* Process crypto information blob */ +static void +sec_process_crypt_info(STREAM s) +{ + uint8 *server_random, *modulus, *exponent; + uint8 client_random[SEC_RANDOM_SIZE]; + uint32 rc4_key_size; + uint8 inr[SEC_MODULUS_SIZE]; + + if (!sec_parse_crypt_info(s, &rc4_key_size, &server_random, &modulus, &exponent)) + { + DEBUG(("Failed to parse crypt info\n")); + return; + } + + DEBUG(("Generating client random\n")); + /* Generate a client random, and hence determine encryption keys */ + /* This is what the MS client do: */ + memset(inr, 0, SEC_RANDOM_SIZE); + /* *ARIGL!* Plaintext attack, anyone? + I tried doing: + generate_random(inr); + ..but that generates connection errors now and then (yes, + "now and then". Something like 0 to 3 attempts needed before a + successful connection. Nice. Not! + */ + + generate_random(client_random); + if (NULL != server_public_key) + { /* Which means we should use + RDP5-style encryption */ + + memcpy(inr + SEC_RANDOM_SIZE, client_random, SEC_RANDOM_SIZE); + reverse(inr + SEC_RANDOM_SIZE, SEC_RANDOM_SIZE); + + RSA_public_encrypt(SEC_MODULUS_SIZE, + inr, sec_crypted_random, server_public_key, RSA_NO_PADDING); + + reverse(sec_crypted_random, SEC_MODULUS_SIZE); + + } + else + { /* RDP4-style encryption */ + sec_rsa_encrypt(sec_crypted_random, + client_random, SEC_RANDOM_SIZE, modulus, exponent); + } + sec_generate_keys(client_random, server_random, rc4_key_size); +} + + +/* Process SRV_INFO, find RDP version supported by server */ +static void +sec_process_srv_info(STREAM s) +{ + in_uint16_le(s, g_server_rdp_version); + DEBUG_RDP5(("Server RDP version is %d\n", g_server_rdp_version)); +/* if (1 == g_server_rdp_version) + g_use_rdp5 = 0; */ +} + + +/* Process connect response data blob */ +void +sec_process_mcs_data(STREAM s) +{ + uint16 tag, length; + uint8 *next_tag; + uint8 len; + + in_uint8s(s, 21); /* header (T.124 stuff, probably) */ + in_uint8(s, len); + if (len & 0x80) + in_uint8(s, len); + + while (s->p < s->end) + { + in_uint16_le(s, tag); + in_uint16_le(s, length); + + if (length <= 4) + return; + + next_tag = s->p + length - 4; + + switch (tag) + { + case SEC_TAG_SRV_INFO: + sec_process_srv_info(s); + break; + + case SEC_TAG_SRV_CRYPT: + sec_process_crypt_info(s); + break; + + case SEC_TAG_SRV_CHANNELS: + /* FIXME: We should parse this information and + use it to map RDP5 channels to MCS + channels */ + break; + + default: + unimpl("response tag 0x%x\n", tag); + } + + s->p = next_tag; + } +} + +/* Receive secure transport packet */ +STREAM +sec_recv(void) +{ + uint32 sec_flags; + uint16 channel; + STREAM s; + + while ((s = mcs_recv(&channel)) != NULL) + { + if (/*g_encryption || !g_licence_issued*/ 0) + { + in_uint32_le(s, sec_flags); + + if (sec_flags & SEC_ENCRYPT) + { + in_uint8s(s, 8); /* signature */ + sec_decrypt(s->p, s->end - s->p); + } + + if (sec_flags & SEC_LICENCE_NEG) + { + licence_process(s); + continue; + } + } + + if (channel != MCS_GLOBAL_CHANNEL) + { + channel_process(s, channel); + continue; + } + + return s; + } + + return NULL; +} + +/* Disconnect a connection */ +void +sec_disconnect(void) +{ + mcs_disconnect(); +} diff --git a/util.c b/util.c index 2376a61..24365bf 100644 --- a/util.c +++ b/util.c @@ -75,6 +75,51 @@ generate_random_egd(uint8 * buf) } #endif +/* Generate a 32-byte random for the secure transport code. */ +void +generate_random(uint8 * random) +{ + struct stat st; + struct tms tmsbuf; + MD5_CTX md5; + uint32 *r; + int fd, n; + + /* If we have a kernel random device, try that first */ + if (((fd = open("/dev/urandom", O_RDONLY)) != -1) + || ((fd = open("/dev/random", O_RDONLY)) != -1)) + { + n = read(fd, random, 32); + close(fd); + if (n == 32) + return; + } + +#ifdef EGD_SOCKET + /* As a second preference use an EGD */ + if (generate_random_egd(random)) + return; +#endif + + /* Otherwise use whatever entropy we can gather - ideas welcome. */ + r = (uint32 *) random; + r[0] = (getpid()) | (getppid() << 16); + r[1] = (getuid()) | (getgid() << 16); + r[2] = times(&tmsbuf); /* system uptime (clocks) */ + gettimeofday((struct timeval *) &r[3], NULL); /* sec and usec */ + stat("/tmp", &st); + r[5] = st.st_atime; + r[6] = st.st_mtime; + r[7] = st.st_ctime; + + /* Hash both halves with MD5 to obscure possible patterns */ + MD5_Init(&md5); + MD5_Update(&md5, random, 16); + MD5_Final(random, &md5); + MD5_Update(&md5, random + 16, 16); + MD5_Final(random + 16, &md5); +} + /* malloc; exit if out of memory */ void * xmalloc(int size)