Branch: Tag:

2014-05-02

2014-05-02 13:23:21 by Henrik Grubbström (Grubba) <grubba@grubba.org>

Compat: Added 7.8::SSL.

This is based on the last version of SSL on the 8.0 branch
that was still compatible with Pike 7.8 (ie before the PEM
API changes).

Intended use is Crypto API compatibility testing, though it
is probably more competent than the current Pike 7.8 SSL.

1: + #pike 7.8 + //#pragma strict_types    -  + //! SSL packet layer. + //! + //! @[SSL.connection] inherits @[SSL.handshake], and in addition to the state in + //! the handshake super class, it contains the current read and write + //! states, packet queues. This object is responsible for receiving and + //! sending packets, processing handshake packets, and providing a clear + //! text packages for some application. +  + // SSL/TLS Protocol Specification documents: + // + // SSL 2 http://wp.netscape.com/eng/security/SSL_2.html + // SSL 3.0 http://wp.netscape.com/eng/ssl3/draft302.txt + // (aka draft-freier-ssl-version3-02.txt). + // TLS 1.0 (SSL 3.1) RFC 2246 "The TLS Protocol Version 1.0". + // TLS 1.1 (SSL 3.2) draft-ietf-tls-rfc2246-bis + // Renegotiation RFC 5746 "Renegotiation Indication Extension". +  + #if constant(SSL.Cipher.CipherAlgorithm) +  + .state current_read_state; + .state current_write_state; + string left_over; + Packet packet; +  + int dying; + int closing; // Bitfield: 1 if a close is sent, 2 of one is received. +  + function(object,int|object,string:void) alert_callback; +  + import .Constants; +  + inherit .handshake; +  + #ifdef SSL3_DEBUG + #define SSL3_DEBUG_MSG(X ...) werror(X) + #else /*! SSL3_DEBUG */ + #define SSL3_DEBUG_MSG(X ...) + #endif /* SSL3_DEBUG */ +  + constant PRI_alert = 1; + constant PRI_urgent = 2; + constant PRI_application = 3; +  + inherit ADT.Queue : alert; + inherit ADT.Queue : urgent; + inherit ADT.Queue : application; +  + void create(int is_server, void|SSL.context ctx, +  void|ProtocolVersion min_version, +  void|ProtocolVersion max_version) + { +  alert::create(); +  urgent::create(); +  application::create(); +  current_read_state = SSL.state(this); +  current_write_state = SSL.state(this); +  handshake::create(is_server, ctx, min_version, max_version); + } +  + #if 0 + protected void destroy() + { +  werror("Connection destructed:\n%s\n", describe_backtrace(backtrace())); + } + #endif /* 0 */ +  + //! Called with alert object, sequence number of bad packet, + //! and raw data as arguments, if a bad packet is received. + //! + //! Can be used to support a fallback redirect https->http. + void set_alert_callback(function(object,int|object,string:void) callback) + { +  alert_callback = callback; + } +  + //! Low-level receive handler. Returns a packet, an alert, or zero if + //! more data is needed to get a complete packet. + protected object recv_packet(string data) + { +  mixed res; +  +  // SSL3_DEBUG_MSG("SSL.connection->recv_packet(%O)\n", data); +  if (left_over || !packet) +  { +  packet = Packet(2048); +  res = packet->recv( (left_over || "") + data, version[1]); +  } +  else +  res = packet->recv(data, version[1]); +  +  if (stringp(res)) +  { /* Finished a packet */ +  left_over = [string]res; +  if (current_read_state) { +  SSL3_DEBUG_MSG("Decrypting packet.. version[1]="+version[1]+"\n"); +  return current_read_state->decrypt_packet(packet,version[1]); +  } else { +  SSL3_DEBUG_MSG("SSL.connection->recv_packet(): current_read_state is zero!\n"); +  return 0; +  } +  } +  else /* Partial packet read, or error */ +  left_over = 0; +  return res; + } +  + //! Queues a packet for write. Handshake and and change cipher + //! must use the same priority, so must application data and + //! close_notifies. + void send_packet(object packet, int|void priority) + { +  if (closing & 1) { +  SSL3_DEBUG_MSG("SSL.connection->send_packet: ignoring packet after close\n"); +  return; +  } +  +  if (packet->content_type == PACKET_alert && +  packet->description == ALERT_close_notify) +  closing |= 1; +  + #ifdef SSL3_FRAGDEBUG +  werror(" SSL.connection->send_packet: sizeof(packet)="+sizeof(packet)+"\n"); + #endif +  if (!priority) +  priority = ([ PACKET_alert : PRI_alert, +  PACKET_change_cipher_spec : PRI_urgent, +  PACKET_handshake : PRI_urgent, +  PACKET_application_data : PRI_application ])[packet->content_type]; +  SSL3_DEBUG_MSG("SSL.connection->send_packet: type %d, desc %d, pri %d, %O\n", +  packet->content_type, packet->description, priority, +  packet->fragment[..5]); +  switch (priority) +  { +  default: +  error( "Internal error\n" ); +  case PRI_alert: +  alert::put(packet); +  break; +  case PRI_urgent: +  urgent::put(packet); +  break; +  case PRI_application: +  application::put(packet); +  break; +  } +  + } +  + //! Extracts data from the packet queues. Returns a string of data + //! to be written, "" if there are no pending packets, 1 of the + //! connection is being closed politely, and -1 if the connection + //! died unexpectedly. + //! + //! This function is intended to be called from an i/o write callback. + string|int to_write() + { +  if (dying) +  return -1; +  +  object packet = alert::get() || urgent::get() || application::get(); +  if (!packet) { +  return closing ? 1 : ""; +  } +  +  SSL3_DEBUG_MSG("SSL.connection: writing packet of type %d, %O\n", +  packet->content_type, packet->fragment[..6]); +  if (packet->content_type == PACKET_alert) +  { +  if (packet->level == ALERT_fatal) +  dying = 1; +  } +  string res = current_write_state->encrypt_packet(packet,version[1])->send(); +  if (packet->content_type == PACKET_change_cipher_spec) +  current_write_state = pending_write_state; +  return res; + } +  + //! Initiate close. + void send_close() + { +  send_packet(Alert(ALERT_warning, ALERT_close_notify, version[1]), +  PRI_application,); + } +  + //! Send an application data packet. If the data block is too large + //! then as much as possible of the beginning of it is sent. The size + //! of the sent data is returned. + int send_streaming_data (string data) + { +  Packet packet = Packet(); +  packet->content_type = PACKET_application_data; +  int size = sizeof ((packet->fragment = data[..PACKET_MAX_SIZE-1])); +  send_packet (packet); +  return size; + } +  + 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, version[1], +  "SSL.connection->handle_alert: invalid alert\n", backtrace())); +  return -1; +  } +  if (level == ALERT_fatal) +  { +  SSL3_DEBUG_MSG("SSL.connection: Fatal alert %d\n", description); +  return -1; +  } +  if (description == ALERT_close_notify) +  { +  SSL3_DEBUG_MSG("SSL.connection: Close notify alert %d\n", description); +  closing |= 2; +  return 1; +  } +  if (description == ALERT_no_certificate) +  { +  SSL3_DEBUG_MSG("SSL.connection: No certificate alert %d\n", description); +  +  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), version[1])); +  return -1; +  } +  } + #ifdef SSL3_DEBUG +  else +  werror("SSL.connection: Received warning alert %d\n", description); + #endif +  return 0; + } +  + int handle_change_cipher(int c) + { +  if (!expect_change_cipher || (c != 1)) +  { +  SSL3_DEBUG_MSG("SSL.connection: handle_change_cipher: Unexcepted message!"); +  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, version[1])); +  return -1; +  } +  else +  { +  current_read_state = pending_read_state; +  expect_change_cipher = 0; +  return 0; +  } + } +  + string alert_buffer = ""; + string handshake_buffer = ""; +  + //! Main receive handler. Returns a string of received application + //! data, or 1 if a close was received, or -1 if an error occurred. + //! + //! This function is intended to be called from an i/o read callback. + string|int got_data(string|int s) + { +  if(!stringp(s)) { +  return s; +  } +  +  if (closing & 2) { +  return 1; +  } +  // If closing == 1 we continue to try to read a remote close +  // message. That enables the caller to check for a clean close, and +  // to get the leftovers after the SSL connection. +  +  /* If alert_callback is called, this data is passed as an argument */ +  string alert_context = (left_over || "") + s; +  +  string res = ""; +  object packet; +  while (packet = recv_packet(s)) +  { +  s = ""; +  +  if (packet->is_alert) +  { /* Reply alert */ +  SSL3_DEBUG_MSG("SSL.connection: Bad received packet\n"); +  send_packet(packet); +  if (alert_callback) +  alert_callback(packet, current_read_state->seq_num, alert_context); +  if ((!packet) || (!this) || (packet->level == ALERT_fatal)) +  return -1; +  if (alert_callback) +  break; +  } +  else +  { +  SSL3_DEBUG_MSG("SSL.connection: received packet of type %d\n", +  packet->content_type); +  switch (packet->content_type) +  { +  case PACKET_alert: +  { +  SSL3_DEBUG_MSG("SSL.connection: ALERT\n"); +  +  int i; +  int err = 0; +  alert_buffer += packet->fragment; +  for (i = 0; +  !err && ((sizeof(alert_buffer) - i) >= 2); +  i+= 2) +  err = handle_alert(alert_buffer[i..i+1]); +  +  alert_buffer = alert_buffer[i..]; +  if (err) +  if (err > 0 && sizeof (res)) +  // If we get a close then we return the data we got so far. +  return res; +  else +  return err; +  break; +  } +  case PACKET_change_cipher_spec: +  { +  SSL3_DEBUG_MSG("SSL.connection: CHANGE_CIPHER_SPEC\n"); +  +  int i; +  int err; +  for (i = 0; (i < sizeof(packet->fragment)); i++) +  { +  err = handle_change_cipher(packet->fragment[i]); +  SSL3_DEBUG_MSG("tried change_cipher: %d\n", err); +  if (err) +  return err; +  } +  break; +  } +  case PACKET_handshake: +  { +  SSL3_DEBUG_MSG("SSL.connection: HANDSHAKE\n"); +  +  if (handshake_finished && !secure_renegotiation) { +  // Don't allow renegotiation in unsecure mode, to address +  // http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-3555. +  // For details see: http://www.g-sec.lu/practicaltls.pdf and +  // RFC 5746. +  send_packet (Alert (ALERT_warning, ALERT_no_renegotiation, +  version[1])); +  return -1; +  } +  if (expect_change_cipher) +  { +  /* No change_cipher message was received */ +  // FIXME: There's a bug somewhere since expect_change_cipher often +  // remains set after the handshake is completed. The effect is that +  // renegotiation doesn't work all the time. +  // +  // A side effect is that we are partly invulnerable to the +  // renegotiation vulnerability mentioned above. It is however not +  // safe to assume that, since there might be routes past this, +  // maybe through the use of a version 2 hello message below. +  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, +  version[1])); +  return -1; +  } +  int err, len; +  handshake_buffer += packet->fragment; +  +  while (sizeof(handshake_buffer) >= 4) +  { +  sscanf(handshake_buffer, "%*c%3c", len); +  if (sizeof(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: +  SSL3_DEBUG_MSG("SSL.connection: APPLICATION_DATA\n"); +  +  if (!handshake_finished) +  { +  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message, version[1])); +  return -1; +  } +  res += packet->fragment; +  break; +  case PACKET_V2: +  { +  SSL3_DEBUG_MSG("SSL.connection: V2\n"); +  +  if (handshake_finished) { +  // Don't allow renegotiation using SSLv2 packets at all, to address +  // http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-3555. +  send_packet (Alert (ALERT_warning, ALERT_no_renegotiation, +  version[1])); +  return -1; +  } +  int err = handle_handshake(HANDSHAKE_hello_v2, +  packet->fragment[1 .. ], +  packet->fragment); +  // FIXME: Can err ever be 1 here? In that case we're probably +  // not returning the right value below. +  if (err) +  return err; +  } +  } +  } +  } +  return closing & 2 ? 1 : res; + } +  + #else // constant(SSL.Cipher.CipherAlgorithm) + constant this_program_does_not_exist = 1; + #endif   Newline at end of file added.