Sending root certificate for validation This patch modify squid cert validation subsystem to sent to cert validator helper the complete certificates chain, not only the certificates sent by web server. This is may not be possible in all cases, for example in cases where the root certificate is not stored localy. Also this patch includes a small optimization, it checks for domain mismatch error only when the checked (current) certificate is the server certificate. This is a Measurement Factory project === modified file 'src/globals.h' --- src/globals.h 2013-05-13 22:48:23 +0000 +++ src/globals.h 2013-06-07 09:39:53 +0000 @@ -119,31 +119,32 @@ extern unsigned int WIN32_Socks_initialized; /* 0 */ #endif #if _SQUID_WINDOWS_ extern unsigned int WIN32_OS_version; /* 0 */ extern char *WIN32_OS_string; /* NULL */ extern char *WIN32_Service_name; /* NULL */ extern char *WIN32_Command_Line; /* NULL */ extern char *WIN32_Service_Command_Line; /* NULL */ extern unsigned int WIN32_run_mode; /* _WIN_SQUID_RUN_MODE_INTERACTIVE */ #endif #if HAVE_SBRK extern void *sbrk_start; /* 0 */ #endif extern int ssl_ex_index_server; /* -1 */ extern int ssl_ctx_ex_index_dont_verify_domain; /* -1 */ extern int ssl_ex_index_cert_error_check; /* -1 */ extern int ssl_ex_index_ssl_error_detail; /* -1 */ extern int ssl_ex_index_ssl_peeked_cert; /* -1 */ extern int ssl_ex_index_ssl_errors; /* -1 */ +extern int ssl_ex_index_ssl_cert_chain; /* -1 */ extern const char *external_acl_message; /* NULL */ extern int opt_send_signal; /* -1 */ extern int opt_no_daemon; /* 0 */ extern int opt_parse_cfg_only; /* 0 */ /// current Squid process number (e.g., 4). /// Zero for SMP-unaware code and in no-SMP mode. extern int KidIdentifier; /* 0 */ #endif /* SQUID_GLOBALS_H */ === modified file 'src/ssl/cert_validate_message.cc' --- src/ssl/cert_validate_message.cc 2013-06-07 00:18:11 +0000 +++ src/ssl/cert_validate_message.cc 2013-06-07 09:42:18 +0000 @@ -1,33 +1,38 @@ #include "squid.h" #include "acl/FilledChecklist.h" +#include "globals.h" #include "helper.h" #include "ssl/support.h" #include "ssl/cert_validate_message.h" #include "ssl/ErrorDetail.h" void Ssl::CertValidationMsg::composeRequest(CertValidationRequest const &vcert) { body.clear(); body += Ssl::CertValidationMsg::param_host + "=" + vcert.domainName; - STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(vcert.ssl); + STACK_OF(X509) *peerCerts = static_cast(SSL_get_ex_data(vcert.ssl, ssl_ex_index_ssl_cert_chain)); + + if (!peerCerts) + peerCerts = SSL_get_peer_cert_chain(vcert.ssl); + if (peerCerts) { Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); for (int i = 0; i < sk_X509_num(peerCerts); ++i) { X509 *cert = sk_X509_value(peerCerts, i); PEM_write_bio_X509(bio.get(), cert); body = body + "\n" + param_cert + xitoa(i) + "="; char *ptr; long len = BIO_get_mem_data(bio.get(), &ptr); body.append(ptr, (ptr[len-1] == '\n' ? len - 1 : len)); if (!BIO_reset(bio.get())) { // print an error? } } } if (vcert.errors) { int i = 0; for (const Ssl::CertErrors *err = vcert.errors; err; err = err->next, ++i) { body +="\n"; body = body + param_error_name + xitoa(i) + "=" + GetErrorName(err->element.code) + "\n"; === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2013-06-07 00:18:11 +0000 +++ src/ssl/support.cc 2013-06-07 10:34:37 +0000 @@ -225,41 +225,42 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx) { // preserve original ctx->error before SSL_ calls can overwrite it Ssl::ssl_error_t error_no = ok ? SSL_ERROR_NONE : ctx->error; char buffer[256] = ""; SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); SSL_CTX *sslctx = SSL_get_SSL_CTX(ssl); const char *server = (const char *)SSL_get_ex_data(ssl, ssl_ex_index_server); void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain); ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check); X509 *peeked_cert = (X509 *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_peeked_cert); X509 *peer_cert = ctx->cert; X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer, sizeof(buffer)); if (ok) { debugs(83, 5, "SSL Certificate signature OK: " << buffer); - if (server) { + // Check for domain mismatch only if the current certificate is the peer certificate. + if (server && peer_cert == X509_STORE_CTX_get_current_cert(ctx)) { if (!Ssl::checkX509ServerValidity(peer_cert, server)) { debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << buffer << " does not match domainname " << server); ok = 0; error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH; } } } if (ok && peeked_cert) { // Check whether the already peeked certificate matches the new one. if (X509_cmp(peer_cert, peeked_cert) != 0) { debugs(83, 2, "SQUID_X509_V_ERR_CERT_CHANGE: Certificate " << buffer << " does not match peeked certificate"); ok = 0; error_no = SQUID_X509_V_ERR_CERT_CHANGE; } } if (!ok) { X509 *broken_cert = X509_STORE_CTX_get_current_cert(ctx); if (!broken_cert) @@ -281,42 +282,49 @@ else debugs(83, DBG_IMPORTANT, "SSL unknown certificate error " << error_no << " in " << buffer); if (check) { ACLFilledChecklist *filledCheck = Filled(check); assert(!filledCheck->sslErrors); filledCheck->sslErrors = new Ssl::CertErrors(Ssl::CertError(error_no, broken_cert)); filledCheck->serverCert.resetAndLock(peer_cert); if (check->fastCheck() == ACCESS_ALLOWED) { debugs(83, 3, "bypassing SSL error " << error_no << " in " << buffer); ok = 1; } else { debugs(83, 5, "confirming SSL error " << error_no); } delete filledCheck->sslErrors; filledCheck->sslErrors = NULL; filledCheck->serverCert.reset(NULL); } // If the certificate validator is used then we need to allow all errors and // pass them to certficate validator for more processing - else if (Ssl::TheConfig.ssl_crt_validator) + else if (Ssl::TheConfig.ssl_crt_validator) { ok = 1; + // Check if we have stored certificates chain. Store if not. + if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_cert_chain)) { + STACK_OF(X509) *certStack = X509_STORE_CTX_get1_chain(ctx); + if (certStack && !SSL_set_ex_data(ssl, ssl_ex_index_ssl_cert_chain, certStack)) + sk_X509_pop_free(certStack, X509_free); + } + } } if (!dont_verify_domain && server) {} if (!ok && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) { // Find the broken certificate. It may be intermediate. X509 *broken_cert = peer_cert; // reasonable default if search fails // Our SQUID_X509_V_ERR_DOMAIN_MISMATCH implies peer_cert is at fault. if (error_no != SQUID_X509_V_ERR_DOMAIN_MISMATCH) { if (X509 *last_used_cert = X509_STORE_CTX_get_current_cert(ctx)) broken_cert = last_used_cert; } Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(error_no, peer_cert, broken_cert); if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) { debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer); delete errDetail; @@ -626,40 +634,51 @@ delete static_cast(ptr); // may be NULL } // "free" function for SSL_get_ex_new_index("ssl_error_detail") static void ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *) { Ssl::ErrorDetail *errDetail = static_cast (ptr); delete errDetail; } static void ssl_free_SslErrors(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *) { Ssl::CertErrors *errs = static_cast (ptr); delete errs; } +/// \ingroup ServerProtocolSSLInternal +/// Callback handler function to release STACK_OF(X509) "ex" data stored +/// in an SSL object. +static void +ssl_free_CertChain(void *, void *ptr, CRYPTO_EX_DATA *, + int, long, void *) +{ + STACK_OF(X509) *certsChain = static_cast (ptr); + sk_X509_pop_free(certsChain,X509_free); +} + // "free" function for X509 certificates static void ssl_free_X509(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *) { X509 *cert = static_cast (ptr); X509_free(cert); } /// \ingroup ServerProtocolSSLInternal static void ssl_initialize(void) { static int ssl_initialized = 0; if (!ssl_initialized) { ssl_initialized = 1; SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); #if HAVE_OPENSSL_ENGINE_H @@ -676,40 +695,41 @@ fatalf("Failed to initialise SSL engine: %s\n", ERR_error_string(ssl_error, NULL)); } } #else if (Config.SSL.ssl_engine) { fatalf("Your OpenSSL has no SSL engine support\n"); } #endif } ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", NULL, NULL, NULL); ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL); ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist); ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail); ssl_ex_index_ssl_peeked_cert = SSL_get_ex_new_index(0, (void *) "ssl_peeked_cert", NULL, NULL, &ssl_free_X509); ssl_ex_index_ssl_errors = SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors); + ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", NULL, NULL, &ssl_free_CertChain); } /// \ingroup ServerProtocolSSLInternal static int ssl_load_crl(SSL_CTX *sslContext, const char *CRLfile) { X509_STORE *st = SSL_CTX_get_cert_store(sslContext); X509_CRL *crl; BIO *in = BIO_new_file(CRLfile, "r"); int count = 0; if (!in) { debugs(83, 2, "WARNING: Failed to open CRL file '" << CRLfile << "'"); return 0; } while ((crl = PEM_read_bio_X509_CRL(in,NULL,NULL,NULL))) { if (!X509_STORE_add_crl(st, crl)) debugs(83, 2, "WARNING: Failed to add CRL from file '" << CRLfile << "'"); else