pikelang/Pike

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

Summary

Maintainability
Test Coverage
#pike 7.8
#pragma no_deprecation_warnings
//#pragma strict_types

//! Keeps the state that is shared by all SSL-connections for
//! one server (or one port). It includes policy configuration, a server
//! certificate, the server's private key(s), etc. It also includes the
//! session cache.

#ifdef SSL3_DEBUG
#define SSL3_DEBUG_MSG(X ...)  werror(X)
#else /*! SSL3_DEBUG */
#define SSL3_DEBUG_MSG(X ...)
#endif /* SSL3_DEBUG */

#if constant(Gmp.mpz) && constant(Crypto.Hash)

import .Constants;

//! The server's default private key
//!
//! @note
//!   If SNI (Server Name Indication) is used and multiple keys
//!   are available, this key will not be used, instead the appropriate
//!   SNI key will be used (the default implementation stores these in
//!   @[sni_keys].
Crypto.RSA rsa;

//! Should an SSL client include the Server Name extension?
//!
//! If so, then client_server_names should specify the values to
//! send.
int client_use_sni = 0;

//! Host names to send to the server when using the Server Name extension.
array(string) client_server_names = ({});

/* For client authentication */

//! The client's private key (used with client certificate authentication)
Crypto.RSA client_rsa;

//! An array of certificate chains a client may present to a server
//! when client certificate authentication is requested.
array(array(string)) client_certificates = ({});

//! A function which will select an acceptable client certificate for
//! presentation to a remote server. This function will receive
//! the SSL context, an array of acceptable certificate types,
//! and a list of DNs of acceptable certificate authorities. This function
//! should return an array of strings containing a certificate chain,
//! with the client certificate first, (and the root certificate last, if
//! applicable.)
function (.context,array(int),array(string):array(string))client_certificate_selector
  = internal_select_client_certificate;

//! A function which will select an acceptable server certificate for
//! presentation to a client. This function will receive
//! the SSL context, and an array of server names, if provided by the
//! client. This function should return an array of strings containing a
//! certificate chain, with the client certificate first, (and the root
//! certificate last, if applicable.)
//!
//! The default implementation will select a certificate chain for a given server
//! based on values contained in @[sni_certificates].
function (.context,array(string):array(string)) select_server_certificate_func
  = internal_select_server_certificate;

//! A function which will select an acceptable server key for
//! presentation to a client. This function will receive
//! the SSL context, and an array of server names, if provided by the
//! client. This function should return an object matching the certificate
//! for the server hostname.
//!
//! The default implementation will select the key for a given server
//! based on values contained in @[sni_keys].
function (.context,array(string):object) select_server_key_func
  = internal_select_server_key;

//! Policy for client authentication. One of @[SSL.Constants.AUTHLEVEL_none],
//! @[SSL.Constants.AUTHLEVEL_ask] and @[SSL.Constants.AUTHLEVEL_require].
int auth_level;

//! Array of authorities that are accepted for client certificates.
//! The server will only accept connections from clients whose certificate
//! is signed by one of these authorities. The string is a DER-encoded certificate,
//! which typically must be decoded using @[MIME.decode_base64] or
//! @[Tools.PEM.Msg] first.
//!
//! Note that it is presumed that the issuer will also be trusted by the server. See
//! @[trusted_issuers] for details on specifying trusted issuers.
//!
//! If empty, the server will accept any client certificate whose issuer is trusted by the
//! server.
void set_authorities(array(string) a)
{
  authorities = a;
  update_authorities();
}

//! When set, require the chain to be known, even if the root is self signed.
//!
//! Note that if set, and certificates are set to be verified, trusted issuers must be
//! provided, or no connections will be accepted.
int require_trust=0;

//! Get the list of allowed authorities. See @[set_authorities].
array(string) get_authorities()
{
  return authorities;
}

protected array(string) authorities = ({});
array(Tools.X509.TBSCertificate) authorities_cache = ({});

//! Sets the list of trusted certificate issuers.
//!
//! @param a
//!
//! An array of certificate chains whose root is self signed (ie a root issuer), and whose
//! final certificate is an issuer that we trust. The root of the certificate should be
//! first certificate in the chain. The string is a DER-encoded
//! certificate, which typically must be decoded using
//! @[MIME.decode_base64] or @[Tools.PEM.Msg] first.
//!
//! If this array is left empty, and the context is set to verify certificates,
//! a certificate chain must have a root that is self signed.
void set_trusted_issuers(array(array(string))  i)
{
  trusted_issuers = i;
  update_trusted_issuers();
}

//! Get the list of trusted issuers. See @[set_trusted_issuers].
array(array(string)) get_trusted_issuers()
{
  return trusted_issuers;
}

protected array(array(string)) trusted_issuers = ({});
array(array(Tools.X509.TBSCertificate)) trusted_issuers_cache = ({});

//! Determines whether certificates presented by the peer are verified, or
//! just accepted as being valid.
int verify_certificates = 0;

//! Temporary, non-certified, private keys, used with a
//! server_key_exchange message. The rules are as follows:
//!
//! If the long_rsa is not zero its public part will be sent. If it is
//! zero and short_rsa is set, its public part will be sent instead.
//! If they are both zero, no server_key_exchange message is sent.
Crypto.RSA long_rsa;
Crypto.RSA short_rsa;

//! Servers default dsa key.
//!
//! @note
//!   If SNI (Server Name Indication) is used and multiple keys
//!   are available, this key will not be used, instead the appropriate
//!   SNI key will be used (the default implementation stores these in
//!   @[sni_keys].
Crypto.DSA dsa;

//! Parameters for dh keyexchange.
.Cipher.DHParameters dh_params;

//! Used to generate random cookies for the hello-message. If we use
//! the RSA keyexchange method, and this is a server, this random
//! number generator is not used for generating the master_secret.
function(int:string) random;

//! The server's certificate, or a chain of X509.v3 certificates, with the
//! server's certificate first and root certificate last.
array(string) certificates;

//! A mapping containing certificate chains for use by SNI (Server Name
//! Indication). Each entry should consist of a key indicating the server
//! hostname and the value containing the certificate chain for that hostname.
mapping(string:array(string)) sni_certificates = ([]);

//! A mapping containing private keys for use by SNI (Server Name
//! Indication). Each entry should consist of a key indicating the server
//! hostname and the value containing the private key object for that hostname.
//!
//! @note
//!  keys objects may be generated from a decoded key string using
//!  @[Standards.PKCS.RSA.parse_private_key()].
mapping(string:object) sni_keys = ([]);

//! For client authentication. Used only if auth_level is AUTH_ask or
//! AUTH_require.
array(int) preferred_auth_methods =
({ AUTH_rsa_sign });

//! Cipher suites we want to support, in order of preference, best first.
array(int) preferred_suites;

//! List of advertised protocols using using TLS next protocol negotiation.
array(string) advertised_protocols;

//! Protocols to advertise during handshake using the next protocol
//! negotiation extension. Currently only used together with spdy.
void advertise_protocols(string ... protos) {
    advertised_protocols = protos;
}

//! Filter cipher suites from @[preferred_suites] that don't have
//! a key with an effective length of at least @[min_keylength] bits.
void filter_weak_suites(int min_keylength)
{
  if (!preferred_suites || !min_keylength) return;
  preferred_suites =
    filter(preferred_suites,
       lambda(int suite) {
         array(int) def = [array(int)]CIPHER_SUITES[suite];
         return def && (CIPHER_algorithms[def[1]] >= min_keylength);
       });
}

//! Set @[preferred_suites] to RSA based methods.
//!
//! @param min_keylength
//!   Minimum acceptable key length in bits.
//!
//! @seealso
//!   @[dhe_dss_mode()], @[filter_weak_suites()]
void rsa_mode(int|void min_keylength)
{
  SSL3_DEBUG_MSG("SSL.context: rsa_mode()\n");
  preferred_suites = preferred_rsa_suites;
  filter_weak_suites(min_keylength);
}

//! Set @[preferred_suites] to DSS based methods.
//!
//! @param min_keylength
//!   Minimum acceptable key length in bits.
//!
//! @seealso
//!   @[rsa_mode()], @[filter_weak_suites()]
void dhe_dss_mode(int|void min_keylength)
{
  SSL3_DEBUG_MSG("SSL.context: dhe_dss_mode()\n");
  preferred_suites = preferred_dhe_dss_suites;
  filter_weak_suites(min_keylength);
}

//! Always ({ COMPRESSION_null })
array(int) preferred_compressors =
({ COMPRESSION_null });

//! Non-zero to enable cahing of sessions
int use_cache = 1;

//! Sessions are removed from the cache when they are older than this
//! limit (in seconds). Sessions are also removed from the cache if a
//! connection using the session dies unexpectedly.
int session_lifetime = 600;

//! Maximum number of sessions to keep in the cache.
int max_sessions = 300;

/* Session cache */
ADT.Queue active_sessions;  /* Queue of pairs (time, id), in cronological order */
mapping(string:.session) session_cache;

int session_number; /* Incremented for each session, and used when constructing the
             * session id */

// Remove sessions older than @[session_lifetime] from the session cache.
void forget_old_sessions()
{
  int t = time() - session_lifetime;
  array pair;
  while ( (pair = [array]active_sessions->peek())
      && (pair[0] < t)) {
    SSL3_DEBUG_MSG("SSL.context->forget_old_sessions: "
                   "garbing session %O due to session_lifetime limit\n",
                   pair[1]);
    m_delete (session_cache, [string]([array]active_sessions->get())[1]);
  }
}

//! Lookup a session identifier in the cache. Returns the
//! corresponding session, or zero if it is not found or caching is
//! disabled.
.session lookup_session(string id)
{
  if (use_cache)
  {
    return session_cache[id];
  }
  else
    return 0;
}

//! Create a new session.
.session new_session()
{
  .session s = .session();
  s->identity = (use_cache) ? sprintf("%4cPikeSSL3%4c",
                      time(), session_number++) : "";
  return s;
}

//! Add a session to the cache (if caching is enabled).
void record_session(.session s)
{
  if (use_cache && s->identity)
  {
    while (sizeof (active_sessions) >= max_sessions) {
      array pair = [array] active_sessions->get();
      SSL3_DEBUG_MSG("SSL.context->record_session: "
                     "garbing session %O due to max_sessions limit\n", pair[1]);
      m_delete (session_cache, [string]pair[1]);
    }
    forget_old_sessions();
    SSL3_DEBUG_MSG("SSL.context->record_session: caching session %O\n",
                   s->identity);
    active_sessions->put( ({ time(), s->identity }) );
    session_cache[s->identity] = s;
  }
}

//! Remove a session from the cache.
void purge_session(.session s)
{
  SSL3_DEBUG_MSG("SSL.context->purge_session: %O\n", s->identity || "");
  if (s->identity)
    m_delete (session_cache, s->identity);
  /* There's no need to remove the id from the active_sessions queue */
}

void create()
{
  SSL3_DEBUG_MSG("SSL.context->create\n");
  active_sessions = ADT.Queue();
  session_cache = ([ ]);
  /* Backwards compatibility */
  rsa_mode(128);
}

// update the cached decoded authorities list
private void update_authorities()
{
  authorities_cache=({});
  foreach(authorities, string a)
  {
    authorities_cache += ({ Tools.X509.decode_certificate(a)});
  }

}

private array(string) internal_select_server_certificate(.context context,
  array(string) server_names)
{
  array(string) certs;

  if(server_names && sizeof(server_names))
  {
    foreach(server_names;; string sn)
    {
      if(context->sni_certificates && (certs = context->sni_certificates[lower_case(sn)]))
        return certs;
    }
  }

  return 0;
}

private object internal_select_server_key(.context context,
  array(string) server_names)
{
  object key;

  if(server_names && sizeof(server_names))
  {
    foreach(server_names;; string sn)
    {
      if(context->sni_keys && (key = context->sni_keys[lower_case(sn)]))
        return key;
    }
  }

  return 0;
}

// FIXME: we only really know that RSA and DSS keys will get caught here.
private array(string) internal_select_client_certificate(.context context,
  array(int) acceptable_types, array(string) acceptable_authority_dns)
{
  if(!context->client_certificates||
         ![int]sizeof((context->client_certificates)))
    return ({});

  array(mapping(string:mixed)) c = ({});
  int i = 0;
  foreach(context->client_certificates, array(string) chain)
  {
    if(!sizeof(chain)) { i++; continue; }

    c += ({ (["cert":Tools.X509.decode_certificate(chain[0]), "chain":i ]) });
    i++;
  }

  string wantedtype;
  mapping cert_types = ([1:"rsa", 2:"dss", 3:"rsa_fixed_dh", 4:"dss_fixed_dh"]);
  foreach(acceptable_types, int t)
  {
      wantedtype= [string]cert_types[t];

      foreach(c, mapping(string:mixed) cert)
      {
        object crt = [object](cert->cert);
        if((string)((object)(crt->public_key))->type == "rsa")
          return context->client_certificates[[int](cert->chain)];
      }
  }
  // FIXME: Check acceptable_authority_dns.
  acceptable_authority_dns;
}

// update the cached decoded issuers list
private void update_trusted_issuers()
{
  trusted_issuers_cache=({});
  foreach(trusted_issuers, array(string) i)
  {
    array(array) chain = ({});
    // make sure the chain is valid and intact.
    mapping result = Tools.X509.verify_certificate_chain(i, ([]), 0);

    if(!result->verified)
      error("Broken trusted issuer chain!\n");

    foreach(i, string chain_element)
    {
      chain += ({ Tools.X509.decode_certificate(chain_element) });
    }
    trusted_issuers_cache += ({ chain });
  }
}

#else // constant(Gmp.mpz) && constant(Crypto.Hash)
constant this_program_does_not_exist = 1;
#endif