diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index f68132e280..d5017a5f89 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2812,7 +2812,7 @@ export { ## Result of an X509 certificate chain verification type Result: record { ## OpenSSL result code - result: count; + result: int; ## Result as string result_string: string; ## References to the final certificate chain, if verification successful. End-host certificate is first. diff --git a/src/file_analysis/analyzer/x509/functions.bif b/src/file_analysis/analyzer/x509/functions.bif index 1fa81a0fd0..2a517f6154 100644 --- a/src/file_analysis/analyzer/x509/functions.bif +++ b/src/file_analysis/analyzer/x509/functions.bif @@ -5,6 +5,7 @@ #include #include #include +#include // This is the indexed map of X509 certificate stores. static map x509_stores; @@ -34,6 +35,68 @@ 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,27 +149,28 @@ function x509_get_certificate_string%(cert: opaque of x509, pem: bool &default=F return ext_val; %} - -## Verifies a certificate. +## Verifies an OCSP reply. ## -## 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. +## 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. In case of success also returns the full certificate chain. +## operation. ## ## .. bro:see:: x509_certificate x509_extension x509_ext_basic_constraints ## x509_ext_subject_alternative_name x509_parse -## x509_get_certificate_string -function x509_verify%(certs: x509_opaque_vector, root_certs: table_string_of_string, verify_time: time &default=network_time()%): X509::Result +## 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 %{ - X509_STORE* ctx = 0; - int i = 0; + 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 ) @@ -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; - // 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! + X509* cert = cert_handle->GetCertificate(); + if ( ! cert ) { - 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; + 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 ) { @@ -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"); } - 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 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); - } + 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); diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout b/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout index a8735f6d41..527e63f4b9 100644 --- a/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.ocsp-stapling/.stdout @@ -1 +1,2 @@ F, 1995 +[result=1, result_string=good, chain_certs=] diff --git a/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test b/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test index b50f04a92e..1114335811 100644 --- a/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test +++ b/testing/btest/scripts/base/protocols/ssl/ocsp-stapling.test @@ -3,5 +3,10 @@ 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 x509_ocsp_verify(chain, response, SSL::root_certs); }