SSL server certificate validator implementation This patch implements the certificate validation helper interface described at: http://wiki.squid-cache.org/Features/SslServerCertValidator The helper consulted after the internal OpenSSL validation, regardless of the validation results. The helper will receive: 1) the origin server certificate [chain], 2) the intended domain name, and 3) a list of OpenSSL validation errors (if any). If the helper decides to honor an OpenSSL error or report another validation error(s), the helper will return: 1) A list of certificates. 2) A list of items consists the the validation error name (see %err_name error page macro and %err_details logformat code), error reason (%ssl_lib_error macro), and the offending certificate. The returned information mimics what the internal OpenSSL-based validation code collects now. Returned errors, if any, fed to sslproxy_cert_error, triggering the existing SSL error processing code. The helper invocation controlled by the "sslcrtvalidator_program" and "sslcrtvalidator_children" configurations options which are similar to the ssl_crtd related options. A simple testing cert validation helper developed in perl included in this patch. This helper just echo back the certificate errors. This is a Measurement Factory Project === modified file 'configure.ac' --- configure.ac 2012-10-26 11:36:45 +0000 +++ configure.ac 2012-11-13 17:20:06 +0000 @@ -3609,30 +3609,31 @@ helpers/negotiate_auth/kerberos/Makefile \ helpers/negotiate_auth/SSPI/Makefile \ helpers/negotiate_auth/wrapper/Makefile \ helpers/external_acl/Makefile \ helpers/external_acl/AD_group/Makefile \ helpers/external_acl/eDirectory_userip/Makefile \ helpers/external_acl/file_userip/Makefile \ helpers/external_acl/kerberos_ldap_group/Makefile \ helpers/external_acl/LDAP_group/Makefile \ helpers/external_acl/LM_group/Makefile \ helpers/external_acl/session/Makefile \ helpers/external_acl/SQL_session/Makefile \ helpers/external_acl/unix_group/Makefile \ helpers/external_acl/wbinfo_group/Makefile \ helpers/external_acl/time_quota/Makefile \ helpers/log_daemon/Makefile \ helpers/log_daemon/DB/Makefile \ helpers/log_daemon/file/Makefile \ helpers/url_rewrite/Makefile \ helpers/url_rewrite/fake/Makefile \ + helpers/ssl/Makefile \ tools/Makefile tools/purge/Makefile ]) AC_CONFIG_SUBDIRS(lib/libTrie) # must configure libltdl subdir unconditionally for "make distcheck" to work #AC_CONFIG_SUBDIRS(libltdl) AC_OUTPUT === modified file 'helpers/Makefile.am' --- helpers/Makefile.am 2010-06-30 12:32:50 +0000 +++ helpers/Makefile.am 2012-09-20 15:44:31 +0000 @@ -1,22 +1,28 @@ EXTRA_DIST = defines.h DIST_SUBDIRS = \ basic_auth \ digest_auth \ external_acl \ log_daemon \ negotiate_auth \ ntlm_auth \ - url_rewrite + url_rewrite \ + ssl SUBDIRS = \ basic_auth \ digest_auth \ external_acl \ log_daemon \ negotiate_auth \ url_rewrite if ENABLE_AUTH_NTLM SUBDIRS += ntlm_auth endif + +if ENABLE_SSL +SUBDIRS += ssl +endif + === added directory 'helpers/ssl' === added file 'helpers/ssl/Makefile.am' --- helpers/ssl/Makefile.am 1970-01-01 00:00:00 +0000 +++ helpers/ssl/Makefile.am 2012-09-15 16:40:00 +0000 @@ -0,0 +1,5 @@ +include $(top_srcdir)/src/Common.am + +libexec_SCRIPTS = cert_valid.pl +EXTRA_DIST= \ + cert_valid.pl === added file 'helpers/ssl/cert_valid.pl' --- helpers/ssl/cert_valid.pl 1970-01-01 00:00:00 +0000 +++ helpers/ssl/cert_valid.pl 2012-11-24 13:57:53 +0000 @@ -0,0 +1,204 @@ +#!/usr/bin/perl -w +# +# A dummy SSL certificate validator helper that +# echos back all the SSL errors sent by Squid. +# + +use warnings; +use strict; +use Getopt::Long; +use Pod::Usage; +use Crypt::OpenSSL::X509; +use FileHandle; +use POSIX qw(strftime); + +my $debug = 0; +my $help = 0; + +=pod + +=head1 NAME + +cert_valid.pl - A fake cert validation helper for Squid + +=head1 SYNOPSIS + +cert_valid.pl [-d | --debug] [-h | --help] + +=over 8 + +=item B<-h | --help> + +brief help message + +=item B<-d | --debug> + +enable debug messages to stderr + +=back + +=head1 DESCRIPTION + +Retrieves the SSL certificate error list from squid and echo back without any change. + +=head1 COPYRIGHT + +(C) 2012 The Measurement Factory, Author: Tsantilas Christos + +This program is free software. You may redistribute copies of it under the +terms of the GNU General Public License version 2, or (at your opinion) any +later version. + +=cut + +GetOptions( + 'help' => \$help, + 'debug' => \$debug, + ) or pod2usage(1); + +pod2usage(1) if ($help); + +$|=1; +while (<>) { + my $first_line = $_; + my @line_args = split; + + if ($first_line =~ /^\s*$/) { + next; + } + + my $response; + my $haserror = 0; + my $channelId = $line_args[0]; + my $code = $line_args[1]; + my $bodylen = $line_args[2]; + my $body = $line_args[3] . "\n"; + if ($channelId !~ /\d+/) { + $response = $channelId." BH message=\"This helper is concurrent and requires the concurrency option to be specified.\"\1"; + } elsif ($bodylen !~ /\d+/) { + $response = $channelId." BH message=\"cert validator request syntax error \" \1"; + } else { + my $readlen = length($body); + my %certs = (); + my @errors = (); + my @responseErrors = (); + + while($readlen < $bodylen) { + my $t = <>; + if (defined $t) { + $body = $body . $t; + $readlen = length($body); + } + } + + print(STDERR logPrefix()."GOT ". "Code=".$code." $bodylen \n") if ($debug); #.$body; + my $hostname; + parseRequest($body, \$hostname, \@errors, \%certs); + print(STDERR logPrefix()."Parse result: \n") if ($debug); + print(STDERR logPrefix()."\tFOUND host:".$hostname."\n") if ($debug); + print(STDERR logPrefix()."\tFOUND ERRORS:") if ($debug); + foreach my $err (@errors) { + print(STDERR logPrefix()."$err ,") if ($debug); + } + print(STDERR "\n") if ($debug); + foreach my $key (keys %certs) { + ## Use "perldoc Crypt::OpenSSL::X509" for X509 available methods. + print(STDERR logPrefix()."\tFOUND cert ".$key.": ".$certs{$key}->subject() . "\n") if ($debug); + } + + #got the peer certificate ID. Assume that the peer certificate is the first one. + my $peerCertId = (keys %certs)[0]; + + # Echo back the errors: fill the responseErrors array with the errors we read. + foreach my $err (@errors) { + $haserror = 1; + appendError (\@responseErrors, + $err, #The error name + "Checked by Cert Validator", # An error reason + $peerCertId # The cert ID. We are always filling with the peer certificate. + ); + } + + $response = createResponse(\@responseErrors); + my $len = length($response); + if ($haserror) { + $response = $channelId." ERR ".$len." ".$response."\1"; + } else { + $response = $channelId." OK ".$len." ".$response."\1"; + } + } + + print $response; + print(STDERR logPrefix().">> ".$response."\n") if ($debug); +} + +sub trim +{ + my $s = shift; + $s =~ s/^\s+//; + $s =~ s/\s+$//; + return $s; +} + +sub appendError +{ + my ($errorArrays) = shift; + my($errorName) = shift; + my($errorReason) = shift; + my($errorCert) = shift; + push @$errorArrays, { "error_name" => $errorName, "error_reason" => $errorReason, "error_cert" => $errorCert}; +} + +sub createResponse +{ + my ($responseErrors) = shift; + my $response=""; + my $i = 0; + foreach my $err (@$responseErrors) { + $response=$response."error_name_".$i."=".$err->{"error_name"}."\n". + "error_reason_".$i."=".$err->{"error_reason"}."\n". + "error_cert_".$i."=".$err->{"error_cert"}."\n"; + $i++; + } + return $response; +} + +sub parseRequest +{ + my($request)=shift; + my $hostname = shift; + my $errors = shift; + my $certs = shift; + while ($request !~ /^\s*$/) { + $request = trim($request); + if ($request =~ /^host=/) { + my($vallen) = index($request, "\n"); + my $host = substr($request, 5, $vallen - 5); + $$hostname = $host; + $request =~ s/^host=.*\n//; + } + if ($request =~ /^errors=/) { + my($vallen) = index($request, "\n"); + my $listerrors = substr($request, 7, $vallen - 7); + @$errors = split /,/, $listerrors; + $request =~ s/^errors=.*\n//; + } + elsif ($request =~ /^cert_(\d+)=/) { + my $certId = "cert_".$1; + my($vallen) = index($request, "-----END CERTIFICATE-----") + length("-----END CERTIFICATE-----"); + my $x509 = Crypt::OpenSSL::X509->new_from_string(substr($request, index($request, "-----BEGIN"))); + $certs->{$certId} = $x509; + $request = substr($request, $vallen); + } + else { + print(STDERR logPrefix()."ParseError on \"".$request."\"\n") if ($debug); + $request = "";# finish processing.... + } + } +} + + +sub logPrefix +{ + return strftime("%Y/%m/%d %H:%M:%S.0", localtime)." ".$0." ".$$." | " ; +} === modified file 'src/cf.data.pre' --- src/cf.data.pre 2012-10-29 01:31:29 +0000 +++ src/cf.data.pre 2012-11-24 14:16:16 +0000 @@ -2336,40 +2336,93 @@ startup=N Sets the minimum number of processes to spawn when Squid starts or reconfigures. When set to zero the first request will cause spawning of the first child process to handle it. Starting too few children temporary slows Squid under load while it tries to spawn enough additional processes to cope with traffic. idle=N Sets a minimum of how many processes Squid is to try and keep available at all times. When traffic begins to rise above what the existing processes can handle this many more will be spawned up to the maximum configured. A minimum setting of 1 is required. You must have at least one ssl_crtd process. DOC_END +NAME: sslcrtvalidator_program +TYPE: eol +IFDEF: USE_SSL +DEFAULT: none +LOC: Ssl::TheConfig.ssl_crt_validator +DOC_START + Specify the location and options of the executable for ssl_crt_validator + process. +DOC_END + +NAME: sslcrtvalidator_children +TYPE: HelperChildConfig +IFDEF: USE_SSL +DEFAULT: 32 startup=5 idle=1 concurrency=1 +LOC: Ssl::TheConfig.ssl_crt_validator_Children +DOC_START + The maximum number of processes spawn to service ssl server. + The maximum this may be safely set to is 32. + + The startup= and idle= options allow some measure of skew in your + tuning. + + startup=N + + Sets the minimum number of processes to spawn when Squid + starts or reconfigures. When set to zero the first request will + cause spawning of the first child process to handle it. + + Starting too few children temporary slows Squid under load while it + tries to spawn enough additional processes to cope with traffic. + + idle=N + + Sets a minimum of how many processes Squid is to try and keep available + at all times. When traffic begins to rise above what the existing + processes can handle this many more will be spawned up to the maximum + configured. A minimum setting of 1 is required. + + concurrency= + + The number of requests each certificate validator helper can handle in + parallel. Defaults to 0 which indicates the certficate validator + is a old-style single threaded redirector. + + When this directive is set to a value >= 1 then the protocol + used to communicate with the helper is modified to include + a request ID in front of the request/response. The request + ID from the request must be echoed back with the response + to that request. + + You must have at least one ssl_crt_validator process. +DOC_END + COMMENT_START OPTIONS WHICH AFFECT THE NEIGHBOR SELECTION ALGORITHM ----------------------------------------------------------------------------- COMMENT_END NAME: cache_peer TYPE: peer DEFAULT: none LOC: Config.peers DOC_START To specify other caches in a hierarchy, use the format: cache_peer hostname type http-port icp-port [options] For example, # proxy icp # hostname type port port options # -------------------- -------- ----- ----- ----------- cache_peer parent.foo.net parent 3128 3130 default === modified file 'src/forward.cc' --- src/forward.cc 2012-10-26 11:36:45 +0000 +++ src/forward.cc 2012-11-24 12:32:26 +0000 @@ -55,40 +55,45 @@ #include "http.h" #include "HttpReply.h" #include "HttpRequest.h" #include "icmp/net_db.h" #include "internal.h" #include "ip/Intercept.h" #include "ip/QosConfig.h" #include "ip/tools.h" #include "MemObject.h" #include "mgr/Registration.h" #include "neighbors.h" #include "pconn.h" #include "PeerSelectState.h" #include "SquidConfig.h" #include "SquidTime.h" #include "Store.h" #include "StoreClient.h" #include "urn.h" #include "whois.h" #if USE_SSL +#if 1 // USE_SSL_CERT_VALIDATOR +#include "ssl/cert_validate_message.h" +#include "ssl/Config.h" +#include "ssl/helper.h" +#endif #include "ssl/support.h" #include "ssl/ErrorDetail.h" #include "ssl/ServerBump.h" #endif #if HAVE_ERRNO_H #include #endif static PSC fwdPeerSelectionCompleteWrapper; static CLCB fwdServerClosedWrapper; #if USE_SSL static PF fwdNegotiateSSLWrapper; #endif static CNCB fwdConnectDoneWrapper; static OBJH fwdStats; #define MAX_FWD_STATS_IDX 9 static int FwdReplyCodes[MAX_FWD_STATS_IDX + 1][HTTP_INVALID_HEADER + 1]; @@ -722,43 +727,226 @@ } if (request->clientConnectionManager.valid()) { // remember the server certificate from the ErrorDetail object if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { serverBump->serverCert.reset(SSL_get_peer_certificate(ssl)); // remember validation errors, if any if (Ssl::Errors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) serverBump->sslErrors = cbdataReference(errs); } } if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) { if (serverConnection()->getPeer()->sslSession) SSL_SESSION_free(serverConnection()->getPeer()->sslSession); serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl); } +#if 1 // USE_SSL_CERT_VALIDATOR + if (Ssl::TheConfig.ssl_crt_validator) { + Ssl::CertValidationRequest validationRequest; + // WARNING: The STACK_OF(*) OpenSSL objects does not support locking. + // If we need to support locking we need to sk_X509_dup the STACK_OF(X509) + // list and lock all of the X509 members of the list. + // Currently we do not use any locking for any of the members of the + // Ssl::CertValidationRequest class. If the ssl object gone, the value returned + // from SSL_get_peer_cert_chain may not exist any more. In this code the + // Ssl::CertValidationRequest object used only to pass data to + // Ssl::CertValidationHelper::submit method. + validationRequest.peerCerts = SSL_get_peer_cert_chain(ssl); + validationRequest.domainName = request->GetHost(); + if (Ssl::Errors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) + // validationRequest disappears on return so no need to cbdataReference + validationRequest.errors = errs; + else + validationRequest.errors = NULL; + try { + debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd."); + Ssl::CertValidationMsg requestMsg(Ssl::CrtdMessage::REQUEST); + requestMsg.setCode(Ssl::CertValidationMsg::code_cert_validate); + requestMsg.composeRequest(validationRequest); + debugs(83, 5, "SSL crtvd request: " << requestMsg.compose().c_str()); + Ssl::CertValidationHelper::GetInstance()->sslSubmit(requestMsg, sslCrtvdHandleReplyWrapper, this); + return; + } catch (const std::exception &e) { + debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " << + "request for " << validationRequest.domainName << + " certificate: " << e.what() << "; will now block to " << + "validate that certificate."); + // fall through to do blocking in-process generation. + ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); + fail(anErr); + if (serverConnection()->getPeer()) { + peerConnectFailed(serverConnection()->getPeer()); + } + serverConn->close(); + self = NULL; + return; + } + } +#endif // USE_SSL_CERT_VALIDATOR + dispatch(); } +#if 1 // USE_SSL_CERT_VALIDATOR +void +FwdState::sslCrtvdHandleReplyWrapper(void *data, const HelperReply &reply) +{ + FwdState * fwd = (FwdState *)(data); + fwd->sslCrtvdHandleReply(reply); +} + +void +FwdState::sslCrtvdHandleReply(const HelperReply &reply) +{ + Ssl::Errors *errs = NULL; + Ssl::ErrorDetail *errDetails = NULL; + bool validatorFailed = false; + if (!Comm::IsConnOpen(serverConnection())) { + return; + } + SSL *ssl = fd_table[serverConnection()->fd].ssl; + + if (!reply.other().hasContent()) { + debugs(83, DBG_IMPORTANT, "\"ssl_crtvd\" helper return reply"); + validatorFailed = true; + } else if (reply.result == HelperReply::BrokenHelper) { + debugs(83, DBG_IMPORTANT, "\"ssl_crtvd\" helper error response: " << reply.other().content()); + validatorFailed = true; + } else { + Ssl::CertValidationMsg replyMsg(Ssl::CrtdMessage::REPLY); + Ssl::CertValidationResponse validationResponse; + std::string error; + STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(ssl); + if (replyMsg.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK || + !replyMsg.parseResponse(validationResponse, peerCerts, error) ) { + debugs(83, 5, "Reply from ssl_crtvd for " << request->GetHost() << " is incorrect"); + validatorFailed = true; + } else { + if (reply.result == HelperReply::Okay) { + debugs(83, 5, "Certificate for " << request->GetHost() << " was successfully validated from ssl_crtvd"); + } else if (reply.result == HelperReply::Error) { + debugs(83, 5, "Certificate for " << request->GetHost() << " found buggy by ssl_crtvd"); + errs = sslCrtvdCheckForErrors(validationResponse, errDetails); + } else { + debugs(83, 5, "Certificate for " << request->GetHost() << " cannot be validated. ssl_crtvd response: " << replyMsg.getBody()); + validatorFailed = true; + } + + if (!errDetails && !validatorFailed) { + dispatch(); + return; + } + } + } + + ErrorState *anErr = NULL; + if (validatorFailed) { + anErr = new ErrorState(ERR_GATEWAY_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); + } else { + + // Check the list error with + if (errDetails && request->clientConnectionManager.valid()) { + // remember the server certificate from the ErrorDetail object + if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { + // remember validation errors, if any + if (errs) { + if (serverBump->sslErrors) + cbdataReferenceDone(serverBump->sslErrors); + serverBump->sslErrors = cbdataReference(errs); + } + } + } + + anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL); + anErr->detail = errDetails; + /*anErr->xerrno= Should preserved*/ + } + + fail(anErr); + if (serverConnection()->getPeer()) { + peerConnectFailed(serverConnection()->getPeer()); + } + serverConn->close(); + self = NULL; + return; +} + +/// Checks errors in the cert. validator response against sslproxy_cert_error. +/// The first honored error, if any, is returned via errDetails parameter. +/// The method returns all seen errors except SSL_ERROR_NONE as Ssl::Errors. +Ssl::Errors * +FwdState::sslCrtvdCheckForErrors(Ssl::CertValidationResponse &resp, Ssl::ErrorDetail *& errDetails) +{ + Ssl::Errors *errs = NULL; + + ACLFilledChecklist *check = NULL; + if (acl_access *acl = Config.ssl_client.cert_error) + check = new ACLFilledChecklist(acl, request, dash_str); + + SSL *ssl = fd_table[serverConnection()->fd].ssl; + typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; + for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { + debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason); + + assert(i->error_no != SSL_ERROR_NONE); + + if (!errDetails) { + bool allowed = false; + if (check) { + check->sslErrors = new Ssl::Errors(i->error_no); + if (check->fastCheck() == ACCESS_ALLOWED) + allowed = true; + } + // else the Config.ssl_client.cert_error access list is not defined + // and the first error will cause the error page + + if (allowed) { + debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer"); + } else { + debugs(83, 5, "confirming SSL error " << i->error_no); + X509 *brokenCert = i->cert.get(); + Ssl::X509_Pointer peerCert(SSL_get_peer_certificate(ssl)); + const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str(); + errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason); + } + delete check->sslErrors; + check->sslErrors = NULL; + } + + if (!errs) + errs = new Ssl::Errors(i->error_no); + else + errs->push_back_unique(i->error_no); + } + if (check) + delete check; + + return errs; +} + +#endif // USE_SSL_CERT_VALIDATOR + void FwdState::initiateSSL() { SSL *ssl; SSL_CTX *sslContext = NULL; const CachePeer *peer = serverConnection()->getPeer(); int fd = serverConnection()->fd; if (peer) { assert(peer->use_ssl); sslContext = peer->sslContext; } else { sslContext = Config.ssl_client.sslContext; } assert(sslContext); if ((ssl = SSL_new(sslContext)) == NULL) { debugs(83, DBG_IMPORTANT, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL) ); ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); @@ -787,46 +975,54 @@ if (peer->sslSession) SSL_set_session(ssl, peer->sslSession); } else { // While we are peeking at the certificate, we may not know the server // name that the client will request (after interception or CONNECT) // unless it was the CONNECT request with a user-typed address. const char *hostname = request->GetHost(); const bool hostnameIsIp = request->GetHostIsNumeric(); const bool isConnectRequest = !request->clientConnectionManager->port->spoof_client_ip && !request->clientConnectionManager->port->intercepted; if (!request->flags.sslPeek || isConnectRequest) SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname); // Use SNI TLS extension only when we connect directly // to the origin server and we know the server host name. if (!hostnameIsIp) Ssl::setClientSNI(ssl, hostname); } - // Create the ACL check list now, while we have access to more info. - // The list is used in ssl_verify_cb() and is freed in ssl_free(). - if (acl_access *acl = Config.ssl_client.cert_error) { - ACLFilledChecklist *check = new ACLFilledChecklist(acl, request, dash_str); - SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check); +#if 1 // USE_SSL_CERT_VALIDATOR + // If CertValidation Helper used do not lookup checklist for errors, + // but keep a list of errors to send it to CertValidator + if (!Ssl::TheConfig.ssl_crt_validator) { +#endif + // Create the ACL check list now, while we have access to more info. + // The list is used in ssl_verify_cb() and is freed in ssl_free(). + if (acl_access *acl = Config.ssl_client.cert_error) { + ACLFilledChecklist *check = new ACLFilledChecklist(acl, request, dash_str); + SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check); + } +#if 1 // USE_SSL_CERT_VALIDATOR } +#endif // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE X509 *peeked_cert; if (request->clientConnectionManager.valid() && request->clientConnectionManager->serverBump() && (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) { CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509); SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert); } fd_table[fd].ssl = ssl; fd_table[fd].read_method = &ssl_read_method; fd_table[fd].write_method = &ssl_write_method; negotiateSSL(fd); } #endif void FwdState::connectDone(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno) === modified file 'src/forward.h' --- src/forward.h 2012-10-29 04:59:58 +0000 +++ src/forward.h 2012-11-23 08:58:20 +0000 @@ -1,51 +1,64 @@ #ifndef SQUID_FORWARD_H #define SQUID_FORWARD_H #include "Array.h" #include "base/RefCount.h" #include "comm.h" #include "comm/Connection.h" #include "err_type.h" #include "fde.h" #include "HttpStatusCode.h" #include "ip/Address.h" +#if USE_SSL //&& USE_SSL_CERT_VALIDATOR +#include "ssl/support.h" +#endif /* forward decls */ class AccessLogEntry; typedef RefCount AccessLogEntryPointer; class ErrorState; class HttpRequest; +#if USE_SSL //&& USE_SSL_CERT_VALIDATOR +namespace Ssl +{ +class ErrorDetail; +class CertValidationResponse; +}; +#endif + /** * Returns the TOS value that we should be setting on the connection * to the server, based on the ACL. */ tos_t GetTosToServer(HttpRequest * request); /** * Returns the Netfilter mark value that we should be setting on the * connection to the server, based on the ACL. */ nfmark_t GetNfmarkToServer(HttpRequest * request); +class HelperReply; + class FwdState : public RefCountable { public: typedef RefCount Pointer; ~FwdState(); static void initModule(); /// Initiates request forwarding to a peer or origin server. static void Start(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *, const AccessLogEntryPointer &alp); /// Same as Start() but no master xaction info (AccessLogEntry) available. static void fwdStart(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *); /// This is the real beginning of server connection. Call it whenever /// the forwarding server destination has changed and a new one needs to be opened. /// Produces the cannot-forward error on fail if no better error exists. void startConnectionOrFail(); void fail(ErrorState *err); void unregister(Comm::ConnectionPointer &conn); void unregister(int fd); @@ -54,40 +67,48 @@ int reforward(); bool reforwardableStatus(http_status s); void serverClosed(int fd); void connectStart(); void connectDone(const Comm::ConnectionPointer & conn, comm_err_t status, int xerrno); void connectTimeout(int fd); void initiateSSL(); void negotiateSSL(int fd); bool checkRetry(); bool checkRetriable(); void dispatch(); void pconnPush(Comm::ConnectionPointer & conn, const char *domain); bool dontRetry() { return flags.dont_retry; } void dontRetry(bool val) { flags.dont_retry = val; } /** return a ConnectionPointer to the current server connection (may or may not be open) */ Comm::ConnectionPointer const & serverConnection() const { return serverConn; }; +#if USE_SSL //&& USE_SSL_CERT_VALIDATOR + /// Callback function called when squid receive message from cert validator helper + static void sslCrtvdHandleReplyWrapper(void *data, const HelperReply &reply); + /// Process response from cert validator helper + void sslCrtvdHandleReply(const HelperReply &reply); + /// Check SSL errors returned from cert validator against sslproxy_cert_error access list + Ssl::Errors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse &, Ssl::ErrorDetail *&); +#endif private: // hidden for safer management of self; use static fwdStart FwdState(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *, const AccessLogEntryPointer &alp); void start(Pointer aSelf); #if STRICT_ORIGINAL_DST void selectPeerForIntercepted(); #endif static void logReplyStatus(int tries, http_status status); void doneWithRetries(); void completed(); void retryOrBail(); ErrorState *makeConnectingError(const err_type type) const; static void RegisterWithCacheManager(void); public: StoreEntry *entry; HttpRequest *request; AccessLogEntryPointer al; ///< info for the future access.log entry === modified file 'src/main.cc' --- src/main.cc 2012-10-08 08:40:01 +0000 +++ src/main.cc 2012-11-24 14:07:34 +0000 @@ -103,44 +103,44 @@ #if USE_ECAP #include "adaptation/ecap/Config.h" #endif #if ICAP_CLIENT #include "adaptation/icap/Config.h" #include "adaptation/icap/icap_log.h" #endif #if USE_AUTH #include "auth/Gadgets.h" #endif #if USE_DELAY_POOLS #include "ClientDelayConfig.h" #endif #if USE_DELAY_POOLS #include "DelayPools.h" #endif #if USE_LOADABLE_MODULES #include "LoadableModules.h" #endif #if USE_SSL_CRTD -#include "ssl/helper.h" #include "ssl/certificate_db.h" #endif #if USE_SSL +#include "ssl/helper.h" #include "ssl/context_storage.h" #endif #if ICAP_CLIENT #include "adaptation/icap/Config.h" #endif #if USE_ECAP #include "adaptation/ecap/Config.h" #endif #if USE_ADAPTATION #include "adaptation/Config.h" #endif #if USE_SQUID_ESI #include "esi/Module.h" #endif #if SQUID_SNMP #include "snmp_core.h" #endif #if HAVE_PATHS_H #include @@ -749,40 +749,44 @@ } } static void mainReconfigureStart(void) { debugs(1, DBG_IMPORTANT, "Reconfiguring Squid Cache (version " << version_string << ")..."); reconfiguring = 1; // Initiate asynchronous closing sequence serverConnectionsClose(); icpClosePorts(); #if USE_HTCP htcpClosePorts(); #endif dnsShutdown(); #if USE_SSL_CRTD Ssl::Helper::GetInstance()->Shutdown(); #endif #if USE_SSL +#if 1 // USE_SSL_CERT_VALIDATOR + if (Ssl::CertValidationHelper::GetInstance()) + Ssl::CertValidationHelper::GetInstance()->Shutdown(); +#endif Ssl::TheGlobalContextStorage.reconfigureStart(); #endif redirectShutdown(); #if USE_AUTH authenticateReset(); #endif externalAclShutdown(); storeDirCloseSwapLogs(); storeLogClose(); accessLogClose(); #if ICAP_CLIENT icapLogClose(); #endif eventAdd("mainReconfigureFinish", &mainReconfigureFinish, NULL, 0, 1, false); } static void mainReconfigureFinish(void *) @@ -831,40 +835,44 @@ bool enableAdaptation = false; #if ICAP_CLIENT Adaptation::Icap::TheConfig.finalize(); enableAdaptation = Adaptation::Icap::TheConfig.onoff || enableAdaptation; #endif #if USE_ECAP Adaptation::Ecap::TheConfig.finalize(); // must be after we load modules enableAdaptation = Adaptation::Ecap::TheConfig.onoff || enableAdaptation; #endif Adaptation::Config::Finalize(enableAdaptation); #endif #if ICAP_CLIENT icapLogOpen(); #endif storeLogOpen(); dnsInit(); #if USE_SSL_CRTD Ssl::Helper::GetInstance()->Init(); #endif +#if USE_SSL // && USE_SSL_CERT_VALIDATOR + if (Ssl::CertValidationHelper::GetInstance()) + Ssl::CertValidationHelper::GetInstance()->Init(); +#endif redirectInit(); #if USE_AUTH authenticateInit(&Auth::TheConfig); #endif externalAclInit(); if (IamPrimaryProcess()) { #if USE_WCCP wccpInit(); #endif #if USE_WCCPv2 wccp2Init(); #endif } serverConnectionsOpen(); @@ -1028,40 +1036,45 @@ WIN32_IpAddrChangeMonitorInit(); } #endif if (!configured_once) disk_init(); /* disk_init must go before ipcache_init() */ ipcache_init(); fqdncache_init(); parseEtcHosts(); dnsInit(); #if USE_SSL_CRTD Ssl::Helper::GetInstance()->Init(); #endif +#if USE_SSL // && USE_SSL_CERT_VALIDATOR + if (Ssl::CertValidationHelper::GetInstance()) + Ssl::CertValidationHelper::GetInstance()->Init(); +#endif + redirectInit(); #if USE_AUTH authenticateInit(&Auth::TheConfig); #endif externalAclInit(); httpHeaderInitModule(); /* must go before any header processing (e.g. the one in errorInitialize) */ httpReplyInitModule(); /* must go before accepting replies */ errorInitialize(); accessLogInit(); #if ICAP_CLIENT icapLogOpen(); #endif #if USE_IDENT Ident::Init(); @@ -1821,40 +1834,44 @@ } static void SquidShutdown() { /* XXX: This function is called after the main loop has quit, which * means that no AsyncCalls would be called, including close handlers. * TODO: We need to close/shut/free everything that needs calls before * exiting the loop. */ #if USE_WIN32_SERVICE WIN32_svcstatusupdate(SERVICE_STOP_PENDING, 10000); #endif debugs(1, DBG_IMPORTANT, "Shutting down..."); dnsShutdown(); #if USE_SSL_CRTD Ssl::Helper::GetInstance()->Shutdown(); #endif +#if USE_SSL //&& USE_SSL_CERT_VALIDATOR + if (Ssl::CertValidationHelper::GetInstance()) + Ssl::CertValidationHelper::GetInstance()->Shutdown(); +#endif redirectShutdown(); externalAclShutdown(); icpClosePorts(); #if USE_HTCP htcpClosePorts(); #endif #if SQUID_SNMP snmpClosePorts(); #endif #if USE_WCCP wccpConnectionClose(); #endif #if USE_WCCPv2 wccp2ConnectionClose(); #endif releaseServerSockets(); commCloseAllSockets(); === modified file 'src/ssl/Config.cc' --- src/ssl/Config.cc 2012-10-04 11:10:17 +0000 +++ src/ssl/Config.cc 2012-11-24 14:00:15 +0000 @@ -1,19 +1,20 @@ #include "squid.h" #include "ssl/Config.h" Ssl::Config Ssl::TheConfig; -Ssl::Config::Config() +Ssl::Config::Config(): #if USE_SSL_CRTD - : - ssl_crtd(NULL) + ssl_crtd(NULL), #endif + ssl_crt_validator(NULL) { } Ssl::Config::~Config() { #if USE_SSL_CRTD xfree(ssl_crtd); #endif + xfree(ssl_crt_validator); } === modified file 'src/ssl/Config.h' --- src/ssl/Config.h 2012-10-04 11:10:17 +0000 +++ src/ssl/Config.h 2012-11-23 09:18:32 +0000 @@ -1,27 +1,31 @@ #ifndef SQUID_SSL_CONFIG_H #define SQUID_SSL_CONFIG_H #include "HelperChildConfig.h" namespace Ssl { class Config { public: #if USE_SSL_CRTD char *ssl_crtd; ///< Name of external ssl_crtd application. /// The number of processes spawn for ssl_crtd. HelperChildConfig ssl_crtdChildren; #endif +#if 1 // USE_SSL_CERT_VALIDATOR + char *ssl_crt_validator; + HelperChildConfig ssl_crt_validator_Children; +#endif Config(); ~Config(); private: Config(const Config &); // not implemented Config &operator =(const Config &); // not implemented }; extern Config TheConfig; } // namespace Ssl #endif === modified file 'src/ssl/ErrorDetail.cc' --- src/ssl/ErrorDetail.cc 2012-08-14 11:53:07 +0000 +++ src/ssl/ErrorDetail.cc 2012-09-15 16:40:00 +0000 @@ -321,41 +321,43 @@ snprintf(tmpBuffer, 64, "%d", (int)error_no); err = tmpBuffer; } return err; } /** * A short description of the error_no */ const char *Ssl::ErrorDetail::err_descr() const { if (error_no == SSL_ERROR_NONE) return "[No Error]"; if (const char *err = detailEntry.descr.termedBuf()) return err; return "[Not available]"; } const char *Ssl::ErrorDetail::err_lib_error() const { - if (lib_error_no != SSL_ERROR_NONE) + if (errReason.defined()) + return errReason.termedBuf(); + else if (lib_error_no != SSL_ERROR_NONE) return ERR_error_string(lib_error_no, NULL); else return "[No Error]"; } /** * Converts the code to a string value. Supported formating codes are: * * Error meta information: * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*) * %ssl_error_descr: A short description of the SSL error * %ssl_lib_error: human-readable low-level error string by ERR_error_string(3SSL) * * Certificate information extracted from broken (not necessarily peer!) cert * %ssl_cn: The comma-separated list of common and alternate names * %ssl_subject: The certificate subject * %ssl_ca_name: The certificate issuer name * %ssl_notbefore: The certificate "not before" field * %ssl_notafter: The certificate "not after" field * @@ -397,41 +399,41 @@ assert(s); while ((p = strchr(s, '%'))) { errDetailStr.append(s, p - s); code_len = convert(++p, &t); if (code_len) errDetailStr.append(t); else errDetailStr.append("%"); s = p + code_len; } errDetailStr.append(s, strlen(s)); } const String &Ssl::ErrorDetail::toString() const { if (!errDetailStr.defined()) buildDetail(); return errDetailStr; } -Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert, X509 *broken): error_no (err_no), lib_error_no(SSL_ERROR_NONE) +Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert, X509 *broken, const char *aReason): error_no (err_no), lib_error_no(SSL_ERROR_NONE), errReason(aReason) { if (cert) peer_cert.resetAndLock(cert); if (broken) broken_cert.resetAndLock(broken); else broken_cert.resetAndLock(cert); detailEntry.error_no = SSL_ERROR_NONE; } Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail) { error_no = anErrDetail.error_no; request = anErrDetail.request; if (anErrDetail.peer_cert.get()) { peer_cert.resetAndLock(anErrDetail.peer_cert.get()); } === modified file 'src/ssl/ErrorDetail.h' --- src/ssl/ErrorDetail.h 2012-06-19 21:51:49 +0000 +++ src/ssl/ErrorDetail.h 2012-09-15 16:40:00 +0000 @@ -32,41 +32,41 @@ \ingroup ServerProtocolSSLAPI * The string representation of the SSL error "value" */ const char *GetErrorName(ssl_error_t value); /** \ingroup ServerProtocolSSLAPI * A short description of the SSL error "value" */ const char *GetErrorDescr(ssl_error_t value); /** \ingroup ServerProtocolSSLAPI * Used to pass SSL error details to the error pages returned to the * end user. */ class ErrorDetail { public: // if broken certificate is nil, the peer certificate is broken - ErrorDetail(ssl_error_t err_no, X509 *peer, X509 *broken); + ErrorDetail(ssl_error_t err_no, X509 *peer, X509 *broken, const char *aReason = NULL); ErrorDetail(ErrorDetail const &); const String &toString() const; ///< An error detail string to embed in squid error pages void useRequest(HttpRequest *aRequest) { if (aRequest != NULL) request = aRequest;} /// The error name to embed in squid error pages const char *errorName() const {return err_code();} /// The error no ssl_error_t errorNo() const {return error_no;} ///Sets the low-level error returned by OpenSSL ERR_get_error() void setLibError(unsigned long lib_err_no) {lib_error_no = lib_err_no;} /// the peer certificate X509 *peerCert() { return peer_cert.get(); } /// peer or intermediate certificate that failed validation X509 *brokenCert() {return broken_cert.get(); } private: typedef const char * (ErrorDetail::*fmt_action_t)() const; /** * Holds a formating code and its conversion method */ class err_frm_code { @@ -76,26 +76,27 @@ }; static err_frm_code ErrorFormatingCodes[]; ///< The supported formating codes const char *subject() const; const char *ca_name() const; const char *cn() const; const char *notbefore() const; const char *notafter() const; const char *err_code() const; const char *err_descr() const; const char *err_lib_error() const; int convert(const char *code, const char **value) const; void buildDetail() const; mutable String errDetailStr; ///< Caches the error detail message ssl_error_t error_no; ///< The error code unsigned long lib_error_no; ///< low-level error returned by OpenSSL ERR_get_error(3SSL) X509_Pointer peer_cert; ///< A pointer to the peer certificate X509_Pointer broken_cert; ///< A pointer to the broken certificate (peer or intermediate) + String errReason; ///< A custom reason for error, else retrieved from OpenSSL. mutable ErrorDetailEntry detailEntry; HttpRequest::Pointer request; }; }//namespace Ssl #endif === modified file 'src/ssl/Makefile.am' --- src/ssl/Makefile.am 2012-09-23 10:30:46 +0000 +++ src/ssl/Makefile.am 2012-11-23 09:27:07 +0000 @@ -1,53 +1,51 @@ include $(top_srcdir)/src/Common.am include $(top_srcdir)/src/TestHeaders.am noinst_LTLIBRARIES = libsslsquid.la libsslutil.la EXTRA_PROGRAMS = \ ssl_crtd EXTRA_DIST = \ ssl_crtd.8 if USE_SSL_CRTD SSL_CRTD = ssl_crtd -SSL_CRTD_SOURCE = \ - helper.cc \ - helper.h else SSL_CRTD = -SSL_CRTD_SOURCE = endif ## SSL stuff used by main Squid but not by ssl_crtd libsslsquid_la_SOURCES = \ + cert_validate_message.cc \ + cert_validate_message.h \ context_storage.cc \ context_storage.h \ Config.cc \ Config.h \ ErrorDetail.cc \ ErrorDetail.h \ ErrorDetailManager.cc \ ErrorDetailManager.h \ ProxyCerts.h \ ServerBump.cc \ ServerBump.h \ support.cc \ support.h \ - \ - $(SSL_CRTD_SOURCE) + helper.cc \ + helper.h ## SSL stuff used by main Squid and ssl_crtd libsslutil_la_SOURCES = \ gadgets.cc \ gadgets.h \ crtd_message.cc \ crtd_message.h libexec_PROGRAMS = \ $(SSL_CRTD) if USE_SSL_CRTD ssl_crtd_SOURCES = ssl_crtd.cc certificate_db.cc certificate_db.h ssl_crtd_LDADD = libsslutil.la $(SSLLIB) $(COMPAT_LIB) endif === added file 'src/ssl/cert_validate_message.cc' --- src/ssl/cert_validate_message.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/cert_validate_message.cc 2012-11-24 14:16:54 +0000 @@ -0,0 +1,209 @@ +#include "squid.h" +#include "acl/FilledChecklist.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; + if (vcert.errors) { + body += "\n" + Ssl::CertValidationMsg::param_error + "="; + bool comma = false; + for (const Ssl::Errors *err = vcert.errors; err; err = err->next ) { + if (comma) + body += ","; + body += GetErrorName(err->element); + comma = true; + } + } + + if (vcert.peerCerts) { + body +="\n"; + Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); + for (int i = 0; i < sk_X509_num(vcert.peerCerts); ++i) { + X509 *cert = sk_X509_value(vcert.peerCerts, i); + PEM_write_bio_X509(bio.get(), cert); + body = body + "cert_" + xitoa(i) + "="; + char *ptr; + long len = BIO_get_mem_data(bio.get(), &ptr); + body.append(ptr, len); + // Normally openssl toolkit terminates Certificate with a '\n'. + if (ptr[len-1] != '\n') + body +="\n"; + if (!BIO_reset(bio.get())) { + // print an error? + } + } + } +} + +static int +get_error_id(const char *label, size_t len) +{ + const char *e = label + len -1; + while (e != label && xisdigit(*e)) --e; + if (e != label) ++e; + return strtol(e, 0 , 10); +} + +bool +Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp, STACK_OF(X509) *peerCerts, std::string &error) +{ + std::vector certs; + + const char *param = body.c_str(); + while (*param) { + while (xisspace(*param)) param++; + if (! *param) + break; + + size_t param_len = strcspn(param, "=\r\n"); + if (param[param_len] != '=') { + debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: " << param); + return false; + } + const char *value=param+param_len+1; + + if (param_len > param_cert.length() && + strncmp(param, param_cert.c_str(), param_cert.length()) == 0) { + CertItem ci; + ci.name.assign(param, param_len); + X509_Pointer x509; + readCertFromMemory(x509, value); + ci.setCert(x509.get()); + certs.push_back(ci); + + const char *b = strstr(value, "-----END CERTIFICATE-----"); + if (b == NULL) { + debugs(83, DBG_IMPORTANT, "WARNING: cert Validator response parse error: Failed to find certificate boundary " << value); + return false; + } + b += strlen("-----END CERTIFICATE-----"); + param = b + 1; + continue; + } + + size_t value_len = strcspn(value, "\r\n"); + std::string v(value, value_len); + + debugs(83, 5, "Returned value: " << std::string(param, param_len).c_str() << ": " << + v.c_str()); + + int errorId = get_error_id(param, param_len); + Ssl::CertValidationResponse::RecvdError ¤tItem = resp.getError(errorId); + + if (param_len > param_error_name.length() && + strncmp(param, param_error_name.c_str(), param_error_name.length()) == 0) { + currentItem.error_no = Ssl::GetErrorCode(v.c_str()); + if (currentItem.error_no == SSL_ERROR_NONE) { + debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: Unknown SSL Error: " << v); + return false; + } + } else if (param_len > param_error_reason.length() && + strncmp(param, param_error_reason.c_str(), param_error_reason.length()) == 0) { + currentItem.error_reason = v; + } else if (param_len > param_error_cert.length() && + strncmp(param, param_error_cert.c_str(), param_error_cert.length()) == 0) { + + if (X509 *cert = getCertByName(certs, v)) { + debugs(83, 6, "The certificate with id \"" << v << "\" found."); + currentItem.setCert(cert); + } else { + //In this case we assume that the certID is one of the certificates sent + // to cert validator. The certificates sent to cert validator have names in + // form "cert_xx" where the "xx" is an integer represents the position of + // the certificate inside peer certificates list. + const int certId = get_error_id(v.c_str(), v.length()); + debugs(83, 6, "Cert index in peer certificates list:" << certId); + //if certId is not correct sk_X509_value returns NULL + currentItem.setCert(sk_X509_value(peerCerts, certId)); + } + } else { + debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: Unknown parameter name " << std::string(param, param_len).c_str()); + return false; + } + + param = value + value_len +1; + } + + /*Run through parsed errors to check for errors*/ + + return true; +} + +X509 * +Ssl::CertValidationMsg::getCertByName(std::vector const &certs, std::string const & name) +{ + typedef std::vector::const_iterator SVCI; + for (SVCI ci = certs.begin(); ci != certs.end(); ++ci) { + if (ci->name.compare(name) == 0) + return ci->cert.get(); + } + return NULL; +} + +Ssl::CertValidationResponse::RecvdError & +Ssl::CertValidationResponse::getError(int errorId) +{ + typedef Ssl::CertValidationResponse::RecvdErrors::iterator SVCREI; + for (SVCREI i = errors.begin(); i != errors.end(); ++i) { + if (i->id == errorId) + return *i; + } + Ssl::CertValidationResponse::RecvdError errItem; + errItem.id = errorId; + errors.push_back(errItem); + return errors.back(); +} + +Ssl::CertValidationResponse::RecvdError::RecvdError(const RecvdError &old) +{ + error_no = old.error_no; + error_reason = old.error_reason; + setCert(old.cert.get()); +} + +Ssl::CertValidationResponse::RecvdError & Ssl::CertValidationResponse::RecvdError::operator = (const RecvdError &old) +{ + error_no = old.error_no; + error_reason = old.error_reason; + setCert(old.cert.get()); + return *this; +} + +void +Ssl::CertValidationResponse::RecvdError::setCert(X509 *aCert) +{ + cert.resetAndLock(aCert); +} + +Ssl::CertValidationMsg::CertItem::CertItem(const CertItem &old) +{ + name = old.name; + setCert(old.cert.get()); +} + +Ssl::CertValidationMsg::CertItem & Ssl::CertValidationMsg::CertItem::operator = (const CertItem &old) +{ + name = old.name; + setCert(old.cert.get()); + return *this; +} + +void +Ssl::CertValidationMsg::CertItem::setCert(X509 *aCert) +{ + cert.resetAndLock(aCert); +} + +const std::string Ssl::CertValidationMsg::code_cert_validate("cert_validate"); +const std::string Ssl::CertValidationMsg::param_domain("domain"); +const std::string Ssl::CertValidationMsg::param_error("errors"); +const std::string Ssl::CertValidationMsg::param_cert("cert_"); +const std::string Ssl::CertValidationMsg::param_error_name("error_name_"); +const std::string Ssl::CertValidationMsg::param_error_reason("error_reason_"); +const std::string Ssl::CertValidationMsg::param_error_cert("error_cert_"); + === added file 'src/ssl/cert_validate_message.h' --- src/ssl/cert_validate_message.h 1970-01-01 00:00:00 +0000 +++ src/ssl/cert_validate_message.h 2012-11-24 14:17:07 +0000 @@ -0,0 +1,113 @@ +/* + */ + +#ifndef SQUID_SSL_CERT_VALIDATE_MESSAGE_H +#define SQUID_SSL_CERT_VALIDATE_MESSAGE_H + +#include "ssl/support.h" +#include "ssl/crtd_message.h" +#include + +namespace Ssl +{ + +/** + * This class is used to hold the required informations to build + * a request message for the certificate validator helper + */ +class CertValidationRequest +{ +public: + STACK_OF(X509) *peerCerts; ///< The list of sent by SSL server + Errors *errors; ///< The list of errors detected + std::string domainName; ///< The server name + CertValidationRequest() : peerCerts(NULL), errors(NULL) {} +}; + +/** + * This class is used to store informations found in certificate validation + * response messages read from certificate validator helper + */ +class CertValidationResponse +{ +public: + /** + * This class used to hold error informations returned from + * cert validator helper. + */ + class RecvdError + { + public: + RecvdError(): id(0), error_no(SSL_ERROR_NONE), cert(NULL) {} + RecvdError(const RecvdError &); + RecvdError & operator = (const RecvdError &); + void setCert(X509 *); ///< Sets cert to the given certificate + int id; ///< The id of the error + ssl_error_t error_no; ///< The OpenSSL error code + std::string error_reason; ///< A string describing the error + X509_Pointer cert; ///< The broken certificate + }; + + typedef std::vector RecvdErrors; + + /// Search in errors list for the error item with id=errorId. + /// If none found a new RecvdError item added with the given id; + RecvdError &getError(int errorId); + RecvdErrors errors; ///< The list of parsed errors +}; + +/** + * This class is responsible for composing or parsing messages destined to + * or comming from a cert validator helper. + * The messages format is: + * ...\1 + */ +class CertValidationMsg: public CrtdMessage +{ +private: + /** + * This class used to hold the certId/cert pairs found + * in cert validation messages. + */ + class CertItem + { + public: + std::string name; ///< The certificate Id to use + X509_Pointer cert; ///< A pointer to certificate + CertItem(): cert(NULL) {} + CertItem(const CertItem &); + CertItem & operator = (const CertItem &); + void setCert(X509 *); ///< Sets cert to the given certificate + }; + +public: + CertValidationMsg(MessageKind kind): CrtdMessage(kind) {} + + /// Build a request message for the cert validation helper + /// using informations provided by vcert object + void composeRequest(CertValidationRequest const &vcert); + + /// Parse a response message and fill the resp object with parsed informations + bool parseResponse(CertValidationResponse &resp, STACK_OF(X509) *peerCerts, std::string &error); + + /// Search a CertItems list for the certificate with ID "name" + X509 *getCertByName(std::vector const &, std::string const & name); + + /// String code for "cert_validate" messages + static const std::string code_cert_validate; + /// Parameter name for passing intended domain name + static const std::string param_domain; + /// Parameter name for passing SSL errors + static const std::string param_error; + /// Parameter name for passing SSL certificates + static const std::string param_cert; + /// Parameter name for passing the major SSL error + static const std::string param_error_name; + /// Parameter name for passing the error reason + static const std::string param_error_reason; + /// Parameter name for passing the error cert ID + static const std::string param_error_cert; +}; + +}//namespace Ssl +#endif // SQUID_SSL_CERT_VALIDATE_MESSAGE_H === modified file 'src/ssl/crtd_message.h' --- src/ssl/crtd_message.h 2012-11-07 19:26:45 +0000 +++ src/ssl/crtd_message.h 2012-11-13 17:20:06 +0000 @@ -63,39 +63,39 @@ The other multistring part of body. \endverbatim */ void composeBody(BodyParams const & map, std::string const & other_part); /// orchestrates entire request parsing bool parseRequest(Ssl::CertificateProperties &, std::string &error); void composeRequest(Ssl::CertificateProperties const &); // throws /// String code for "new_certificate" messages static const std::string code_new_certificate; /// Parameter name for passing hostname static const std::string param_host; /// Parameter name for passing SetValidAfter cert adaptation variable static const std::string param_SetValidAfter; /// Parameter name for passing SetValidBefore cert adaptation variable static const std::string param_SetValidBefore; /// Parameter name for passing SetCommonName cert adaptation variable static const std::string param_SetCommonName; /// Parameter name for passing signing algorithm static const std::string param_Sign; -private: +protected: enum ParseState { BEFORE_CODE, CODE, BEFORE_LENGTH, LENGTH, BEFORE_BODY, BODY, END }; size_t body_size; ///< The body size if exist or 0. ParseState state; ///< Parsing state. std::string body; ///< Current body. std::string code; ///< Current response/request code. std::string current_block; ///< Current block buffer. }; } //namespace Ssl #endif // SQUID_SSL_CRTD_MESSAGE_H === modified file 'src/ssl/helper.cc' --- src/ssl/helper.cc 2012-10-30 09:16:33 +0000 +++ src/ssl/helper.cc 2012-11-23 09:59:28 +0000 @@ -1,30 +1,31 @@ #include "squid.h" #include "anyp/PortCfg.h" #include "ssl/Config.h" #include "ssl/helper.h" #include "SquidString.h" #include "SquidTime.h" #include "SwapDir.h" #include "wordlist.h" #include "SquidConfig.h" +#if USE_SSL_CRTD Ssl::Helper * Ssl::Helper::GetInstance() { static Ssl::Helper sslHelper; return &sslHelper; } Ssl::Helper::Helper() { } Ssl::Helper::~Helper() { Shutdown(); } void Ssl::Helper::Init() { assert(ssl_crtd == NULL); // we need to start ssl_crtd only if some port(s) need to bump SSL @@ -87,20 +88,107 @@ static time_t first_warn = 0; assert(ssl_crtd); if (ssl_crtd->stats.queue_size >= (int)(ssl_crtd->childs.n_running * 2)) { if (first_warn == 0) first_warn = squid_curtime; if (squid_curtime - first_warn > 3 * 60) fatal("SSL servers not responding for 3 minutes"); debugs(34, DBG_IMPORTANT, HERE << "Queue overload, rejecting"); const char *errMsg = "BH error 45 Temporary network problem, please retry later"; // XXX: upgrade to message="" HelperReply failReply(errMsg,strlen(errMsg)); callback(data, failReply); return; } first_warn = 0; std::string msg = message.compose(); msg += '\n'; helperSubmit(ssl_crtd, msg.c_str(), callback, data); } +#endif //USE_SSL_CRTD + +#if 1 // USE_SSL_CERT_VALIDATOR +/*ssl_crtd_validator*/ + +Ssl::CertValidationHelper * Ssl::CertValidationHelper::GetInstance() +{ + static Ssl::CertValidationHelper sslHelper; + if (!Ssl::TheConfig.ssl_crt_validator) + return NULL; + return &sslHelper; +} + +Ssl::CertValidationHelper::CertValidationHelper() : ssl_crt_validator(NULL) +{ +} + +Ssl::CertValidationHelper::~CertValidationHelper() +{ + Shutdown(); +} + +void Ssl::CertValidationHelper::Init() +{ + assert(ssl_crt_validator == NULL); + + // we need to start ssl_crtd only if some port(s) need to bump SSL + bool found = false; + for (AnyP::PortCfg *s = ::Config.Sockaddr.http; !found && s; s = s->next) + found = s->sslBump; + for (AnyP::PortCfg *s = ::Config.Sockaddr.https; !found && s; s = s->next) + found = s->sslBump; + if (!found) + return; + + ssl_crt_validator = new helper("ssl_crt_validator"); + ssl_crt_validator->childs.updateLimits(Ssl::TheConfig.ssl_crt_validator_Children); + ssl_crt_validator->ipc_type = IPC_STREAM; + // The crtd messages may contain the eol ('\n') character. We are + // going to use the '\1' char as the end-of-message mark. + ssl_crt_validator->eom = '\1'; + assert(ssl_crt_validator->cmdline == NULL); + { + char *tmp = xstrdup(Ssl::TheConfig.ssl_crt_validator); + char *tmp_begin = tmp; + char * token = NULL; + while ((token = strwordtok(NULL, &tmp))) { + wordlistAdd(&ssl_crt_validator->cmdline, token); + } + xfree(tmp_begin); + } + helperOpenServers(ssl_crt_validator); +} + +void Ssl::CertValidationHelper::Shutdown() +{ + if (!ssl_crt_validator) + return; + helperShutdown(ssl_crt_validator); + wordlistDestroy(&ssl_crt_validator->cmdline); + delete ssl_crt_validator; + ssl_crt_validator = NULL; +} + +void Ssl::CertValidationHelper::sslSubmit(CrtdMessage const & message, HLPCB * callback, void * data) +{ + static time_t first_warn = 0; + assert(ssl_crt_validator); + + if (ssl_crt_validator->stats.queue_size >= (int)(ssl_crt_validator->childs.n_running * 2)) { + if (first_warn == 0) + first_warn = squid_curtime; + if (squid_curtime - first_warn > 3 * 60) + fatal("ssl_crtvd queue being overloaded for long time"); + debugs(83, DBG_IMPORTANT, "WARNING: ssl_crtvd queue overload, rejecting"); + const char *errMsg = "BH error 45 Temporary network problem, please retry later"; + HelperReply failReply(errMsg,strlen(errMsg)); + callback(data, failReply); + return; + } + + first_warn = 0; + std::string msg = message.compose(); + msg += '\n'; + helperSubmit(ssl_crt_validator, msg.c_str(), callback, data); +} +#endif // USE_SSL_CERT_VALIDATOR === modified file 'src/ssl/helper.h' --- src/ssl/helper.h 2012-10-04 11:10:17 +0000 +++ src/ssl/helper.h 2012-11-23 09:24:58 +0000 @@ -1,30 +1,49 @@ #ifndef SQUID_SSL_HELPER_H #define SQUID_SSL_HELPER_H #include "../helper.h" #include "ssl/crtd_message.h" namespace Ssl { /** * Set of thread for ssl_crtd. This class is singleton. Use this class only * over GetIntance() static method. This class use helper structure * for threads management. */ +#if USE_SSL_CRTD class Helper { public: static Helper * GetInstance(); ///< Instance class. void Init(); ///< Init helper structure. void Shutdown(); ///< Shutdown helper structure. /// Submit crtd message to external crtd server. void sslSubmit(CrtdMessage const & message, HLPCB * callback, void *data); private: Helper(); ~Helper(); helper * ssl_crtd; ///< helper for management of ssl_crtd. }; +#endif + +#if 1 // USE_SSL_CERT_VALIDATOR +class CertValidationHelper +{ +public: + static CertValidationHelper * GetInstance(); ///< Instance class. + void Init(); ///< Init helper structure. + void Shutdown(); ///< Shutdown helper structure. + /// Submit crtd message to external crtd server. + void sslSubmit(CrtdMessage const & message, HLPCB * callback, void *data); +private: + CertValidationHelper(); + ~CertValidationHelper(); + + helper * ssl_crt_validator; ///< helper for management of ssl_crtd. +}; +#endif // USE_SSL_CERT_VALIDATOR } //namespace Ssl #endif // SQUID_SSL_HELPER_H === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2012-09-09 19:41:47 +0000 +++ src/ssl/support.cc 2012-11-22 16:45:29 +0000 @@ -26,40 +26,41 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" /* MS Visual Studio Projects are monolithic, so we need the following * #if to exclude the SSL code from compile process when not needed. */ #if USE_SSL #include "acl/FilledChecklist.h" #include "anyp/PortCfg.h" #include "fde.h" #include "globals.h" #include "SquidConfig.h" +#include "ssl/Config.h" #include "ssl/ErrorDetail.h" #include "ssl/support.h" #include "ssl/gadgets.h" #include "URL.h" #if HAVE_ERRNO_H #include #endif const char *Ssl::BumpModeStr[] = { "none", "client-first", "server-first", NULL }; /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals \ingroup ServerProtocolSSLAPI */ @@ -270,40 +271,46 @@ errs->push_back_unique(error_no); if (const char *err_descr = Ssl::GetErrorDescr(error_no)) debugs(83, 5, err_descr << ": " << buffer); 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::Errors(error_no); 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; } +#if 1 // USE_SSL_CERT_VALIDATOR + // 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) + ok = 1; +#endif } 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; === modified file 'src/tests/stub_libsslsquid.cc' --- src/tests/stub_libsslsquid.cc 2012-09-06 13:12:26 +0000 +++ src/tests/stub_libsslsquid.cc 2012-09-20 15:00:32 +0000 @@ -20,41 +20,41 @@ void Ssl::CertificateStorageAction::dump(StoreEntry *sentry) STUB Ssl::LocalContextStorage::Item::Item(SSL_CTX * aSsl_ctx, std::string const & aName) STUB Ssl::LocalContextStorage::Item::~Item() STUB Ssl::LocalContextStorage::LocalContextStorage(size_t aMax_memory) STUB Ssl::LocalContextStorage::~LocalContextStorage() STUB void Ssl::LocalContextStorage::SetSize(size_t aMax_memory) STUB SSL_CTX * Ssl::LocalContextStorage::add(char const * host_name, SSL_CTX * ssl_ctx) STUB_RETVAL(NULL) SSL_CTX * Ssl::LocalContextStorage::find(char const * host_name) STUB_RETVAL(NULL) void Ssl::LocalContextStorage::remove(char const * host_name) STUB //Ssl::GlobalContextStorage::GlobalContextStorage() STUB //Ssl::GlobalContextStorage::~GlobalContextStorage() STUB void Ssl::GlobalContextStorage::addLocalStorage(Ip::Address const & address, size_t size_of_store) STUB Ssl::LocalContextStorage & Ssl::GlobalContextStorage::getLocalStorage(Ip::Address const & address) { fatal(STUB_API " required"); static Ssl::LocalContextStorage v(0); return v; } void Ssl::GlobalContextStorage::reconfigureStart() STUB //Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage; #include "ssl/ErrorDetail.h" Ssl::ssl_error_t parseErrorString(const char *name) STUB_RETVAL(0) //const char *Ssl::getErrorName(ssl_error_t value) STUB_RETVAL(NULL) -Ssl::ErrorDetail::ErrorDetail(ssl_error_t err_no, X509 *, X509 *) STUB +Ssl::ErrorDetail::ErrorDetail(ssl_error_t err_no, X509 *, X509 *, const char *) STUB Ssl::ErrorDetail::ErrorDetail(ErrorDetail const &) STUB const String & Ssl::ErrorDetail::toString() const STUB_RETSTATREF(String) #include "ssl/support.h" SSL_CTX *sslCreateServerContext(AnyP::PortCfg &) STUB_RETVAL(NULL) SSL_CTX *sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile) STUB_RETVAL(NULL) int ssl_read_method(int, char *, int) STUB_RETVAL(0) int ssl_write_method(int, const char *, int) STUB_RETVAL(0) void ssl_shutdown_method(SSL *) STUB const char *sslGetUserEmail(SSL *ssl) STUB_RETVAL(NULL) // typedef char const *SSLGETATTRIBUTE(SSL *, const char *); // SSLGETATTRIBUTE sslGetUserAttribute; // SSLGETATTRIBUTE sslGetCAAttribute; const char *sslGetUserCertificatePEM(SSL *ssl) STUB_RETVAL(NULL) const char *sslGetUserCertificateChainPEM(SSL *ssl) STUB_RETVAL(NULL) SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &) STUB_RETVAL(NULL) SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &) STUB_RETVAL(NULL) int Ssl::matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)) STUB_RETVAL(0) int Ssl::asn1timeToString(ASN1_TIME *tm, char *buf, int len) STUB_RETVAL(0)