%%{ #include "file_analysis/analyzer/x509/X509.h" #include "types.bif.h" #include #include #include #include // This is the indexed map of X509 certificate stores. static map x509_stores; // ### NOTE: while d2i_X509 does not take a const u_char** pointer, // here we assume d2i_X509 does not write to , 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_result_record(uint64_t num, const char* reason, Val* chainVector = 0) { RecordVal* rrecord = new RecordVal(BifType::Record::X509::Result); rrecord->Assign(0, new Val(num, TYPE_INT)); rrecord->Assign(1, new StringVal(reason)); if ( chainVector ) rrecord->Assign(2, chainVector); 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); X509_free(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); if ( ! sv ) continue; // 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; } // We need this function to be able to identify the signer certificate of an // OCSP request out of a list of possible certificates. X509* x509_get_ocsp_signer(STACK_OF(X509) *certs, OCSP_RESPID *rid) { // We support two lookup types - either by response id or by key. if ( rid->type == V_OCSP_RESPID_NAME ) return X509_find_by_subject(certs, rid->value.byName); // There only should be name and type - but let's be sure... if ( rid->type != V_OCSP_RESPID_KEY ) return 0; // Just like OpenSSL, we just support SHA-1 lookups and bail out otherwhise. if ( rid->value.byKey->length != SHA_DIGEST_LENGTH ) return 0; unsigned char* key_hash = rid->value.byKey->data; for ( int i = 0; i < sk_X509_num(certs); ++i ) { unsigned char digest[SHA_DIGEST_LENGTH]; X509* cert = sk_X509_value(certs, i); if ( ! X509_pubkey_digest(cert, EVP_sha1(), digest, NULL) ) // digest failed for this certificate, try with next continue; if ( memcmp(digest, key_hash, SHA_DIGEST_LENGTH) == 0 ) // keys match, return certificate return cert; } return 0; } %%} ## Parses a certificate into an X509::Certificate structure. ## ## cert: The X509 certificate 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 %{ RecordVal* rval = 0; X509_STORE* ctx = x509_get_root_store(root_certs->AsTableVal()); if ( ! ctx ) return x509_result_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_result_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_result_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_result_record(-1, "No certificate in opaque"); } const unsigned char* start = ocsp_reply->Bytes(); STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec); if ( ! untrusted_certs ) return x509_result_record(-1, "Problem initializing list of untrusted certificates"); // from here, always goto cleanup. Initialize all other required variables... time_t vtime = (time_t) verify_time; OCSP_BASICRESP *basic = 0; OCSP_SINGLERESP *single = 0; X509_STORE_CTX *csc = 0; OCSP_CERTID *certid = 0; int status = -1; int out = -1; int result = -1; X509* issuer_certificate = 0; X509* signer = 0; OCSP_RESPONSE *resp = d2i_OCSP_RESPONSE(NULL, &start, ocsp_reply->Len()); if ( ! resp ) { rval = x509_result_record(-1, "Could not parse OCSP response"); goto x509_ocsp_cleanup; } status = OCSP_response_status(resp); if ( status != OCSP_RESPONSE_STATUS_SUCCESSFUL ) { rval = x509_result_record(-2, OCSP_response_status_str(status)); goto x509_ocsp_cleanup; } basic = OCSP_response_get1_basic(resp); if ( ! basic ) { rval = x509_result_record(-1, "Could not parse OCSP response"); goto x509_ocsp_cleanup; } // 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. if ( ! basic->certs ) { basic->certs = sk_X509_new_null(); if ( ! basic->certs ) { rval = x509_result_record(-1, "Could not allocate basic x509 stack"); goto x509_ocsp_cleanup; } } issuer_certificate = 0; for ( int i = 0; i < sk_X509_num(untrusted_certs); i++) { sk_X509_push(basic->certs, X509_dup(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))) == 0 ) 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. signer = x509_get_ocsp_signer(basic->certs, basic->tbsResponseData->responderId); /* Do this perhaps - OpenSSL also cannot do it, so I do not really feel bad about it. Needs a different lookup because the root store is no stack of X509 certs if ( !s igner ) // if we did not find it in the certificates that were sent, search in the root store signer = x509_get_ocsp_signer(basic->certs, basic->tbsResponseData->responderId); */ if ( ! signer ) { rval = x509_result_record(-1, "Could not find OCSP responder certificate"); goto x509_ocsp_cleanup; } csc = X509_STORE_CTX_new(); X509_STORE_CTX_init(csc, ctx, signer, basic->certs); X509_STORE_CTX_set_time(csc, 0, (time_t) verify_time); X509_STORE_CTX_set_purpose(csc, X509_PURPOSE_OCSP_HELPER); result = X509_verify_cert(csc); if ( result != 1 ) { const char *reason = X509_verify_cert_error_string((*csc).error); rval = x509_result_record(result, X509_verify_cert_error_string((*csc).error)); goto x509_ocsp_cleanup; } out = OCSP_basic_verify(basic, NULL, ctx, 0); if ( result < 1 ) { rval = x509_result_record(out, ERR_error_string(ERR_get_error(),NULL)); goto x509_ocsp_cleanup; } // 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. 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) { rval = x509_result_record(lookup, "Could not find issuer of host certificate"); goto x509_ocsp_cleanup; } certid = OCSP_cert_to_id(NULL, cert, obj.data.x509); } if ( ! certid ) { rval = x509_result_record(-1, "Certificate ID construction failed"); goto x509_ocsp_cleanup; } // for now, assume we have one reply... single = sk_OCSP_SINGLERESP_value(basic->tbsResponseData->responses, 0); if ( ! single ) { rval = x509_result_record(-1, "Could not lookup OCSP response information"); goto x509_ocsp_cleanup; } if ( OCSP_id_cmp(certid, single->certId) != 0 ) return x509_result_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) ) { rval = x509_result_record(-1, "OCSP reply contains invalid dates"); goto x509_ocsp_cleanup; } // 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. if ( X509_cmp_time(single->thisUpdate, &vtime) > 0 ) rval = x509_result_record(-1, "OCSP reply specifies time in future"); else if ( X509_cmp_time(single->nextUpdate, &vtime) < 0 ) rval = x509_result_record(-1, "OCSP reply expired"); else if ( single->certStatus->type != V_OCSP_CERTSTATUS_GOOD ) rval = x509_result_record(-1, OCSP_cert_status_str(single->certStatus->type)); // if we have no error so far, we are done. if ( !rval ) rval = x509_result_record(1, OCSP_cert_status_str(single->certStatus->type)); x509_ocsp_cleanup: if ( untrusted_certs ) sk_X509_free(untrusted_certs); if ( resp ) OCSP_RESPONSE_free(resp); if ( basic ) OCSP_BASICRESP_free(basic); if ( csc ) { X509_STORE_CTX_cleanup(csc); X509_STORE_CTX_free(csc); } if ( certid ) OCSP_CERTID_free(certid); return rval; %} ## 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_result_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_result_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_result_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_result_record(-1, "No certificate in opaque"); } STACK_OF(X509)* untrusted_certs = x509_get_untrusted_stack(certs_vec); if ( ! untrusted_certs ) return x509_result_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"); sk_X509_pop_free(chain, X509_free); goto x509_verify_chainerror; } } sk_X509_free(chain); } x509_verify_chainerror: X509_STORE_CTX_cleanup(&csc); sk_X509_free(untrusted_certs); RecordVal* rrecord = x509_result_record(csc.error, X509_verify_cert_error_string(csc.error), chainVector); return rrecord; %}