diff --git a/scripts/policy/protocols/ssl/cert-hash.bro b/scripts/policy/protocols/ssl/cert-hash.bro new file mode 100644 index 0000000000..0d0397e9c7 --- /dev/null +++ b/scripts/policy/protocols/ssl/cert-hash.bro @@ -0,0 +1,21 @@ +##! This script calculates MD5 sums for server DER formatted certificates. + +@load base/protocols/ssl + +module SSL; + +export { + redef record Info += { + cert_hash: string &log &optional; + }; +} + +event x509_certificate(c: connection, cert: X509, is_server: bool, chain_idx: count, chain_len: count, der_cert: string) &priority=10 + { + # We aren't tracking client certificates yet and we are also only tracking + # the primary cert. + if ( ! is_server || chain_idx != 0 ) + return; + + c$ssl$cert_hash = md5_hash(der_cert); + } \ No newline at end of file diff --git a/scripts/policy/protocols/ssl/expiring-certs.bro b/scripts/policy/protocols/ssl/expiring-certs.bro index 53f76c525a..81369d9c26 100644 --- a/scripts/policy/protocols/ssl/expiring-certs.bro +++ b/scripts/policy/protocols/ssl/expiring-certs.bro @@ -7,6 +7,8 @@ @load base/frameworks/notice @load base/utils/directions-and-hosts +@load protocols/ssl/cert-hash + module SSL; export { @@ -31,10 +33,6 @@ export { const notify_when_cert_expiring_in = 30days &redef; } -redef Notice::type_suppression_intervals += { - [[Certificate_Expired, Certificate_Expires_Soon, Certificate_Not_Valid_Yet]] = 1day -}; - event x509_certificate(c: connection, cert: X509, is_server: bool, chain_idx: count, chain_len: count, der_cert: string) &priority=5 { # If this isn't the host cert or we aren't interested in the server, just return. @@ -45,17 +43,17 @@ event x509_certificate(c: connection, cert: X509, is_server: bool, chain_idx: co NOTICE([$note=Certificate_Not_Valid_Yet, $conn=c, $suppress_for=1day, $msg=fmt("Certificate %s isn't valid until %T", cert$subject, cert$not_valid_before), - $identifier=fmt("%s:%d-%s", c$id$resp_h, c$id$resp_p, md5_hash(der_cert))]); + $identifier=cat(c$id$resp_h, c$id$resp_p, c$ssl$cert_hash)]); else if ( cert$not_valid_after < network_time() ) NOTICE([$note=Certificate_Expired, $conn=c, $suppress_for=1day, $msg=fmt("Certificate %s expired at %T", cert$subject, cert$not_valid_after), - $identifier=fmt("%s:%d-%s", c$id$resp_h, c$id$resp_p, md5_hash(der_cert))]); + $identifier=cat(c$id$resp_h, c$id$resp_p, c$ssl$cert_hash)]); else if ( cert$not_valid_after - notify_when_cert_expiring_in < network_time() ) NOTICE([$note=Certificate_Expires_Soon, $msg=fmt("Certificate %s is going to expire at %T", cert$subject, cert$not_valid_after), $conn=c, $suppress_for=1day, - $identifier=fmt("%s:%d-%s", c$id$resp_h, c$id$resp_p, md5_hash(der_cert))]); + $identifier=cat(c$id$resp_h, c$id$resp_p, c$ssl$cert_hash)]); } diff --git a/scripts/policy/protocols/ssl/extract-certs-pem.bro b/scripts/policy/protocols/ssl/extract-certs-pem.bro index f433d4fd12..ba0d71a165 100644 --- a/scripts/policy/protocols/ssl/extract-certs-pem.bro +++ b/scripts/policy/protocols/ssl/extract-certs-pem.bro @@ -30,19 +30,18 @@ export { ## the certificate. global extracted_certs: set[string] = set() &read_expire=1hr &redef; -event ssl_established(c: connection) +event ssl_established(c: connection) &priority=5 { if ( ! c$ssl?$cert ) return; if ( ! addr_matches_host(c$id$resp_h, extract_certs_pem) ) return; - local cert_hash = md5_hash(c$ssl$cert); - if ( cert_hash in extracted_certs ) + if ( c$ssl$cert_hash in extracted_certs ) # If we already extracted this cert, don't do it again. return; - add extracted_certs[cert_hash]; + add extracted_certs[c$ssl$cert_hash]; local side = Site::is_local_addr(c$id$resp_h) ? "local" : "remote"; local cmd = fmt("%s x509 -inform DER -outform PEM >> certs-%s.pem", openssl_util, side); piped_exec(cmd, c$ssl$cert); diff --git a/scripts/policy/protocols/ssl/known-certs.bro b/scripts/policy/protocols/ssl/known-certs.bro index 573cfaac0e..0782cef3e7 100644 --- a/scripts/policy/protocols/ssl/known-certs.bro +++ b/scripts/policy/protocols/ssl/known-certs.bro @@ -1,5 +1,8 @@ @load base/utils/directions-and-hosts +@load base/protocols/ssl +@load protocols/ssl/cert-hash + module Known; export { @@ -39,7 +42,7 @@ event bro_init() &priority=5 Log::create_stream(Known::CERTS_LOG, [$columns=CertsInfo, $ev=log_known_certs]); } -event x509_certificate(c: connection, cert: X509, is_server: bool, chain_idx: count, chain_len: count, der_cert: string) +event x509_certificate(c: connection, cert: X509, is_server: bool, chain_idx: count, chain_len: count, der_cert: string) &priority=5 { # We aren't tracking client certificates yet. if ( ! is_server ) return; @@ -53,6 +56,7 @@ event x509_certificate(c: connection, cert: X509, is_server: bool, chain_idx: co 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]); + $serial=cert$serial, + $identifier=cat(c$id$resp_h,c$id$resp_p,c$ssl$cert_hash)]); } } diff --git a/scripts/policy/protocols/ssl/validate-certs.bro b/scripts/policy/protocols/ssl/validate-certs.bro index bf6421b5c1..f9294ed650 100644 --- a/scripts/policy/protocols/ssl/validate-certs.bro +++ b/scripts/policy/protocols/ssl/validate-certs.bro @@ -1,6 +1,10 @@ +##! Perform full certificate chain validation for SSL certificates. + @load base/frameworks/notice/main @load base/protocols/ssl/main +@load protocols/ssl/cert-hash + module SSL; export { @@ -11,22 +15,36 @@ export { redef record Info += { validation_status: string &log &optional; }; - + + ## MD5 hash values for recently validated certs along with the validation + ## status message are kept in this table so avoid constant validation + ## everytime the same certificate is seen. + global recently_validated_certs: table[string] of string = table() + &read_expire=5mins &synchronized; } event ssl_established(c: connection) &priority=3 { # If there aren't any certs we can't very well do certificate validation. - if ( !c$ssl?$cert || !c$ssl?$cert_chain ) + if ( ! c$ssl?$cert || ! c$ssl?$cert_chain ) return; + + if ( c$ssl?$cert_hash && c$ssl$cert_hash in recently_validated_certs ) + { + c$ssl$validation_status = recently_validated_certs[c$ssl$cert_hash]; + } + else + { + local result = x509_verify(c$ssl$cert, c$ssl$cert_chain, root_certs); + c$ssl$validation_status = x509_err2str(result); + } - local result = x509_verify(c$ssl$cert, c$ssl$cert_chain, root_certs); - c$ssl$validation_status = x509_err2str(result); - if ( result != 0 ) + if ( c$ssl$validation_status != "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]); + $sub=c$ssl$subject, $conn=c, + $identifier=cat(c$id$resp_h,c$id$resp_p,c$ssl$validation_status,c$ssl$cert_hash)]); } }