mirror of
https://github.com/zeek/zeek.git
synced 2025-10-13 20:18:20 +00:00

Good stuff! (but I admit I didn't look at the OpenSSL code too closely :) * origin/topic/bernhard/even-more-ssl-changes: small test update & script fix update baselines & add ocsp leak check Add policy script adding ocsp validation to ssl.log Implement verification of OCSP replies. Add tls flag to smtp.log. Will be set if a connection switched to startls. add starttls support for pop3 Add smtp starttls support Replace errors when parsing x509 certs with weirds (as requested by Seth). move tls content types from heartbleed to consts.bro. Seems better to put them there... Add new features from other branch to the heartbleed-detector (and clean them up). Let TLS analyzer fail better when no longer in sync with the data stream. The version field in each record-layer packet is now re-checked. BIT-1190 #merged Conflicts: testing/btest/Baseline/scripts.policy.misc.dump-events/all-events.log testing/btest/Baseline/scripts.policy.misc.dump-events/smtp-events.log
435 lines
14 KiB
Text
435 lines
14 KiB
Text
%%{
|
|
#include "file_analysis/analyzer/x509/X509.h"
|
|
#include "types.bif.h"
|
|
|
|
#include <openssl/x509.h>
|
|
#include <openssl/asn1.h>
|
|
#include <openssl/x509_vfy.h>
|
|
#include <openssl/ocsp.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
|
|
}
|
|
|
|
// construct an error record
|
|
RecordVal* x509_error_record(uint64_t num, const char* reason)
|
|
{
|
|
RecordVal* rrecord = new RecordVal(BifType::Record::X509::Result);
|
|
|
|
rrecord->Assign(0, new Val(num, TYPE_COUNT));
|
|
rrecord->Assign(1, new StringVal(reason));
|
|
|
|
return rrecord;
|
|
}
|
|
|
|
X509_STORE* x509_get_root_store(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();
|
|
ListVal* idxs = root_certs->ConvertToPureList();
|
|
|
|
// Build the validation store
|
|
for ( int i = 0; i < idxs->Length(); ++i )
|
|
{
|
|
Val* key = idxs->Index(i);
|
|
StringVal *sv = root_certs->Lookup(key)->AsStringVal();
|
|
assert(sv);
|
|
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_get_error(),NULL)));
|
|
return 0;
|
|
}
|
|
|
|
X509_STORE_add_cert(ctx, x);
|
|
}
|
|
|
|
delete idxs;
|
|
|
|
// Save the newly constructed certificate store into the cacheing map.
|
|
x509_stores[root_certs] = ctx;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
// get all cretificates starting at the second one (assuming the first one is the host certificate)
|
|
STACK_OF(X509)* x509_get_untrusted_stack(VectorVal* certs_vec)
|
|
{
|
|
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_get_error(),NULL)));
|
|
return 0;
|
|
}
|
|
|
|
for ( int i = 1; i < (int) certs_vec->Size(); ++i ) // start at 1 - 0 is host cert
|
|
{
|
|
Val *sv = certs_vec->Lookup(i);
|
|
|
|
// Fixme: check type
|
|
|
|
X509* x = ((file_analysis::X509Val*) sv)->GetCertificate();
|
|
if ( ! x )
|
|
{
|
|
sk_X509_free(untrusted_certs);
|
|
builtin_error(fmt("No certificate in opaque in stack"));
|
|
return 0;
|
|
}
|
|
|
|
sk_X509_push(untrusted_certs, x);
|
|
}
|
|
|
|
return untrusted_certs;
|
|
}
|
|
|
|
%%}
|
|
|
|
## Parses a certificate into an X509::Certificate structure.
|
|
##
|
|
## cert: The X509 certificicate opaque handle
|
|
##
|
|
## Returns: A X509::Certificate structure
|
|
##
|
|
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
|
|
## x509_ext_subject_alternative_name x509_verify
|
|
## x509_get_certificate_string
|
|
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);
|
|
%}
|
|
|
|
## Returns the string form of a certificate.
|
|
##
|
|
## cert: The X509 certificate opaque handle
|
|
##
|
|
## pem: A boolean that specifies if the certificate is returned
|
|
## in pem-form (true), or as the raw ASN1 encoded binary
|
|
## (false).
|
|
##
|
|
## Returns: X509 certificate as a string
|
|
##
|
|
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
|
|
## x509_ext_subject_alternative_name x509_parse x509_verify
|
|
function x509_get_certificate_string%(cert: opaque of x509, pem: bool &default=F%): string
|
|
%{
|
|
assert(cert);
|
|
file_analysis::X509Val* h = (file_analysis::X509Val*) cert;
|
|
|
|
BIO *bio = BIO_new(BIO_s_mem());
|
|
|
|
if ( pem )
|
|
PEM_write_bio_X509(bio, h->GetCertificate());
|
|
|
|
else
|
|
i2d_X509_bio(bio, h->GetCertificate());
|
|
|
|
StringVal* ext_val = file_analysis::X509::GetExtensionFromBIO(bio);
|
|
|
|
if ( ! ext_val )
|
|
ext_val = new StringVal("");
|
|
|
|
return ext_val;
|
|
%}
|
|
|
|
## Verifies an OCSP reply.
|
|
##
|
|
## certs: Specifies the certificate chain to use. Server certificate first.
|
|
##
|
|
## ocsp_reply: the ocsp reply to validate
|
|
##
|
|
## root_certs: A list of root certificates to validate the certificate chain
|
|
##
|
|
## verify_time: Time for the validity check of the certificates.
|
|
##
|
|
## Returns: A record of type X509::Result containing the result code of the verify
|
|
## operation.
|
|
##
|
|
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
|
|
## x509_ext_subject_alternative_name x509_parse
|
|
## x509_get_certificate_string x509_verify
|
|
function x509_ocsp_verify%(certs: x509_opaque_vector, ocsp_reply: string, root_certs: table_string_of_string, verify_time: time &default=network_time()%): X509::Result
|
|
%{
|
|
BIO* bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
|
X509_STORE* ctx = x509_get_root_store(root_certs->AsTableVal());
|
|
if ( ! ctx )
|
|
return x509_error_record(-1, "Problem initializing root store");
|
|
|
|
VectorVal *certs_vec = certs->AsVectorVal();
|
|
if ( certs_vec->Size() < 1 )
|
|
{
|
|
reporter->Error("No certificates given in vector");
|
|
return x509_error_record(-1, "no certificates");
|
|
}
|
|
|
|
// host certificate
|
|
unsigned int index = 0; // to prevent overloading to 0pointer
|
|
Val *sv = certs_vec->Lookup(index);
|
|
if ( ! sv )
|
|
{
|
|
builtin_error("undefined value in certificate vector");
|
|
return x509_error_record(-1, "undefined value in certificate vector");
|
|
}
|
|
|
|
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
|
|
|
|
X509* cert = cert_handle->GetCertificate();
|
|
if ( ! cert )
|
|
{
|
|
builtin_error(fmt("No certificate in opaque"));
|
|
return x509_error_record(-1, "No certificate in opaque");
|
|
}
|
|
|
|
const unsigned char* start = ocsp_reply->Bytes();
|
|
|
|
OCSP_RESPONSE *resp = d2i_OCSP_RESPONSE(NULL, &start, ocsp_reply->Len());
|
|
if ( ! resp )
|
|
return x509_error_record(-1, "Could not parse OCSP response");
|
|
|
|
int status = OCSP_response_status(resp);
|
|
if ( status != OCSP_RESPONSE_STATUS_SUCCESSFUL )
|
|
return x509_error_record(-2, OCSP_response_status_str(status));
|
|
|
|
OCSP_BASICRESP *basic = OCSP_response_get1_basic(resp);
|
|
if ( ! basic )
|
|
return x509_error_record(-1, "Could not parse OCSP response");
|
|
|
|
|
|
STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec);
|
|
if ( ! untrusted_certs )
|
|
return x509_error_record(-1, "Problem initializing list of untrusted certificates");
|
|
|
|
// the following code took me _forever_ to get right.
|
|
// The OCSP_basic_verify command takes a list of certificates. However (which is not immediately
|
|
// visible or understandable), those are only used to find the signer certificate. They are _not_
|
|
// used for chain building during the actual verification (this would be stupid). But - if we sneakily
|
|
// inject the certificates in the certificate list of the OCSP reply, they actually are used during
|
|
// the lookup.
|
|
// Yay.
|
|
X509* issuer_certificate = 0;
|
|
for ( int i = 0; i < sk_X509_num(untrusted_certs); i++)
|
|
{
|
|
sk_X509_push(basic->certs, sk_X509_value(untrusted_certs, i));
|
|
|
|
if ( X509_NAME_cmp(X509_get_issuer_name(cert), X509_get_subject_name(sk_X509_value(untrusted_certs, i))) )
|
|
issuer_certificate = sk_X509_value(untrusted_certs, i);
|
|
}
|
|
|
|
// Because we actually want to be able to give nice error messages that show why we were
|
|
// not able to verify the OCSP response - do our own verification logic first.
|
|
X509_STORE_CTX csc;
|
|
X509_STORE_CTX_init(&csc, ctx, sk_X509_value(basic->certs, 0), basic->certs);
|
|
X509_STORE_CTX_set_time(&csc, 0, (time_t) verify_time);
|
|
X509_STORE_CTX_set_purpose(&csc, X509_PURPOSE_OCSP_HELPER);
|
|
|
|
int result = X509_verify_cert(&csc);
|
|
if ( result != 1 )
|
|
{
|
|
const char *reason = X509_verify_cert_error_string(csc.error);
|
|
X509_STORE_CTX_cleanup(&csc);
|
|
sk_X509_free(untrusted_certs);
|
|
return x509_error_record(result, X509_verify_cert_error_string(csc.error));
|
|
}
|
|
|
|
int out = OCSP_basic_verify(basic, NULL, ctx, 0);
|
|
if ( result < 1 )
|
|
{
|
|
X509_STORE_CTX_cleanup(&csc);
|
|
sk_X509_free(untrusted_certs);
|
|
return x509_error_record(out, ERR_error_string(ERR_get_error(),NULL));
|
|
}
|
|
|
|
// ok, now we verified the OCSP response. This means that we have a valid chain tying it
|
|
// to a root that we trust and that the signature also hopefully is valid. This does not yet
|
|
// mean that the ocsp response actually matches the certificate the server send us or that
|
|
// the OCSP response even says that the certificate is valid.
|
|
|
|
// let's start this out by checking that the response is actually for the certificate we want
|
|
// to validate and not for something completely unrelated that the server is trying to trick us
|
|
// into accepting.
|
|
OCSP_CERTID *certid;
|
|
|
|
if ( issuer_certificate )
|
|
certid = OCSP_cert_to_id(NULL, cert, issuer_certificate);
|
|
else
|
|
{
|
|
// issuer not in list sent by server, check store
|
|
X509_OBJECT obj;
|
|
int lookup = X509_STORE_get_by_subject(&csc, X509_LU_X509, X509_get_subject_name(cert), &obj);
|
|
if ( lookup <= 0)
|
|
{
|
|
sk_X509_free(untrusted_certs);
|
|
X509_STORE_CTX_cleanup(&csc);
|
|
return x509_error_record(lookup, "Could not find issuer of host certificate");
|
|
}
|
|
|
|
certid = OCSP_cert_to_id(NULL, cert, obj.data.x509);
|
|
}
|
|
|
|
sk_X509_free(untrusted_certs);
|
|
X509_STORE_CTX_cleanup(&csc);
|
|
|
|
if ( ! certid )
|
|
return x509_error_record(-1, "Certificate ID construction failed");
|
|
|
|
// for now, assume we have one reply...
|
|
OCSP_SINGLERESP *single = sk_OCSP_SINGLERESP_value(basic->tbsResponseData->responses, 0);
|
|
if ( ! single )
|
|
return x509_error_record(-1, "Could not lookup OCSP response information");
|
|
|
|
if ( ! OCSP_id_cmp(certid, single->certId) )
|
|
return x509_error_record(-1, "OCSP reply is not for host certificate");
|
|
|
|
// next - check freshness of proof...
|
|
if ( ! ASN1_GENERALIZEDTIME_check(single->thisUpdate) || ! ASN1_GENERALIZEDTIME_check(single->nextUpdate) )
|
|
return x509_error_record(-1, "OCSP reply contains invalid dates");
|
|
|
|
// now - nearly done. Check freshness and status code.
|
|
// There is a function to check the freshness of the ocsp reply in the ocsp code of OpenSSL. But - it only
|
|
// supports comparing it against the current time, not against arbitrary times. Hence it is kind of unusable
|
|
// for us...
|
|
// Well, we will do it manually.
|
|
time_t vtime = (time_t) verify_time;
|
|
|
|
if ( X509_cmp_time(single->thisUpdate, &vtime) > 0 )
|
|
return x509_error_record(-1, "OCSP reply specifies time in future");
|
|
|
|
if ( X509_cmp_time(single->nextUpdate, &vtime) < 0 )
|
|
return x509_error_record(-1, "OCSP reply expired");
|
|
|
|
if ( single->certStatus->type != V_OCSP_CERTSTATUS_GOOD )
|
|
return x509_error_record(-1, OCSP_cert_status_str(single->certStatus->type));
|
|
|
|
return x509_error_record(1, OCSP_cert_status_str(single->certStatus->type));
|
|
%}
|
|
|
|
## Verifies a certificate.
|
|
##
|
|
## certs: Specifies a certificate chain that is being used to validate
|
|
## the given certificate against the root store given in *root_certs*.
|
|
## The host certificate has to be at index 0.
|
|
##
|
|
## root_certs: A list of root certificates to validate the certificate chain
|
|
##
|
|
## verify_time: Time for the validity check of the certificates.
|
|
##
|
|
## 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_certificate x509_extension x509_ext_basic_constraints
|
|
## x509_ext_subject_alternative_name x509_parse
|
|
## x509_get_certificate_string x509_ocsp_verify
|
|
function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_string, verify_time: time &default=network_time()%): X509::Result
|
|
%{
|
|
X509_STORE* ctx = x509_get_root_store(root_certs->AsTableVal());
|
|
if ( ! ctx )
|
|
return x509_error_record(-1, "Problem initializing root store");
|
|
|
|
VectorVal *certs_vec = certs->AsVectorVal();
|
|
if ( ! certs_vec || certs_vec->Size() < 1 )
|
|
{
|
|
reporter->Error("No certificates given in vector");
|
|
return x509_error_record(-1, "no certificates");
|
|
}
|
|
|
|
// host certificate
|
|
unsigned int index = 0; // to prevent overloading to 0pointer
|
|
Val *sv = certs_vec->Lookup(index);
|
|
if ( !sv )
|
|
{
|
|
builtin_error("undefined value in certificate vector");
|
|
return x509_error_record(-1, "undefined value in certificate vector");
|
|
}
|
|
|
|
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
|
|
|
|
X509* cert = cert_handle->GetCertificate();
|
|
if ( ! cert )
|
|
{
|
|
builtin_error(fmt("No certificate in opaque"));
|
|
return x509_error_record(-1, "No certificate in opaque");
|
|
}
|
|
|
|
STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec);
|
|
if ( ! untrusted_certs )
|
|
return x509_error_record(-1, "Problem initializing list of untrusted certificates");
|
|
|
|
X509_STORE_CTX csc;
|
|
X509_STORE_CTX_init(&csc, ctx, cert, untrusted_certs);
|
|
X509_STORE_CTX_set_time(&csc, 0, (time_t) verify_time);
|
|
X509_STORE_CTX_set_flags(&csc, X509_V_FLAG_USE_CHECK_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");
|
|
sk_X509_pop_free(chain, X509_free);
|
|
goto x509_verify_chainerror;
|
|
}
|
|
|
|
int num_certs = sk_X509_num(chain);
|
|
chainVector = new VectorVal(internal_type("x509_opaque_vector")->AsVectorType());
|
|
|
|
for ( int i = 0; i < num_certs; i++ )
|
|
{
|
|
X509* currcert = sk_X509_value(chain, i);
|
|
|
|
if ( currcert )
|
|
// X509Val takes ownership of currcert.
|
|
chainVector->Assign(i, new file_analysis::X509Val(currcert));
|
|
else
|
|
{
|
|
reporter->InternalWarning("OpenSSL returned null certificate");
|
|
|
|
for ( int j = i + 1; i < num_certs; ++j )
|
|
X509_free(sk_X509_value(chain, j));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
sk_X509_free(chain);
|
|
}
|
|
|
|
x509_verify_chainerror:
|
|
|
|
X509_STORE_CTX_cleanup(&csc);
|
|
|
|
sk_X509_free(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;
|
|
%}
|