mirror of
https://github.com/zeek/zeek.git
synced 2025-10-07 00:58:19 +00:00
Merge remote-tracking branch 'origin/topic/bernhard/even-more-ssl-changes'
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
This commit is contained in:
commit
ed4cd9352a
46 changed files with 880 additions and 254 deletions
|
@ -46,7 +46,7 @@ bool file_analysis::X509::EndOfFile()
|
|||
::X509* ssl_cert = d2i_X509(NULL, &cert_char, cert_data.size());
|
||||
if ( ! ssl_cert )
|
||||
{
|
||||
reporter->Error("Could not parse X509 certificate (fuid %s)", GetFile()->GetID().c_str());
|
||||
reporter->Weird(fmt("Could not parse X509 certificate (fuid %s)", GetFile()->GetID().c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ StringVal* file_analysis::X509::GetExtensionFromBIO(BIO* bio)
|
|||
{
|
||||
char tmp[120];
|
||||
ERR_error_string_n(ERR_get_error(), tmp, sizeof(tmp));
|
||||
reporter->Error("X509::GetExtensionFromBIO: %s", tmp);
|
||||
reporter->Weird(fmt("X509::GetExtensionFromBIO: %s", tmp));
|
||||
BIO_free_all(bio);
|
||||
return 0;
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ void file_analysis::X509::ParseBasicConstraints(X509_EXTENSION* ex)
|
|||
}
|
||||
|
||||
else
|
||||
reporter->Error("Certificate with invalid BasicConstraint. fuid %s", GetFile()->GetID().c_str());
|
||||
reporter->Weird(fmt("Certificate with invalid BasicConstraint. fuid %s", GetFile()->GetID().c_str()));
|
||||
}
|
||||
|
||||
void file_analysis::X509::ParseSAN(X509_EXTENSION* ext)
|
||||
|
@ -289,7 +289,7 @@ void file_analysis::X509::ParseSAN(X509_EXTENSION* ext)
|
|||
GENERAL_NAMES *altname = (GENERAL_NAMES*)X509V3_EXT_d2i(ext);
|
||||
if ( ! altname )
|
||||
{
|
||||
reporter->Error("Could not parse subject alternative names. fuid %s", GetFile()->GetID().c_str());
|
||||
reporter->Weird(fmt("Could not parse subject alternative names. fuid %s", GetFile()->GetID().c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -309,7 +309,7 @@ void file_analysis::X509::ParseSAN(X509_EXTENSION* ext)
|
|||
{
|
||||
if ( ASN1_STRING_type(gen->d.ia5) != V_ASN1_IA5STRING )
|
||||
{
|
||||
reporter->Error("DNS-field does not contain an IA5String. fuid %s", GetFile()->GetID().c_str());
|
||||
reporter->Weird(fmt("DNS-field does not contain an IA5String. fuid %s", GetFile()->GetID().c_str()));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -356,7 +356,7 @@ void file_analysis::X509::ParseSAN(X509_EXTENSION* ext)
|
|||
|
||||
else
|
||||
{
|
||||
reporter->Error("Weird IP address length %d in subject alternative name. fuid %s", gen->d.ip->length, GetFile()->GetID().c_str());
|
||||
reporter->Weird(fmt("Weird IP address length %d in subject alternative name. fuid %s", gen->d.ip->length, GetFile()->GetID().c_str()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#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;
|
||||
|
@ -34,6 +35,70 @@ RecordVal* x509_error_record(uint64_t num, const char* 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.
|
||||
|
@ -86,6 +151,176 @@ function x509_get_certificate_string%(cert: opaque of x509, pem: bool &default=F
|
|||
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.
|
||||
##
|
||||
|
@ -102,14 +337,15 @@ function x509_get_certificate_string%(cert: opaque of x509, pem: bool &default=F
|
|||
##
|
||||
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
|
||||
## x509_ext_subject_alternative_name x509_parse
|
||||
## x509_get_certificate_string
|
||||
## 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 = 0;
|
||||
int i = 0;
|
||||
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 )
|
||||
if ( ! certs_vec || certs_vec->Size() < 1 )
|
||||
{
|
||||
reporter->Error("No certificates given in vector");
|
||||
return x509_error_record(-1, "no certificates");
|
||||
|
@ -126,38 +362,6 @@ function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_str
|
|||
|
||||
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
|
||||
|
||||
// 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 x509_error_record((uint64) ERR_get_error(), ERR_error_string(ERR_peek_last_error(),NULL));
|
||||
}
|
||||
|
||||
X509_STORE_add_cert(ctx, x);
|
||||
}
|
||||
|
||||
delete idxs;
|
||||
|
||||
// Save the newly constructed certificate store into the cacheing map.
|
||||
x509_stores[root_certs] = ctx;
|
||||
}
|
||||
|
||||
X509* cert = cert_handle->GetCertificate();
|
||||
if ( ! cert )
|
||||
{
|
||||
|
@ -165,27 +369,9 @@ function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_str
|
|||
return x509_error_record(-1, "No certificate in opaque");
|
||||
}
|
||||
|
||||
STACK_OF(X509)* untrusted_certs = sk_X509_new_null();
|
||||
STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec);
|
||||
if ( ! untrusted_certs )
|
||||
{
|
||||
builtin_error(fmt("Untrusted certificate stack initialization error: %s", ERR_error_string(ERR_peek_last_error(),NULL)));
|
||||
return x509_error_record((uint64) ERR_get_error(), ERR_error_string(ERR_peek_last_error(),NULL));
|
||||
}
|
||||
|
||||
for ( 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 x509_error_record(-1, "No certificate in opaque");
|
||||
}
|
||||
|
||||
sk_X509_push(untrusted_certs, x);
|
||||
}
|
||||
return x509_error_record(-1, "Problem initializing list of untrusted certificates");
|
||||
|
||||
X509_STORE_CTX csc;
|
||||
X509_STORE_CTX_init(&csc, ctx, cert, untrusted_certs);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue