From d84d1d24e80e14d29bad8217261a1a4e1979d883 Mon Sep 17 00:00:00 2001 From: Liang Zhu Date: Wed, 17 Jun 2015 19:18:37 -0700 Subject: [PATCH] add ocsp logging --- scripts/base/files/ocsp/main.bro | 233 +++++++++++++++++++++- scripts/base/init-bare.bro | 10 +- src/file_analysis/analyzer/ocsp/types.bif | 3 +- 3 files changed, 241 insertions(+), 5 deletions(-) diff --git a/scripts/base/files/ocsp/main.bro b/scripts/base/files/ocsp/main.bro index 1b3dfdeffc..a6b91529ba 100644 --- a/scripts/base/files/ocsp/main.bro +++ b/scripts/base/files/ocsp/main.bro @@ -1,11 +1,240 @@ @load base/frameworks/files @load base/utils/paths +@load base/utils/queue -module FileOCSP; +module OCSP; export { - ## add one more argument to indicate is ocsp response or request + ## add one more argument to tell ocsp response or request redef record Files::AnalyzerArgs += { ocsp_type: string &optional; }; + + ## ocsp logging + redef enum Log::ID += { LOG }; + + ## tyep for pending ocsp request + type PendingRequests: table[OCSP::CertId] of Queue::Queue; + + ## NOTE: one file could contain several requests + ## one ocsp request record + type Info_req: record { + ## time for the request + ts: time; + ## file id for this request + id: string &log; + ## version + version: count &log &optional; + ## requestor name + requestorName: string &log &optional; + ## NOTE: the above are for one file which may constain + ## several ocsp requests + ## request cert id + certId: OCSP::CertId &log &optional; + }; + + ## NOTE: one file could contain several response + ## one ocsp response record + type Info_resp: record { + ## time for the response + ts: time; + ## file id for this response + id: string &log; + ## responseStatus (different from cert status?) + responseStatus: string &log; + ## responseType + responseType: string &log; + ## version + version: count &log; + ## responderID + responderID: string &log; + ## producedAt + producedAt: string &log; + + ## NOTE: the following are specific to one cert id + ## the above are for one file which may contain + ## several responses + + ## certStatus (this is the response to look at) + certStatus: string &log &optional; + ## thisUpdate + thisUpdate: string &log &optional; + ## nextUpdate + nextUpdate: string &log &optional; + }; + + type Info: record { + ## timestamp for request if both request is present + ## OR timestamp for response if request is not found + ts: time &log; + req: Info_req &log &optional; + resp: Info_resp &log &optional; + }; + + ## Event for accessing logged OCSP records. + global log_ocsp: event(rec: Info); } + +redef record connection += { + ## keep track of pending requests received so for + ocsp_requests: PendingRequests &optional; + }; + +event bro_init() &priority=5 + { + Log::create_stream(LOG, [$columns=Info, $ev=log_ocsp, $path="ocsp"]); + } + +function get_http_info(f: fa_file, meta: fa_metadata) + { + if (f$source != "HTTP" || !meta?$mime_type) + return; + + # call OCSP file analyzer + if (meta$mime_type == "application/ocsp-request") + Files::add_analyzer(f, Files::ANALYZER_OCSP, [$ocsp_type = "request"]); + else if (meta$mime_type == "application/ocsp-response") + Files::add_analyzer(f, Files::ANALYZER_OCSP, [$ocsp_type = "response"]); + } + +event file_sniff(f: fa_file, meta: fa_metadata) &priority = 5 + { + if (f$source == "HTTP") + get_http_info(f, meta); + } + +event ocsp_request(f: fa_file, req_ref: opaque of ocsp_req, req: OCSP::Request) &priority = 5 + { + local conn: connection; + local cid: conn_id; + + # there should be only one loop: one connection + for (id in f$conns) + { + cid = id; + conn = f$conns[id]; + } + + if (req?$requestList) + { + for (x in req$requestList) + { + local one_req = req$requestList[x]; + local cert_id: OCSP::CertId = [$hashAlgorithm = one_req$hashAlgorithm, + $issuerNameHash = one_req$issuerNameHash, + $issuerKeyHash = one_req$issuerKeyHash, + $serialNumber = one_req$serialNumber]; + + local req_rec: Info_req = [$ts=network_time(), $id=f$id, $certId=cert_id]; + + if (req?$version) + req_rec$version = req$version; + + if (req?$requestorName) + req_rec$requestorName = req$requestorName; + + if (!conn?$ocsp_requests) + conn$ocsp_requests = table(); + + if (cert_id !in conn$ocsp_requests) + conn$ocsp_requests[cert_id] = Queue::init(); + + Queue::put(conn$ocsp_requests[cert_id], req_rec); + } + } + else + { + # no request content? this is weird but log it anyway + local req_rec_empty: Info_req = [$ts=network_time(), $id=f$id]; + if (req?$version) + req_rec_empty$version = req$version; + if (req?$requestorName) + req_rec_empty$requestorName = req$requestorName; + Log::write(LOG, [$ts=req_rec_empty$ts, $req=req_rec_empty]); + } + } + +event ocsp_response(f: fa_file, resp_ref: opaque of ocsp_resp, resp: OCSP::Response) &priority = 5 + { + local conn: connection; + local cid: conn_id; + + # there should be only one loop + for (id in f$conns) + { + cid = id; + conn = f$conns[id]; + } + + if (resp?$responses) + { + for (x in resp$responses) + { + local single_resp: OCSP::SingleResp = resp$responses[x]; + local cert_id: OCSP::CertId = [$hashAlgorithm = single_resp$hashAlgorithm, + $issuerNameHash = single_resp$issuerNameHash, + $issuerKeyHash = single_resp$issuerKeyHash, + $serialNumber = single_resp$serialNumber]; + local resp_rec: Info_resp = [$ts = network_time(), $id = f$id, + $responseStatus = resp$responseStatus, + $responseType = resp$responseType, + $version = resp$version, + $responderID = resp$responderID, + $producedAt = resp$producedAt, + $certStatus = single_resp$certStatus, + $thisUpdate = single_resp$thisUpdate]; + if (single_resp?$nextUpdate) + resp_rec$nextUpdate = single_resp$nextUpdate; + + if (cert_id in conn$ocsp_requests) + { + # find a match + local req_rec: Info_req = Queue::get(conn$ocsp_requests[cert_id]); + Log::write(LOG, [$ts=req_rec$ts, $req=req_rec, $resp=resp_rec]); + if (Queue::len(conn$ocsp_requests[cert_id]) == 0) + delete conn$ocsp_requests[cert_id]; #if queue is empty, delete it? + } + else + { + # do not find a match; this is weird but log it + Log::write(LOG, [$ts=resp_rec$ts, $resp=resp_rec]); + } + } + } + else + { + # no response content? this is weird but log it anyway + local resp_rec_empty: Info_resp = [$ts=network_time(), $id=f$id, + $responseStatus = resp$responseStatus, + $responseType = resp$responseType, + $version = resp$version, + $responderID = resp$responderID, + $producedAt = resp$producedAt]; + Log::write(LOG, [$ts=resp_rec_empty$ts, $resp=resp_rec_empty]); + } + } + +function log_unmatched_msgs_queue(q: Queue::Queue) + { + local reqs: vector of Info_req; + Queue::get_vector(q, reqs); + + for ( i in reqs ) + Log::write(LOG, [$ts=reqs[i]$ts, $req=reqs[i]]); + } + +function log_unmatched_msgs(msgs: PendingRequests) + { + for ( cert_id in msgs ) + log_unmatched_msgs_queue(msgs[cert_id]); + + clear_table(msgs); + } + +# need to log unmatched ocsp request if any +event connection_state_remove(c: connection) &priority= -5 + { + if (! c?$ocsp_requests) + return; + log_unmatched_msgs(c$ocsp_requests); + } diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index fdc05f2df4..a1dfe2ee06 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2961,8 +2961,14 @@ export { producedAt: string &log; responses: vector of SingleResp; #responseExtensions:xxx - signatureAlgorithm: string &log; - signature: string; #&log; + signatureAlgorithm: string &log &optional; + signature: string &optional; #&log; + }; + type CertId: record { + hashAlgorithm: string &log; + issuerNameHash: string &log; + issuerKeyHash: string &log; + serialNumber: string &log; }; } diff --git a/src/file_analysis/analyzer/ocsp/types.bif b/src/file_analysis/analyzer/ocsp/types.bif index e005f1274e..8d8cb5dd9b 100644 --- a/src/file_analysis/analyzer/ocsp/types.bif +++ b/src/file_analysis/analyzer/ocsp/types.bif @@ -1,4 +1,5 @@ type OCSP::Request: record; type OCSP::Response: record; type OCSP::OneReq: record; -type OCSP::SingleResp: record; \ No newline at end of file +type OCSP::SingleResp: record; +type OCSP::CertId: record;