diff --git a/src/file_analysis/analyzer/x509/Asn1Time.h b/src/file_analysis/analyzer/x509/Asn1Time.h deleted file mode 100644 index 31013ec2a7..0000000000 --- a/src/file_analysis/analyzer/x509/Asn1Time.h +++ /dev/null @@ -1,157 +0,0 @@ -static double GetTimeFromAsn1(const ASN1_TIME* atime, const char* arg_fid, Reporter* reporter) - { - const char *fid = arg_fid ? arg_fid : ""; - time_t lResult = 0; - - char lBuffer[26]; - char* pBuffer = lBuffer; - - const char *pString = (const char *) atime->data; - unsigned int remaining = atime->length; - - if ( atime->type == V_ASN1_UTCTIME ) - { - if ( remaining < 11 || remaining > 17 ) - { - reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- UTCTime has wrong length", fid)); - return 0; - } - - if ( pString[remaining-1] != 'Z' ) - { - // not valid according to RFC 2459 4.1.2.5.1 - reporter->Weird(fmt("Could not parse UTC time in non-YY-format in X509 certificate (x509 %s)", fid)); - return 0; - } - - // year is first two digits in YY format. Buffer expects YYYY format. - if ( pString[0] < '5' ) // RFC 2459 4.1.2.5.1 - { - *(pBuffer++) = '2'; - *(pBuffer++) = '0'; - } - else - { - *(pBuffer++) = '1'; - *(pBuffer++) = '9'; - } - - memcpy(pBuffer, pString, 10); - pBuffer += 10; - pString += 10; - remaining -= 10; - } - else if ( atime->type == V_ASN1_GENERALIZEDTIME ) - { - // generalized time. We apparently ignore the YYYYMMDDHH case - // for now and assume we always have minutes and seconds. - // This should be ok because it is specified as a requirement in RFC 2459 4.1.2.5.2 - - if ( remaining < 12 || remaining > 23 ) - { - reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- Generalized time has wrong length", fid)); - return 0; - } - - memcpy(pBuffer, pString, 12); - pBuffer += 12; - pString += 12; - remaining -= 12; - } - else - { - reporter->Weird(fmt("Invalid time type in X509 certificate (fuid %s)", fid)); - return 0; - } - - if ( (remaining == 0) || (*pString == 'Z') || (*pString == '-') || (*pString == '+') ) - { - *(pBuffer++) = '0'; - *(pBuffer++) = '0'; - } - - else if ( remaining >= 2 ) - { - *(pBuffer++) = *(pString++); - *(pBuffer++) = *(pString++); - - remaining -= 2; - - // Skip any fractional seconds... - if ( (remaining > 0) && (*pString == '.') ) - { - pString++; - remaining--; - - while ( (remaining > 0) && (*pString >= '0') && (*pString <= '9') ) - { - pString++; - remaining--; - } - } - } - - else - { - reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- additional char after time", fid)); - return 0; - } - - *(pBuffer++) = 'Z'; - *(pBuffer++) = '\0'; - - time_t lSecondsFromUTC; - - if ( remaining == 0 || *pString == 'Z' ) - lSecondsFromUTC = 0; - else - { - if ( remaining < 5 ) - { - reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- not enough bytes remaining for offset", fid)); - return 0; - } - - if ((*pString != '+') && (*pString != '-')) - { - reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- unknown offset type", fid)); - return 0; - } - - lSecondsFromUTC = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60; - lSecondsFromUTC += (pString[3] - '0') * 10 + (pString[4] - '0'); - - if (*pString == '-') - lSecondsFromUTC = -lSecondsFromUTC; - } - - tm lTime; - lTime.tm_sec = ((lBuffer[12] - '0') * 10) + (lBuffer[13] - '0'); - lTime.tm_min = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0'); - lTime.tm_hour = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0'); - lTime.tm_mday = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0'); - lTime.tm_mon = (((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0')) - 1; - lTime.tm_year = (lBuffer[0] - '0') * 1000 + (lBuffer[1] - '0') * 100 + ((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0'); - - if ( lTime.tm_year > 1900) - lTime.tm_year -= 1900; - - lTime.tm_wday = 0; - lTime.tm_yday = 0; - lTime.tm_isdst = 0; // No DST adjustment requested - - lResult = mktime(&lTime); - - if ( lResult ) - { - if ( lTime.tm_isdst != 0 ) - lResult -= 3600; // mktime may adjust for DST (OS dependent) - - lResult += lSecondsFromUTC; - } - - else - lResult = 0; - - return lResult; -} diff --git a/src/file_analysis/analyzer/x509/CMakeLists.txt b/src/file_analysis/analyzer/x509/CMakeLists.txt index 1eb3732022..a4c5767e56 100644 --- a/src/file_analysis/analyzer/x509/CMakeLists.txt +++ b/src/file_analysis/analyzer/x509/CMakeLists.txt @@ -5,7 +5,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) bro_plugin_begin(Bro X509) -bro_plugin_cc(X509.cc OCSP.cc Plugin.cc) +bro_plugin_cc(X509Common.cc X509.cc OCSP.cc Plugin.cc) bro_plugin_bif(events.bif types.bif functions.bif ocsp_events.bif) bro_plugin_pac(x509-extension.pac x509-signed_certificate_timestamp.pac) bro_plugin_end() diff --git a/src/file_analysis/analyzer/x509/OCSP.cc b/src/file_analysis/analyzer/x509/OCSP.cc index 704df3a6db..1c27720e36 100644 --- a/src/file_analysis/analyzer/x509/OCSP.cc +++ b/src/file_analysis/analyzer/x509/OCSP.cc @@ -17,7 +17,6 @@ #include #include "file_analysis/analyzer/x509/X509.h" -#include "Asn1Time.h" // helper function of sk_X509_value to avoid namespace problem // sk_X509_value(X,Y) = > SKM_sk_value(X509,X,Y) @@ -87,7 +86,7 @@ file_analysis::Analyzer* OCSP::InstantiateReply(RecordVal* args, File* file) } file_analysis::OCSP::OCSP(RecordVal* args, file_analysis::File* file, bool arg_request) - : file_analysis::Analyzer(file_mgr->GetComponentTag("OCSP"), args, file), request(arg_request) + : file_analysis::X509Common::X509Common(file_mgr->GetComponentTag("OCSP"), args, file), request(arg_request) { } @@ -295,7 +294,7 @@ void file_analysis::OCSP::ParseResponse(OCSP_RESPVal *resp_val, const char* fid) if ( ! ex ) continue; - ParseExtension(ex, false); + ParseExtension(ex, ocsp_extension, false); } } @@ -334,63 +333,23 @@ void file_analysis::OCSP::ParseResponse(OCSP_RESPVal *resp_val, const char* fid) if ( ! ex ) continue; - ParseExtension(ex, true); + ParseExtension(ex, ocsp_extension, true); } - clean_up: if (basic_resp) OCSP_BASICRESP_free(basic_resp); BIO_free(bio); } -// This is a near copy from X509 -void file_analysis::OCSP::ParseExtension(X509_EXTENSION* ex, bool global) +void file_analysis::OCSP::ParseExtensionsSpecific(X509_EXTENSION* ex, bool global, ASN1_OBJECT* ext_asn, const char* oid) { - char name[256]; - char oid[256]; - - ASN1_OBJECT* ext_asn = X509_EXTENSION_get_object(ex); - const char* short_name = OBJ_nid2sn(OBJ_obj2nid(ext_asn)); - - OBJ_obj2txt(name, 255, ext_asn, 0); - OBJ_obj2txt(oid, 255, ext_asn, 1); - - int critical = 0; - if ( X509_EXTENSION_get_critical(ex) != 0 ) - critical = 1; - - BIO *bio = BIO_new(BIO_s_mem()); - if( ! X509V3_EXT_print(bio, ex, 0, 0)) - M_ASN1_OCTET_STRING_print(bio,ex->value); - - StringVal* ext_val = X509::GetExtensionFromBIO(bio); - - if ( ! ext_val ) - ext_val = new StringVal(0, ""); - - RecordVal* pX509Ext = new RecordVal(BifType::Record::X509::Extension); - pX509Ext->Assign(0, new StringVal(name)); - - if ( short_name and strlen(short_name) > 0 ) - pX509Ext->Assign(1, new StringVal(short_name)); - - pX509Ext->Assign(2, new StringVal(oid)); - pX509Ext->Assign(3, new Val(critical, TYPE_BOOL)); - pX509Ext->Assign(4, ext_val); - - // send off generic extension event - // - // and then look if we have a specialized event for the extension we just - // parsed. And if we have it, we send the specialized event on top of the - // generic event that we just had. I know, that is... kind of not nice, - // but I am not sure if there is a better way to do it... - val_list* vl = new val_list(); - vl->append(GetFile()->GetVal()->Ref()); - vl->append(pX509Ext); - vl->append(new Val(global ? 1 : 0, TYPE_BOOL)); - - mgr.QueueEvent(ocsp_extension, vl); +#ifdef NID_ct_cert_scts + if ( OBJ_obj2nid(ext_asn) == NID_ct_cert_scts || OBJ_obj2nid(ext_asn) == NID_ct_precert_scts ) +#else + if ( strcmp(oid, "1.3.6.1.4.1.11129.2.4.2") == 0 || strcmp(oid, "1.3.6.1.4.1.11129.2.4.4") == 0 ) +#endif + ParseSignedCertificateTimestamps(ex); } OCSP_RESPVal::OCSP_RESPVal(OCSP_RESPONSE* arg_ocsp_resp) : OpaqueVal(ocsp_resp_opaque_type) diff --git a/src/file_analysis/analyzer/x509/OCSP.h b/src/file_analysis/analyzer/x509/OCSP.h index f3560ecea4..d1d15dd14f 100644 --- a/src/file_analysis/analyzer/x509/OCSP.h +++ b/src/file_analysis/analyzer/x509/OCSP.h @@ -8,20 +8,19 @@ #include "Val.h" #include "../File.h" #include "Analyzer.h" +#include "X509Common.h" #include -#include -#include namespace file_analysis { class OCSP_RESPVal; -class OCSP : public file_analysis::Analyzer { +class OCSP : public file_analysis::X509Common { public: - virtual bool DeliverStream(const u_char* data, uint64 len); - virtual bool Undelivered(uint64 offset, uint64 len); - virtual bool EndOfFile(); + bool DeliverStream(const u_char* data, uint64 len) override; + bool Undelivered(uint64 offset, uint64 len) override; + bool EndOfFile() override; static file_analysis::Analyzer* InstantiateRequest(RecordVal* args, File* file); static file_analysis::Analyzer* InstantiateReply(RecordVal* args, File* file); @@ -32,7 +31,7 @@ protected: private: void ParseResponse(OCSP_RESPVal *, const char* fid = 0); void ParseRequest(OCSP_REQUEST *, const char* fid = 0); - void ParseExtension(X509_EXTENSION*, bool global); + void ParseExtensionsSpecific(X509_EXTENSION* ex, bool, ASN1_OBJECT*, const char*) override; std::string ocsp_data; bool request = false; // true if ocsp request, false if reply diff --git a/src/file_analysis/analyzer/x509/X509.cc b/src/file_analysis/analyzer/x509/X509.cc index 4a2a9cc5c2..f9ec91c9d1 100644 --- a/src/file_analysis/analyzer/x509/X509.cc +++ b/src/file_analysis/analyzer/x509/X509.cc @@ -4,7 +4,6 @@ #include "X509.h" #include "Event.h" -#include "x509-extension_pac.h" #include "events.bif.h" #include "types.bif.h" @@ -17,14 +16,12 @@ #include #include -#include "Asn1Time.h" - using namespace file_analysis; IMPLEMENT_SERIAL(X509Val, SER_X509_VAL); file_analysis::X509::X509(RecordVal* args, file_analysis::File* file) - : file_analysis::Analyzer(file_mgr->GetComponentTag("X509"), args, file) + : file_analysis::X509Common::X509Common(file_mgr->GetComponentTag("X509"), args, file) { cert_data.clear(); } @@ -75,7 +72,7 @@ bool file_analysis::X509::EndOfFile() if ( ! ex ) continue; - ParseExtension(ex); + ParseExtension(ex, x509_extension, false); } // X509_free(ssl_cert); We do _not_ free the certificate here. It is refcounted @@ -208,152 +205,6 @@ RecordVal* file_analysis::X509::ParseCertificate(X509Val* cert_val, const char* return pX509Cert; } -StringVal* file_analysis::X509::GetExtensionFromBIO(BIO* bio) - { - BIO_flush(bio); - ERR_clear_error(); - int length = BIO_pending(bio); - - if ( ERR_peek_error() != 0 ) - { - char tmp[120]; - ERR_error_string_n(ERR_get_error(), tmp, sizeof(tmp)); - reporter->Weird(fmt("X509::GetExtensionFromBIO: %s", tmp)); - BIO_free_all(bio); - return 0; - } - - if ( length == 0 ) - { - BIO_free_all(bio); - return new StringVal(""); - } - - char* buffer = (char*) malloc(length); - - if ( ! buffer ) - { - // Just emit an error here and try to continue instead of aborting - // because it's unclear the length value is very reliable. - reporter->Error("X509::GetExtensionFromBIO malloc(%d) failed", length); - BIO_free_all(bio); - return 0; - } - - BIO_read(bio, (void*) buffer, length); - StringVal* ext_val = new StringVal(length, buffer); - - free(buffer); - BIO_free_all(bio); - - return ext_val; - } - -// this is nearly replicated in the OCSP analyzer -void file_analysis::X509::ParseExtension(X509_EXTENSION* ex) - { - char name[256]; - char oid[256]; - - ASN1_OBJECT* ext_asn = X509_EXTENSION_get_object(ex); - const char* short_name = OBJ_nid2sn(OBJ_obj2nid(ext_asn)); - - OBJ_obj2txt(name, 255, ext_asn, 0); - OBJ_obj2txt(oid, 255, ext_asn, 1); - - int critical = 0; - if ( X509_EXTENSION_get_critical(ex) != 0 ) - critical = 1; - - BIO *bio = BIO_new(BIO_s_mem()); - if( ! X509V3_EXT_print(bio, ex, 0, 0)) - M_ASN1_OCTET_STRING_print(bio,ex->value); - - StringVal* ext_val = GetExtensionFromBIO(bio); - - if ( ! ext_val ) - ext_val = new StringVal(0, ""); - - RecordVal* pX509Ext = new RecordVal(BifType::Record::X509::Extension); - pX509Ext->Assign(0, new StringVal(name)); - - if ( short_name and strlen(short_name) > 0 ) - pX509Ext->Assign(1, new StringVal(short_name)); - - pX509Ext->Assign(2, new StringVal(oid)); - pX509Ext->Assign(3, new Val(critical, TYPE_BOOL)); - pX509Ext->Assign(4, ext_val); - - // send off generic extension event - // - // and then look if we have a specialized event for the extension we just - // parsed. And if we have it, we send the specialized event on top of the - // generic event that we just had. I know, that is... kind of not nice, - // but I am not sure if there is a better way to do it... - val_list* vl = new val_list(); - vl->append(GetFile()->GetVal()->Ref()); - vl->append(pX509Ext); - - mgr.QueueEvent(x509_extension, vl); - - // look if we have a specialized handler for this event... - if ( OBJ_obj2nid(ext_asn) == NID_basic_constraints ) - ParseBasicConstraints(ex); - - else if ( OBJ_obj2nid(ext_asn) == NID_subject_alt_name ) - ParseSAN(ex); - -#ifdef NID_ct_cert_scts - else if ( OBJ_obj2nid(ext_asn) == NID_ct_cert_scts || OBJ_obj2nid(ext_asn) == NID_ct_precert_scts ) -#else - else if ( strcmp(oid, "1.3.6.1.4.1.11129.2.4.2") == 0 || strcmp(oid, "1.3.6.1.4.1.11129.2.4.4") == 0 ) -#endif - ParseSignedCertificateTimestamps(ex); - } - -void file_analysis::X509::ParseSignedCertificateTimestamps(X509_EXTENSION* ext) - { - // Ok, signed certificate timestamps are a bit of an odd case out; we don't - // want to use the (basically nonexistant) OpenSSL functionality to parse them. - // Instead we have our own, self-written binpac parser to parse just them, - // which we will initialize here and tear down immediately again. - - ASN1_OCTET_STRING* ext_val = X509_EXTENSION_get_data(ext); - // the octet string of the extension contains the octet string which in turn - // contains the SCT. Obviously. - - unsigned char* ext_val_copy = (unsigned char*) OPENSSL_malloc(ext_val->length); - unsigned char* ext_val_second_pointer = ext_val_copy; - memcpy(ext_val_copy, ext_val->data, ext_val->length); - - ASN1_OCTET_STRING* inner = d2i_ASN1_OCTET_STRING(NULL, (const unsigned char**) &ext_val_copy, ext_val->length); - if ( !inner ) - { - reporter->Error("X509::ParseSignedCertificateTimestamps could not parse inner octet string"); - return; - } - - binpac::X509Extension::MockConnection* conn = new binpac::X509Extension::MockConnection(this); - binpac::X509Extension::SignedCertTimestampExt* interp = new binpac::X509Extension::SignedCertTimestampExt(conn); - - try - { - interp->NewData(inner->data, inner->data + inner->length); - } - catch( const binpac::Exception& e ) - { - // throw a warning or sth - reporter->Error("X509::ParseSignedCertificateTimestamps could not parse SCT"); - } - - OPENSSL_free(ext_val_second_pointer); - - interp->FlowEOF(); - - delete interp; - delete conn; - } - void file_analysis::X509::ParseBasicConstraints(X509_EXTENSION* ex) { assert(OBJ_obj2nid(X509_EXTENSION_get_object(ex)) == NID_basic_constraints); @@ -380,6 +231,23 @@ void file_analysis::X509::ParseBasicConstraints(X509_EXTENSION* ex) reporter->Weird(fmt("Certificate with invalid BasicConstraint. fuid %s", GetFile()->GetID().c_str())); } +void file_analysis::X509::ParseExtensionsSpecific(X509_EXTENSION* ex, bool global, ASN1_OBJECT* ext_asn, const char* oid) + { + // look if we have a specialized handler for this event... + if ( OBJ_obj2nid(ext_asn) == NID_basic_constraints ) + ParseBasicConstraints(ex); + + else if ( OBJ_obj2nid(ext_asn) == NID_subject_alt_name ) + ParseSAN(ex); + +#ifdef NID_ct_cert_scts + else if ( OBJ_obj2nid(ext_asn) == NID_ct_cert_scts || OBJ_obj2nid(ext_asn) == NID_ct_precert_scts ) +#else + else if ( strcmp(oid, "1.3.6.1.4.1.11129.2.4.2") == 0 || strcmp(oid, "1.3.6.1.4.1.11129.2.4.4") == 0 ) +#endif + ParseSignedCertificateTimestamps(ex); + } + void file_analysis::X509::ParseSAN(X509_EXTENSION* ext) { assert(OBJ_obj2nid(X509_EXTENSION_get_object(ext)) == NID_subject_alt_name); diff --git a/src/file_analysis/analyzer/x509/X509.h b/src/file_analysis/analyzer/x509/X509.h index 9bbc0827cd..325e8becac 100644 --- a/src/file_analysis/analyzer/x509/X509.h +++ b/src/file_analysis/analyzer/x509/X509.h @@ -6,21 +6,18 @@ #include #include "Val.h" -#include "../File.h" -#include "Analyzer.h" +#include "X509Common.h" -#include -#include namespace file_analysis { class X509Val; -class X509 : public file_analysis::Analyzer { +class X509 : public file_analysis::X509Common { public: - virtual bool DeliverStream(const u_char* data, uint64 len); - virtual bool Undelivered(uint64 offset, uint64 len); - virtual bool EndOfFile(); + bool DeliverStream(const u_char* data, uint64 len) override; + bool Undelivered(uint64 offset, uint64 len) override; + bool EndOfFile() override; /** * Converts an X509 certificate into a \c X509::Certificate record @@ -40,25 +37,13 @@ public: static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file) { return new X509(args, file); } - /** - * Retrieve an X509 extension value from an OpenSSL BIO to which it was - * written. - * - * @param bio the OpenSSL BIO to read. It will be freed by the function, - * including when an error occurs. - * - * @return The X509 extension value. - */ - static StringVal* GetExtensionFromBIO(BIO* bio); - protected: X509(RecordVal* args, File* file); private: - void ParseExtension(X509_EXTENSION* ex); void ParseBasicConstraints(X509_EXTENSION* ex); void ParseSAN(X509_EXTENSION* ex); - void ParseSignedCertificateTimestamps(X509_EXTENSION* ext); + void ParseExtensionsSpecific(X509_EXTENSION* ex, bool, ASN1_OBJECT*, const char*) override; std::string cert_data; diff --git a/src/file_analysis/analyzer/x509/X509Common.cc b/src/file_analysis/analyzer/x509/X509Common.cc new file mode 100644 index 0000000000..367b7e562a --- /dev/null +++ b/src/file_analysis/analyzer/x509/X509Common.cc @@ -0,0 +1,315 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "X509Common.h" +#include "x509-extension_pac.h" + +#include "events.bif.h" +#include "ocsp_events.bif.h" +#include "types.bif.h" + +#include +#include +#include +#include +#include + +using namespace file_analysis; + +X509Common::X509Common(file_analysis::Tag arg_tag, RecordVal* arg_args, File* arg_file) + : file_analysis::Analyzer(arg_tag, arg_args, arg_file) + { + } + +double X509Common::GetTimeFromAsn1(const ASN1_TIME* atime, const char* arg_fid, Reporter* reporter) + { + const char *fid = arg_fid ? arg_fid : ""; + time_t lResult = 0; + + char lBuffer[26]; + char* pBuffer = lBuffer; + + const char *pString = (const char *) atime->data; + unsigned int remaining = atime->length; + + if ( atime->type == V_ASN1_UTCTIME ) + { + if ( remaining < 11 || remaining > 17 ) + { + reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- UTCTime has wrong length", fid)); + return 0; + } + + if ( pString[remaining-1] != 'Z' ) + { + // not valid according to RFC 2459 4.1.2.5.1 + reporter->Weird(fmt("Could not parse UTC time in non-YY-format in X509 certificate (x509 %s)", fid)); + return 0; + } + + // year is first two digits in YY format. Buffer expects YYYY format. + if ( pString[0] < '5' ) // RFC 2459 4.1.2.5.1 + { + *(pBuffer++) = '2'; + *(pBuffer++) = '0'; + } + else + { + *(pBuffer++) = '1'; + *(pBuffer++) = '9'; + } + + memcpy(pBuffer, pString, 10); + pBuffer += 10; + pString += 10; + remaining -= 10; + } + else if ( atime->type == V_ASN1_GENERALIZEDTIME ) + { + // generalized time. We apparently ignore the YYYYMMDDHH case + // for now and assume we always have minutes and seconds. + // This should be ok because it is specified as a requirement in RFC 2459 4.1.2.5.2 + + if ( remaining < 12 || remaining > 23 ) + { + reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- Generalized time has wrong length", fid)); + return 0; + } + + memcpy(pBuffer, pString, 12); + pBuffer += 12; + pString += 12; + remaining -= 12; + } + else + { + reporter->Weird(fmt("Invalid time type in X509 certificate (fuid %s)", fid)); + return 0; + } + + if ( (remaining == 0) || (*pString == 'Z') || (*pString == '-') || (*pString == '+') ) + { + *(pBuffer++) = '0'; + *(pBuffer++) = '0'; + } + + else if ( remaining >= 2 ) + { + *(pBuffer++) = *(pString++); + *(pBuffer++) = *(pString++); + + remaining -= 2; + + // Skip any fractional seconds... + if ( (remaining > 0) && (*pString == '.') ) + { + pString++; + remaining--; + + while ( (remaining > 0) && (*pString >= '0') && (*pString <= '9') ) + { + pString++; + remaining--; + } + } + } + + else + { + reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- additional char after time", fid)); + return 0; + } + + *(pBuffer++) = 'Z'; + *(pBuffer++) = '\0'; + + time_t lSecondsFromUTC; + + if ( remaining == 0 || *pString == 'Z' ) + lSecondsFromUTC = 0; + else + { + if ( remaining < 5 ) + { + reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- not enough bytes remaining for offset", fid)); + return 0; + } + + if ((*pString != '+') && (*pString != '-')) + { + reporter->Weird(fmt("Could not parse time in X509 certificate (fuid %s) -- unknown offset type", fid)); + return 0; + } + + lSecondsFromUTC = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60; + lSecondsFromUTC += (pString[3] - '0') * 10 + (pString[4] - '0'); + + if (*pString == '-') + lSecondsFromUTC = -lSecondsFromUTC; + } + + tm lTime; + lTime.tm_sec = ((lBuffer[12] - '0') * 10) + (lBuffer[13] - '0'); + lTime.tm_min = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0'); + lTime.tm_hour = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0'); + lTime.tm_mday = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0'); + lTime.tm_mon = (((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0')) - 1; + lTime.tm_year = (lBuffer[0] - '0') * 1000 + (lBuffer[1] - '0') * 100 + ((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0'); + + if ( lTime.tm_year > 1900) + lTime.tm_year -= 1900; + + lTime.tm_wday = 0; + lTime.tm_yday = 0; + lTime.tm_isdst = 0; // No DST adjustment requested + + lResult = mktime(&lTime); + + if ( lResult ) + { + if ( lTime.tm_isdst != 0 ) + lResult -= 3600; // mktime may adjust for DST (OS dependent) + + lResult += lSecondsFromUTC; + } + + else + lResult = 0; + + return lResult; +} + +void file_analysis::X509Common::ParseSignedCertificateTimestamps(X509_EXTENSION* ext) + { + // Ok, signed certificate timestamps are a bit of an odd case out; we don't + // want to use the (basically nonexistant) OpenSSL functionality to parse them. + // Instead we have our own, self-written binpac parser to parse just them, + // which we will initialize here and tear down immediately again. + + ASN1_OCTET_STRING* ext_val = X509_EXTENSION_get_data(ext); + // the octet string of the extension contains the octet string which in turn + // contains the SCT. Obviously. + + unsigned char* ext_val_copy = (unsigned char*) OPENSSL_malloc(ext_val->length); + unsigned char* ext_val_second_pointer = ext_val_copy; + memcpy(ext_val_copy, ext_val->data, ext_val->length); + + ASN1_OCTET_STRING* inner = d2i_ASN1_OCTET_STRING(NULL, (const unsigned char**) &ext_val_copy, ext_val->length); + if ( !inner ) + { + reporter->Error("X509::ParseSignedCertificateTimestamps could not parse inner octet string"); + return; + } + + binpac::X509Extension::MockConnection* conn = new binpac::X509Extension::MockConnection(this); + binpac::X509Extension::SignedCertTimestampExt* interp = new binpac::X509Extension::SignedCertTimestampExt(conn); + + try + { + interp->NewData(inner->data, inner->data + inner->length); + } + catch( const binpac::Exception& e ) + { + // throw a warning or sth + reporter->Error("X509::ParseSignedCertificateTimestamps could not parse SCT"); + } + + OPENSSL_free(ext_val_second_pointer); + + interp->FlowEOF(); + + delete interp; + delete conn; + } + +void file_analysis::X509Common::ParseExtension(X509_EXTENSION* ex, EventHandlerPtr h, bool global) + { + char name[256]; + char oid[256]; + + ASN1_OBJECT* ext_asn = X509_EXTENSION_get_object(ex); + const char* short_name = OBJ_nid2sn(OBJ_obj2nid(ext_asn)); + + OBJ_obj2txt(name, 255, ext_asn, 0); + OBJ_obj2txt(oid, 255, ext_asn, 1); + + int critical = 0; + if ( X509_EXTENSION_get_critical(ex) != 0 ) + critical = 1; + + BIO *bio = BIO_new(BIO_s_mem()); + if( ! X509V3_EXT_print(bio, ex, 0, 0)) + M_ASN1_OCTET_STRING_print(bio,ex->value); + + StringVal* ext_val = GetExtensionFromBIO(bio); + + if ( ! ext_val ) + ext_val = new StringVal(0, ""); + + RecordVal* pX509Ext = new RecordVal(BifType::Record::X509::Extension); + pX509Ext->Assign(0, new StringVal(name)); + + if ( short_name and strlen(short_name) > 0 ) + pX509Ext->Assign(1, new StringVal(short_name)); + + pX509Ext->Assign(2, new StringVal(oid)); + pX509Ext->Assign(3, new Val(critical, TYPE_BOOL)); + pX509Ext->Assign(4, ext_val); + + // send off generic extension event + // + // and then look if we have a specialized event for the extension we just + // parsed. And if we have it, we send the specialized event on top of the + // generic event that we just had. I know, that is... kind of not nice, + // but I am not sure if there is a better way to do it... + val_list* vl = new val_list(); + vl->append(GetFile()->GetVal()->Ref()); + vl->append(pX509Ext); + if ( h == ocsp_extension ) + vl->append(new Val(global ? 1 : 0, TYPE_BOOL)); + + mgr.QueueEvent(h, vl); + + // let individual analyzers parse more. + ParseExtensionsSpecific(ex, global, ext_asn, oid); + } + +StringVal* file_analysis::X509Common::GetExtensionFromBIO(BIO* bio) + { + BIO_flush(bio); + ERR_clear_error(); + int length = BIO_pending(bio); + + if ( ERR_peek_error() != 0 ) + { + char tmp[120]; + ERR_error_string_n(ERR_get_error(), tmp, sizeof(tmp)); + reporter->Weird(fmt("X509::GetExtensionFromBIO: %s", tmp)); + BIO_free_all(bio); + return 0; + } + + if ( length == 0 ) + { + BIO_free_all(bio); + return new StringVal(""); + } + + char* buffer = (char*) malloc(length); + + if ( ! buffer ) + { + // Just emit an error here and try to continue instead of aborting + // because it's unclear the length value is very reliable. + reporter->Error("X509::GetExtensionFromBIO malloc(%d) failed", length); + BIO_free_all(bio); + return 0; + } + + BIO_read(bio, (void*) buffer, length); + StringVal* ext_val = new StringVal(length, buffer); + + free(buffer); + BIO_free_all(bio); + + return ext_val; + } diff --git a/src/file_analysis/analyzer/x509/X509Common.h b/src/file_analysis/analyzer/x509/X509Common.h new file mode 100644 index 0000000000..1e1a9b94ee --- /dev/null +++ b/src/file_analysis/analyzer/x509/X509Common.h @@ -0,0 +1,44 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +// Common base class for the X509 and OCSP analyzer, which share a fair amount of +// code + +#ifndef FILE_ANALYSIS_X509_COMMON +#define FILE_ANALYSIS_X509_COMMON + +#include "../File.h" +#include "Analyzer.h" + +#include +#include + +namespace file_analysis { + +class X509Common : public file_analysis::Analyzer { +public: + virtual ~X509Common() {}; + + /** + * Retrieve an X509 extension value from an OpenSSL BIO to which it was + * written. + * + * @param bio the OpenSSL BIO to read. It will be freed by the function, + * including when an error occurs. + * + * @return The X509 extension value. + */ + static StringVal* GetExtensionFromBIO(BIO* bio); + + static double GetTimeFromAsn1(const ASN1_TIME* atime, const char* arg_fid, Reporter* reporter); + +protected: + X509Common(file_analysis::Tag arg_tag, RecordVal* arg_args, File* arg_file); + + void ParseExtension(X509_EXTENSION* ex, EventHandlerPtr h, bool global); + void ParseSignedCertificateTimestamps(X509_EXTENSION* ext); + virtual void ParseExtensionsSpecific(X509_EXTENSION* ex, bool, ASN1_OBJECT*, const char*) = 0; +}; + +} + +#endif /* FILE_ANALYSIS_X509_COMMON */ diff --git a/src/file_analysis/analyzer/x509/events.bif b/src/file_analysis/analyzer/x509/events.bif index 6e4df4393f..f8084fa864 100644 --- a/src/file_analysis/analyzer/x509/events.bif +++ b/src/file_analysis/analyzer/x509/events.bif @@ -73,4 +73,4 @@ event x509_ext_subject_alternative_name%(f: fa_file, ext: X509::SubjectAlternati ## digitally_signed struct ## ## signature: signature part of the digitally_signed struct -event x509_ext_signed_certificate_timestamp%(f: fa_file, version: count, logid: string, timestamp: time, hash_algorithm: count, signature_algorithm: count, signature: string%); +event x509_ocsp_ext_signed_certificate_timestamp%(f: fa_file, version: count, logid: string, timestamp: time, hash_algorithm: count, signature_algorithm: count, signature: string%); diff --git a/src/file_analysis/analyzer/x509/x509-extension.pac b/src/file_analysis/analyzer/x509/x509-extension.pac index 56ca27d909..e77a82307a 100644 --- a/src/file_analysis/analyzer/x509/x509-extension.pac +++ b/src/file_analysis/analyzer/x509/x509-extension.pac @@ -35,7 +35,7 @@ refine connection MockConnection += { function proc_signedcertificatetimestamp(rec: HandshakeRecord, version: uint8, logid: const_bytestring, timestamp: uint64, digitally_signed_algorithms: SignatureAndHashAlgorithm, digitally_signed_signature: const_bytestring) : bool %{ - BifEvent::generate_x509_ext_signed_certificate_timestamp((analyzer::Analyzer *) bro_analyzer(), + BifEvent::generate_x509_ocsp_ext_signed_certificate_timestamp((analyzer::Analyzer *) bro_analyzer(), bro_analyzer()->GetFile()->GetVal()->Ref(), version, new StringVal(logid.length(), reinterpret_cast(logid.begin())), diff --git a/testing/btest/scripts/base/files/x509/signed_certificate_timestamp.test b/testing/btest/scripts/base/files/x509/signed_certificate_timestamp.test index 63be26448f..5203bda7ee 100644 --- a/testing/btest/scripts/base/files/x509/signed_certificate_timestamp.test +++ b/testing/btest/scripts/base/files/x509/signed_certificate_timestamp.test @@ -1,7 +1,7 @@ # @TEST-EXEC: bro -r $TRACES/tls/certificate-with-sct.pcap %INPUT # @TEST-EXEC: btest-diff .stdout -event x509_ext_signed_certificate_timestamp(f: fa_file, version: count, logid: string, timestamp: time, hash_algorithm: count, signature_algorithm: count, signature: string) +event x509_ocsp_ext_signed_certificate_timestamp(f: fa_file, version: count, logid: string, timestamp: time, hash_algorithm: count, signature_algorithm: count, signature: string) { print version, timestamp, hash_algorithm, signature_algorithm; }