From 33ef435c8c8077366a8291fd6cc301ac4a215338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20M=C3=B6ller?= <nisse@lysator.liu.se> Date: Thu, 13 Mar 1997 21:02:58 +0100 Subject: [PATCH] New SSL module Rev: lib/modules/SSL.pmod/TODO:1.1 Rev: lib/modules/SSL.pmod/alert.pike:1.1 Rev: lib/modules/SSL.pmod/cipher.pike:1.1 Rev: lib/modules/SSL.pmod/connection.pike:1.1 Rev: lib/modules/SSL.pmod/constants.pike:1.1 Rev: lib/modules/SSL.pmod/context.pike:1.1 Rev: lib/modules/SSL.pmod/handshake.pike:1.1 Rev: lib/modules/SSL.pmod/https.pike:1.1 Rev: lib/modules/SSL.pmod/packet.pike:1.1 Rev: lib/modules/SSL.pmod/queue.pike:1.1 Rev: lib/modules/SSL.pmod/server.pike:1.1 Rev: lib/modules/SSL.pmod/session.pike:1.1 Rev: lib/modules/SSL.pmod/sslport.pike:1.1 Rev: lib/modules/SSL.pmod/state.pike:1.1 Rev: lib/modules/SSL.pmod/struct.pike:1.1 --- lib/modules/SSL.pmod/TODO | 7 + lib/modules/SSL.pmod/alert.pike | 32 ++ lib/modules/SSL.pmod/cipher.pike | 202 ++++++++++++ lib/modules/SSL.pmod/connection.pike | 277 ++++++++++++++++ lib/modules/SSL.pmod/constants.pike | 153 +++++++++ lib/modules/SSL.pmod/context.pike | 99 ++++++ lib/modules/SSL.pmod/handshake.pike | 470 +++++++++++++++++++++++++++ lib/modules/SSL.pmod/https.pike | 177 ++++++++++ lib/modules/SSL.pmod/packet.pike | 115 +++++++ lib/modules/SSL.pmod/queue.pike | 50 +++ lib/modules/SSL.pmod/server.pike | 4 + lib/modules/SSL.pmod/session.pike | 131 ++++++++ lib/modules/SSL.pmod/sslport.pike | 234 +++++++++++++ lib/modules/SSL.pmod/state.pike | 94 ++++++ lib/modules/SSL.pmod/struct.pike | 160 +++++++++ 15 files changed, 2205 insertions(+) create mode 100644 lib/modules/SSL.pmod/TODO create mode 100644 lib/modules/SSL.pmod/alert.pike create mode 100644 lib/modules/SSL.pmod/cipher.pike create mode 100644 lib/modules/SSL.pmod/connection.pike create mode 100644 lib/modules/SSL.pmod/constants.pike create mode 100644 lib/modules/SSL.pmod/context.pike create mode 100644 lib/modules/SSL.pmod/handshake.pike create mode 100644 lib/modules/SSL.pmod/https.pike create mode 100644 lib/modules/SSL.pmod/packet.pike create mode 100644 lib/modules/SSL.pmod/queue.pike create mode 100644 lib/modules/SSL.pmod/server.pike create mode 100644 lib/modules/SSL.pmod/session.pike create mode 100644 lib/modules/SSL.pmod/sslport.pike create mode 100644 lib/modules/SSL.pmod/state.pike create mode 100644 lib/modules/SSL.pmod/struct.pike diff --git a/lib/modules/SSL.pmod/TODO b/lib/modules/SSL.pmod/TODO new file mode 100644 index 0000000000..3116bf27e8 --- /dev/null +++ b/lib/modules/SSL.pmod/TODO @@ -0,0 +1,7 @@ +SSL TODO-list, 970307 + +* ASN.1 support +* Client connections +* Splitting of large handshake packets +* Combining small application packets +* Blocking I/O diff --git a/lib/modules/SSL.pmod/alert.pike b/lib/modules/SSL.pmod/alert.pike new file mode 100644 index 0000000000..577814a76c --- /dev/null +++ b/lib/modules/SSL.pmod/alert.pike @@ -0,0 +1,32 @@ +/* alert.pike + * + */ + +inherit "packet" : packet; + +int level; +int description; + +string message; +mixed trace; + +constant is_alert = 1; + +object create(int l, int d, string|void m, mixed|void t) +{ + if (! ALERT_levels[l]) + throw( ({ "SSL.alert->create: Invalid level\n", backtrace() })); + if (! ALERT_descriptions[d]) + throw( ({ "SSL.alert->create: Invalid description\n", backtrace() })); + + level = l; + description = d; + message = m; + trace = t; + + packet::create(); + packet::content_type = PACKET_alert; + packet::protocol_version = ({ 3, 0 }); + packet::fragment = sprintf("%c%c", level, description); +} + diff --git a/lib/modules/SSL.pmod/cipher.pike b/lib/modules/SSL.pmod/cipher.pike new file mode 100644 index 0000000000..3e90eacf61 --- /dev/null +++ b/lib/modules/SSL.pmod/cipher.pike @@ -0,0 +1,202 @@ +/* cipher.pike + * + */ + +inherit "constants"; + +class CipherSpec { + program bulk_cipher_algorithm; + int cipher_type; + program mac_algorithm; + int is_exportable; + int hash_size; + int key_material; + int iv_size; +} + +class mac_none +{ + /* Dummy MAC algorithm */ +// string hash_raw(string data) { return ""; } + string hash(string data, object seq_num) { return ""; } +} + +class mac_sha +{ + string pad_1 = "6666666666666666666666666666666666666666"; + string pad_2 = ("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" + "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"); + + program algorithm = Crypto.sha; + + string secret; + + string hash_raw(string data) + { + object h = algorithm(); + h->update(data); + return h->digest(); + } + + string hash(object packet, object seq_num) + { + string s = sprintf("%~8s%c%2c%s", + "\0\0\0\0\0\0\0\0", seq_num->digits(256), + packet->content_type, strlen(packet->fragment), + packet->fragment); +// werror(sprintf("SSL.cipher: hashing '%s'\n", s)); + return hash_raw(secret + pad_2 + + hash_raw(secret + pad_1 + s)); + } + + string hash_master(string data, string|void s) + { + s = s || secret; + return hash_raw(s + pad_2 + + hash_raw(data + s + pad_1)); + } + + void create (string|void s) + { + secret = s || ""; + } +} + +class mac_md5 { + inherit mac_sha; + + string pad_1 = "666666666666666666666666666666666666666666666666"; + string pad_2 = ("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" + "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"); + + program algorithm = Crypto.md5; +} + +class crypt_none +{ + /* Dummy stream cipher */ + object set_encrypt_key(string k) { return this_object(); } + object set_decrypt_key(string k) { return this_object(); } + string crypt(string s) { return s; } +} + +class des +{ + inherit Crypto.des_cbc : c; + + object set_encrypt_key(string k) + { + c::set_encrypt_key(Crypto.des_parity(k)); + return this_object(); + } + + object set_decrypt_key(string k) + { + c::set_decrypt_key(Crypto.des_parity(k)); + return this_object(); + } +} + +class des3 +{ + inherit Crypto.des3_cbc : c; + + object set_encrypt_key(string k) + { + c::set_encrypt_key(Crypto.des_parity(k)); + return this_object(); + } + + object set_decrypt_key(string k) + { + c::set_decrypt_key(Crypto.des_parity(k)); + return this_object(); + } +} + +class rsa_auth +{ +} + +/* Return array of auth_method, cipher_spec */ +array lookup(int suite) +{ + object res = CipherSpec(); + int ke_method; + + array algorithms = CIPHER_SUITES[suite]; + if (!algorithms) + return 0; + + ke_method = algorithms[0]; + + switch(algorithms[1]) + { + case CIPHER_rc4: + res->bulk_cipher_algorithm = Crypto.rc4; + res->cipher_type = CIPHER_stream; + res->is_exportable = 0; + res->key_material = 16; + res->iv_size = 0; + break; + case CIPHER_rc4_40: + res->bulk_cipher_algorithm = Crypto.rc4; + res->cipher_type = CIPHER_stream; + res->is_exportable = 1; + res->key_material = 5; + res->iv_size = 0; + break; + case CIPHER_des: + res->bulk_cipher_algorithm = des; + res->cipher_type = CIPHER_block; + res->is_exportable = 0; + res->key_material = 7; + res->iv_size = 8; + break; + case CIPHER_3des: + res->bulk_cipher_algorithm = des3; + res->cipher_type = CIPHER_block; + res->is_exportable = 0; + res->key_material = 16; + res->iv_size = 8; + break; + case CIPHER_idea: + res->bulk_cipher_algorithm = Crypto.idea_cbc; + res->cipher_type = CIPHER_block; + res->is_exportable = 0; + res->key_material = 16; + res->iv_size = 8; + break; + case CIPHER_null: + res->bulk_cipher_algorithm = crypt_none; + res->cipher_type = CIPHER_stream; + res->is_exportable = 1; + res->key_material = 0; + res->iv_size = 0; + break; + default: + return 0; + } + + switch(algorithms[2]) + { + case HASH_sha: + res->mac_algorithm = mac_sha; + res->hash_size = 20; + break; + case HASH_md5: + res->mac_algorithm = mac_md5; + res->hash_size = 16; + break; + case 0: + res->mac_algorithm = mac_none; + res->hash_size = 0; + break; + default: + return 0; + } + + return ({ ke_method, res }); +} + + diff --git a/lib/modules/SSL.pmod/connection.pike b/lib/modules/SSL.pmod/connection.pike new file mode 100644 index 0000000000..d3e95429b1 --- /dev/null +++ b/lib/modules/SSL.pmod/connection.pike @@ -0,0 +1,277 @@ +/* connection.pike + * + * SSL packet layer + */ + + +object current_read_state; +object current_write_state; +string left_over; +object packet; + +int dying; +int closing; + +inherit "constants"; +inherit "handshake"; + +constant Queue = (program) "queue"; +constant State = (program) "state"; + +inherit Queue : alert; +inherit Queue : urgent; +inherit Queue : application; + +void create(int is_server) +{ + handshake::create(is_server); + alert::create(); + urgent::create(); + application::create(); + current_read_state = State(this_object()); + current_write_state = State(this_object()); +} + +object recv_packet(string data) +{ + mixed res; + +// werror(sprintf("SSL.connection->recv_packet('%s')\n", data)); + if (left_over || !packet) + { + packet = Packet(2048); + res = packet->recv( (left_over || "") + data); + } + else + res = packet->recv(data); + + if (stringp(res)) + { /* Finished a packet */ + left_over = res; + return current_read_state->decrypt_packet(packet); + } + else /* Partial packet read, or error */ + left_over = 0; + return res; +} + +void send_packet(object packet, int|void fatal) +{ +// werror(sprintf("SSL.connection->send_packet: type %d, '%s'\n", +// packet->content_type, packet->fragment)); + switch (packet->content_type) + { + default: + throw( ({"SSL.connection->send_packet: internal error\n", backtrace() }) ); + case PACKET_alert: + alert::put(packet); + if (packet->description == ALERT_close_notify) + { + if (packet->level == ALERT_fatal) + fatal = 1; + else + closing = 1; + } + break; + case PACKET_application_data: + application::put(packet); + break; + /* Handshake and and change cipher must use the same queue */ + case PACKET_change_cipher_spec: + case PACKET_handshake: + urgent::put(packet); + break; + } + if (fatal) + dying = 1; +} + +/* Returns a string of data to be written, "" if there's no pending packets, + * 1 if the connection is being closed politely, and -1 if the connection + * died unexpectedly. */ +string|int to_write() +{ + string res = 0; + + object packet = alert::get(); + if (!packet) + { + if (dying) + return closing ? 1 : -1; + packet = urgent::get() || application::get(); + } + if (packet) + { + werror(sprintf("SSL.connection: writing packet of type %d, '%s'\n", + packet->content_type, packet->fragment[..6])); + res = current_write_state->encrypt_packet(packet)->send(); + if (packet->content_type == PACKET_change_cipher_spec) + current_write_state = pending_write_state; + } + else + res = closing ? 1 : ""; + return res; +} + +int handle_alert(string s) +{ + int level = s[0]; + int description = s[1]; + + if (! (ALERT_levels[level] && ALERT_descriptions[description])) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.connection->handle_alert: invalid alert\n", backtrace())); + return -1; + } + if (level == ALERT_fatal) + { + werror(sprintf("SSL.connection: Fatal alert %d\n", description)); + return -1; + } + if (description == ALERT_close_notify) + { + closing = 1; + return 0; + } + if (description == ALERT_no_certificate) + { + if ((certificate_state == CERT_requested) && (context->auth_level == AUTHLEVEL_ask)) + { + certificate_state = CERT_no_certificate; + return 0; + } else { + send_packet(Alert(ALERT_fatal, ((certificate_state == CERT_requested) + ? ALERT_handshake_failure + : ALERT_unexpected_message))); + return -1; + } + } + else + werror(sprintf("SSL.connection: Received warning alert %d\n", description)); + return 0; +} + +int handle_change_cipher(int c) +{ + if (!expect_change_cipher || (c != 1)) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + else + { + current_read_state = pending_read_state; + expect_change_cipher = 0; + return 0; + } +} + +string alert_buffer = ""; +string handshake_buffer = ""; +int handshake_finished = 0; + +/* Returns a string of application data, or -1 if a fatal error occured */ +string|int got_data(string s) +{ + string res = ""; + object packet; + while (packet = recv_packet(s)) + { + s = ""; + + if (packet->is_alert) + { /* Reply alert */ + werror("Bad recieved packet\n"); + send_packet(packet); + if (packet->level == ALERT_fatal) + return -1; + } + else + { + werror(sprintf("SSL.connection: recieved packet of type %d\n", + packet->content_type)); + switch (packet->content_type) + { + case PACKET_alert: + { + int i; + mixed err = 0; + alert_buffer += packet->fragment; + for (i = 0; + !err && ((strlen(alert_buffer) - i) >= 2); + i+= 2) + err = handle_alert(alert_buffer[i..i+1]); + + alert_buffer = alert_buffer[i..]; + if (err) + return err; + break; + } + case PACKET_change_cipher_spec: + { + int i; + int err; + for (i = 0; (i < strlen(packet->fragment)); i++) + { + err = handle_change_cipher(packet->fragment[i]); + werror(sprintf("tried change_cipher: %d\n", err)); + if (err) + return err; + } + break; + } + case PACKET_handshake: + { + if (expect_change_cipher) + { + /* No change_cipher message was recieved */ + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + mixed err; + int len; + handshake_buffer += packet->fragment; + + while (strlen(handshake_buffer) >= 4) + { + sscanf(handshake_buffer, "%*c%3c", len); + if (strlen(handshake_buffer) < (len + 4)) + break; + err = handle_handshake(handshake_buffer[0], + handshake_buffer[4..len + 3], + handshake_buffer[.. len + 3]); + handshake_buffer = handshake_buffer[len + 4..]; + if (err < 0) + return err; + if (err > 0) + handshake_finished = 1; + } + break; + } + case PACKET_application_data: + if (!handshake_finished) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + res += packet->fragment; + break; + case PACKET_V2: + { + mixed err = handle_handshake(HANDSHAKE_hello_v2, + packet->fragment[1 .. ], + packet->fragment); + if (err) + return err; + } + } + } + } + return res; +} + +void server() +{ + handshake_state = STATE_server_wait_for_hello; +} diff --git a/lib/modules/SSL.pmod/constants.pike b/lib/modules/SSL.pmod/constants.pike new file mode 100644 index 0000000000..af7eb68e60 --- /dev/null +++ b/lib/modules/SSL.pmod/constants.pike @@ -0,0 +1,153 @@ +/* constants.pike + * + */ + +/* Packet types */ +constant PACKET_change_cipher_spec = 20; +constant PACKET_alert = 21; +constant PACKET_handshake = 22; +constant PACKET_application_data = 23; +constant PACKET_types = (< PACKET_change_cipher_spec, + PACKET_alert, + PACKET_handshake, + PACKET_application_data >); +constant PACKET_V2 = -1; /* Backwards compatibility */ + +constant PACKET_MAX_SIZE = 0x4000; + +/* Cipher specification */ +constant CIPHER_stream = 0; +constant CIPHER_block = 1; +constant CIPHER_types = (< CIPHER_stream, CIPHER_block >); + +constant CIPHER_null = 0; +constant CIPHER_rc4 = 1; +constant CIPHER_rc4_40 = 2; +constant CIPHER_rc2 = 3; +constant CIPHER_des = 4; +constant CIPHER_3des = 5; +constant CIPHER_des40 = 6; +constant CIPHER_fortezza = 7; +constant CIPHER_idea = 8; +constant CIPHER_algorithms = (< CIPHER_null, + CIPHER_rc4, + CIPHER_rc4_40, + CIPHER_rc2, + CIPHER_des, + CIPHER_3des, + CIPHER_des40, + CIPHER_fortezza, + CIPHER_idea >); + +constant HASH_md5 = 1; +constant HASH_sha = 2; +constant HASH_hashes = (< HASH_md5, HASH_sha >); + +/* Key exchange */ +constant KE_rsa = 1; +constant KE_dh = 2; +constant KE_dms = 3; + +/* Compression methods */ +constant COMPRESSION_null = 0; + +/* Alert messages */ +constant ALERT_warning = 1; +constant ALERT_fatal = 2; +constant ALERT_levels = (< ALERT_warning, ALERT_fatal >); + +constant ALERT_close_notify = 0; +constant ALERT_unexpected_message = 10; +constant ALERT_bad_record_mac = 20; +constant ALERT_decompression_failure = 30; +constant ALERT_handshake_failure = 40; +constant ALERT_no_certificate = 41; +constant ALERT_bad_certificate = 42; +constant ALERT_unsupported_certificate = 43; +constant ALERT_certificate_revoked = 44; +constant ALERT_certificate_expired = 45; +constant ALERT_certificate_unknown = 46; +constant ALERT_illegal_parameter = 47; +constant ALERT_descriptions = (< ALERT_close_notify, + ALERT_unexpected_message, + ALERT_bad_record_mac, + ALERT_decompression_failure, + ALERT_handshake_failure, + ALERT_no_certificate, + ALERT_bad_certificate, + ALERT_unsupported_certificate, + ALERT_certificate_revoked, + ALERT_certificate_expired, + ALERT_certificate_unknown, + ALERT_illegal_parameter >); + +constant CONNECTION_client = 0; +constant CONNECTION_server = 1; +constant CONNECTION_client_auth = 2; + +/* Cipher suites */ +constant SSL_null_with_null_null = 0x0000; +constant SSL_rsa_with_null_md5 = 0x0001; +constant SSL_rsa_with_null_sha = 0x0002; +constant SSL_rsa_export_with_rc4_40_md5 = 0x0003; +constant SSL_rsa_with_rc4_128_md5 = 0x0004; +constant SSL_rsa_with_rc4_128_sha = 0x0005; +constant SSL_rsa_export_with_rc2_cbc_40_md5 = 0x0006; +constant SSL_rsa_with_idea_cbc_sha = 0x0007; +constant SSL_rsa_export_with_des40_cbc_sha = 0x0008; +constant SSL_rsa_with_des_cbc_sha = 0x0009; +constant SSL_rsa_with_3des_ede_cbc_sha = 0x000a; +constant SSL_dh_dss_export_with_des40_cbc_sha = 0x000b; +constant SSL_dh_dss_with_des_cbc_sha = 0x000c; +constant SSL_dh_dss_with_3des_ede_cbc_sha = 0x000d; +constant SSL_dh_rsa_export_with_des40_cbc_sha = 0x000e; +constant SSL_dh_rsa_with_des_cbc_sha = 0x000f; +constant SSL_dh_rsa_with_3des_ede_cbc_sha = 0x0010; +constant SSL_dhe_dss_export_with_des40_cbc_sha = 0x0011; +constant SSL_dhe_dss_with_des_cbc_sha = 0x0012; +constant SSL_dhe_dss_with_3des_ede_cbc_sha = 0x0013; +constant SSL_dhe_rsa_export_with_des40_cbc_sha = 0x0014; +constant SSL_dhe_rsa_with_des_cbc_sha = 0x0015; +constant SSL_dhe_rsa_with_3des_ede_cbc_sha = 0x0016; +constant SSL_dh_anon_export_with_rc4_40_md5 = 0x0017; +constant SSL_dh_anon_with_rc4_128_md5 = 0x0018; +constant SSL_dh_anon_export_with_des40_cbc_sha = 0x0019; +constant SSL_dh_anon_with_des_cbc_sha = 0x001a; +constant SSL_dh_anon_with_3des_ede_cbc_sha = 0x001b; +constant SSL_fortezza_dms_with_null_sha = 0x001c; +constant SSL_fortezza_dms_with_fortezza_cbc_sha = 0x001d; +constant SSL_fortezza_dms_with_rc4_128_sha = 0x001e; + +constant CIPHER_SUITES = +([ SSL_null_with_null_null : ({ 0, 0, 0 }), + SSL_rsa_with_null_md5 : ({ KE_rsa, 0, HASH_md5 }), + SSL_rsa_with_null_sha : ({ KE_rsa, 0, HASH_sha }), + SSL_rsa_export_with_rc4_40_md5 : ({ KE_rsa, CIPHER_rc4_40, HASH_md5 }), + SSL_rsa_with_rc4_128_sha : ({ KE_rsa, CIPHER_rc4, HASH_sha }), + SSL_rsa_with_idea_cbc_sha : ({ KE_rsa, CIPHER_idea, HASH_sha }), + SSL_rsa_with_des_cbc_sha : ({ KE_rsa, CIPHER_des, HASH_sha }), + SSL_rsa_with_3des_ede_cbc_sha : ({ KE_rsa, CIPHER_3des, HASH_sha }) ]); + +constant HANDSHAKE_hello_v2 = -1; /* Backwards compatibility */ +constant HANDSHAKE_hello_request = 0; +constant HANDSHAKE_client_hello = 1; +constant HANDSHAKE_server_hello = 2; +constant HANDSHAKE_certificate = 11; +constant HANDSHAKE_server_key_exchange = 12; +constant HANDSHAKE_certificate_request = 13; +constant HANDSHAKE_server_hello_done = 14; +constant HANDSHAKE_certificate_verify = 15; +constant HANDSHAKE_client_key_exchange = 16; +constant HANDSHAKE_finished = 20; + +constant AUTHLEVEL_none = 1; +constant AUTHLEVEL_ask = 2; +constant AUTHLEVEL_require = 3; + +constant AUTH_rsa_sign = 1; +constant AUTH_dss_sign = 2; +constant AUTH_rsa_fixed_dh = 3; +constant AUTH_dss_fixed_dh = 4; +constant AUTH_rsa_ephemeral_dh = 5; +constant AUTH_dss_ephemeral_dh = 6; +constant AUTH_fortezza_dms = 20; diff --git a/lib/modules/SSL.pmod/context.pike b/lib/modules/SSL.pmod/context.pike new file mode 100644 index 0000000000..b5b3ad9fa9 --- /dev/null +++ b/lib/modules/SSL.pmod/context.pike @@ -0,0 +1,99 @@ +/* context.pike + * + * Keeps track of global data for an SSL server, + * such as preferred encryption algorithms and session cache. + */ + +inherit "constants"; + +int auth_level; + +object rsa; /* Servers private key */ + +function(int:string) random; /* Random number generator */ + +/* Chain of X509.v3 certificates + * Senders certificate first, root certificate last .*/ +array(string) certificates; + +/* For client authentication */ +array(string) authorities; /* List of authorities distinguished names */ + +array(int) preferred_auth_methods = +({ AUTH_rsa_sign }); + +array(int) preferred_suites = +({ SSL_rsa_with_idea_cbc_sha, + SSL_rsa_with_rc4_128_sha, + SSL_rsa_with_rc4_128_md5, + SSL_rsa_with_3des_ede_cbc_sha, + SSL_rsa_with_des_cbc_sha, +// SSL_rsa_export_with_rc4_40_md5, + SSL_rsa_with_null_sha, + SSL_rsa_with_null_md5 +}); + +array(int) preferred_compressors = +({ COMPRESSION_null }); + +constant Session = (program) "session"; +constant Queue = (program) "queue"; + +int use_cache = 1; +int session_lifetime = 600; /* Time to remember a session, in seconds */ + +/* Session cache */ +object active_sessions; /* Queue of pairs (time, id), in cronological order */ +mapping(string:object(Session)) session_cache; + +int session_number; /* Incremented for each session, and used when constructing the + * session id */ + +void forget_old_sessions() +{ + int t = time() - session_lifetime; + array pair; + while ( (pair = active_sessions->peek()) + && (pair[0] < t)) + session_cache[active_sessions->get()[1]] = 0; +} + +object lookup_session(string id) +{ + if (use_cache) + { + forget_old_sessions(); + return session_cache[id]; + } + else + return 0; +} + +object new_session() +{ + object s = Session(); + s->identity = (use_cache) ? sprintf("%4c%4c", time(), session_number++) : ""; + return s; +} + +void record_session(object s) +{ + if (use_cache && s->identity) + { + active_sessions->put( ({ time(), s->identity }) ); + session_cache[s->identity] = s; + } +} + +void purge_session(object s) +{ + if (s->identity) + session_cache[s->identity] = 0; + /* There's no need to remove the id from the active_sessions queue */ +} + +void create() +{ + active_sessions = Queue(); + session_cache = ([ ]); +} diff --git a/lib/modules/SSL.pmod/handshake.pike b/lib/modules/SSL.pmod/handshake.pike new file mode 100644 index 0000000000..e8776e4245 --- /dev/null +++ b/lib/modules/SSL.pmod/handshake.pike @@ -0,0 +1,470 @@ +/* handshake.pike + * + */ + +// int is_server; + +inherit "cipher"; + +object session; +object context; + +object pending_read_state; +object pending_write_state; + +/* State variables */ + +constant STATE_server_wait_for_hello = 1; +constant STATE_server_wait_for_client = 2; +constant STATE_server_wait_for_finish = 3; +constant STATE_server_wait_for_verify = 4; + +constant STATE_client_wait_for_hello = 10; +constant STATE_client_wait_for_server = 11; +constant STATE_client_wait_for_finish = 12; +int handshake_state; + +constant CERT_none = 0; +constant CERT_requested = 1; +constant CERT_recieved = 2; +constant CERT_no_certificate = 3; +int certificate_state; + +int expect_change_cipher; /* Reset to 0 if a change_cipher message is recieved */ + +int reuse; + +string my_random; +string other_random; + +constant Struct = (program) "struct"; +constant Session = (program) "session"; +constant Packet = (program) "packet"; +constant Alert = (program) "alert"; + +/* Defined in connection.pike */ +void send_packet(object packet, int|void fatal); + +string handshake_messages; + +object handshake_packet(int type, string data) +{ + /* Perhaps one need to split large packages? */ + object packet = Packet(); + packet->content_type = PACKET_handshake; + packet->fragment = sprintf("%c%3c%s", type, strlen(data), data); + handshake_messages += packet->fragment; + return packet; +} + +object server_hello_packet() +{ + object struct = Struct(); + /* Build server_hello message */ + struct->put_int(3,1); struct->put_int(0,1); /* version */ + struct->put_fix_string(my_random); + struct->put_var_string(session->identity, 1); + struct->put_int(session->cipher_suite, 2); + struct->put_int(session->compression_algorithm, 1); + + string data = struct->pop_data(); + werror(sprintf("SSL.handshake: Server hello: '%s'\n", data)); + return handshake_packet(HANDSHAKE_server_hello, data); +} + +int reply_new_session(array(int) cipher_suites, array(int) compression_methods) +{ + reuse = 0; + session = context->new_session(); + +// werror(sprintf("ciphers: me: %O, client: %O\n", +// context->preferred_suites, cipher_suites)); +// werror(sprintf("compr: me: %O, client: %O\n", +// context->preferred_compressors, compression_methods)); + cipher_suites &= context->preferred_suites; + if (sizeof(cipher_suites)) + session->set_cipher_suite(cipher_suites[0]); + else + { + send_packet(Alert(ALERT_fatal, ALERT_handshake_failure)); + return -1; + } + + compression_methods &= context->preferred_compressors; + if (sizeof(compression_methods)) + session->set_compression_method(compression_methods[0]); + else + { + send_packet(Alert(ALERT_fatal, ALERT_handshake_failure)); + return -1; + } + + send_packet(server_hello_packet()); + + /* Send Certificate, ServerKeyExchange and CertificateRequest as + * appropriate, and then ServerHelloDone. + */ + if (context->certificates) + { + object struct = Struct(); + + int len = `+( @ Array.map(context->certificates, strlen)); +// werror(sprintf("SSL.handshake: certificate_message size %d\n", len)); + struct->put_int(len + 3 * sizeof(context->certificates), 3); + foreach(context->certificates, string cert) + struct->put_var_string(cert, 3); + send_packet(handshake_packet(HANDSHAKE_certificate, struct->pop_data())); + } + if (0) + { + /* Send a ServerKeyExchange message. Not supported, so far. */ + } + if (context->auth_level >= AUTHLEVEL_ask) + { + /* Send a CertificateRequest message */ + object struct = Struct(); + struct->put_var_array(context->preferred_auth_methods, 1, 1); + + int len = `+(@ Array.map(context->authorities, strlen)); + struct->put_int(len + 2 * sizeof(context->authorities), 2); + foreach(context->authorities, string auth) + struct->put_var_string(auth, 2); + send_packet(handshake_packet(HANDSHAKE_certificate_request, + struct->pop_data())); + certificate_state = CERT_requested; + } + send_packet(handshake_packet(HANDSHAKE_server_hello_done, "")); + return 0; +} + +object change_cipher_packet() +{ + object packet = Packet(); + packet->content_type = PACKET_change_cipher_spec; + packet->fragment = "\001"; + return packet; +} + +string hash_messages(string sender) +{ + return mac_md5(session->master_secret)->hash_master(handshake_messages + sender) + + mac_sha(session->master_secret)->hash_master(handshake_messages + sender); +} + +object finished_packet(string sender) +{ + return handshake_packet(HANDSHAKE_finished, hash_messages(sender)); +} + +string server_derive_master_secret(string data) +{ + string res = ""; + switch(session->ke_method) + { + default: + throw( ({ "SSL.handshake: internal error\n", backtrace() }) ); + case 0: + return 0; + case KE_rsa: + { + /* Decrypt the pre_master_secret */ + string s = context->rsa->decrypt(data); +// werror(sprintf("premaster_secret: '%s'\n", s)); + if (!s || (strlen(s) != 48) || (s[0] != 3)) + return 0; + if (s[1] > 0) + werror("SSL.handshake: Newer version detected in key exchange message.\n"); + object sha = mac_sha(); + object md5 = mac_md5(); + foreach( ({ "A", "BB", "CCC" }), string cookie) + res += md5->hash_raw(s + sha->hash_raw(cookie + s + + other_random + my_random)); + break; + } + } +// werror(sprintf("master: '%s'\n", res)); + return res; +} + +/* return 0 if handshake is in progress, 1 if finished, -1 if there's a + * fatal error. */ +int handle_handshake(int type, string data, string raw) +{ + object input = Struct(data); + + werror(sprintf("SSL.handshake: state %d, type %d\n", handshake_state, type)); + + switch(handshake_state) + { + default: + throw( ({ "SSL.handshake: internal error\n", backtrace() }) ); + case STATE_server_wait_for_hello: + { + array(int) cipher_suites; + + /* Reset all extra state variables */ + expect_change_cipher = certificate_state = 0; + + handshake_messages = raw; + my_random = sprintf("%4c%s", time(), context->random(28)); + + switch(type) + { + default: + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + case HANDSHAKE_client_hello: + { + array(int) version; + string id; + int cipher_len; + array(int) cipher_suites; + array(int) compression_methods; + + if (catch{ + version = input->get_fix_array(1, 2); + other_random = input->get_fix_string(32); + id = input->get_var_string(1); + cipher_len = input->get_int(2); + cipher_suites = input->get_fix_array(2, cipher_len/2); + compression_methods = input->get_var_array(1, 1); + } || (version[0] != 3) || (cipher_len & 1)) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + + if (!input->is_empty()) + werror("SSL.connection->handle_handshake: " + "extra data in hello message ignored\n"); + + if (version[1] > 0) + werror(sprintf("SSL.connection->handle_handshake: " + "Version %d.%d hello detected\n", @version)); + + if (strlen(id)) + werror(sprintf("Looking up session %s\n", id)); + session = strlen(id) && context->lookup_session(id); + if (session) + { + werror(sprintf("Reusing session %s\n", id)); + /* Reuse session */ + reuse = 1; + if (! ( (cipher_suites & ({ session->cipher_suite })) + && (compression_methods & ({ session->compression_algorithm })))) + { + send_packet(Alert(ALERT_fatal, ALERT_handshake_failure)); + return -1; + } + send_packet(server_hello_packet()); + + array res = session->new_server_states(other_random, my_random); + pending_read_state = res[0]; + pending_write_state = res[1]; + send_packet(change_cipher_packet()); + send_packet(finished_packet("SRVR")); + expect_change_cipher = 1; + + handshake_state = STATE_server_wait_for_finish; + } else { + /* New session, do full handshake. */ + + int err = reply_new_session(cipher_suites, compression_methods); + if (err) + return err; + handshake_state = STATE_server_wait_for_client; + } + break; + } + case HANDSHAKE_hello_v2: + { + werror("SSL.handshake: SSL2 hello message recieved\n"); + int ci_len; + int id_len; + int ch_len; + array(int) version; + if (catch{ + version = input->get_fix_array(1, 2); + ci_len = input->get_int(2); + id_len = input->get_int(2); + ch_len = input->get_int(2); + } || (ci_len % 3) || !ci_len || (id_len) || (ch_len < 16) + || (version[0] != 3)) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + + if (version[1] > 0) + werror(sprintf("SSL.connection->handle_handshake: " + "Version %d.%d hello detected\n", @version)); + + + string challenge; + if (catch{ + cipher_suites = input->get_fix_array(3, ci_len/3); + challenge = input->get_fix_string(ch_len); + } || !input->is_empty()) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + other_random = ("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + challenge)[..31]; + int err = reply_new_session(cipher_suites, ({ COMPRESSION_null }) ); + if (err) + return err; + handshake_state = STATE_server_wait_for_client; + + break; + } + } + break; + } + case STATE_server_wait_for_finish: + switch(type) + { + default: + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + case HANDSHAKE_finished: + { + string my_digest = hash_messages("CLNT"); + string digest; + if (catch { + digest = input->get_fix_string(36); + } || !input->is_empty()) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + if (my_digest != digest) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message)); + return -1; + } + handshake_messages += raw; /* Second hash includes this message, + * the first doesn't */ + /* Handshake complete */ + + if (!reuse) + { + send_packet(change_cipher_packet()); + send_packet(finished_packet("SRVR")); + expect_change_cipher = 1; + context->record_session(session); /* Cache this session */ + } + handshake_state = STATE_server_wait_for_hello; + + return 1; + } + } + break; + case STATE_server_wait_for_client: + handshake_messages += raw; + switch(type) + { + default: + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + case HANDSHAKE_client_key_exchange: +// werror("client_key_exchange\n"); + if (certificate_state == CERT_requested) + { /* Certificate should be sent before key exchange message */ + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + } + if (!(session->master_secret = server_derive_master_secret(data))) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + } + array res = session->new_server_states(other_random, my_random); + pending_read_state = res[0]; + pending_write_state = res[1]; + +// werror(sprintf("certificate_state: %d\n", certificate_state)); + if (certificate_state != CERT_recieved) + { + handshake_state = STATE_server_wait_for_finish; + expect_change_cipher = 1; + } + else + handshake_state = STATE_server_wait_for_verify; + + break; + case HANDSHAKE_certificate: + { + if (certificate_state == CERT_requested) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + } + if (catch { + session->client_certificate = input->get_var_string(3); + } || !input->is_empty()) + { + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + } + certificate_state = CERT_recieved; + break; + } + } + break; + case STATE_server_wait_for_verify: + handshake_messages += raw; + switch(type) + { + default: + send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.session->handle_handshake: unexpected message\n", + backtrace())); + return -1; + case HANDSHAKE_certificate_verify: + session->client_challenge = + mac_md5(session->master_secret)->hash_master(handshake_messages) + + mac_sha(session->master_secret)->hash_master(handshake_messages); + session->client_signature = data; + handshake_messages += raw; + handshake_state = STATE_server_wait_for_finish; + expect_change_cipher = 1; + break; + } + break; + + case STATE_client_wait_for_hello: + { + } + case STATE_client_wait_for_server: + { + } + case STATE_client_wait_for_finish: + { + } + } +// werror(sprintf("SSL.handshake: messages = '%s'\n", handshake_messages)); + return 0; +} + +void create(int is_server) +{ + if (is_server) + handshake_state = STATE_server_wait_for_hello; + else + throw( ({ "SSL.handshake->create: client handshake not implemented\n", + backtrace() }) ); +} diff --git a/lib/modules/SSL.pmod/https.pike b/lib/modules/SSL.pmod/https.pike new file mode 100644 index 0000000000..1b73024001 --- /dev/null +++ b/lib/modules/SSL.pmod/https.pike @@ -0,0 +1,177 @@ +/* test_server.pike + * + * echoes packets over the SSL-packet layer + */ + +#define PORT 25678 + +import Stdio; + +inherit "sslport"; + +string my_certificate = MIME.decode_base64( + "MIIBxDCCAW4CAQAwDQYJKoZIhvcNAQEEBQAwbTELMAkGA1UEBhMCREUxEzARBgNV\n" + "BAgTClRodWVyaW5nZW4xEDAOBgNVBAcTB0lsbWVuYXUxEzARBgNVBAoTClRVIEls\n" + "bWVuYXUxDDAKBgNVBAsTA1BNSTEUMBIGA1UEAxMLZGVtbyBzZXJ2ZXIwHhcNOTYw\n" + "NDMwMDUzNjU4WhcNOTYwNTMwMDUzNjU5WjBtMQswCQYDVQQGEwJERTETMBEGA1UE\n" + "CBMKVGh1ZXJpbmdlbjEQMA4GA1UEBxMHSWxtZW5hdTETMBEGA1UEChMKVFUgSWxt\n" + "ZW5hdTEMMAoGA1UECxMDUE1JMRQwEgYDVQQDEwtkZW1vIHNlcnZlcjBcMA0GCSqG\n" + "SIb3DQEBAQUAA0sAMEgCQQDBB6T7bGJhRhRSpDESxk6FKh3iKKrpn4KcDtFM0W6s\n" + "16QSPz6J0Z2a00lDxudwhJfQFkarJ2w44Gdl/8b+de37AgMBAAEwDQYJKoZIhvcN\n" + "AQEEBQADQQB5O9VOLqt28vjLBuSP1De92uAiLURwg41idH8qXxmylD39UE/YtHnf\n" + "bC6QS0pqetnZpQj1yEsjRTeVfuRfANGw\n"); + +string my_key = MIME.decode_base64( + "MIIBOwIBAAJBAMEHpPtsYmFGFFKkMRLGToUqHeIoqumfgpwO0UzRbqzXpBI/PonR\n" + "nZrTSUPG53CEl9AWRqsnbDjgZ2X/xv517fsCAwEAAQJBALzUbJmkQm1kL9dUVclH\n" + "A2MTe15VaDTY3N0rRaZ/LmSXb3laiOgBnrFBCz+VRIi88go3wQ3PKLD8eQ5to+SB\n" + "oWECIQDrmq//unoW1+/+D3JQMGC1KT4HJprhfxBsEoNrmyIhSwIhANG9c0bdpJse\n" + "VJA0y6nxLeB9pyoGWNZrAB4636jTOigRAiBhLQlAqhJnT6N+H7LfnkSVFDCwVFz3\n" + "eygz2yL3hCH8pwIhAKE6vEHuodmoYCMWorT5tGWM0hLpHCN/z3Btm38BGQSxAiAz\n" + "jwsOclu4b+H8zopfzpAaoB8xMcbs0heN+GNNI0h/dQ==\n"); + +class conn { + import Stdio; + + object sslfile; + + string message = "<html><head><title>SSL-3 server</title></head>\n" + "<body><h1>This is a minimal SSL-3 http server</h1>\n" + "<hr><it>/nisse</it></body></html>\n"; + int index = 0; + + void do_write() + { + if (index < strlen(message)) + { + int written = sslfile->write(message[index..]); + if (written > 0) + index += written; + else + sslfile->close(); + } + if (index == strlen(message)) + sslfile->close(); + } + + void read_callback(mixed id, string data) + { + werror("Recieved: '" + data + "'\n"); + do_write(); + } + + void write_callback(mixed id) + { + do_write(); + } + + void create(object f) + { + sslfile = f; + sslfile->set_nonblocking(read_callback, write_callback, 0); + } +} + +class no_random { + object rc4 = Crypto.rc4(); + + void create(string|void secret) + { + if (!secret) + secret = sprintf("Foo!%4c", time()); + object sha = Crypto.sha(); + sha->update(secret); + rc4->set_encrypt_key(sha->digest()); + } + + string read(int size) + { + return rc4->crypt(replace(allocate(size), 0, "\021") * ""); + } +} + +/* ad-hoc asn.1-decoder */ + +class ber_decode { + inherit "struct"; + + array get_asn1() + { + int tag = get_int(1); + int len; + string contents; + + werror(sprintf("decoding tag %x\n", tag)); + if ( (tag & 0x1f) == 0x1f) + throw( ({ "high tag numbers is not supported\n", backtrace() }) ); + int len = get_int(1); + if (len & 0x80) + len = get_int(len & 0x7f); + + werror(sprintf("len : %d\n", len)); + + contents = get_fix_string(len); + werror(sprintf("contents: %O\n", contents)); + if (tag & 0x20) + { + object seq = object_program(this_object())(contents); + array res = ({ }); + while(! seq->is_empty()) + { + array elem = seq->get_asn1(); + // werror(sprintf("elem: %O\n", elem)); + res += ({ elem }); + } + return ({ tag, res }); + } + else + return ({ tag, contents }); + } +} + +/* PKCS#1 Private key structure: + +RSAPrivateKey ::= SEQUENCE { + version Version, + modulus INTEGER, -- n + publicExponent INTEGER, -- e + privateExponent INTEGER, -- d + prime1 INTEGER, -- p + prime2 INTEGER, -- q + exponent1 INTEGER, -- d mod (p-1) + exponent2 INTEGER, -- d mod (q-1) + coefficient INTEGER -- (inverse of q) mod p } + +Version ::= INTEGER + +*/ + +void my_accept_callback(object f) +{ + werror("Accept!\n"); + conn(f->accept()); +} + +int main() +{ + werror(sprintf("Cert: '%s'\n", Crypto.string_to_hex(my_certificate))); + werror(sprintf("Key: '%s'\n", Crypto.string_to_hex(my_key))); +// werror(sprintf("Decoded cert: %O\n", ber_decode(my_certificate)->get_asn1())); + array key = ber_decode(my_key)->get_asn1()[1]; + werror(sprintf("Decoded key: %O\n", key)); + object n = Gmp.mpz(key[1][1], 256); + object e = Gmp.mpz(key[2][1], 256); + object d = Gmp.mpz(key[3][1], 256); + object p = Gmp.mpz(key[4][1], 256); + object q = Gmp.mpz(key[5][1], 256); + + werror(sprintf("n = %s\np = %s\nq = %s\npq = %s\n", + n->digits(), p->digits(), q->digits(), (p*q)->digits())); + rsa = Crypto.rsa(); + rsa->set_public_key(n, e); + rsa->set_private_key(d); + certificates = ({ my_certificate }); + random = no_random()->read; + werror("Starting\n"); + return bind(PORT, my_accept_callback) ? -17 : 17; +} diff --git a/lib/modules/SSL.pmod/packet.pike b/lib/modules/SSL.pmod/packet.pike new file mode 100644 index 0000000000..0fcbdaa899 --- /dev/null +++ b/lib/modules/SSL.pmod/packet.pike @@ -0,0 +1,115 @@ +/* packet.pike + * + * SSL Record Layer */ + +inherit "constants"; + +constant SUPPORT_V2 = 1; + +int content_type; +array(int) protocol_version; +string fragment; /* At most 2^14 */ + +constant HEADER_SIZE = 5; + +private string buffer; +private int needed_chars; + +int marginal_size; + +// constant Alert = (program) "alert"; +#define Alert ((program) "alert") + +void create(void|int extra) +{ + marginal_size = extra; + buffer = ""; + needed_chars = HEADER_SIZE; + protocol_version = ({ 3, 0 }); +} + +object check_size(int|void extra) +{ + marginal_size = extra; + return (strlen(fragment) > (PACKET_MAX_SIZE + extra)) + ? Alert(ALERT_fatal, ALERT_unexpected_message) : 0; +} + +/* Called with data read from network. + * + * Return string of leftover data if packet is complete, otherwise 0. + * If there's an error, an alert object is returned. + */ + +object|string recv(string data) +{ + buffer += data; + while (strlen(buffer) >= needed_chars) + { +// werror(sprintf("SSL.packet->recv: needed = %d, avail = %d\n", +// needed_chars, strlen(buffer))); + if (needed_chars == HEADER_SIZE) + { + content_type = buffer[0]; + int length; + if (! PACKET_types[content_type] ) + { + if (SUPPORT_V2) + { +// werror(sprintf("SSL.packet: Recieving SSL2 packet '%s'\n", buffer[..4])); + + content_type = PACKET_V2; + if ( (!(buffer[0] & 0x80)) /* Support only short SSL2 headers */ + || (buffer[2] != 1)) + return Alert(ALERT_fatal, ALERT_unexpected_message); + length = ((buffer[0] & 0x7f) << 8 | buffer[1] + - 3); +// werror(sprintf("SSL2 length = %d\n", length)); + protocol_version = values(buffer[3..4]); + } + else + return Alert(ALERT_fatal, ALERT_unexpected_message, + "SSL.packet->recv: invalid type\n", backtrace()); + } else { + protocol_version = values(buffer[1..2]); + sscanf(buffer[3..4], "%2c", length); + if ( (length <= 0) || (length > (PACKET_MAX_SIZE + marginal_size))) + return Alert(ALERT_fatal, ALERT_unexpected_message); + } + if (protocol_version[0] != 3) + return Alert(ALERT_fatal, ALERT_unexpected_message, + sprintf("SSL.packet->send: Version %d is not supported\n", + protocol_version[0]), backtrace()); + if (protocol_version[1] > 0) + werror(sprintf("SSL.packet->recv: recieved version %d.%d packet\n", + @ protocol_version)); + + needed_chars += length; + } else { + if (content_type == PACKET_V2) + fragment = buffer[2 .. needed_chars - 1]; + else + fragment = buffer[HEADER_SIZE .. needed_chars - 1]; + return buffer[needed_chars ..]; + } + } + return 0; +} + +string send() +{ + if (! PACKET_types[content_type] ) + throw( ({ "SSL.packet->send: invalid type", backtrace() }) ); + + if (protocol_version[0] != 3) + throw( ({ sprintf("SSL.packet->send: Version %d is not supported\n", + protocol_version[0]), backtrace() }) ); + if (protocol_version[1] > 0) + werror(sprintf("SSL.packet->send: recieved version %d.%d packet\n", + @ protocol_version)); + if (strlen(fragment) > (PACKET_MAX_SIZE + marginal_size)) + throw( ({ "SSL.packet->send: maximum packet size exceeded\n", + backtrace() }) ); + return sprintf("%c%c%c%2c%s", content_type, @protocol_version, + strlen(fragment), fragment); +} diff --git a/lib/modules/SSL.pmod/queue.pike b/lib/modules/SSL.pmod/queue.pike new file mode 100644 index 0000000000..9e0ba58e97 --- /dev/null +++ b/lib/modules/SSL.pmod/queue.pike @@ -0,0 +1,50 @@ +/* queue.pike + * + * A FIFO queue. Used by connection* + */ + +#define QUEUE_SIZE 100 + +array l; +int head; +int tail; + +void create() +{ + l = allocate(QUEUE_SIZE); + head = tail = 0; +} + +void put(mixed item) +{ + if (head == sizeof(l)) + { + l = l[tail ..]; + head -= tail; + tail = 0; + l += allocate(sizeof(l) + QUEUE_SIZE); + } + l[head++] = item; +// werror(sprintf("Queue->put: %O\n", l[tail..head-1])); +} + +mixed get() +{ +// werror(sprintf("Queue->get: %O\n", l[tail..head-1])); + mixed res; + if (tail == head) + return 0; + res = l[tail]; + l[tail++] = 0; + return res; +} + +mixed peek() +{ + return (tail < head) && l[tail]; +} + +void flush() +{ + create(); +} diff --git a/lib/modules/SSL.pmod/server.pike b/lib/modules/SSL.pmod/server.pike new file mode 100644 index 0000000000..806d3e77f9 --- /dev/null +++ b/lib/modules/SSL.pmod/server.pike @@ -0,0 +1,4 @@ +/* server.pike + * + */ + diff --git a/lib/modules/SSL.pmod/session.pike b/lib/modules/SSL.pmod/session.pike new file mode 100644 index 0000000000..a7be305eac --- /dev/null +++ b/lib/modules/SSL.pmod/session.pike @@ -0,0 +1,131 @@ +/* session.pike + * + */ + +inherit "cipher" : cipher; + +// object server; + +string identity; /* Identifies the session to the server */ +int compression_algorithm; +int cipher_suite; +object cipher_spec; +int ke_method; +string master_secret; /* 48 byte secret shared between client and server */ + +constant Struct = (program) "struct"; +constant State = (program) "state"; + +void set_cipher_suite(int suite) +{ + array res = cipher::lookup(suite); + cipher_suite = suite; + ke_method = res[0]; + cipher_spec = res[1]; +} + +void set_compression_method(int compr) +{ + if (compr != COMPRESSION_null) + throw( ({ "SSL.session->set_compression_method: Method not supported\n", + backtrace() }) ); + compression_algorithm = compr; +} + +string generate_key_block(string client_random, string server_random) +{ + int required = 2 * (cipher_spec->key_material + + cipher_spec->hash_size + + cipher_spec->iv_size); + object sha = mac_sha(); + object md5 = mac_md5(); + int i = 0; + string key = ""; + while (strlen(key) < required) + { + i++; + string cookie = replace(allocate(i), 0, sprintf("%c", 64+i)) * ""; +// werror(sprintf("cookie '%s'\n", cookie)); + key += md5->hash_raw(master_secret + + sha->hash_raw(cookie + master_secret + + server_random + client_random)); + } +// werror(sprintf("key_block: '%s'\n", key)); + return key; +} + +array new_server_states(string client_random, string server_random) +{ + object key_data = Struct(generate_key_block(client_random, server_random)); + object write_state = State(this_object()); + object read_state = State(this_object()); + + write(sprintf("client_random: '%s'\nserver_random: '%s'\n", + client_random, server_random)); + read_state->mac = cipher_spec-> + mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size)); + write_state->mac = cipher_spec-> + mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size)); + read_state->crypt = cipher_spec->bulk_cipher_algorithm(); + read_state->crypt-> + set_decrypt_key(key_data->get_fix_string(cipher_spec->key_material)); + + write_state->crypt = cipher_spec->bulk_cipher_algorithm(); + write_state->crypt-> + set_encrypt_key(key_data->get_fix_string(cipher_spec->key_material)); + + if (cipher_spec->iv_size) + { + read_state->crypt-> + set_iv(key_data->get_fix_string(cipher_spec->iv_size)); + write_state->crypt-> + set_iv(key_data->get_fix_string(cipher_spec->iv_size)); + } + return ({ read_state, write_state }); +} + +array new_client_states(string client_random, string server_random) +{ + object key_data = Struct(generate_key_block(client_random, server_random)); + object write_state = State(this_object()); + object read_state = State(this_object()); + + write_state->mac = cipher_spec-> + mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size)); + read_state->mac = cipher_spec-> + mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size)); + + write_state->crypt = cipher_spec->bulk_cipher_algorithm(); + write_state->crypt-> + set_encrypt_key(key_data->get_fix_string(cipher_spec->key_material)); + + read_state->crypt = cipher_spec->bulk_cipher_algorithm(); + read_state->crypt-> + set_decrypt_key(key_data->get_fix_string(cipher_spec->key_material)); + + if (cipher_spec->iv_size) + { + write_state->crypt-> + set_iv(key_data->get_fix_string(cipher_spec->iv_size)); + read_state->crypt-> + set_iv(key_data->get_fix_string(cipher_spec->iv_size)); + } + return ({ read_state, write_state }); +} + +#if 0 +void create(int is_s, int|void auth) +{ + is_server = is_s; + if (is_server) + { + handshake_state = STATE_SERVER_WAIT_FOR_HELLO; + auth_type = auth || AUTH_none; + } + else + { + handshake_state = STATE_CLIENT_WAIT_FOR_HELLO; + auth_type = auth || AUTH_require; + } +} +#endif diff --git a/lib/modules/SSL.pmod/sslport.pike b/lib/modules/SSL.pmod/sslport.pike new file mode 100644 index 0000000000..47cc3303c8 --- /dev/null +++ b/lib/modules/SSL.pmod/sslport.pike @@ -0,0 +1,234 @@ +/* sslport.pike + * + */ + +inherit Stdio.Port : socket; +inherit "context"; + +function(object:void) accept_callback; + +class sslfile +{ + inherit Stdio.File : socket; + inherit "connection" : connection; + + object context; + + string read_buffer; /* Data that is recieved before there is any + * read_callback */ + string write_buffer; /* Data to be written */ + function(mixed,string:void) read_callback; + function(mixed:void) write_callback; + function(mixed:void) close_callback; + + int connected; /* 1 if the connect callback has been called */ + + private void die(int status) + { + if (status < 0) + { + /* Other end closed without sending a close_notify alert */ + context->purge_session(this_object()); + werror("SSL.sslfile: Killed\n"); + } + if (close_callback) + close_callback(socket::query_id()); + } + + /* Return 0 if the connection is still alive, + * 1 if it was closed politely, and -1 if it died unexpectedly + */ + private int try_write() + { + int|string data = to_write(); +// werror(sprintf("sslport->try_write: %O\n", data)); + if (stringp(data)) + { + write_buffer += data; + if (strlen(write_buffer)) + { + int written = socket::write(write_buffer); + if (written > 0) + write_buffer = write_buffer[written..]; + else + werror("SSL.sslfile->try_write: write failed\n"); + } + return 0; + } + socket::close(); + return data; + } + + void close() + { + send_packet(Alert(ALERT_warning, ALERT_close_notify)); + try_write(); + read_callback = 0; + write_callback = 0; + close_callback = 0; + } + + int write(string s) + { + object packet; + int res; + while(strlen(s)) + { + packet = Packet(); + packet->content_type = PACKET_application_data; + packet->fragment = s[..PACKET_MAX_SIZE-1]; + send_packet(packet); + s = s[PACKET_MAX_SIZE..]; + } + (res = try_write()) && die(res); + } + + string read(mixed ...args) + { + throw( ({ "SSL->sslfile: read() is not supported.\n", backtrace() }) ); + } + + private void ssl_read_callback(mixed id, string s) + { + werror(sprintf("sslfile->ssl_read_callback\n")); + string|int data = got_data(s); + if (stringp(data)) + { + werror(sprintf("sslfile: application_data: '%s'\n", data)); + if (strlen(data)) + { + read_buffer += data; + if (!connected) + { + connected = 1; + context->accept_callback(this_object()); + } + if (read_callback && strlen(read_buffer)) + { + read_callback(id, read_buffer + data); + read_buffer = ""; + } + } + } + else + { + if (data < 0) + /* Fatal error, remove from session cache */ + context->purge_session(this_object()); + } + try_write() || (close_callback && close_callback()); + } + + private void ssl_write_callback(mixed id) + { + werror("SSL.sslport: ssl_write_callback\n"); + int res; + if ( !(res = try_write()) && !strlen(write_buffer) + && handshake_finished && write_callback) + { + write_callback(id); + res = try_write(); + } + if (res) + die(res); + } + + private void ssl_close_callback(mixed id) + { + werror("SSL.sslport: ssl_close_callback\n"); + socket::close(); + die(-1); + } + + void set_read_callback(function(mixed,string:void) r) + { + read_callback = r; + } + + void set_write_callback(function(mixed:void) w) + { + write_callback = w; + } + + void set_close_callback(function(mixed:void) c) + { + close_callback = c; + } + + void set_nonblocking(function ...args) + { + switch (sizeof(args)) + { + case 0: + break; + case 3: + set_read_callback(args[0]); + set_write_callback(args[1]); + set_close_callback(args[2]); + break; + default: + throw( ({ "SSL.sslfile->set_blocking: Wrong number of arguments\n", + backtrace() }) ); + } + } + + void set_blocking() + { + throw( ({ "SSL.sslfile->set_blocking: Not supported\n", + backtrace() }) ); + } + + object accept() + { + /* Dummy method, for compatibility with Stdio.Port */ + return this_object(); + } + + void create(object f, object c) + { + context = c; + read_buffer = write_buffer = ""; + socket::assign(f); + socket::set_nonblocking(ssl_read_callback, ssl_write_callback, ssl_close_callback); + connection::create(1); + } +} + +void ssl_callback(mixed id) +{ + object f = id->socket_accept(); + if (f) + sslfile(f, this_object()); +} + +void set_id(mixed id) +{ + throw( ({ "SSL.sslport->set_id: Not supported\n", backtrace() }) ); +} + +mixed query_id() +{ + throw( ({ "SSL.sslport->query_id: Not supported\n", backtrace() }) ); +} + +int bind(int port, function callback, string|void ip) +{ + accept_callback = callback; + return socket::bind(port, ssl_callback, ip); +} + +int listen_fd(int fd, function callback) +{ + accept_callback = callback; + return socket::listen_fd(fd, callback); +} + +object socket_accept() +{ + return socket::accept(); +} + +int accept() +{ + throw( ({ "SSL.sslport->accept: Not supported\n", backtrace() }) ); +} diff --git a/lib/modules/SSL.pmod/state.pike b/lib/modules/SSL.pmod/state.pike new file mode 100644 index 0000000000..13e19c194a --- /dev/null +++ b/lib/modules/SSL.pmod/state.pike @@ -0,0 +1,94 @@ +/* state.pike + * + */ + +inherit "constants"; + +object session; + +// string my_random; +// string other_random; +object mac; +object crypt; +object compress; + +object(Gmp.mpz) seq_num; /* Bignum, values 0, .. 2^64-1 are valid */ + +constant Alert = (program) "alert"; + +void create(object s) +{ + session = s; + seq_num = Gmp.mpz(0); +} + + +/* Destructively decrypt a packet. Returns an Alert object if + * there was an error, otherwise 0. */ +object decrypt_packet(object packet) +{ + if (crypt) + { + string msg; +// werror("Trying decrypt..\n"); + msg = crypt->crypt(packet->fragment); + if (! msg) + return Alert(ALERT_fatal, ALERT_unexpected_message); + if (session->cipher_spec->cipher_type == CIPHER_block) + if (catch { msg = crypt->unpad(msg); }) + return Alert(ALERT_fatal, ALERT_unexpected_message); + packet->fragment = msg; + } + +// werror(sprintf("Decrypted_packet '%s'\n", packet->fragment)); + + if (mac) + { +// werror("Trying mac verification...\n"); + int length = strlen(packet->fragment) - session->cipher_spec->hash_size; + string digest = packet->fragment[length ..]; + packet->fragment = packet->fragment[.. length - 1]; + + if (digest != mac->hash(packet, seq_num)) + return Alert(ALERT_fatal, ALERT_bad_record_mac); + seq_num += 1; + } + + if (compress) + { +// werror("Trying decompression...\n"); + string msg; + msg = compress(packet->fragment); + if (!msg) + return Alert(ALERT_fatal, ALERT_unexpected_message); + packet->fragment = msg; + } + return packet->check_size() || packet; +} + +object encrypt_packet(object packet) +{ + string digest; + + if (compress) + { + packet->fragment = compress(packet->fragment); + } + + if (mac) + digest = mac->hash(packet, seq_num); + else + digest = ""; + seq_num += 1; + + if (crypt) + { + packet->fragment = crypt->crypt(packet->fragment + digest); + if (session->cipher_spec->cipher_type == CIPHER_block) + packet->fragment += crypt->pad(); + } + else + packet->fragment += digest; + + return packet->check_size(2048) || packet; +} diff --git a/lib/modules/SSL.pmod/struct.pike b/lib/modules/SSL.pmod/struct.pike new file mode 100644 index 0000000000..e6062ddbe2 --- /dev/null +++ b/lib/modules/SSL.pmod/struct.pike @@ -0,0 +1,160 @@ +/* struct.pike + * + * Read and write structures from strings. + */ + +string buffer; +int index; + +void create(void|string s) +{ + buffer = s || ""; + index = 0; +} + +void add_data(string s) +{ + buffer += s; +} + +string pop_data() +{ + string res = buffer; + create(); + return res; +} + +void put_int(int i, int len) +{ + add_data(sprintf("%*c", len, i)); +} + +void put_var_string(string s, int len) +{ + put_int(strlen(s), len); + add_data(s); +} + +void put_fix_string(string s) +{ + add_data(s); +} + +void put_fix_array(array(int) data, int item_size) +{ + foreach(data, int i) + put_int(i, item_size); +} + +void put_var_array(array(int) data, int item_size, int len) +{ + put_int(sizeof(data), len); + put_fix_array(data, item_size); +} + +mixed get_int(int len) +{ + mixed i; + if ( (strlen(buffer) - index) < len) + throw( ({ "SSL.struct->get_int: no data\n", backtrace() }) ); + if (len <= 3) + { + sscanf(buffer, "%*" + (string) index +"s%" + (string) len + "c", i); + } + else + i = Gmp.mpz(buffer[index .. index+len-1], 256); + index += len; + return i; +} + +string get_fix_string(int len) +{ + string res; + + if ((strlen(buffer) - index) < len) + throw( ({ "SSL.struct->get_fix_string: no data\n", backtrace() }) ); + res = buffer[index .. index + len - 1]; + index += len; + return res; +} + +string get_var_string(int len) +{ + return get_fix_string(get_int(len)); +} + +array(mixed) get_fix_array(int item_size, int size) +{ + array(mixed) res = allocate(size); + for(int i = 0; i<size; i++) + res[i] = get_int(item_size); + return res; +} + +array(mixed) get_var_array(int item_size, int len) +{ + return get_fix_array(item_size, get_int(len)); +} + +int is_empty() +{ + return (index == strlen(buffer)); +} + +#if 0 + +constant FIELD_int = 1; +constant FIELD_string = 2; + +class field +{ + string name; /* Name of field */ + int type; + int len; /* For integers: length, + * for strings: index to the field that holds the length */ +} + + + +array(object(field)) description; +array(function) conversions; + +class parser +{ + string buffer = ""; + object info; + int field; /* Field being read */ + object o; /* The object to fill in */ + object me; + + mapping(int:function(string, object:void)) conversions; + + create(object i) + { + info = i; + o = i->prog(); + me = this_object(); + } + + object|string recv(string data) + { + buffer += data; + while(strlen(buffer) >= info->needed_chars[field]) + { + object err; + if (err = info->conversions[field](me)) + return err; + field++; + if (field >= sizeof(info->description)) + return buffer[info->needed_chars[field-1]..]; + } + return 0; + } +} + +array compile() +{ + conversions = + + +#endif -- GitLab