diff --git a/scripts/base/files/x509/__load__.bro b/scripts/base/files/x509/__load__.bro new file mode 100644 index 0000000000..a10fe855df --- /dev/null +++ b/scripts/base/files/x509/__load__.bro @@ -0,0 +1 @@ +@load ./main diff --git a/scripts/base/files/x509/main.bro b/scripts/base/files/x509/main.bro new file mode 100644 index 0000000000..205b8fbd25 --- /dev/null +++ b/scripts/base/files/x509/main.bro @@ -0,0 +1,14 @@ + +@load base/frameworks/files + +module X509; + +export { + redef enum Log::ID += { LOG }; +} + +event x509_cert(f: fa_file, cert: X509::Certificate) + { + print cert; + } + diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index fe3b84a93b..5d7914dc6b 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2721,6 +2721,26 @@ export { }; } +module X509; +export { + type X509::Certificate: record { + version: count; ##< Version number. + serial: string; ##< Serial number. + subject: string; ##< Subject. + issuer: string; ##< Issuer. + not_valid_before: time; ##< Timestamp before when certificate is not valid. + not_valid_after: time; ##< Timestamp after when certificate is not valid. + key_alg: string; ##< name of the key algorithm + sig_alg: string; ##< name of the signature algorithm + key_type: string &optional; ##< key-type, if key parseable by openssl (either rsa, dsa or ec) + key_length: count &optional; ##< key-length in bits + exponent: string &optional; ##< exponent, if RSA-certificate + curve: string &optional; ##< curve, if EC-certificate + ca: bool &optional; ##< indicates the CA value in the X509v3 BasicConstraints extension + path_len: count &optional; ##< indicates the path_length value in the X509v3 BasicConstraints extension + }; +} + module SOCKS; export { ## This record is for a SOCKS client or server to provide either a diff --git a/scripts/base/init-default.bro b/scripts/base/init-default.bro index 202f8eaaab..c0fb29f081 100644 --- a/scripts/base/init-default.bro +++ b/scripts/base/init-default.bro @@ -57,6 +57,6 @@ @load base/files/hash @load base/files/extract @load base/files/unified2 - +@load base/files/x509 @load base/misc/find-checksum-offloading diff --git a/scripts/base/protocols/ssl/__load__.bro b/scripts/base/protocols/ssl/__load__.bro index 5a8590f234..42287fb039 100644 --- a/scripts/base/protocols/ssl/__load__.bro +++ b/scripts/base/protocols/ssl/__load__.bro @@ -1,5 +1,6 @@ @load ./consts @load ./main @load ./mozilla-ca-list +@load ./files @load-sigs ./dpd.sig diff --git a/scripts/base/protocols/ssl/files.bro b/scripts/base/protocols/ssl/files.bro new file mode 100644 index 0000000000..7582a428ae --- /dev/null +++ b/scripts/base/protocols/ssl/files.bro @@ -0,0 +1,48 @@ +@load ./main +@load base/utils/conn-ids +@load base/frameworks/files + +module SSL; + +export { + redef record Info += { + ## An ordered vector of file unique IDs which contains + ## all the certificates sent over the connection + fuids: vector of string &log &default=string_vec(); + }; + + ## Default file handle provider for SSL. + global get_file_handle: function(c: connection, is_orig: bool): string; + + ## Default file describer for SSL. + global describe_file: function(f: fa_file): string; +} + +function get_file_handle(c: connection, is_orig: bool): string + { + return cat(Analyzer::ANALYZER_SMTP, c$start_time); + } + +function describe_file(f: fa_file): string + { + # This shouldn't be needed, but just in case... + if ( f$source != "SSL" ) + return ""; + + return ""; + } + +event bro_init() &priority=5 + { + Files::register_protocol(Analyzer::ANALYZER_SSL, + [$get_file_handle = SSL::get_file_handle, + $describe = SSL::describe_file]); + } + +event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5 + { + if ( c?$ssl ) + c$ssl$fuids[|c$ssl$fuids|] = f$id; + + Files::add_analyzer(f, Files::ANALYZER_X509); + } diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index 3d9564eaab..4cd7599ef7 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -10,6 +10,8 @@ #include #include + +#include "file_analysis/Manager.h" %} @@ -253,6 +255,11 @@ refine connection SSL_Conn += { { const bytestring& cert = (*certificates)[i]; const uint8* data = cert.data(); + + file_mgr->DataIn(reinterpret_cast(data), cert.length(), + bro_analyzer()->GetAnalyzerTag(), bro_analyzer()->Conn(), false); + file_mgr->EndOfFile(bro_analyzer()->GetAnalyzerTag(), bro_analyzer()->Conn()); + X509* pTemp = d2i_X509_binpac(NULL, &data, cert.length()); if ( ! pTemp ) { @@ -261,6 +268,7 @@ refine connection SSL_Conn += { return false; } + RecordVal* pX509Cert = new RecordVal(x509_type); char tmp[256]; BIO *bio = BIO_new(BIO_s_mem()); diff --git a/src/file_analysis/analyzer/CMakeLists.txt b/src/file_analysis/analyzer/CMakeLists.txt index 1e19b7bd11..ede63dbd1b 100644 --- a/src/file_analysis/analyzer/CMakeLists.txt +++ b/src/file_analysis/analyzer/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(data_event) add_subdirectory(extract) add_subdirectory(hash) add_subdirectory(unified2) +add_subdirectory(x509) diff --git a/src/file_analysis/analyzer/x509/CMakeLists.txt b/src/file_analysis/analyzer/x509/CMakeLists.txt new file mode 100644 index 0000000000..759a01b55c --- /dev/null +++ b/src/file_analysis/analyzer/x509/CMakeLists.txt @@ -0,0 +1,10 @@ + +include(BroPlugin) + +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}) + +bro_plugin_begin(Bro X509) +bro_plugin_cc(X509.cc Plugin.cc ../../Analyzer.cc) +bro_plugin_bif(events.bif types.bif) +bro_plugin_end() diff --git a/src/file_analysis/analyzer/x509/Plugin.cc b/src/file_analysis/analyzer/x509/Plugin.cc new file mode 100644 index 0000000000..1e76e3fdb7 --- /dev/null +++ b/src/file_analysis/analyzer/x509/Plugin.cc @@ -0,0 +1,10 @@ +#include "plugin/Plugin.h" + +#include "X509.h" + +BRO_PLUGIN_BEGIN(Bro, X509) + BRO_PLUGIN_DESCRIPTION("Parse X509 Certificate"); + BRO_PLUGIN_FILE_ANALYZER("X509", X509); + BRO_PLUGIN_BIF_FILE(events); + BRO_PLUGIN_BIF_FILE(types); +BRO_PLUGIN_END diff --git a/src/file_analysis/analyzer/x509/X509.cc b/src/file_analysis/analyzer/x509/X509.cc new file mode 100644 index 0000000000..78d746ac9b --- /dev/null +++ b/src/file_analysis/analyzer/x509/X509.cc @@ -0,0 +1,295 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include + +#include "X509.h" +#include "Event.h" + +#include "events.bif.h" +#include "types.bif.h" + +#include "file_analysis/Manager.h" + +#include +#include +#include +#include + +using namespace file_analysis; + +file_analysis::X509::X509(RecordVal* args, file_analysis::File* file) + : file_analysis::Analyzer(file_mgr->GetComponentTag("X509"), args, file) + { + cert_data.clear(); + } + +bool file_analysis::X509::DeliverStream(const u_char* data, uint64 len) + { + // just add it to the data we have so far, since we cannot do anything else anyways... + cert_data.append(reinterpret_cast(data), len); + return true; + } + +bool file_analysis::X509::Undelivered(uint64 offset, uint64 len) + { + return false; + } + +bool file_analysis::X509::EndOfFile() + { + // ok, now we can try to parse the certificate with openssl. Should + // be rather straightforward... + const unsigned char* cert_char = reinterpret_cast(cert_data.data()); + ::X509* ssl_cert = d2i_X509(NULL, &cert_char, cert_data.size()); + if ( !ssl_cert ) + { + reporter->Error("Could not parse X509 certificate"); + return false; + } + + char buf[256]; // we need a buffer for some of the openssl functions + memset(buf, 0, 256); + + RecordVal* pX509Cert = new RecordVal(BifType::Record::X509::Certificate); + BIO *bio = BIO_new(BIO_s_mem()); + + pX509Cert->Assign(0, new Val((uint64) X509_get_version(ssl_cert), TYPE_COUNT)); + i2a_ASN1_INTEGER(bio, X509_get_serialNumber(ssl_cert)); + int len = BIO_read(bio, &(*buf), sizeof buf); + pX509Cert->Assign(1, new StringVal(len, buf)); + + X509_NAME_print_ex(bio, X509_get_subject_name(ssl_cert), 0, XN_FLAG_RFC2253); + len = BIO_gets(bio, &(*buf), sizeof buf); + pX509Cert->Assign(2, new StringVal(len, buf)); + X509_NAME_print_ex(bio, X509_get_issuer_name(ssl_cert), 0, XN_FLAG_RFC2253); + len = BIO_gets(bio, &(*buf), sizeof buf); + pX509Cert->Assign(3, new StringVal(len, buf)); + BIO_free(bio); + + pX509Cert->Assign(4, new Val(get_time_from_asn1(X509_get_notBefore(ssl_cert)), TYPE_TIME)); + pX509Cert->Assign(5, new Val(get_time_from_asn1(X509_get_notAfter(ssl_cert)), TYPE_TIME)); + + // we only read 255 bytes because byte 256 is always 0. + // if the string is longer than 255, that will be our null-termination, + // otherwhise i2t does null-terminate. + if ( ! i2t_ASN1_OBJECT(buf, 255, ssl_cert->cert_info->key->algor->algorithm) ) + buf[0] = 0; + pX509Cert->Assign(6, new StringVal(buf)); + + if ( ! i2t_ASN1_OBJECT(buf, 255, ssl_cert->sig_alg->algorithm) ) + buf[0] = 0; + pX509Cert->Assign(7, new StringVal(buf)); + + // Things we can do when we have the key... + EVP_PKEY *pkey = X509_extract_key(ssl_cert); + if ( pkey != NULL ) + { + if ( pkey->type == EVP_PKEY_DSA ) + { + pX509Cert->Assign(8, new StringVal("dsa")); + } + else if ( pkey->type == EVP_PKEY_RSA ) + { + pX509Cert->Assign(8, new StringVal("rsa")); + char *exponent = BN_bn2dec(pkey->pkey.rsa->e); + if ( exponent != NULL ) + { + pX509Cert->Assign(10, new StringVal(exponent)); + OPENSSL_free(exponent); + exponent = NULL; + } + } +#ifndef OPENSSL_NO_EC + else if ( pkey->type == EVP_PKEY_EC ) + { + pX509Cert->Assign(8, new StringVal("dsa")); + pX509Cert->Assign(11, key_curve(pkey)); + } +#endif + + unsigned int length = key_length(pkey); + if ( length > 0 ) + pX509Cert->Assign(9, new Val(length, TYPE_COUNT)); + } + + val_list* vl = new val_list(); + vl->append(GetFile()->GetVal()->Ref()); + vl->append(pX509Cert); + + mgr.QueueEvent(x509_cert, vl); + + return false; + } + +StringVal* file_analysis::X509::key_curve(EVP_PKEY *key) + { + assert(key != NULL); + +#ifdef OPENSSL_NO_EC + // well, we do not have EC-Support... + return NULL; +#else + if ( key->type != EVP_PKEY_EC ) { + // no EC-key - no curve name + return NULL; + } + + const EC_GROUP *group; + int nid; + if ( (group = EC_KEY_get0_group(key->pkey.ec)) == NULL) + // I guess we could not parse this + return NULL; + + nid = EC_GROUP_get_curve_name(group); + if ( nid == 0 ) + // and an invalid nid... + return NULL; + + const char * curve_name = OBJ_nid2sn(nid); + if ( curve_name == NULL ) + return NULL; + + return new StringVal(curve_name); +#endif + } + +unsigned int file_analysis::X509::key_length(EVP_PKEY *key) + { + assert(key != NULL); + unsigned int length; + + switch(key->type) { + case EVP_PKEY_RSA: + length = BN_num_bits(key->pkey.rsa->n); + break; + case EVP_PKEY_DSA: + length = BN_num_bits(key->pkey.dsa->p); + break; +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + { + const EC_GROUP *group; + BIGNUM* ec_order; + ec_order = BN_new(); + if ( !ec_order ) + // could not malloc bignum? + return 0; + + if ( (group = EC_KEY_get0_group(key->pkey.ec)) == NULL) + // unknown ex-group + return 0; + + if (!EC_GROUP_get_order(group, ec_order, NULL)) + // could not get ec-group-order + return 0; + + length = BN_num_bits(ec_order); + BN_free(ec_order); + break; + } +#endif + default: + return 0; // unknown public key type + } + + return length; + } + +double file_analysis::X509::get_time_from_asn1(const ASN1_TIME * atime) + { + time_t lResult = 0; + + char lBuffer[24]; + char * pBuffer = lBuffer; + + size_t lTimeLength = atime->length; + char * pString = (char *) atime->data; + + if ( atime->type == V_ASN1_UTCTIME ) + { + if ( lTimeLength < 11 || lTimeLength > 17 ) + return 0; + + memcpy(pBuffer, pString, 10); + pBuffer += 10; + pString += 10; + } + else + { + if ( lTimeLength < 13 ) + return 0; + + memcpy(pBuffer, pString, 12); + pBuffer += 12; + pString += 12; + } + + if ((*pString == 'Z') || (*pString == '-') || (*pString == '+')) + { + *(pBuffer++) = '0'; + *(pBuffer++) = '0'; + } + else + { + *(pBuffer++) = *(pString++); + *(pBuffer++) = *(pString++); + + // Skip any fractional seconds... + if (*pString == '.') + { + pString++; + while ((*pString >= '0') && (*pString <= '9')) + pString++; + } + } + + *(pBuffer++) = 'Z'; + *(pBuffer++) = '\0'; + + time_t lSecondsFromUTC; + + if ( *pString == 'Z' ) + lSecondsFromUTC = 0; + + else + { + if ((*pString != '+') && (pString[5] != '-')) + 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[10] - '0') * 10) + (lBuffer[11] - '0'); + lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0'); + lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0'); + lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0'); + lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1; + lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0'); + + if ( lTime.tm_year < 50 ) + lTime.tm_year += 100; // RFC 2459 + + lTime.tm_wday = 0; + lTime.tm_yday = 0; + lTime.tm_isdst = 0; // No DST adjustment requested + + lResult = mktime(&lTime); + + if ( lResult ) + { + if ( 0 != lTime.tm_isdst ) + lResult -= 3600; // mktime may adjust for DST (OS dependent) + + lResult += lSecondsFromUTC; + } + else + lResult = 0; + + return lResult; +} + diff --git a/src/file_analysis/analyzer/x509/X509.h b/src/file_analysis/analyzer/x509/X509.h new file mode 100644 index 0000000000..ce74190b69 --- /dev/null +++ b/src/file_analysis/analyzer/x509/X509.h @@ -0,0 +1,38 @@ +#ifndef FILE_ANALYSIS_X509_H +#define FILE_ANALYSIS_X509_H + +#include + +#include "Val.h" +#include "../File.h" +#include "Analyzer.h" + +#include + +namespace file_analysis { + +class X509 : public file_analysis::Analyzer { +public: + //~X509(); + + static file_analysis::Analyzer* Instantiate(RecordVal* args, File* file) + { return new X509(args, file); } + + virtual bool DeliverStream(const u_char* data, uint64 len); + virtual bool Undelivered(uint64 offset, uint64 len); + virtual bool EndOfFile(); + +protected: + X509(RecordVal* args, File* file); + +private: + static double get_time_from_asn1(const ASN1_TIME * atime); + static StringVal* key_curve(EVP_PKEY *key); + static unsigned int key_length(EVP_PKEY *key); + + std::string cert_data; +}; + +} + +#endif diff --git a/src/file_analysis/analyzer/x509/events.bif b/src/file_analysis/analyzer/x509/events.bif new file mode 100644 index 0000000000..3c3049559d --- /dev/null +++ b/src/file_analysis/analyzer/x509/events.bif @@ -0,0 +1 @@ +event x509_cert%(f: fa_file, cert: X509::Certificate%); diff --git a/src/file_analysis/analyzer/x509/types.bif b/src/file_analysis/analyzer/x509/types.bif new file mode 100644 index 0000000000..9e4fd48420 --- /dev/null +++ b/src/file_analysis/analyzer/x509/types.bif @@ -0,0 +1 @@ +type X509::Certificate: record;