Tying validation errors to certificates When Squid sends errors to the certificate validation daemon, the daemon cannot tell which certificate caused which error. This is especially bad because the validator has to return that same information in the response (the response format requires the validator to match the error to the certificate). This patch adjust the validation request format to provide that information using a set of the following key=value pairs: error_name_N=the name of the certificate error number N error_cert_N=the ID of the certificate which caused error_name_N where N is non-negative integer. N values start from zero and increase sequentially. This is a Measurement Factory project === modified file 'helpers/ssl/cert_valid.pl' --- helpers/ssl/cert_valid.pl 2012-12-05 01:13:21 +0000 +++ helpers/ssl/cert_valid.pl 2013-05-29 08:14:55 +0000 @@ -63,76 +63,76 @@ 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 %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); + 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); + foreach my $err (keys %errors) { + print(STDERR logPrefix().$errors{$err}{"name"}."/".$errors{$err}{"cert"}." ,") 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) { + foreach my $err (keys %errors) { $haserror = 1; appendError (\@responseErrors, - $err, #The error name + $errors{$err}{"name"}, #The error name "Checked by Cert Validator", # An error reason - $peerCertId # The cert ID. We are always filling with the peer certificate. + $errors{$err}{"cert"} # 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+//; @@ -158,47 +158,53 @@ $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//; + $request =~ s/^host=.*$//m; } - 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+)=/) { + if ($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); } + elsif ($request =~ /^error_name_(\d+)=(.*)$/m) { + my $errorId = $1; + my $errorName = $2; + $request =~ s/^error_name_\d+=.*$//m; + $errors->{$errorId}{"name"} = $errorName; + } + elsif ($request =~ /^error_cert_(\d+)=(.*)$/m) { + my $errorId = $1; + my $certId = $2; + $request =~ s/^error_cert_\d+=.*$//m; + $errors->{$errorId}{"cert"} = $certId; + } 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/AclRegs.cc' --- src/AclRegs.cc 2013-01-27 17:35:07 +0000 +++ src/AclRegs.cc 2013-05-27 14:50:21 +0000 @@ -129,41 +129,41 @@ ACL::Prototype ACLSourceDomain::LiteralRegistryProtoype(&ACLSourceDomain::LiteralRegistryEntry_, "srcdomain"); ACLStrategised ACLSourceDomain::LiteralRegistryEntry_(new ACLDomainData, ACLSourceDomainStrategy::Instance(), "srcdomain"); ACL::Prototype ACLSourceDomain::RegexRegistryProtoype(&ACLSourceDomain::RegexRegistryEntry_, "srcdom_regex"); ACLStrategised ACLSourceDomain::RegexRegistryEntry_(new ACLRegexData,ACLSourceDomainStrategy::Instance() ,"srcdom_regex"); ACL::Prototype ACLSourceIP::RegistryProtoype(&ACLSourceIP::RegistryEntry_, "src"); ACLSourceIP ACLSourceIP::RegistryEntry_; ACL::Prototype ACLTime::RegistryProtoype(&ACLTime::RegistryEntry_, "time"); ACLStrategised ACLTime::RegistryEntry_(new ACLTimeData, ACLTimeStrategy::Instance(), "time"); ACL::Prototype ACLUrl::RegistryProtoype(&ACLUrl::RegistryEntry_, "url_regex"); ACLStrategised ACLUrl::RegistryEntry_(new ACLRegexData, ACLUrlStrategy::Instance(), "url_regex"); ACL::Prototype ACLUrlLogin::RegistryProtoype(&ACLUrlLogin::RegistryEntry_, "urllogin"); ACLStrategised ACLUrlLogin::RegistryEntry_(new ACLRegexData, ACLUrlLoginStrategy::Instance(), "urllogin"); ACL::Prototype ACLUrlPath::LegacyRegistryProtoype(&ACLUrlPath::RegistryEntry_, "pattern"); ACL::Prototype ACLUrlPath::RegistryProtoype(&ACLUrlPath::RegistryEntry_, "urlpath_regex"); ACLStrategised ACLUrlPath::RegistryEntry_(new ACLRegexData, ACLUrlPathStrategy::Instance(), "urlpath_regex"); ACL::Prototype ACLUrlPort::RegistryProtoype(&ACLUrlPort::RegistryEntry_, "port"); ACLStrategised ACLUrlPort::RegistryEntry_(new ACLIntRange, ACLUrlPortStrategy::Instance(), "port"); #if USE_SSL ACL::Prototype ACLSslError::RegistryProtoype(&ACLSslError::RegistryEntry_, "ssl_error"); -ACLStrategised ACLSslError::RegistryEntry_(new ACLSslErrorData, ACLSslErrorStrategy::Instance(), "ssl_error"); +ACLStrategised ACLSslError::RegistryEntry_(new ACLSslErrorData, ACLSslErrorStrategy::Instance(), "ssl_error"); ACL::Prototype ACLCertificate::UserRegistryProtoype(&ACLCertificate::UserRegistryEntry_, "user_cert"); ACLStrategised ACLCertificate::UserRegistryEntry_(new ACLCertificateData (Ssl::GetX509UserAttribute, "*"), ACLCertificateStrategy::Instance(), "user_cert"); ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEntry_, "ca_cert"); ACLStrategised ACLCertificate::CARegistryEntry_(new ACLCertificateData (Ssl::GetX509CAAttribute, "*"), ACLCertificateStrategy::Instance(), "ca_cert"); ACL::Prototype ACLServerCertificate::X509FingerprintRegistryProtoype(&ACLServerCertificate::X509FingerprintRegistryEntry_, "server_cert_fingerprint"); ACLStrategised ACLServerCertificate::X509FingerprintRegistryEntry_(new ACLCertificateData(Ssl::GetX509Fingerprint, "-sha1", true), ACLServerCertificateStrategy::Instance(), "server_cert_fingerprint"); #endif #if USE_SQUID_EUI ACL::Prototype ACLARP::RegistryProtoype(&ACLARP::RegistryEntry_, "arp"); ACLARP ACLARP::RegistryEntry_("arp"); ACL::Prototype ACLEui64::RegistryProtoype(&ACLEui64::RegistryEntry_, "eui64"); ACLEui64 ACLEui64::RegistryEntry_("eui64"); #endif #if USE_IDENT ACL::Prototype ACLIdent::UserRegistryProtoype(&ACLIdent::UserRegistryEntry_, "ident"); ACLIdent ACLIdent::UserRegistryEntry_(new ACLUserData, "ident"); ACL::Prototype ACLIdent::RegexRegistryProtoype(&ACLIdent::RegexRegistryEntry_, "ident_regex" ); ACLIdent ACLIdent::RegexRegistryEntry_(new ACLRegexData, "ident_regex"); === modified file 'src/acl/FilledChecklist.h' --- src/acl/FilledChecklist.h 2013-05-17 11:07:15 +0000 +++ src/acl/FilledChecklist.h 2013-05-27 14:43:45 +0000 @@ -52,41 +52,41 @@ public: Ip::Address src_addr; Ip::Address dst_addr; Ip::Address my_addr; CachePeer *dst_peer; char *dst_rdns; HttpRequest *request; HttpReply *reply; char rfc931[USER_IDENT_SZ]; #if USE_AUTH Auth::UserRequest::Pointer auth_user_request; #endif #if SQUID_SNMP char *snmp_community; #endif #if USE_SSL /// SSL [certificate validation] errors, in undefined order - Ssl::Errors *sslErrors; + Ssl::CertErrors *sslErrors; /// The peer certificate Ssl::X509_Pointer serverCert; #endif ExternalACLEntry *extacl_entry; private: ConnStateData * conn_; /**< hack for ident and NTLM */ int fd_; /**< may be available when conn_ is not */ bool destinationDomainChecked_; bool sourceDomainChecked_; /// not implemented; will cause link failures if used ACLFilledChecklist(const ACLFilledChecklist &); /// not implemented; will cause link failures if used ACLFilledChecklist &operator=(const ACLFilledChecklist &); CBDATA_CLASS2(ACLFilledChecklist); }; /// convenience and safety wrapper for dynamic_cast === modified file 'src/acl/SslError.h' --- src/acl/SslError.h 2013-01-27 17:35:07 +0000 +++ src/acl/SslError.h 2013-05-27 14:49:33 +0000 @@ -1,33 +1,33 @@ #ifndef SQUID_ACLSSL_ERROR_H #define SQUID_ACLSSL_ERROR_H #include "acl/Strategy.h" #include "acl/Strategised.h" #include "ssl/support.h" -class ACLSslErrorStrategy : public ACLStrategy +class ACLSslErrorStrategy : public ACLStrategy { public: virtual int match (ACLData * &, ACLFilledChecklist *, ACLFlags &); static ACLSslErrorStrategy *Instance(); /* Not implemented to prevent copies of the instance. */ /* Not private to prevent brain dead g+++ warnings about * private constructors with no friends */ ACLSslErrorStrategy(ACLSslErrorStrategy const &); private: static ACLSslErrorStrategy Instance_; ACLSslErrorStrategy() {} ACLSslErrorStrategy&operator=(ACLSslErrorStrategy const &); }; class ACLSslError { private: static ACL::Prototype RegistryProtoype; - static ACLStrategised RegistryEntry_; + static ACLStrategised RegistryEntry_; }; #endif /* SQUID_ACLSSL_ERROR_H */ === modified file 'src/acl/SslErrorData.cc' --- src/acl/SslErrorData.cc 2012-08-31 16:57:39 +0000 +++ src/acl/SslErrorData.cc 2013-05-27 14:45:37 +0000 @@ -35,44 +35,44 @@ #include "acl/SslErrorData.h" #include "acl/Checklist.h" #include "cache_cf.h" #include "wordlist.h" ACLSslErrorData::ACLSslErrorData() : values (NULL) {} ACLSslErrorData::ACLSslErrorData(ACLSslErrorData const &old) : values (NULL) { assert (!old.values); } ACLSslErrorData::~ACLSslErrorData() { if (values) delete values; } bool -ACLSslErrorData::match(const Ssl::Errors *toFind) +ACLSslErrorData::match(const Ssl::CertErrors *toFind) { - for (const Ssl::Errors *err = toFind; err; err = err->next ) { - if (values->findAndTune(err->element)) + for (const Ssl::CertErrors *err = toFind; err; err = err->next ) { + if (values->findAndTune(err->element.code)) return true; } return false; } /* explicit instantiation required for some systems */ /** \cond AUTODOCS-IGNORE */ // AYJ: 2009-05-20 : Removing. clashes with template instantiation for other ACLs. // template cbdata_type Ssl::Errors::CBDATA_CbDataList; /** \endcond */ wordlist * ACLSslErrorData::dump() { wordlist *W = NULL; Ssl::Errors *data = values; while (data != NULL) { wordlistAdd(&W, Ssl::GetErrorName(data->element)); data = data->next; === modified file 'src/acl/SslErrorData.h' --- src/acl/SslErrorData.h 2012-10-04 11:10:17 +0000 +++ src/acl/SslErrorData.h 2013-05-27 14:48:58 +0000 @@ -1,31 +1,31 @@ #ifndef SQUID_ACLSSL_ERRORDATA_H #define SQUID_ACLSSL_ERRORDATA_H #include "acl/Acl.h" #include "acl/Data.h" #include "CbDataList.h" #include "ssl/support.h" #include "ssl/ErrorDetail.h" #include -class ACLSslErrorData : public ACLData +class ACLSslErrorData : public ACLData { public: MEMPROXY_CLASS(ACLSslErrorData); ACLSslErrorData(); ACLSslErrorData(ACLSslErrorData const &); ACLSslErrorData &operator= (ACLSslErrorData const &); virtual ~ACLSslErrorData(); - bool match(const Ssl::Errors *); + bool match(const Ssl::CertErrors *); wordlist *dump(); void parse(); bool empty() const; virtual ACLSslErrorData *clone() const; Ssl::Errors *values; }; MEMPROXY_CLASS_INLINE(ACLSslErrorData); #endif /* SQUID_ACLSSL_ERRORDATA_H */ === modified file 'src/client_side.cc' --- src/client_side.cc 2013-05-23 08:18:09 +0000 +++ src/client_side.cc 2013-05-29 08:18:36 +0000 @@ -2589,72 +2589,72 @@ clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert(repContext); debugs(33, 5, "Responding with delated error for " << http->uri); repContext->setReplyToStoreEntry(sslServerBump->entry); // save the original request for logging purposes if (!context->http->al->request) { context->http->al->request = http->request; HTTPMSGLOCK(context->http->al->request); } // Get error details from the fake certificate-peeking request. http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail); context->pullData(); return true; } // In bump-server-first mode, we have not necessarily seen the intended // server name at certificate-peeking time. Check for domain mismatch now, // when we can extract the intended name from the bumped HTTP request. - if (sslServerBump->serverCert.get()) { + if (X509 *srvCert = sslServerBump->serverCert.get()) { HttpRequest *request = http->request; - if (!Ssl::checkX509ServerValidity(sslServerBump->serverCert.get(), request->GetHost())) { + if (!Ssl::checkX509ServerValidity(srvCert, request->GetHost())) { debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << "does not match domainname " << request->GetHost()); bool allowDomainMismatch = false; if (Config.ssl_client.cert_error) { ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str); - check.sslErrors = new Ssl::Errors(SQUID_X509_V_ERR_DOMAIN_MISMATCH); + check.sslErrors = new Ssl::CertErrors(Ssl::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert)); allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED); delete check.sslErrors; check.sslErrors = NULL; } if (!allowDomainMismatch) { quitAfterError(request); clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); // Fill the server IP and hostname for error page generation. HttpRequest::Pointer const & peekerRequest = sslServerBump->request; request->hier.note(peekerRequest->hier.tcpServer, request->GetHost()); // Create an error object and fill it ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request); err->src_addr = clientConnection->remote; Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail( SQUID_X509_V_ERR_DOMAIN_MISMATCH, - sslServerBump->serverCert.get(), NULL); + srvCert, NULL); err->detail = errDetail; // Save the original request for logging purposes. if (!context->http->al->request) { context->http->al->request = request; HTTPMSGLOCK(context->http->al->request); } repContext->setReplyToError(request->method, err); assert(context->http->out.offset == 0); context->pullData(); return true; } } } return false; } #endif // USE_SSL static void clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver) === modified file 'src/forward.cc' --- src/forward.cc 2013-05-17 08:36:45 +0000 +++ src/forward.cc 2013-05-28 19:29:30 +0000 @@ -693,138 +693,138 @@ // The errFromFailure is attached to the ssl object // and will be released when ssl object destroyed. // Copy errFromFailure to a new Ssl::ErrorDetail object. errDetails = new Ssl::ErrorDetail(*errFromFailure); } else { // server_cert can be NULL here X509 *server_cert = SSL_get_peer_certificate(ssl); errDetails = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL); X509_free(server_cert); } if (ssl_lib_error != SSL_ERROR_NONE) errDetails->setLibError(ssl_lib_error); if (request->clientConnectionManager.valid()) { // remember the server certificate from the ErrorDetail object if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { serverBump->serverCert.resetAndLock(errDetails->peerCert()); // remember validation errors, if any - if (Ssl::Errors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) + if (Ssl::CertErrors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) serverBump->sslErrors = cbdataReference(errs); } } // For intercepted connections, set the host name to the server // certificate CN. Otherwise, we just hope that CONNECT is using // a user-entered address (a host name or a user-entered IP). const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted(); if (request->flags.sslPeek && !isConnectRequest) { if (X509 *srvX509 = errDetails->peerCert()) { if (const char *name = Ssl::CommonHostName(srvX509)) { request->SetHost(name); debugs(83, 3, HERE << "reset request host: " << name); } } } ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL); anErr->xerrno = sysErrNo; anErr->detail = errDetails; fail(anErr); if (serverConnection()->getPeer()) { peerConnectFailed(serverConnection()->getPeer()); } serverConn->close(); return; } } 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))) + if (Ssl::CertErrors *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 (Ssl::TheConfig.ssl_crt_validator) { Ssl::CertValidationRequest validationRequest; // WARNING: Currently we do not use any locking for any of the // members of the Ssl::CertValidationRequest class. In this code the // Ssl::CertValidationRequest object used only to pass data to // Ssl::CertValidationHelper::submit method. validationRequest.ssl = ssl; validationRequest.domainName = request->GetHost(); - if (Ssl::Errors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) + if (Ssl::CertErrors *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::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, 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::scInternalServerError, request); fail(anErr); if (serverConnection()->getPeer()) { peerConnectFailed(serverConnection()->getPeer()); } serverConn->close(); self = NULL; return; } } dispatch(); } void FwdState::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse) { FwdState * fwd = (FwdState *)(data); fwd->sslCrtvdHandleReply(validationResponse); } void FwdState::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse) { - Ssl::Errors *errs = NULL; + Ssl::CertErrors *errs = NULL; Ssl::ErrorDetail *errDetails = NULL; bool validatorFailed = false; if (!Comm::IsConnOpen(serverConnection())) { return; } debugs(83,5, request->GetHost() << " cert validation result: " << validationResponse.resultCode); if (validationResponse.resultCode == HelperReply::Error) errs = sslCrtvdCheckForErrors(validationResponse, errDetails); else if (validationResponse.resultCode != HelperReply::Okay) validatorFailed = true; if (!errDetails && !validatorFailed) { dispatch(); return; } ErrorState *anErr = NULL; if (validatorFailed) { @@ -843,86 +843,86 @@ } } } 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 * +/// The method returns all seen errors except SSL_ERROR_NONE as Ssl::CertErrors. +Ssl::CertErrors * FwdState::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails) { - Ssl::Errors *errs = NULL; + Ssl::CertErrors *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); + check->sslErrors = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get())); 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); } if (check) { delete check->sslErrors; check->sslErrors = NULL; } } if (!errs) - errs = new Ssl::Errors(i->error_no); + errs = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get())); else - errs->push_back_unique(i->error_no); + errs->push_back_unique(Ssl::CertError(i->error_no, i->cert.get())); } if (check) delete check; return errs; } 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; === modified file 'src/forward.h' --- src/forward.h 2013-05-13 16:21:23 +0000 +++ src/forward.h 2013-05-27 15:47:49 +0000 @@ -73,41 +73,41 @@ 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 /// Callback function called when squid receive message from cert validator helper static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &); /// Process response from cert validator helper void sslCrtvdHandleReply(Ssl::CertValidationResponse const &); /// Check SSL errors returned from cert validator against sslproxy_cert_error access list - Ssl::Errors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&); + Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, 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, const Http::StatusCode 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/ssl/ServerBump.h' --- src/ssl/ServerBump.h 2012-08-14 11:53:07 +0000 +++ src/ssl/ServerBump.h 2013-05-27 15:35:06 +0000 @@ -10,31 +10,31 @@ class ConnStateData; class store_client; namespace Ssl { /** \ingroup ServerProtocolSSLAPI * Maintains bump-server-first related information. */ class ServerBump { public: explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL); ~ServerBump(); /// faked, minimal request; required by server-side API HttpRequest::Pointer request; StoreEntry *entry; ///< for receiving Squid-generated error messages Ssl::X509_Pointer serverCert; ///< HTTPS server certificate - Ssl::Errors *sslErrors; ///< SSL [certificate validation] errors + Ssl::CertErrors *sslErrors; ///< SSL [certificate validation] errors private: store_client *sc; ///< dummy client to prevent entry trimming CBDATA_CLASS2(ServerBump); }; } // namespace Ssl #endif === modified file 'src/ssl/cert_validate_message.cc' --- src/ssl/cert_validate_message.cc 2012-12-14 13:34:13 +0000 +++ src/ssl/cert_validate_message.cc 2013-05-29 08:47:52 +0000 @@ -1,62 +1,64 @@ #include "squid.h" #include "acl/FilledChecklist.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; - 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; - } - } - STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(vcert.ssl); if (peerCerts) { - body +="\n"; 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 + "cert_" + xitoa(i) + "="; + body = body + "\n" + param_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"; + 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"; + int errorCertPos = -1; + if (err->element.cert.get()) + errorCertPos = sk_X509_find(peerCerts, err->element.cert.get()); + if (errorCertPos < 0) { + // assert this error ? + debugs(83, 4, "WARNING: wrong cert in cert validator request"); + } + body += param_error_cert + xitoa(i) + "="; + body += param_cert + xitoa((errorCertPos >= 0 ? errorCertPos : 0)); + } + } } 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) @@ -195,26 +197,25 @@ { 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_"); === modified file 'src/ssl/cert_validate_message.h' --- src/ssl/cert_validate_message.h 2012-12-14 08:25:59 +0000 +++ src/ssl/cert_validate_message.h 2013-05-28 19:04:04 +0000 @@ -3,41 +3,41 @@ #ifndef SQUID_SSL_CERT_VALIDATE_MESSAGE_H #define SQUID_SSL_CERT_VALIDATE_MESSAGE_H #include "HelperReply.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: SSL *ssl; - Errors *errors; ///< The list of errors detected + CertErrors *errors; ///< The list of errors detected std::string domainName; ///< The server name CertValidationRequest() : ssl(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 &); @@ -82,34 +82,32 @@ 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/support.cc' --- src/ssl/support.cc 2013-05-24 15:14:50 +0000 +++ src/ssl/support.cc 2013-05-29 08:22:37 +0000 @@ -244,60 +244,64 @@ if (server) { 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) { - Ssl::Errors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)); + X509 *broken_cert = X509_STORE_CTX_get_current_cert(ctx); + if (!broken_cert) + broken_cert = peer_cert; + + Ssl::CertErrors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)); if (!errs) { - errs = new Ssl::Errors(error_no); + errs = new Ssl::CertErrors(Ssl::CertError(error_no, broken_cert)); if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_errors, (void *)errs)) { debugs(83, 2, "Failed to set ssl error_no in ssl_verify_cb: Certificate " << buffer); delete errs; errs = NULL; } } else // remember another error number - errs->push_back_unique(error_no); + errs->push_back_unique(Ssl::CertError(error_no, broken_cert)); 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); + 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) ok = 1; } if (!dont_verify_domain && server) {} if (!ok && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) { @@ -618,41 +622,41 @@ static void ssl_freeAclChecklist(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *) { 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::Errors *errs = static_cast (ptr); + Ssl::CertErrors *errs = static_cast (ptr); delete errs; } // "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; @@ -1579,21 +1583,51 @@ Ssl::CertificateProperties certProperties; if (const char *cn = CommonHostName(cert.get())) { certProperties.commonName = "Not trusted by \""; certProperties.commonName += cn; certProperties.commonName += "\""; } else if (const char *org = getOrganization(cert.get())) { certProperties.commonName = "Not trusted by \""; certProperties.commonName += org; certProperties.commonName += "\""; } else certProperties.commonName = "Not trusted"; certProperties.setCommonName = true; // O, OU, and other CA subject fields will be mimicked // Expiration date and other common properties will be mimicked certProperties.signAlgorithm = Ssl::algSignSelf; certProperties.signWithPkey.resetAndLock(pkey.get()); certProperties.mimicCert.resetAndLock(cert.get()); return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties); } +Ssl::CertError::CertError(ssl_error_t anErr, X509 *aCert): code(anErr) +{ + cert.resetAndLock(aCert); +} + +Ssl::CertError::CertError(CertError const &err): code(err.code) +{ + cert.resetAndLock(err.cert.get()); +} + +Ssl::CertError & +Ssl::CertError::operator = (const CertError &old) +{ + code = old.code; + cert.resetAndLock(old.cert.get()); + return *this; +} + +bool +Ssl::CertError::operator == (const CertError &ce) const +{ + return code == ce.code && cert.get() == ce.cert.get(); +} + +bool +Ssl::CertError::operator != (const CertError &ce) const +{ + return code != ce.code || cert.get() != ce.cert.get(); +} + #endif /* USE_SSL */ === modified file 'src/ssl/support.h' --- src/ssl/support.h 2012-11-13 18:19:17 +0000 +++ src/ssl/support.h 2013-05-29 08:23:56 +0000 @@ -57,40 +57,56 @@ // Custom SSL errors; assumes all official errors are positive #define SQUID_X509_V_ERR_CERT_CHANGE -3 #define SQUID_ERR_SSL_HANDSHAKE -2 #define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1 // All SSL errors range: from smallest (negative) custom to largest SSL error #define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_CERT_CHANGE #define SQUID_SSL_ERROR_MAX INT_MAX namespace AnyP { class PortCfg; }; namespace Ssl { /// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE typedef int ssl_error_t; typedef CbDataList Errors; +/// An SSL certificate-related error. +/// Pairs an error code with the certificate experiencing the error. +class CertError { +public: + ssl_error_t code; ///< certificate error code + X509_Pointer cert; ///< certificate with the above error code + CertError(ssl_error_t anErr, X509 *aCert); + CertError(CertError const &err); + CertError & operator = (const CertError &old); + bool operator == (const CertError &ce) const; + bool operator != (const CertError &ce) const; +}; + +/// Holds a list of certificate SSL errors +typedef CbDataList CertErrors; + } //namespace Ssl /// \ingroup ServerProtocolSSLAPI SSL_CTX *sslCreateServerContext(AnyP::PortCfg &port); /// \ingroup ServerProtocolSSLAPI 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); /// \ingroup ServerProtocolSSLAPI int ssl_read_method(int, char *, int); /// \ingroup ServerProtocolSSLAPI int ssl_write_method(int, const char *, int); /// \ingroup ServerProtocolSSLAPI void ssl_shutdown_method(SSL *ssl); /// \ingroup ServerProtocolSSLAPI const char *sslGetUserEmail(SSL *ssl);