lib/7.8/modules/SSL.pmod/context.pike
#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