Add verify functionality, including the ability to get the validated

chain. This means that it is now possible to get information about the
root-certificates that were used to secure a connection.

Intermediate commit before changing the script interface again.

addresses BIT-953, BIT-760
This commit is contained in:
Bernhard Amann 2014-03-03 10:49:28 -08:00
parent 7ba6bcff2c
commit a1f2ab34ac
8 changed files with 249 additions and 40 deletions

View file

@ -0,0 +1,171 @@
%%{
#include "file_analysis/analyzer/x509/X509.h"
#include "types.bif.h"
#include <openssl/x509.h>
#include <openssl/asn1.h>
#include <openssl/x509_vfy.h>
// This is the indexed map of X509 certificate stores.
static map<Val*, X509_STORE*> x509_stores;
// ### NOTE: while d2i_X509 does not take a const u_char** pointer,
// here we assume d2i_X509 does not write to <data>, so it is safe to
// convert data to a non-const pointer. Could some X509 guru verify
// this?
X509* d2i_X509_(X509** px, const u_char** in, int len)
{
#ifdef OPENSSL_D2I_X509_USES_CONST_CHAR
return d2i_X509(px, in, len);
#else
return d2i_X509(px, (u_char**)in, len);
#endif
}
%%}
## Parses a certificate into an X509::Certificate structure
##
## cert: The x509 certificicate opaque
##
## Returns: A X509::Certificate structure
##
## .. bro:see:: x509_verify
function x509_parse%(cert: opaque of x509%): X509::Certificate
%{
assert(cert);
file_analysis::X509Val* h = (file_analysis::X509Val*) cert;
return file_analysis::X509::ParseCertificate(h);
%}
## Verifies a certificate.
##
## cert_val: The X.509 certificate in DER format.
##
## cert_stack: Specifies a certificate chain that is being used to validate
## the given certificate against the root store given in *root_certs*
##
## root_certs: A list of root certificates to validate the certificate chain
##
## Returns: A record of type X509::Result containing the result code of the verify
## operation. In case of success also returns the full certificate chain.
##
## .. bro:see:: x509_parse
function x509_verify%(cert_val: opaque of x509, cert_stack: x509_opaque_vector, root_certs: table_string_of_string%): X509::Result
%{
X509_STORE* ctx = 0;
int i = 0;
// If this certificate store was built previously, just reuse the old one.
if ( x509_stores.count(root_certs) > 0 )
ctx = x509_stores[root_certs];
if ( ! ctx ) // lookup to see if we have this one built already!
{
ctx = X509_STORE_new();
TableVal* root_certs2 = root_certs->AsTableVal();
ListVal* idxs = root_certs2->ConvertToPureList();
// Build the validation store
for ( i = 0; i < idxs->Length(); ++i )
{
Val* key = idxs->Index(i);
StringVal *sv = root_certs2->Lookup(key)->AsStringVal();
const uint8* data = sv->Bytes();
X509* x = d2i_X509_(NULL, &data, sv->Len());
if ( ! x )
{
builtin_error(fmt("Root CA error: %s", ERR_error_string(ERR_peek_last_error(),NULL)));
return new Val((uint64) ERR_get_error(), TYPE_COUNT);
}
X509_STORE_add_cert(ctx, x);
}
delete idxs;
// Save the newly constructed certificate store into the cacheing map.
x509_stores[root_certs] = ctx;
}
assert(cert_val);
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) cert_val;
X509* cert = cert_handle->GetCertificate();
if ( ! cert )
{
builtin_error(fmt("No certificate in opaque"));
return new Val(-1, TYPE_COUNT);
}
STACK_OF(X509)* untrusted_certs = sk_X509_new_null();
if ( ! untrusted_certs )
{
builtin_error(fmt("Untrusted certificate stack initialization error: %s", ERR_error_string(ERR_peek_last_error(),NULL)));
return new Val((uint64) ERR_get_error(), TYPE_COUNT);
}
VectorVal *cert_stack_vec = cert_stack->AsVectorVal();
for ( i = 0; i < (int) cert_stack_vec->Size(); ++i )
{
Val *sv = cert_stack_vec->Lookup(i);
// Fixme: check type
X509* x = ((file_analysis::X509Val*) sv)->GetCertificate();
if ( ! x )
{
sk_X509_pop(untrusted_certs);
builtin_error(fmt("No certificate in opaque in stack"));
return new Val(-1, TYPE_COUNT);
}
sk_X509_push(untrusted_certs, x);
}
X509_STORE_CTX csc;
X509_STORE_CTX_init(&csc, ctx, cert, untrusted_certs);
X509_STORE_CTX_set_time(&csc, 0, (time_t) network_time);
int result = X509_verify_cert(&csc);
VectorVal* chainVector = 0;
if ( result == 1 ) // we have a valid chain. try to get it...
{
STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(&csc); // get1 = deep copy
if (!chain)
{
reporter->Error("Encountered valid chain that could not be resolved");
goto x509_verify_chainerror;
}
int num_certs = sk_X509_num(chain);
chainVector = new VectorVal(new VectorType(base_type(TYPE_OPAQUE)));
for ( int i = 0; i < num_certs; i++ )
{
X509* currcert = sk_X509_value(chain, i);
if ( !currcert )
{
reporter->InternalError("OpenSSL returned null certificate");
goto x509_verify_chainerror;
}
chainVector->Assign(i, new file_analysis::X509Val(currcert)); // X509Val takes ownership
}
}
x509_verify_chainerror:
X509_STORE_CTX_cleanup(&csc);
if ( untrusted_certs )
sk_X509_pop(untrusted_certs);
RecordVal* rrecord = new RecordVal(BifType::Record::X509::Result);
rrecord->Assign(0, new Val((uint64) csc.error, TYPE_COUNT));
rrecord->Assign(1, new StringVal(X509_verify_cert_error_string(csc.error)));
if ( chainVector )
rrecord->Assign(2, chainVector);
return rrecord;
%}