mirror of
https://github.com/zeek/zeek.git
synced 2025-10-16 21:48:21 +00:00
SCT: Add signed certificate timestamp validation script.
This also rewrites the certificate validation script (which we need for this) slightly. This could need a bit of caching, but should generally work very reliably.
This commit is contained in:
parent
115a676d08
commit
22b1eda472
6 changed files with 249 additions and 9 deletions
|
@ -120,8 +120,8 @@ function cache_validate(chain: vector of opaque of x509): X509::Result
|
|||
|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 )
|
||||
|
@ -129,33 +129,42 @@ function cache_validate(chain: vector of opaque of x509): X509::Result
|
|||
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;
|
||||
}
|
||||
|
||||
event ssl_established(c: connection) &priority=3
|
||||
# The server issues CCS only after sending the certificates. This should
|
||||
# be more robust than using SSL_established, on the off chance that we don't
|
||||
# get that event.
|
||||
#
|
||||
# This is not TLSv1.3 compatible - but we will not have certificates in
|
||||
# that case in any way, so it even saves us a few cycles.
|
||||
event ssl_change_cipher_spec(c: connection, is_orig: bool) &priority=3
|
||||
{
|
||||
if ( is_orig )
|
||||
return;
|
||||
|
||||
# If there aren't any certs we can't very well do certificate validation.
|
||||
if ( ! c$ssl?$cert_chain || |c$ssl$cert_chain| == 0 ||
|
||||
! c$ssl$cert_chain[0]?$x509 )
|
||||
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: 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$result_string == "ok" )
|
||||
|
@ -188,7 +197,7 @@ event ssl_established(c: connection) &priority=3
|
|||
{
|
||||
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,
|
||||
$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_status)]);
|
||||
}
|
||||
}
|
||||
|
|
173
scripts/policy/protocols/ssl/validate-sct.bro
Normal file
173
scripts/policy/protocols/ssl/validate-sct.bro
Normal file
|
@ -0,0 +1,173 @@
|
|||
##! Perform validation of Signed Certificate Timestamps, as used
|
||||
##! for Certificate Transparency. See https://tools.ietf.org/html/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 {
|
||||
|
||||
type SctSource: enum {
|
||||
SCT_X509_EXT,
|
||||
SCT_TLS_EXT,
|
||||
SCT_OCSP_EXT
|
||||
};
|
||||
|
||||
type SctInfo: record {
|
||||
version: count;
|
||||
logid: string;
|
||||
timestamp: count;
|
||||
sig_alg: count;
|
||||
hash_alg: count;
|
||||
signature: string;
|
||||
source: SctSource;
|
||||
valid: bool &optional;
|
||||
};
|
||||
|
||||
redef record Info += {
|
||||
valid_scts: count &optional;
|
||||
invalid_scts: count &optional;
|
||||
valid_ct_logs: count &log &optional;
|
||||
valid_ct_operators: count &log &optional;
|
||||
valid_ct_operators_list: set[string] &optional;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
redef record SSL::Info += {
|
||||
ct_proofs: vector of SctInfo &default=vector();
|
||||
};
|
||||
|
||||
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[|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[|c$ssl$ct_proofs|] = SctInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_algorithm, $hash_alg=hash_algorithm, $signature=signature, $source=src);
|
||||
}
|
||||
|
||||
# Priority = 2 will be handled after validation is done
|
||||
event ssl_change_cipher_spec(c: connection, is_orig: bool) &priority=2
|
||||
{
|
||||
if ( is_orig )
|
||||
return;
|
||||
|
||||
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 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;
|
||||
|
||||
if ( 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
|
||||
{
|
||||
# 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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