mirror of
https://github.com/zeek/zeek.git
synced 2025-10-09 01:58:20 +00:00
Implement verification of OCSP replies.
The OpenSSL code to do that is a nightmare.
This commit is contained in:
parent
ccccda6da8
commit
55d0c6f7fa
4 changed files with 247 additions and 61 deletions
|
@ -2812,7 +2812,7 @@ export {
|
||||||
## Result of an X509 certificate chain verification
|
## Result of an X509 certificate chain verification
|
||||||
type Result: record {
|
type Result: record {
|
||||||
## OpenSSL result code
|
## OpenSSL result code
|
||||||
result: count;
|
result: int;
|
||||||
## Result as string
|
## Result as string
|
||||||
result_string: string;
|
result_string: string;
|
||||||
## References to the final certificate chain, if verification successful. End-host certificate is first.
|
## References to the final certificate chain, if verification successful. End-host certificate is first.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <openssl/asn1.h>
|
#include <openssl/asn1.h>
|
||||||
#include <openssl/x509_vfy.h>
|
#include <openssl/x509_vfy.h>
|
||||||
|
#include <openssl/ocsp.h>
|
||||||
|
|
||||||
// This is the indexed map of X509 certificate stores.
|
// This is the indexed map of X509 certificate stores.
|
||||||
static map<Val*, X509_STORE*> x509_stores;
|
static map<Val*, X509_STORE*> x509_stores;
|
||||||
|
@ -34,6 +35,68 @@ RecordVal* x509_error_record(uint64_t num, const char* reason)
|
||||||
return rrecord;
|
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.
|
## Parses a certificate into an X509::Certificate structure.
|
||||||
|
@ -86,27 +149,28 @@ function x509_get_certificate_string%(cert: opaque of x509, pem: bool &default=F
|
||||||
return ext_val;
|
return ext_val;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
## Verifies an OCSP reply.
|
||||||
## Verifies a certificate.
|
|
||||||
##
|
##
|
||||||
## certs: Specifies a certificate chain that is being used to validate
|
## certs: Specifies the certificate chain to use. Server certificate first.
|
||||||
## the given certificate against the root store given in *root_certs*.
|
##
|
||||||
## The host certificate has to be at index 0.
|
## ocsp_reply: the ocsp reply to validate
|
||||||
##
|
##
|
||||||
## root_certs: A list of root certificates to validate the certificate chain
|
## root_certs: A list of root certificates to validate the certificate chain
|
||||||
##
|
##
|
||||||
## verify_time: Time for the validity check of the certificates.
|
## verify_time: Time for the validity check of the certificates.
|
||||||
##
|
##
|
||||||
## Returns: A record of type X509::Result containing the result code of the verify
|
## 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.
|
## operation.
|
||||||
##
|
##
|
||||||
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
|
## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints
|
||||||
## x509_ext_subject_alternative_name x509_parse
|
## x509_ext_subject_alternative_name x509_parse
|
||||||
## x509_get_certificate_string
|
## x509_get_certificate_string x509_verify
|
||||||
function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_string, verify_time: time &default=network_time()%): X509::Result
|
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
|
||||||
%{
|
%{
|
||||||
X509_STORE* ctx = 0;
|
BIO* bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
|
||||||
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();
|
VectorVal *certs_vec = certs->AsVectorVal();
|
||||||
if ( certs_vec->Size() < 1 )
|
if ( certs_vec->Size() < 1 )
|
||||||
|
@ -126,38 +190,172 @@ function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_str
|
||||||
|
|
||||||
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
|
file_analysis::X509Val* cert_handle = (file_analysis::X509Val*) sv;
|
||||||
|
|
||||||
// If this certificate store was built previously, just reuse the old one.
|
X509* cert = cert_handle->GetCertificate();
|
||||||
if ( x509_stores.count(root_certs) > 0 )
|
if ( ! cert )
|
||||||
ctx = x509_stores[root_certs];
|
|
||||||
|
|
||||||
if ( ! ctx ) // lookup to see if we have this one built already!
|
|
||||||
{
|
{
|
||||||
ctx = X509_STORE_new();
|
builtin_error(fmt("No certificate in opaque"));
|
||||||
TableVal* root_certs2 = root_certs->AsTableVal();
|
return x509_error_record(-1, "No certificate in opaque");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
X509* cert = cert_handle->GetCertificate();
|
||||||
if ( ! cert )
|
if ( ! cert )
|
||||||
{
|
{
|
||||||
|
@ -165,27 +363,9 @@ function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_str
|
||||||
return x509_error_record(-1, "No certificate in opaque");
|
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 )
|
if ( !untrusted_certs )
|
||||||
{
|
return x509_error_record(-1, "Problem initializing list of untrusted certificates");
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
X509_STORE_CTX csc;
|
X509_STORE_CTX csc;
|
||||||
X509_STORE_CTX_init(&csc, ctx, cert, untrusted_certs);
|
X509_STORE_CTX_init(&csc, ctx, cert, untrusted_certs);
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
F, 1995
|
F, 1995
|
||||||
|
[result=1, result_string=good, chain_certs=<uninitialized>]
|
||||||
|
|
|
@ -3,5 +3,10 @@
|
||||||
|
|
||||||
event ssl_stapled_ocsp(c: connection, is_orig: bool, response: string)
|
event ssl_stapled_ocsp(c: connection, is_orig: bool, response: string)
|
||||||
{
|
{
|
||||||
|
local chain: vector of opaque of x509 = vector();
|
||||||
|
for ( i in c$ssl$cert_chain )
|
||||||
|
chain[i] = c$ssl$cert_chain[i]$x509$handle;
|
||||||
|
|
||||||
print is_orig, |response|;
|
print is_orig, |response|;
|
||||||
|
print x509_ocsp_verify(chain, response, SSL::root_certs);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue