pikelang/Pike

View on GitHub
lib/7.8/modules/SSL.pmod/connection.pike

Summary

Maintainability
Test Coverage
#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);
}

//! 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