zeek/scripts/policy/protocols/ssl/known-certs.zeek

203 lines
5.5 KiB
Text

##! Log information about certificates while attempting to avoid duplicate
##! logging.
@load base/utils/directions-and-hosts
@load base/protocols/ssl
@load base/files/x509
@load base/frameworks/cluster
module Known;
export {
redef enum Log::ID += { CERTS_LOG };
global log_policy_certs: Log::PolicyHook;
type CertsInfo: record {
## The timestamp when the certificate was detected.
ts: time &log;
## The address that offered the certificate.
host: addr &log;
## If the certificate was handed out by a server, this is the
## port that the server was listening on.
port_num: port &log &optional;
## Certificate subject.
subject: string &log &optional;
## Certificate issuer subject.
issuer_subject: string &log &optional;
## Serial number for the certificate.
serial: string &log &optional;
};
## The certificates whose existence should be logged and tracked.
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS.
option cert_tracking = LOCAL_HOSTS;
## Toggles between different implementations of this script.
## When true, use a Broker data store, else use a regular Zeek set
## with keys uniformly distributed over proxy nodes in cluster
## operation.
const use_cert_store = F &redef;
type AddrCertHashPair: record {
host: addr;
hash: string;
};
## Holds the set of all known certificates. Keys in the store are of
## type :zeek:type:`Known::AddrCertHashPair` and their associated value is
## always the boolean value of "true".
global cert_store: Cluster::StoreInfo;
## The Broker topic name to use for :zeek:see:`Known::cert_store`.
const cert_store_name = "zeek/known/certs" &redef;
## The expiry interval of new entries in :zeek:see:`Known::cert_store`.
## This also changes the interval at which certs get logged.
option cert_store_expiry = 1day;
## The timeout interval to use for operations against
## :zeek:see:`Known::cert_store`.
option cert_store_timeout = 15sec;
## The set of all known certificates to store for preventing duplicate
## logging. It can also be used from other scripts to
## inspect if a certificate has been seen in use. The string value
## in the set is for storing the DER formatted certificate' SHA1 hash.
##
## In cluster operation, this set is uniformly distributed across
## proxy nodes.
global certs: set[addr, string] &create_expire=1day &redef;
## Event that can be handled to access the loggable record as it is sent
## on to the logging framework.
global log_known_certs: event(rec: CertsInfo);
}
event zeek_init()
{
if ( ! Known::use_cert_store )
return;
Known::cert_store = Cluster::create_store(Known::cert_store_name);
}
event Known::cert_found(info: CertsInfo, hash: string)
{
if ( ! Known::use_cert_store )
return;
local key = AddrCertHashPair($host = info$host, $hash = hash);
when [info, key] ( local r = Broker::put_unique(Known::cert_store$store, key,
T, Known::cert_store_expiry) )
{
if ( r$status == Broker::SUCCESS )
{
if ( r$result as bool )
Log::write(Known::CERTS_LOG, info);
}
else
Reporter::error(fmt("%s: data store put_unique failure",
Known::cert_store_name));
}
timeout Known::cert_store_timeout
{
# Can't really tell if master store ended up inserting a key.
Log::write(Known::CERTS_LOG, info);
}
}
const should_log_cert =
! Cluster::is_enabled() ||
Cluster::local_node_type() == Cluster::PROXY;
event known_cert_add(info: CertsInfo, hash: string)
{
if ( Known::use_cert_store )
return;
if ( [info$host, hash] in Known::certs )
return;
add Known::certs[info$host, hash];
if ( should_log_cert )
Log::write(Known::CERTS_LOG, info);
}
event Known::cert_found(info: CertsInfo, hash: string)
{
if ( Known::use_cert_store )
return;
if ( [info$host, hash] in Known::certs )
return;
local key = cat(info$host, hash);
Cluster::publish_hrw(Cluster::proxy_pool, key, known_cert_add, info, hash);
event known_cert_add(info, hash);
}
event Cluster::node_up(name: string, id: string)
{
if ( Known::use_cert_store )
return;
if ( Cluster::local_node_type() != Cluster::WORKER )
return;
# Drop local suppression cache on workers to force HRW key repartitioning.
Known::certs = table();
}
event Cluster::node_down(name: string, id: string)
{
if ( Known::use_cert_store )
return;
if ( Cluster::local_node_type() != Cluster::WORKER )
return;
# Drop local suppression cache on workers to force HRW key repartitioning.
Known::certs = table();
}
event ssl_established(c: connection) &priority=3
{
if ( ! c$ssl?$cert_chain )
return;
if ( |c$ssl$cert_chain| < 1 )
return;
if ( ! c$ssl$cert_chain[0]?$x509 )
return;
local fuid = c$ssl$cert_chain[0]$fuid;
if ( ! c$ssl$cert_chain[0]?$sha1 )
{
Reporter::error(fmt("Certificate with fuid %s did not contain sha1 hash when checking for known certs. Aborting",
fuid));
return;
}
local host = c$id$resp_h;
if ( ! addr_matches_host(host, cert_tracking) )
return;
local hash = c$ssl$cert_chain[0]$sha1;
local cert = c$ssl$cert_chain[0]$x509$certificate;
local info = CertsInfo($ts = network_time(), $host = host,
$port_num = c$id$resp_p, $subject = cert$subject,
$issuer_subject = cert$issuer,
$serial = cert$serial);
event Known::cert_found(info, hash);
}
event zeek_init() &priority=5
{
Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs, $path="known_certs", $policy=log_policy_certs]);
}