From c403a7f4e69be16c71d74b307cf17064edbbaf37 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 17 Mar 2017 11:40:49 -0700 Subject: [PATCH] SCT: add validation of proofs for extensions and OCSP. This does not yet work for certificates, because this requires some changing the ASN.1 structure before validation (we need to extract the tbscert and remove the SCT extension before). API will change in the future. --- src/analyzer/protocol/ssl/functions.bif | 1 + src/file_analysis/analyzer/x509/functions.bif | 95 +++++++++++++++++++ .../.stdout | 6 ++ .../.stdout | 6 ++ .../signed_certificate_timestamp_ocsp.test | 31 ++++++ .../ssl/signed_certificate_timestamp.test | 32 +++++++ 6 files changed, 171 insertions(+) create mode 100644 testing/btest/Baseline/scripts.base.files.x509.signed_certificate_timestamp_ocsp/.stdout create mode 100644 testing/btest/scripts/base/files/x509/signed_certificate_timestamp_ocsp.test diff --git a/src/analyzer/protocol/ssl/functions.bif b/src/analyzer/protocol/ssl/functions.bif index f7fa76ca36..17720bcbb1 100644 --- a/src/analyzer/protocol/ssl/functions.bif +++ b/src/analyzer/protocol/ssl/functions.bif @@ -1,6 +1,7 @@ %%{ #include "analyzer/protocol/ssl/SSL.h" +#include %%} ## Sets if the SSL analyzer should consider the connection established (handshake diff --git a/src/file_analysis/analyzer/x509/functions.bif b/src/file_analysis/analyzer/x509/functions.bif index 0fe65c7d55..161a009515 100644 --- a/src/file_analysis/analyzer/x509/functions.bif +++ b/src/file_analysis/analyzer/x509/functions.bif @@ -1,6 +1,7 @@ %%{ #include "file_analysis/analyzer/x509/X509.h" #include "types.bif.h" +#include "net_util.h" #include #include @@ -543,6 +544,100 @@ x509_verify_chainerror: return rrecord; %} +function sct_verify%(cert: opaque of x509, logid: string, log_key: string, signature: string, timestamp: count, hash_algorithm: count%): bool + %{ + assert(cert); + file_analysis::X509Val* h = (file_analysis::X509Val*) cert; + X509* x = ((file_analysis::X509Val*) h)->GetCertificate(); + + assert(sizeof(timestamp) >= 8); + uint64_t timestamp_network = htonll(timestamp); + + std::string data; + data.push_back(0); // version + data.push_back(0); // signature_type -> certificate_timestamp + data.append(reinterpret_cast(×tamp_network), sizeof(timestamp_network)); // timestamp -> 64 bits + data.append("\0\0", 2); // entry-type: x509_entry + + unsigned char *cert_out = nullptr; + uint32 cert_length = i2d_X509(x, &cert_out); + assert( cert_out ); + uint32 cert_length_network = htonl(cert_length); + assert( sizeof(cert_length_network) == 4); + + data.append(reinterpret_cast(&cert_length_network)+1, 3); // 3 bytes certificate length + data.append(reinterpret_cast(cert_out), cert_length); // der-encoded certificate + OPENSSL_free(cert_out); + data.append("\0\0", 2); // no extensions + + // key is given as a DER-encoded SubjectPublicKeyInfo. + const unsigned char *key_char = log_key->Bytes(); + EVP_PKEY* key = d2i_PUBKEY(nullptr, &key_char, log_key->Len()); + + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + assert(mdctx); + + string errstr; + int success = 0; + + const EVP_MD* hash; + // numbers from http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18 + switch ( hash_algorithm ) + { + case 1: + hash = EVP_md5(); + break; + case 2: + hash = EVP_sha1(); + break; + case 3: + hash = EVP_sha224(); + break; + case 4: + hash = EVP_sha256(); + break; + case 5: + hash = EVP_sha384(); + break; + case 6: + hash = EVP_sha512(); + break; + default: + errstr = "Unknown hash algorithm"; + goto sct_verify_err; + } + + if ( ! key ) + { + errstr = "Could not load log key"; + goto sct_verify_err; + } + + if ( ! EVP_DigestVerifyInit(mdctx, NULL, hash, NULL, key) ) + { + errstr = "Could not init signature verification"; + goto sct_verify_err; + } + + if ( ! EVP_DigestVerifyUpdate(mdctx, data.data(), data.size()) ) + { + errstr = "Could not update digest for verification"; + goto sct_verify_err; + } + + success = EVP_DigestVerifyFinal(mdctx, signature->Bytes(), signature->Len()); + EVP_MD_CTX_destroy(mdctx); + + return new Val(success, TYPE_BOOL); + +sct_verify_err: + if (mdctx) + EVP_MD_CTX_destroy(mdctx); + cerr << errstr << endl; + reporter->Error("%s", errstr.c_str()); + return new Val(0, TYPE_BOOL); + %} + ## Get the hash of issuer name of a certificate ## ## cert: The X509 certificate opaque handle. diff --git a/testing/btest/Baseline/scripts.base.files.x509.signed_certificate_timestamp_ocsp/.stdout b/testing/btest/Baseline/scripts.base.files.x509.signed_certificate_timestamp_ocsp/.stdout new file mode 100644 index 0000000000..5384f4e6b7 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.files.x509.signed_certificate_timestamp_ocsp/.stdout @@ -0,0 +1,6 @@ +0, Google 'Pilot' log, 1406997753.366, 4, 3 +Verify of, Google 'Pilot' log, T +Bad verify of, Google 'Pilot' log, F +0, Google 'Aviator' log, 1407002457.456, 4, 3 +Verify of, Google 'Aviator' log, T +Bad verify of, Google 'Aviator' log, F diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.signed_certificate_timestamp/.stdout b/testing/btest/Baseline/scripts.base.protocols.ssl.signed_certificate_timestamp/.stdout index bc56f161ab..342228a1cf 100644 --- a/testing/btest/Baseline/scripts.base.protocols.ssl.signed_certificate_timestamp/.stdout +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.signed_certificate_timestamp/.stdout @@ -1,3 +1,9 @@ 0, Google 'Pilot' log, 1406997753.366, [HashAlgorithm=4, SignatureAlgorithm=3] 0, Google 'Aviator' log, 1407002457.456, [HashAlgorithm=4, SignatureAlgorithm=3] 0, Google 'Rocketeer' log, 1410299366.023, [HashAlgorithm=4, SignatureAlgorithm=3] +Verify of, Google 'Pilot' log, T +Bad verify of, Google 'Pilot' log, F +Verify of, Google 'Aviator' log, T +Bad verify of, Google 'Aviator' log, F +Verify of, Google 'Rocketeer' log, T +Bad verify of, Google 'Rocketeer' log, F diff --git a/testing/btest/scripts/base/files/x509/signed_certificate_timestamp_ocsp.test b/testing/btest/scripts/base/files/x509/signed_certificate_timestamp_ocsp.test new file mode 100644 index 0000000000..01ed128541 --- /dev/null +++ b/testing/btest/scripts/base/files/x509/signed_certificate_timestamp_ocsp.test @@ -0,0 +1,31 @@ +# @TEST-EXEC: bro -r $TRACES/tls/signed_certificate_timestamp.pcap %INPUT +# @TEST-EXEC: btest-diff .stdout + +event bro_init() + { + Files::register_for_mime_type(Files::ANALYZER_OCSP_REPLY, "application/ocsp-response"); + } + +event x509_ocsp_ext_signed_certificate_timestamp(f: fa_file, version: count, logid: string, timestamp: count, hash_algorithm: count, signature_algorithm: count, signature: string) + { + print version, SSL::ct_logs[logid]$description, double_to_time(timestamp/1000.0), hash_algorithm, signature_algorithm; + + if ( |f$conns| != 1 ) + return; + + for ( cid in f$conns ) + { + if ( ! f$conns[cid]?$ssl ) + return; + + local c = f$conns[cid]; + } + + 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; + + print "Verify of", SSL::ct_logs[logid]$description, sct_verify(cert, logid, SSL::ct_logs[logid]$key, signature, timestamp, hash_algorithm); + print "Bad verify of", SSL::ct_logs[logid]$description, sct_verify(cert, logid, SSL::ct_logs[logid]$key, signature, timestamp+1, hash_algorithm); + } diff --git a/testing/btest/scripts/base/protocols/ssl/signed_certificate_timestamp.test b/testing/btest/scripts/base/protocols/ssl/signed_certificate_timestamp.test index d0eaacd096..0b9c5fc157 100644 --- a/testing/btest/scripts/base/protocols/ssl/signed_certificate_timestamp.test +++ b/testing/btest/scripts/base/protocols/ssl/signed_certificate_timestamp.test @@ -1,7 +1,39 @@ # @TEST-EXEC: bro -r $TRACES/tls/signed_certificate_timestamp.pcap %INPUT # @TEST-EXEC: btest-diff .stdout +export { + type LogInfo: record { + version: count; + logid: string; + timestamp: count; + sig_alg: count; + hash_alg: count; + signature: string; + }; +} + +redef record SSL::Info += { + ct_proofs: vector of LogInfo &default=vector(); +}; + event ssl_extension_signed_certificate_timestamp(c: connection, is_orig: bool, version: count, logid: string, timestamp: count, signature_and_hashalgorithm: SSL::SignatureAndHashAlgorithm, signature: string) { print version, SSL::ct_logs[logid]$description, double_to_time(timestamp/1000.0), signature_and_hashalgorithm; + c$ssl$ct_proofs[|c$ssl$ct_proofs|] = LogInfo($version=version, $logid=logid, $timestamp=timestamp, $sig_alg=signature_and_hashalgorithm$SignatureAlgorithm, $hash_alg=signature_and_hashalgorithm$HashAlgorithm, $signature=signature); + } + +event ssl_established(c: connection) + { + 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; + + for ( i in c$ssl$ct_proofs ) + { + local log = c$ssl$ct_proofs[i]; + + print "Verify of", SSL::ct_logs[log$logid]$description, sct_verify(cert, log$logid, SSL::ct_logs[log$logid]$key, log$signature, log$timestamp, log$hash_alg); + print "Bad verify of", SSL::ct_logs[log$logid]$description, sct_verify(cert, log$logid, SSL::ct_logs[log$logid]$key, log$signature, log$timestamp+1, log$hash_alg); + } }