zeek/src/file_analysis/analyzer/x509/X509.cc
Jon Siwek 5b4313b593 Deprecate Val(double, TypeTag) ctor, add TimeVal/DoubleVal subclasses
This also updates all usages of the deprecated Val ctor to use
either IntervalVal, TimeVal, or DoubleVal ctors.  The reason for
doing away with the old constructor is that using it with TYPE_INTERVAL
isn't strictly correct since there exists a more specific subclass,
IntervalVal, with overriden ValDescribe() method that ought to be used
to print such values in a more descriptive way.
2020-06-02 23:33:40 -07:00

583 lines
16 KiB
C++

// See the file "COPYING" in the main distribution directory for copyright.
#include <string>
#include "X509.h"
#include "Event.h"
#include "events.bif.h"
#include "types.bif.h"
#include "file_analysis/File.h"
#include "file_analysis/Manager.h"
#include <broker/error.hh>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
#include <openssl/opensslconf.h>
#include <openssl/err.h>
using namespace file_analysis;
file_analysis::X509::X509(IntrusivePtr<RecordVal> args, file_analysis::File* file)
: file_analysis::X509Common::X509Common(file_mgr->GetComponentTag("X509"),
std::move(args), file)
{
cert_data.clear();
}
bool file_analysis::X509::DeliverStream(const u_char* data, uint64_t len)
{
// just add it to the data we have so far, since we cannot do anything else anyways...
cert_data.append(reinterpret_cast<const char*>(data), len);
return true;
}
bool file_analysis::X509::Undelivered(uint64_t offset, uint64_t len)
{
return false;
}
bool file_analysis::X509::EndOfFile()
{
const unsigned char* cert_char = reinterpret_cast<const unsigned char*>(cert_data.data());
if ( certificate_cache )
{
// first step - let's see if the certificate has been cached.
unsigned char buf[SHA256_DIGEST_LENGTH];
auto ctx = hash_init(Hash_SHA256);
hash_update(ctx, cert_char, cert_data.size());
hash_final(ctx, buf);
std::string cert_sha256 = sha256_digest_print(buf);
auto index = make_intrusive<StringVal>(cert_sha256);
const auto& entry = certificate_cache->Find(index);
if ( entry )
// in this case, the certificate is in the cache and we do not
// do any further processing here. However, if there is a callback, we execute it.
{
if ( ! cache_hit_callback )
return false;
// yup, let's call the callback.
cache_hit_callback->Invoke(GetFile()->ToVal(), entry,
make_intrusive<StringVal>(cert_sha256));
return false;
}
}
// ok, now we can try to parse the certificate with openssl. Should
// be rather straightforward...
::X509* ssl_cert = d2i_X509(NULL, &cert_char, cert_data.size());
if ( ! ssl_cert )
{
reporter->Weird(GetFile(), "x509_cert_parse_error");
return false;
}
X509Val* cert_val = new X509Val(ssl_cert); // cert_val takes ownership of ssl_cert
// parse basic information into record.
auto cert_record = ParseCertificate(cert_val, GetFile());
// and send the record on to scriptland
if ( x509_certificate )
mgr.Enqueue(x509_certificate,
GetFile()->ToVal(),
IntrusivePtr{NewRef{}, cert_val},
cert_record);
// after parsing the certificate - parse the extensions...
int num_ext = X509_get_ext_count(ssl_cert);
for ( int k = 0; k < num_ext; ++k )
{
X509_EXTENSION* ex = X509_get_ext(ssl_cert, k);
if ( ! ex )
continue;
ParseExtension(ex, x509_extension, false);
}
// X509_free(ssl_cert); We do _not_ free the certificate here. It is refcounted
// inside the X509Val that is sent on in the cert record to scriptland.
//
// The certificate will be freed when the last X509Val is Unref'd.
Unref(cert_val); // Same for cert_val
return false;
}
IntrusivePtr<RecordVal> file_analysis::X509::ParseCertificate(X509Val* cert_val, File* f)
{
::X509* ssl_cert = cert_val->GetCertificate();
char buf[2048]; // we need a buffer for some of the openssl functions
memset(buf, 0, sizeof(buf));
auto pX509Cert = make_intrusive<RecordVal>(zeek::BifType::Record::X509::Certificate);
BIO *bio = BIO_new(BIO_s_mem());
pX509Cert->Assign(0, val_mgr->Count((uint64_t) X509_get_version(ssl_cert) + 1));
i2a_ASN1_INTEGER(bio, X509_get_serialNumber(ssl_cert));
int len = BIO_read(bio, buf, sizeof(buf));
pX509Cert->Assign(1, make_intrusive<StringVal>(len, buf));
BIO_reset(bio);
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, make_intrusive<StringVal>(len, buf));
BIO_reset(bio);
X509_NAME *subject_name = X509_get_subject_name(ssl_cert);
// extract the most specific (last) common name from the subject
int namepos = -1;
for ( ;; )
{
int j = X509_NAME_get_index_by_NID(subject_name, NID_commonName, namepos);
if ( j == -1 )
break;
namepos = j;
}
if ( namepos != -1 )
{
// we found a common name
ASN1_STRING_print(bio, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, namepos)));
len = BIO_gets(bio, buf, sizeof(buf));
pX509Cert->Assign(4, make_intrusive<StringVal>(len, buf));
BIO_reset(bio);
}
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, make_intrusive<StringVal>(len, buf));
BIO_free(bio);
pX509Cert->Assign(5, make_intrusive<TimeVal>(GetTimeFromAsn1(X509_get_notBefore(ssl_cert), f, reporter)));
pX509Cert->Assign(6, make_intrusive<TimeVal>(GetTimeFromAsn1(X509_get_notAfter(ssl_cert), f, reporter)));
// 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.
ASN1_OBJECT *algorithm;
X509_PUBKEY_get0_param(&algorithm, NULL, NULL, NULL, X509_get_X509_PUBKEY(ssl_cert));
if ( ! i2t_ASN1_OBJECT(buf, 255, algorithm) )
buf[0] = 0;
pX509Cert->Assign(7, make_intrusive<StringVal>(buf));
// Special case for RDP server certificates. For some reason some (all?) RDP server
// certificates like to specify their key algorithm as md5WithRSAEncryption, which
// is wrong on so many levels. We catch this special case here and set it to what is
// actually should be (namely - rsaEncryption), so that OpenSSL will parse out the
// key later. Otherwise it will just fail to parse the certificate key.
if ( OBJ_obj2nid(algorithm) == NID_md5WithRSAEncryption )
{
ASN1_OBJECT *copy = OBJ_dup(algorithm); // the next line will destroy the original algorithm.
X509_PUBKEY_set0_param(X509_get_X509_PUBKEY(ssl_cert), OBJ_nid2obj(NID_rsaEncryption), 0, NULL, NULL, 0);
algorithm = copy;
// we do not have to worry about freeing algorithm in that case - since it will be re-assigned using
// set0_param and the cert will take ownership.
}
else
algorithm = 0;
if ( ! i2t_ASN1_OBJECT(buf, 255, OBJ_nid2obj(X509_get_signature_nid(ssl_cert))) )
buf[0] = 0;
pX509Cert->Assign(8, make_intrusive<StringVal>(buf));
// Things we can do when we have the key...
EVP_PKEY *pkey = X509_extract_key(ssl_cert);
if ( pkey != NULL )
{
if ( EVP_PKEY_base_id(pkey) == EVP_PKEY_DSA )
pX509Cert->Assign(9, make_intrusive<StringVal>("dsa"));
else if ( EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA )
{
pX509Cert->Assign(9, make_intrusive<StringVal>("rsa"));
const BIGNUM *e;
RSA_get0_key(EVP_PKEY_get0_RSA(pkey), NULL, &e, NULL);
char *exponent = BN_bn2dec(e);
if ( exponent != NULL )
{
pX509Cert->Assign(11, make_intrusive<StringVal>(exponent));
OPENSSL_free(exponent);
exponent = NULL;
}
}
#ifndef OPENSSL_NO_EC
else if ( EVP_PKEY_base_id(pkey) == EVP_PKEY_EC )
{
pX509Cert->Assign(9, make_intrusive<StringVal>("ecdsa"));
pX509Cert->Assign(12, KeyCurve(pkey));
}
#endif
// set key algorithm back. We do not have to free the value that we created because (I think) it
// comes out of a static array from OpenSSL memory.
if ( algorithm )
X509_PUBKEY_set0_param(X509_get_X509_PUBKEY(ssl_cert), algorithm, 0, NULL, NULL, 0);
unsigned int length = KeyLength(pkey);
if ( length > 0 )
pX509Cert->Assign(10, val_mgr->Count(length));
EVP_PKEY_free(pkey);
}
return pX509Cert;
}
X509_STORE* file_analysis::X509::GetRootStore(TableVal* root_certs)
{
// If this certificate store was built previously, just reuse the old one.
if ( x509_stores.count(root_certs) > 0 )
return x509_stores[root_certs];
X509_STORE* ctx = X509_STORE_new();
auto idxs = root_certs->ToPureListVal();
// Build the validation store
for ( int i = 0; i < idxs->Length(); ++i )
{
const auto& key = idxs->Idx(i);
auto val = root_certs->FindOrDefault(key);
StringVal* sv = val->AsStringVal();
assert(sv);
const uint8_t* data = sv->Bytes();
::X509* x = d2i_X509(NULL, &data, sv->Len());
if ( ! x )
{
builtin_error(fmt("Root CA error: %s", ERR_error_string(ERR_get_error(), NULL)));
return nullptr;
}
X509_STORE_add_cert(ctx, x);
X509_free(x);
}
// Save the newly constructed certificate store into the cacheing map.
x509_stores[root_certs] = ctx;
return ctx;
}
void file_analysis::X509::FreeRootStore()
{
for ( const auto& e : x509_stores )
X509_STORE_free(e.second);
}
void file_analysis::X509::ParseBasicConstraints(X509_EXTENSION* ex)
{
assert(OBJ_obj2nid(X509_EXTENSION_get_object(ex)) == NID_basic_constraints);
BASIC_CONSTRAINTS *constr = (BASIC_CONSTRAINTS *) X509V3_EXT_d2i(ex);
if ( constr )
{
if ( x509_ext_basic_constraints )
{
auto pBasicConstraint = make_intrusive<RecordVal>(zeek::BifType::Record::X509::BasicConstraints);
pBasicConstraint->Assign(0, val_mgr->Bool(constr->ca));
if ( constr->pathlen )
pBasicConstraint->Assign(1, val_mgr->Count((int32_t) ASN1_INTEGER_get(constr->pathlen)));
mgr.Enqueue(x509_ext_basic_constraints,
GetFile()->ToVal(),
std::move(pBasicConstraint)
);
}
BASIC_CONSTRAINTS_free(constr);
}
else
reporter->Weird(GetFile(), "x509_invalid_basic_constraint");
}
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);
// In OpenSSL 1.0.2+, we can get the extension by using NID_ct_precert_scts.
// In OpenSSL <= 1.0.1, this is not yet defined yet, so we have to manually
// look it up by performing a string comparison on the oid.
#ifdef NID_ct_precert_scts
else if ( OBJ_obj2nid(ext_asn) == NID_ct_precert_scts )
#else
else if ( strcmp(oid, "1.3.6.1.4.1.11129.2.4.2") == 0 )
#endif
ParseSignedCertificateTimestamps(ex);
}
void file_analysis::X509::ParseSAN(X509_EXTENSION* ext)
{
assert(OBJ_obj2nid(X509_EXTENSION_get_object(ext)) == NID_subject_alt_name);
GENERAL_NAMES *altname = (GENERAL_NAMES*)X509V3_EXT_d2i(ext);
if ( ! altname )
{
reporter->Weird(GetFile(), "x509_san_parse_error");
return;
}
IntrusivePtr<VectorVal> names;
IntrusivePtr<VectorVal> emails;
IntrusivePtr<VectorVal> uris;
IntrusivePtr<VectorVal> ips;
bool otherfields = false;
for ( int i = 0; i < sk_GENERAL_NAME_num(altname); i++ )
{
GENERAL_NAME *gen = sk_GENERAL_NAME_value(altname, i);
assert(gen);
if ( gen->type == GEN_DNS || gen->type == GEN_URI || gen->type == GEN_EMAIL )
{
if ( ASN1_STRING_type(gen->d.ia5) != V_ASN1_IA5STRING )
{
reporter->Weird(GetFile(), "x509_san_non_string");
continue;
}
#if ( OPENSSL_VERSION_NUMBER < 0x10100000L ) || defined(LIBRESSL_VERSION_NUMBER)
const char* name = (const char*) ASN1_STRING_data(gen->d.ia5);
#else
const char* name = (const char*) ASN1_STRING_get0_data(gen->d.ia5);
#endif
auto bs = make_intrusive<StringVal>(name);
switch ( gen->type )
{
case GEN_DNS:
if ( names == nullptr )
names = make_intrusive<VectorVal>(zeek::id::string_vec);
names->Assign(names->Size(), std::move(bs));
break;
case GEN_URI:
if ( uris == nullptr )
uris = make_intrusive<VectorVal>(zeek::id::string_vec);
uris->Assign(uris->Size(), std::move(bs));
break;
case GEN_EMAIL:
if ( emails == nullptr )
emails = make_intrusive<VectorVal>(zeek::id::string_vec);
emails->Assign(emails->Size(), std::move(bs));
break;
}
}
else if ( gen->type == GEN_IPADD )
{
if ( ips == nullptr )
ips = make_intrusive<VectorVal>(zeek::id::find_type<VectorType>("addr_vec"));
uint32_t* addr = (uint32_t*) gen->d.ip->data;
if( gen->d.ip->length == 4 )
ips->Assign(ips->Size(), make_intrusive<AddrVal>(*addr));
else if ( gen->d.ip->length == 16 )
ips->Assign(ips->Size(), make_intrusive<AddrVal>(addr));
else
{
reporter->Weird(GetFile(), "x509_san_ip_length", fmt("%d", gen->d.ip->length));
continue;
}
}
else
{
// reporter->Error("Subject alternative name contained unsupported fields. fuid %s", GetFile()->GetID().c_str());
// This happens quite often - just mark it
otherfields = true;
continue;
}
}
auto sanExt = make_intrusive<RecordVal>(zeek::BifType::Record::X509::SubjectAlternativeName);
if ( names != nullptr )
sanExt->Assign(0, names);
if ( uris != nullptr )
sanExt->Assign(1, uris);
if ( emails != nullptr )
sanExt->Assign(2, emails);
if ( ips != nullptr )
sanExt->Assign(3, ips);
sanExt->Assign(4, val_mgr->Bool(otherfields));
mgr.Enqueue(x509_ext_subject_alternative_name,
GetFile()->ToVal(),
std::move(sanExt));
GENERAL_NAMES_free(altname);
}
IntrusivePtr<StringVal> file_analysis::X509::KeyCurve(EVP_PKEY* key)
{
assert(key != nullptr);
#ifdef OPENSSL_NO_EC
// well, we do not have EC-Support...
return nullptr;
#else
if ( EVP_PKEY_base_id(key) != EVP_PKEY_EC )
{
// no EC-key - no curve name
return nullptr;
}
const EC_GROUP *group;
int nid;
if ( (group = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(key))) == NULL )
// I guess we could not parse this
return nullptr;
nid = EC_GROUP_get_curve_name(group);
if ( nid == 0 )
// and an invalid nid...
return nullptr;
const char * curve_name = OBJ_nid2sn(nid);
if ( curve_name == nullptr )
return nullptr;
return make_intrusive<StringVal>(curve_name);
#endif
}
unsigned int file_analysis::X509::KeyLength(EVP_PKEY *key)
{
assert(key != NULL);
switch(EVP_PKEY_base_id(key)) {
case EVP_PKEY_RSA:
const BIGNUM *n;
RSA_get0_key(EVP_PKEY_get0_RSA(key), &n, NULL, NULL);
return BN_num_bits(n);
case EVP_PKEY_DSA:
const BIGNUM *p;
DSA_get0_pqg(EVP_PKEY_get0_DSA(key), &p, NULL, NULL);
return BN_num_bits(p);
#ifndef OPENSSL_NO_EC
case EVP_PKEY_EC:
{
BIGNUM* ec_order = BN_new();
if ( ! ec_order )
// could not malloc bignum?
return 0;
const EC_GROUP *group = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(key));
if ( ! group )
{
// unknown ex-group
BN_free(ec_order);
return 0;
}
if ( ! EC_GROUP_get_order(group, ec_order, NULL) )
{
// could not get ec-group-order
BN_free(ec_order);
return 0;
}
unsigned int length = BN_num_bits(ec_order);
BN_free(ec_order);
return length;
}
#endif
default:
return 0; // unknown public key type
}
reporter->InternalError("cannot be reached");
}
X509Val::X509Val(::X509* arg_certificate) : OpaqueVal(x509_opaque_type)
{
certificate = arg_certificate;
}
X509Val::X509Val() : OpaqueVal(x509_opaque_type)
{
certificate = 0;
}
X509Val::~X509Val()
{
if ( certificate )
X509_free(certificate);
}
IntrusivePtr<Val> X509Val::DoClone(CloneState* state)
{
auto copy = make_intrusive<X509Val>();
if ( certificate )
copy->certificate = X509_dup(certificate);
return state->NewClone(this, copy);
}
::X509* X509Val::GetCertificate() const
{
return certificate;
}
IMPLEMENT_OPAQUE_VALUE(X509Val)
broker::expected<broker::data> X509Val::DoSerialize() const
{
unsigned char *buf = nullptr;
int length = i2d_X509(certificate, &buf);
if ( length < 0 )
return broker::ec::invalid_data;
auto d = std::string(reinterpret_cast<const char*>(buf), length);
OPENSSL_free(buf);
return {std::move(d)};
}
bool X509Val::DoUnserialize(const broker::data& data)
{
auto s = caf::get_if<std::string>(&data);
if ( ! s )
return false;
auto opensslbuf = reinterpret_cast<const unsigned char*>(s->data());
certificate = d2i_X509(NULL, &opensslbuf, s->size());
return (certificate != nullptr);
}