mirror of
https://github.com/zeek/zeek.git
synced 2025-10-09 18:18:19 +00:00
Merge remote-tracking branch 'origin/master' into topic/johanna/tls-more-data
This commit is contained in:
commit
b1dbd757a6
1468 changed files with 41493 additions and 19065 deletions
|
@ -50,33 +50,33 @@ event bro_init()
|
|||
# Minimum length a heartbeat packet must have for different cipher suites.
|
||||
# Note - tls 1.1f and 1.0 have different lengths :(
|
||||
# This should be all cipher suites usually supported by vulnerable servers.
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA384$/, $min_length=96];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA256$/, $min_length=80];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_256_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA256$/, $min_length=80];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_128_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_SHA$/, $min_length=39];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_128_MD5$/, $min_length=35];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC4_40_MD5$/, $min_length=35];
|
||||
min_lengths_tls11[|min_lengths_tls11|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_256_CBC_SHA$/, $min_length=48];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_128_CBC_SHA$/, $min_length=48];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_SEED_CBC_SHA$/, $min_length=48];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_IDEA_CBC_SHA$/, $min_length=40];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_DES_CBC_SHA$/, $min_length=40];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_DES40_CBC_SHA$/, $min_length=40];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_RC4_128_SHA$/, $min_length=39];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_RC4_128_MD5$/, $min_length=35];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_RC4_40_MD5$/, $min_length=35];
|
||||
min_lengths[|min_lengths|] = [$cipher=/_RC2_CBC_40_MD5$/, $min_length=40];
|
||||
min_lengths_tls11 += [$cipher=/_AES_256_GCM_SHA384$/, $min_length=43];
|
||||
min_lengths_tls11 += [$cipher=/_AES_128_GCM_SHA256$/, $min_length=43];
|
||||
min_lengths_tls11 += [$cipher=/_256_CBC_SHA384$/, $min_length=96];
|
||||
min_lengths_tls11 += [$cipher=/_256_CBC_SHA256$/, $min_length=80];
|
||||
min_lengths_tls11 += [$cipher=/_256_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11 += [$cipher=/_128_CBC_SHA256$/, $min_length=80];
|
||||
min_lengths_tls11 += [$cipher=/_128_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11 += [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_SEED_CBC_SHA$/, $min_length=64];
|
||||
min_lengths_tls11 += [$cipher=/_IDEA_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_DES_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_DES40_CBC_SHA$/, $min_length=48];
|
||||
min_lengths_tls11 += [$cipher=/_RC4_128_SHA$/, $min_length=39];
|
||||
min_lengths_tls11 += [$cipher=/_RC4_128_MD5$/, $min_length=35];
|
||||
min_lengths_tls11 += [$cipher=/_RC4_40_MD5$/, $min_length=35];
|
||||
min_lengths_tls11 += [$cipher=/_RC2_CBC_40_MD5$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_256_CBC_SHA$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_128_CBC_SHA$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_3DES_EDE_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_SEED_CBC_SHA$/, $min_length=48];
|
||||
min_lengths += [$cipher=/_IDEA_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_DES_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_DES40_CBC_SHA$/, $min_length=40];
|
||||
min_lengths += [$cipher=/_RC4_128_SHA$/, $min_length=39];
|
||||
min_lengths += [$cipher=/_RC4_128_MD5$/, $min_length=35];
|
||||
min_lengths += [$cipher=/_RC4_40_MD5$/, $min_length=35];
|
||||
min_lengths += [$cipher=/_RC2_CBC_40_MD5$/, $min_length=40];
|
||||
}
|
||||
|
||||
event ssl_heartbeat(c: connection, is_orig: bool, length: count, heartbeat_type: count, payload_length: count, payload: string)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@load base/utils/directions-and-hosts
|
||||
@load base/protocols/ssl
|
||||
@load base/files/x509
|
||||
@load base/frameworks/cluster
|
||||
|
||||
module Known;
|
||||
|
||||
|
@ -29,27 +30,144 @@ export {
|
|||
## The certificates whose existence should be logged and tracked.
|
||||
## Choices are: LOCAL_HOSTS, REMOTE_HOSTS, ALL_HOSTS, NO_HOSTS.
|
||||
const cert_tracking = LOCAL_HOSTS &redef;
|
||||
|
||||
## Toggles between different implementations of this script.
|
||||
## When true, use a Broker data store, else use a regular Bro set
|
||||
## with keys uniformly distributed over proxy nodes in cluster
|
||||
## operation.
|
||||
const use_cert_store = T &redef;
|
||||
|
||||
type AddrCertHashPair: record {
|
||||
host: addr;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
## Holds the set of all known certificates. Keys in the store are of
|
||||
## type :bro: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 :bro:see:`Known::cert_store`.
|
||||
const cert_store_name = "bro/known/certs" &redef;
|
||||
|
||||
## The expiry interval of new entries in :bro:see:`Known::cert_store`.
|
||||
## This also changes the interval at which certs get logged.
|
||||
const cert_store_expiry = 1day &redef;
|
||||
|
||||
## The timeout interval to use for operations against
|
||||
## :bro:see:`Known::cert_store`.
|
||||
const cert_store_timeout = 15sec &redef;
|
||||
|
||||
## 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.
|
||||
global certs: set[addr, string] &create_expire=1day &synchronized &redef;
|
||||
##
|
||||
## 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 bro_init() &priority=5
|
||||
event bro_init()
|
||||
{
|
||||
Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs, $path="known_certs"]);
|
||||
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 ( 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 ( ! Cluster::is_enabled() ||
|
||||
Cluster::local_node_type() == Cluster::PROXY )
|
||||
Log::write(Known::CERTS_LOG, info);
|
||||
@endif
|
||||
}
|
||||
|
||||
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 || |c$ssl$cert_chain| < 1 ||
|
||||
! c$ssl$cert_chain[0]?$x509 )
|
||||
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_fuids[0];
|
||||
|
@ -61,16 +179,21 @@ event ssl_established(c: connection) &priority=3
|
|||
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 host = c$id$resp_h;
|
||||
if ( [host, hash] !in certs && addr_matches_host(host, cert_tracking) )
|
||||
{
|
||||
add certs[host, hash];
|
||||
Log::write(Known::CERTS_LOG, [$ts=network_time(), $host=host,
|
||||
$port_num=c$id$resp_p, $subject=cert$subject,
|
||||
$issuer_subject=cert$issuer,
|
||||
$serial=cert$serial]);
|
||||
}
|
||||
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 bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs, $path="known_certs"]);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ module X509;
|
|||
|
||||
export {
|
||||
redef record Info += {
|
||||
# Logging is suppressed if field is set to F
|
||||
## Logging of certificate is suppressed if set to F
|
||||
logcert: bool &default=T;
|
||||
};
|
||||
}
|
||||
|
@ -39,14 +39,29 @@ event bro_init() &priority=2
|
|||
Log::add_filter(X509::LOG, f);
|
||||
}
|
||||
|
||||
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=2
|
||||
event file_sniff(f: fa_file, meta: fa_metadata) &priority=4
|
||||
{
|
||||
if ( ! c?$ssl )
|
||||
if ( |f$conns| != 1 )
|
||||
return;
|
||||
|
||||
if ( ! f?$info || ! f$info?$mime_type )
|
||||
return;
|
||||
|
||||
if ( ! ( f$info$mime_type == "application/x-x509-ca-cert" || f$info$mime_type == "application/x-x509-user-cert"
|
||||
|| f$info$mime_type == "application/pkix-cert" ) )
|
||||
return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
if ( ! f$conns[cid]?$ssl )
|
||||
return;
|
||||
|
||||
local c = f$conns[cid];
|
||||
}
|
||||
|
||||
local chain: vector of string;
|
||||
|
||||
if ( is_orig )
|
||||
if ( f$is_orig )
|
||||
chain = c$ssl$client_cert_chain_fuids;
|
||||
else
|
||||
chain = c$ssl$cert_chain_fuids;
|
||||
|
|
|
@ -56,7 +56,7 @@ event ssl_established(c: connection) &priority=3
|
|||
local waits_already = digest in waitlist;
|
||||
if ( ! waits_already )
|
||||
waitlist[digest] = vector();
|
||||
waitlist[digest][|waitlist[digest]|] = c$ssl;
|
||||
waitlist[digest] += c$ssl;
|
||||
if ( waits_already )
|
||||
return;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# Also caches all intermediate certificates encountered so far and use them
|
||||
# for future validations.
|
||||
|
||||
@load base/frameworks/cluster
|
||||
@load base/frameworks/notice
|
||||
@load base/protocols/ssl
|
||||
|
||||
|
@ -19,12 +20,17 @@ export {
|
|||
redef record Info += {
|
||||
## Result of certificate validation for this connection.
|
||||
validation_status: string &log &optional;
|
||||
## Result of certificate validation for this connection, given
|
||||
## as OpenSSL validation code.
|
||||
validation_code: int &optional;
|
||||
## Ordered chain of validated certificate, if validation succeeded.
|
||||
valid_chain: vector of opaque of x509 &optional;
|
||||
};
|
||||
|
||||
## MD5 hash values for recently validated chains along with the
|
||||
## Result values for recently validated chains along with the
|
||||
## validation status are kept in this table to avoid constant
|
||||
## validation every time the same certificate chain is seen.
|
||||
global recently_validated_certs: table[string] of string = table()
|
||||
global recently_validated_certs: table[string] of X509::Result = table()
|
||||
&read_expire=5mins &redef;
|
||||
|
||||
## Use intermediate CA certificate caching when trying to validate
|
||||
|
@ -39,11 +45,16 @@ export {
|
|||
## that you encounter. Only disable if you want to find misconfigured servers.
|
||||
global ssl_cache_intermediate_ca: bool = T &redef;
|
||||
|
||||
## Event from a worker to the manager that it has encountered a new
|
||||
## valid intermediate.
|
||||
## Store the valid chain in c$ssl$valid_chain if validation succeeds.
|
||||
## This has a potentially high memory impact, depending on the local environment
|
||||
## and is thus disabled by default.
|
||||
global ssl_store_valid_chain: bool = F &redef;
|
||||
|
||||
## Event from a manager to workers when encountering a new, valid
|
||||
## intermediate.
|
||||
global intermediate_add: event(key: string, value: vector of opaque of x509);
|
||||
|
||||
## Event from the manager to the workers that a new intermediate chain
|
||||
## Event from workers to the manager when a new intermediate chain
|
||||
## is to be added.
|
||||
global new_intermediate: event(key: string, value: vector of opaque of x509);
|
||||
}
|
||||
|
@ -51,12 +62,13 @@ export {
|
|||
global intermediate_cache: table[string] of vector of opaque of x509;
|
||||
|
||||
@if ( Cluster::is_enabled() )
|
||||
@load base/frameworks/cluster
|
||||
redef Cluster::manager2worker_events += /SSL::intermediate_add/;
|
||||
redef Cluster::worker2manager_events += /SSL::new_intermediate/;
|
||||
event bro_init()
|
||||
{
|
||||
Broker::auto_publish(Cluster::worker_topic, SSL::intermediate_add);
|
||||
Broker::auto_publish(Cluster::manager_topic, SSL::new_intermediate);
|
||||
}
|
||||
@endif
|
||||
|
||||
|
||||
function add_to_cache(key: string, value: vector of opaque of x509)
|
||||
{
|
||||
intermediate_cache[key] = value;
|
||||
|
@ -83,7 +95,7 @@ event SSL::new_intermediate(key: string, value: vector of opaque of x509)
|
|||
}
|
||||
@endif
|
||||
|
||||
function cache_validate(chain: vector of opaque of x509): string
|
||||
function cache_validate(chain: vector of opaque of x509): X509::Result
|
||||
{
|
||||
local chain_hash: vector of string = vector();
|
||||
|
||||
|
@ -97,7 +109,10 @@ function cache_validate(chain: vector of opaque of x509): string
|
|||
return recently_validated_certs[chain_id];
|
||||
|
||||
local result = x509_verify(chain, root_certs);
|
||||
recently_validated_certs[chain_id] = result$result_string;
|
||||
if ( ! ssl_store_valid_chain && result?$chain_certs )
|
||||
recently_validated_certs[chain_id] = X509::Result($result=result$result, $result_string=result$result_string);
|
||||
else
|
||||
recently_validated_certs[chain_id] = result;
|
||||
|
||||
# if we have a working chain where we did not store the intermediate certs
|
||||
# in our cache yet - do so
|
||||
|
@ -107,8 +122,8 @@ function cache_validate(chain: vector of opaque of x509): string
|
|||
|result$chain_certs| > 2 )
|
||||
{
|
||||
local result_chain = result$chain_certs;
|
||||
local icert = x509_parse(result_chain[1]);
|
||||
if ( icert$subject !in intermediate_cache )
|
||||
local isnh = x509_subject_name_hash(result_chain[1], 4); # SHA256
|
||||
if ( isnh !in intermediate_cache )
|
||||
{
|
||||
local cachechain: vector of opaque of x509;
|
||||
for ( i in result_chain )
|
||||
|
@ -116,14 +131,14 @@ function cache_validate(chain: vector of opaque of x509): string
|
|||
if ( i >=1 && i<=|result_chain|-2 )
|
||||
cachechain[i-1] = result_chain[i];
|
||||
}
|
||||
add_to_cache(icert$subject, cachechain);
|
||||
add_to_cache(isnh, cachechain);
|
||||
}
|
||||
}
|
||||
|
||||
return result$result_string;
|
||||
return result;
|
||||
}
|
||||
|
||||
event ssl_established(c: connection) &priority=3
|
||||
hook ssl_finishing(c: connection) &priority=20
|
||||
{
|
||||
# If there aren't any certs we can't very well do certificate validation.
|
||||
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
|
||||
|
@ -131,23 +146,26 @@ event ssl_established(c: connection) &priority=3
|
|||
return;
|
||||
|
||||
local intermediate_chain: vector of opaque of x509 = vector();
|
||||
local issuer = c$ssl$cert_chain[0]$x509$certificate$issuer;
|
||||
local issuer_name_hash = x509_issuer_name_hash(c$ssl$cert_chain[0]$x509$handle, 4); # SHA256
|
||||
local hash = c$ssl$cert_chain[0]$sha1;
|
||||
local result: string;
|
||||
local result: X509::Result;
|
||||
|
||||
# Look if we already have a working chain for the issuer of this cert.
|
||||
# If yes, try this chain first instead of using the chain supplied from
|
||||
# the server.
|
||||
if ( ssl_cache_intermediate_ca && issuer in intermediate_cache )
|
||||
if ( ssl_cache_intermediate_ca && issuer_name_hash in intermediate_cache )
|
||||
{
|
||||
intermediate_chain[0] = c$ssl$cert_chain[0]$x509$handle;
|
||||
for ( i in intermediate_cache[issuer] )
|
||||
intermediate_chain[i+1] = intermediate_cache[issuer][i];
|
||||
for ( i in intermediate_cache[issuer_name_hash] )
|
||||
intermediate_chain[i+1] = intermediate_cache[issuer_name_hash][i];
|
||||
|
||||
result = cache_validate(intermediate_chain);
|
||||
if ( result == "ok" )
|
||||
if ( result$result_string == "ok" )
|
||||
{
|
||||
c$ssl$validation_status = result;
|
||||
c$ssl$validation_status = result$result_string;
|
||||
c$ssl$validation_code = result$result;
|
||||
if ( result?$chain_certs )
|
||||
c$ssl$valid_chain = result$chain_certs;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +181,16 @@ event ssl_established(c: connection) &priority=3
|
|||
}
|
||||
|
||||
result = cache_validate(chain);
|
||||
c$ssl$validation_status = result;
|
||||
c$ssl$validation_status = result$result_string;
|
||||
c$ssl$validation_code = result$result;
|
||||
if ( result?$chain_certs )
|
||||
c$ssl$valid_chain = result$chain_certs;
|
||||
|
||||
if ( result != "ok" )
|
||||
if ( result$result_string != "ok" )
|
||||
{
|
||||
local message = fmt("SSL certificate validation failed with (%s)", c$ssl$validation_status);
|
||||
NOTICE([$note=Invalid_Server_Cert, $msg=message,
|
||||
$sub=c$ssl$subject, $conn=c,
|
||||
$identifier=cat(c$id$resp_h,c$id$resp_p,hash,c$ssl$validation_status)]);
|
||||
$sub=c$ssl$cert_chain[0]$x509$certificate$subject, $conn=c,
|
||||
$identifier=cat(c$id$resp_h,c$id$resp_p,hash,c$ssl$validation_code)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
##! Perform OCSP response validation.
|
||||
##! Perform validation of stapled OCSP responses.
|
||||
#!
|
||||
#! Note: this _only_ performs validation of stapled OCSP responsed. It does
|
||||
#! not validate OCSP responses that are retrieved via HTTP, because we do not
|
||||
#! have a mapping to certificates.
|
||||
|
||||
|
||||
@load base/frameworks/notice
|
||||
@load base/protocols/ssl
|
||||
|
@ -15,7 +20,6 @@ export {
|
|||
redef record Info += {
|
||||
## Result of ocsp validation for this connection.
|
||||
ocsp_status: string &log &optional;
|
||||
|
||||
## ocsp response as string.
|
||||
ocsp_response: string &optional;
|
||||
};
|
||||
|
|
212
scripts/policy/protocols/ssl/validate-sct.bro
Normal file
212
scripts/policy/protocols/ssl/validate-sct.bro
Normal file
|
@ -0,0 +1,212 @@
|
|||
##! Perform validation of Signed Certificate Timestamps, as used
|
||||
##! for Certificate Transparency. See RFC6962 for more details.
|
||||
|
||||
@load base/protocols/ssl
|
||||
@load protocols/ssl/validate-certs
|
||||
|
||||
# We need to know issuer certificates to be able to determine the IssuerKeyHash,
|
||||
# which is required for validating certificate extensions.
|
||||
redef SSL::ssl_store_valid_chain = T;
|
||||
|
||||
module SSL;
|
||||
|
||||
export {
|
||||
|
||||
## List of the different sources for Signed Certificate Timestamp
|
||||
type SctSource: enum {
|
||||
## Signed Certificate Timestamp was encountered in the extension of
|
||||
## an X.509 certificate.
|
||||
SCT_X509_EXT,
|
||||
## Signed Certificate Timestamp was encountered in an TLS session
|
||||
## extension.
|
||||
SCT_TLS_EXT,
|
||||
## Signed Certificate Timestamp was encountered in the extension of
|
||||
## an stapled OCSP reply.
|
||||
SCT_OCSP_EXT
|
||||
};
|
||||
|
||||
## This record is used to store information about the SCTs that are
|
||||
## encountered in a SSL connection.
|
||||
type SctInfo: record {
|
||||
## The version of the encountered SCT (should always be 0 for v1).
|
||||
version: count;
|
||||
## The ID of the log issuing this SCT.
|
||||
logid: string;
|
||||
## The timestamp at which this SCT was issued measured since the
|
||||
## epoch (January 1, 1970, 00:00), ignoring leap seconds, in
|
||||
## milliseconds. Not converted to a Bro timestamp because we need
|
||||
## the exact value for validation.
|
||||
timestamp: count;
|
||||
## The signature algorithm used for this sct.
|
||||
sig_alg: count;
|
||||
## The hash algorithm used for this sct.
|
||||
hash_alg: count;
|
||||
## The signature of this SCT.
|
||||
signature: string;
|
||||
## Source of this SCT.
|
||||
source: SctSource;
|
||||
## Validation result of this SCT.
|
||||
valid: bool &optional;
|
||||
};
|
||||
|
||||
redef record Info += {
|
||||
## Number of valid SCTs that were encountered in the connection.
|
||||
valid_scts: count &optional;
|
||||
## Number of SCTs that could not be validated that were encountered in the connection.
|
||||
invalid_scts: count &optional;
|
||||
## Number of different Logs for which valid SCTs were encountered in the connection.
|
||||
valid_ct_logs: count &log &optional;
|
||||
## Number of different Log operators of which valid SCTs were encountered in the connection.
|
||||
valid_ct_operators: count &log &optional;
|
||||
## List of operators for which valid SCTs were encountered in the connection.
|
||||
valid_ct_operators_list: set[string] &optional;
|
||||
## Information about all SCTs that were encountered in the connection.
|
||||
ct_proofs: vector of SctInfo &default=vector();
|
||||
};
|
||||
}
|
||||
|
||||
# Used to cache validations for 5 minutes to lessen computational load.
|
||||
global recently_validated_scts: table[string] of bool = table()
|
||||
&read_expire=5mins &redef;
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
Files::register_for_mime_type(Files::ANALYZER_OCSP_REPLY, "application/ocsp-response");
|
||||
}
|
||||
|
||||
event ssl_extension_signed_certificate_timestamp(c: connection, is_orig: bool, version: count, logid: string, timestamp: count, signature_and_hashalgorithm: SSL::SignatureAndHashAlgorithm, signature: string) &priority=5
|
||||
{
|
||||
c$ssl$ct_proofs += SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_and_hashalgorithm$SignatureAlgorithm, $hash_alg=signature_and_hashalgorithm$HashAlgorithm, $signature=signature, $source=SCT_TLS_EXT);
|
||||
}
|
||||
|
||||
event x509_ocsp_ext_signed_certificate_timestamp(f: fa_file, version: count, logid: string, timestamp: count, hash_algorithm: count, signature_algorithm: count, signature: string) &priority=5
|
||||
{
|
||||
local src: SctSource;
|
||||
if ( ! f?$info )
|
||||
return;
|
||||
|
||||
if ( f$source == "SSL" && f$info$mime_type == "application/ocsp-response" )
|
||||
src = SCT_OCSP_EXT;
|
||||
else if ( f$source == "SSL" && f$info$mime_type == "application/x-x509-user-cert" )
|
||||
src = SCT_X509_EXT;
|
||||
else
|
||||
return;
|
||||
|
||||
if ( |f$conns| != 1 )
|
||||
return;
|
||||
|
||||
for ( cid in f$conns )
|
||||
{
|
||||
if ( ! f$conns[cid]?$ssl )
|
||||
return;
|
||||
|
||||
local c = f$conns[cid];
|
||||
}
|
||||
|
||||
c$ssl$ct_proofs += SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_algorithm, $hash_alg=hash_algorithm, $signature=signature, $source=src);
|
||||
}
|
||||
|
||||
# Priority = 19 will be handled after validation is done
|
||||
hook ssl_finishing(c: connection) &priority=19
|
||||
{
|
||||
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 || ! c$ssl$cert_chain[0]?$x509 )
|
||||
return;
|
||||
|
||||
local cert = c$ssl$cert_chain[0]$x509$handle;
|
||||
local certhash = c$ssl$cert_chain[0]$sha1;
|
||||
local issuer_name_hash = x509_issuer_name_hash(cert, 4);
|
||||
local valid_proofs = 0;
|
||||
local invalid_proofs = 0;
|
||||
c$ssl$valid_ct_operators_list = string_set();
|
||||
local valid_logs = string_set();
|
||||
local issuer_key_hash = "";
|
||||
|
||||
for ( i in c$ssl$ct_proofs )
|
||||
{
|
||||
local proof = c$ssl$ct_proofs[i];
|
||||
if ( proof$logid !in SSL::ct_logs )
|
||||
{
|
||||
# Well, if we don't know the log, there is nothing to do here...
|
||||
proof$valid = F;
|
||||
next;
|
||||
}
|
||||
local log = SSL::ct_logs[proof$logid];
|
||||
|
||||
local valid = F;
|
||||
local found_cache = F;
|
||||
|
||||
local validatestring = cat(certhash,proof$logid,proof$timestamp,proof$hash_alg,proof$signature,proof$source);
|
||||
if ( proof$source == SCT_X509_EXT && c$ssl?$validation_code )
|
||||
validatestring = cat(validatestring, c$ssl$validation_code);
|
||||
local validate_hash = sha1_hash(validatestring);
|
||||
if ( validate_hash in recently_validated_scts )
|
||||
{
|
||||
valid = recently_validated_scts[validate_hash];
|
||||
found_cache = T;
|
||||
}
|
||||
|
||||
if ( found_cache == F && ( proof$source == SCT_TLS_EXT || proof$source == SCT_OCSP_EXT ) )
|
||||
{
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg);
|
||||
}
|
||||
else if ( found_cache == F )
|
||||
{
|
||||
# X.509 proof. Here things get awkward because we need information about
|
||||
# the issuer cert... and we need to try a few times, because we have to see if we got
|
||||
# the right issuer cert.
|
||||
#
|
||||
# First - Let's try if a previous round already established the correct issuer key hash.
|
||||
if ( issuer_key_hash != "" )
|
||||
{
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
}
|
||||
|
||||
# Second - let's see if we might already know the issuer cert through verification.
|
||||
if ( ! valid && issuer_name_hash in intermediate_cache )
|
||||
{
|
||||
issuer_key_hash = x509_spki_hash(intermediate_cache[issuer_name_hash][0], 4);
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
}
|
||||
if ( ! valid && c$ssl?$valid_chain && |c$ssl$valid_chain| >= 2 )
|
||||
{
|
||||
issuer_key_hash = x509_spki_hash(c$ssl$valid_chain[1], 4);
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
}
|
||||
|
||||
# ok, if it still did not work - let's just try with all the certs that were sent
|
||||
# in the connection. Perhaps it will work with one of them.
|
||||
if ( !valid )
|
||||
for ( i in c$ssl$cert_chain )
|
||||
{
|
||||
if ( i == 0 ) # end-host-cert
|
||||
next;
|
||||
if ( ! c$ssl$cert_chain[i]?$x509 || ! c$ssl$cert_chain[i]$x509?$handle )
|
||||
next;
|
||||
|
||||
issuer_key_hash = x509_spki_hash(c$ssl$cert_chain[i]$x509$handle, 4);
|
||||
valid = sct_verify(cert, proof$logid, log$key, proof$signature, proof$timestamp, proof$hash_alg, issuer_key_hash);
|
||||
if ( valid )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! found_cache )
|
||||
recently_validated_scts[validate_hash] = valid;
|
||||
|
||||
proof$valid = valid;
|
||||
|
||||
if ( valid )
|
||||
{
|
||||
++valid_proofs;
|
||||
add c$ssl$valid_ct_operators_list[log$operator];
|
||||
add valid_logs[proof$logid];
|
||||
}
|
||||
else
|
||||
++invalid_proofs;
|
||||
}
|
||||
|
||||
c$ssl$valid_scts = valid_proofs;
|
||||
c$ssl$invalid_scts = invalid_proofs;
|
||||
c$ssl$valid_ct_operators = |c$ssl$valid_ct_operators_list|;
|
||||
c$ssl$valid_ct_logs = |valid_logs|;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue