cert validation cache This patch add cache to cert validation helper. The following new options added to "sslcrtvalidator_program" configuration parameter to control cache behaviour: ttl=n TTL in seconds for cached results.The default is 60 secs cache=n limit the result cache size. The default value is 2048 To implement the cert validation cache a new template class investigated, the LruMap which implements a simple lru cache. The LruMap templete class also used to replace the old Ssl::LocalContextStorage class which implements a SSL contexts cache. This is a Measurement Factory project === added file 'src/LruMap.h' --- src/LruMap.h 1970-01-01 00:00:00 +0000 +++ src/LruMap.h 2012-11-09 19:50:45 +0000 @@ -0,0 +1,212 @@ + +/* + * $Id$ + */ + +#ifndef SQUID_LRUMAP_H +#define SQUID_LRUMAP_H + +#include "SquidTime.h" +#if HAVE_LIST +#include +#endif +#if HAVE_MAP +#include +#endif + +template class LruMap +{ +public: + class Entry + { + public: + Entry(const char *aKey, EntryValue *t): key(aKey), value(t), date(squid_curtime) {} + ~Entry() {delete value;} + private: + Entry(LruMap::Entry &); + LruMap::Entry & operator = (LruMap::Entry &); + public: + std::string key; ///< the key of entry + EntryValue *value; ///< A pointer to the stored value + time_t date; ///< The date the entry created + }; + typedef std::list Queue; + typedef typename std::list::iterator QueueIterator; + + /// key:queue_item mapping for fast lookups by key + typedef std::map Map; + typedef typename Map::iterator MapIterator; + typedef std::pair MapPair; + + + LruMap(int ttl, size_t size); + ~LruMap(); + /// Search for an entry, and return a pointer + EntryValue *get(const char *key); + /// Add an entry to the map + bool add(const char *key, EntryValue *t); + /// Delete an entry from the map + bool del(const char *key); + /// (Re-)set the maximum size for this map + void setMemLimit(size_t aSize); + /// The available size for the map + size_t memLimit() const {return memLimit_;} + /// The free space of the map + size_t freeMem() const { return (memLimit() - size());} + /// The current size of the map + size_t size() const {return (entries_ * EntryCost);} + /// The number of stored entries + int entries() const {return entries_;} +private: + LruMap(LruMap const &); + LruMap & operator = (LruMap const &); + + bool expired(Entry &e); + void trim(); + void touch(const MapIterator &i); + bool del(const MapIterator &i); + void findEntry(const char *key, LruMap::MapIterator &i); + + Map storage; ///< The Key/value * pairs + Queue index; ///< LRU cache index + int ttl;///< >0 ttl for caching, == 0 cache is disabled, < 0 store for ever + size_t memLimit_; ///< The maximum memory to use + int entries_; ///< The stored entries +}; + +template +LruMap::LruMap(int aTtl, size_t aSize): entries_(0) +{ + ttl = aTtl; + + setMemLimit(aSize); +} + +template +LruMap::~LruMap() +{ + for (QueueIterator i = index.begin(); i != index.end(); ++i) { + delete *i; + } +} + +template +void +LruMap::setMemLimit(size_t aSize) +{ + if (aSize > 0) + memLimit_ = aSize; + else + memLimit_ = 0; +} + +template +void +LruMap::findEntry(const char *key, LruMap::MapIterator &i) +{ + i = storage.find(key); + if (i == storage.end()) { + return; + } + index.push_front(*(i->second)); + index.erase(i->second); + i->second = index.begin(); + + LruMap::Entry *e = *i->second; + + if (e && expired(*e)) { + del(i); + e = NULL; + } +} + +template +EntryValue * +LruMap::get(const char *key) +{ + LruMap::MapIterator i; + findEntry(key, i); + LruMap::Entry *e = *i->second; + if (i != storage.end()) { + touch(i); + return e->value; + } + return NULL; +} + +template +bool +LruMap::add(const char *key, EntryValue *t) +{ + if (ttl == 0) + return false; + + del(key); + trim(); + index.push_front(new Entry(key, t)); + storage.insert(MapPair(key, index.begin())); + + ++entries_; + return true; +} + +template +bool +LruMap::expired(LruMap::Entry &entry) +{ + if (ttl < 0) + return false; + + return (entry.date + ttl < squid_curtime); +} + +template +bool +LruMap::del(LruMap::MapIterator const &i) +{ + if (i != storage.end()) { + delete *(i->second); + index.erase(i->second); + storage.erase(i); + --entries_; + return true; + } + return false; +} + +template +bool +LruMap::del(const char *key) +{ + LruMap::MapIterator i; + findEntry(key, i); + return del(i); +} + +template +void +LruMap::trim() +{ + while(memLimit() > 0 && size() >= memLimit()) { + LruMap::QueueIterator i = index.end(); + --i; + if (i != index.end()) { + del((*i)->key.c_str()); + } + } +} + +template +void +LruMap::touch(LruMap::MapIterator const &i) +{ + // this must not be done when nothing is being cached. + if (ttl == 0) + return; + + index.push_front(*(i->second)); + index.erase(i->second); + i->second = index.begin(); +} + +#endif === modified file 'src/Makefile.am' --- src/Makefile.am 2012-11-02 23:54:44 +0000 +++ src/Makefile.am 2012-11-13 21:29:49 +0000 @@ -407,40 +407,41 @@ HttpRequest.cc \ HttpRequest.h \ HttpRequestMethod.cc \ HttpRequestMethod.h \ HttpVersion.h \ ICP.h \ icp_opcode.h \ icp_v2.cc \ icp_v3.cc \ int.h \ int.cc \ internal.h \ internal.cc \ $(IPC_SOURCE) \ ipcache.cc \ ipcache.h \ $(LEAKFINDERSOURCE) \ SquidList.h \ SquidList.cc \ lookup_t.h \ + LruMap.h \ main.cc \ Mem.h \ mem.cc \ mem_node.cc \ mem_node.h \ Mem.h \ MemBuf.cc \ MemObject.cc \ MemObject.h \ mime.h \ mime.cc \ mime_header.h \ mime_header.cc \ multicast.h \ multicast.cc \ neighbors.h \ neighbors.cc \ Notes.cc \ Notes.h \ Packer.cc \ === modified file 'src/cf.data.pre' --- src/cf.data.pre 2012-11-13 18:13:50 +0000 +++ src/cf.data.pre 2012-11-13 21:29:49 +0000 @@ -2343,41 +2343,46 @@ 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. + process. Usage: + sslcrtvalidator_program [ttl=n] [cache=n] path ... + + Options: + ttl=n TTL in seconds for cached results.The default is 60 secs + cache=n limit the result cache size. The default value is 2048 DOC_END NAME: sslcrtvalidator_children TYPE: HelperChildConfig IFDEF: USE_SSL DEFAULT: 32 startup=5 idle=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. === modified file 'src/client_side.cc' --- src/client_side.cc 2012-11-07 19:26:45 +0000 +++ src/client_side.cc 2012-11-13 21:29:49 +0000 @@ -3802,50 +3802,51 @@ certProperties.signWithPkey.resetAndLock(port->signPkey.get()); } signAlgorithm = certProperties.signAlgorithm; } void ConnStateData::getSslContextStart() { assert(areAllContextsForThisConnection()); freeAllContexts(); /* careful: freeAllContexts() above frees request, host, etc. */ if (port->generateHostCertificates) { Ssl::CertificateProperties certProperties; buildSslCertGenerationParams(certProperties); sslBumpCertKey = certProperties.dbKey().c_str(); assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); - SSL_CTX * dynCtx = ssl_ctx_cache.find(sslBumpCertKey.termedBuf()); - if (dynCtx) { + SSL_CTX * dynCtx = NULL; + Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf()); + if (cachedCtx && (dynCtx = cachedCtx->get())) { debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache"); if (Ssl::verifySslCertificate(dynCtx, certProperties)) { debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid"); getSslContextDone(dynCtx); return; } else { debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); - ssl_ctx_cache.remove(sslBumpCertKey.termedBuf()); + ssl_ctx_cache.del(sslBumpCertKey.termedBuf()); } } else { debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } #if USE_SSL_CRTD try { debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName << " using ssl_crtd."); Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST); request_message.setCode(Ssl::CrtdMessage::code_new_certificate); request_message.composeRequest(certProperties); debugs(33, 5, HERE << "SSL crtd request: " << request_message.compose().c_str()); Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); return; } catch (const std::exception &e) { debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " << "request for " << certProperties.commonName << " certificate: " << e.what() << "; will now block to " << "generate that certificate."); // fall through to do blocking in-process generation. @@ -3856,41 +3857,41 @@ dynCtx = Ssl::generateSslContext(certProperties, *port); getSslContextDone(dynCtx, true); return; } getSslContextDone(NULL); } void ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) { // Try to add generated ssl context to storage. if (port->generateHostCertificates && isNew) { if (signAlgorithm == Ssl::algSignTrusted) Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); //else it is self-signed or untrusted do not attrach any certificate Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); if (sslContext) { - if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), sslContext)) { + if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), new Ssl::SSL_CTX_Pointer(sslContext))) { // If it is not in storage delete after using. Else storage deleted it. fd_table[clientConnection->fd].dynamicSslContext = sslContext; } } else { debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslConnectHostOrIp); } } // If generated ssl context = NULL, try to use static ssl context. if (!sslContext) { if (!port->staticSslContext) { debugs(83, DBG_IMPORTANT, "Closing SSL " << clientConnection->remote << " as lacking SSL context"); clientConnection->close(); return; } else { debugs(33, 5, HERE << "Using static ssl context."); sslContext = port->staticSslContext.get(); } } === modified file 'src/forward.cc' --- src/forward.cc 2012-11-13 18:13:50 +0000 +++ src/forward.cc 2012-11-14 11:12:40 +0000 @@ -728,170 +728,143 @@ // 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 + // 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.peerCerts = SSL_get_peer_cert_chain(ssl); + validationRequest.ssl = 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, HERE << "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, HERE << "SSL crtvd request: " << requestMsg.compose().c_str()); - Ssl::CertValidationHelper::GetInstance()->sslSubmit(requestMsg, sslCrtvdHandleReplyWrapper, this); + Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, sslCrtvdHandleReplyWrapper, this); return; } catch (const std::exception &e) { debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " << "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::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse) { FwdState * fwd = (FwdState *)(data); - fwd->sslCrtvdHandleReply(reply); + fwd->sslCrtvdHandleReply(validationResponse); } void -FwdState::sslCrtvdHandleReply(const HelperReply &reply) +FwdState::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse) { 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, 1, HERE << "\"ssl_crtvd\" helper return reply"); + debugs(83,5, HERE << request->GetHost() << " cert validation result: " << validationResponse.resultCode); + + if (validationResponse.resultCode == HelperReply::Error) + errs = sslCrtvdCheckForErrors(validationResponse, errDetails); + else if (validationResponse.resultCode != HelperReply::Okay) 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, HERE << "Reply from ssl_crtvd for " << request->GetHost() << " is incorrect"); - validatorFailed = true; - } else { - if (reply.result == HelperReply::Okay) { - debugs(83, 5, HERE << "Certificate for " << request->GetHost() << " was successfully validated from ssl_crtvd"); - } else if (reply.result == HelperReply::Error) { - debugs(83, 5, HERE << "Certificate for " << request->GetHost() << " found buggy by ssl_crtvd"); - errs = sslCrtvdCheckForErrors(validationResponse, errDetails); - } else { - debugs(83, 5, HERE << "Certificate for " << request->GetHost() << " cannot be validated. ssl_crtvd response: " << replyMsg.getBody()); - validatorFailed = true; - } - if (!errDetails && !validatorFailed) { - dispatch(); - return; - } - } + 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) +FwdState::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &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); if (i->error_no == SSL_ERROR_NONE) continue; //ignore???? if (!errDetails) { bool allowed = false; if (check) { check->sslErrors = new Ssl::Errors(i->error_no); if (check->fastCheck() == ACCESS_ALLOWED) === modified file 'src/forward.h' --- src/forward.h 2012-11-13 18:13:50 +0000 +++ src/forward.h 2012-11-13 21:35:41 +0000 @@ -68,45 +68,45 @@ 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); + static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &); /// Process response from cert validator helper - void sslCrtvdHandleReply(const HelperReply &reply); + void sslCrtvdHandleReply(Ssl::CertValidationResponse const &); /// Check SSL errors returned from cert validator against sslproxy_cert_error access list - Ssl::Errors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse &, Ssl::ErrorDetail *&); + Ssl::Errors *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, 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/ssl/cert_validate_message.cc' --- src/ssl/cert_validate_message.cc 2012-09-20 18:17:40 +0000 +++ src/ssl/cert_validate_message.cc 2012-10-02 15:48:00 +0000 @@ -3,45 +3,46 @@ #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) { + 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(vcert.peerCerts); ++i) { - X509 *cert = sk_X509_value(vcert.peerCerts, i); + 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) + "="; 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; === modified file 'src/ssl/cert_validate_message.h' --- src/ssl/cert_validate_message.h 2012-11-13 18:13:50 +0000 +++ src/ssl/cert_validate_message.h 2012-11-14 10:00:46 +0000 @@ -2,77 +2,78 @@ * $Id$ */ #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 + SSL *ssl; Errors *errors; ///< The list of errors detected std::string domainName; ///< The server name - CertValidationRequest() : peerCerts(NULL), errors(NULL) {} + 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 &); ~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 *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 + HelperReply::Result_ resultCode; ///< The helper result code }; /** * 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 *cert; ///< A pointer to certificate === modified file 'src/ssl/context_storage.cc' --- src/ssl/context_storage.cc 2012-10-04 11:10:17 +0000 +++ src/ssl/context_storage.cc 2012-11-13 21:29:49 +0000 @@ -17,127 +17,50 @@ Ssl::CertificateStorageAction::Pointer Ssl::CertificateStorageAction::Create(const Mgr::Command::Pointer &cmd) { return new CertificateStorageAction(cmd); } void Ssl::CertificateStorageAction::dump (StoreEntry *sentry) { StoreEntryStream stream(sentry); const char delimiter = '\t'; const char endString = '\n'; // Page title. stream << "Cached ssl certificates statistic.\n"; // Title of statistic table. stream << "Port" << delimiter << "Max mem(KB)" << delimiter << "Cert number" << delimiter << "KB/cert" << delimiter << "Mem used(KB)" << delimiter << "Mem free(KB)" << endString; // Add info for each port. for (std::map::iterator i = TheGlobalContextStorage.storage.begin(); i != TheGlobalContextStorage.storage.end(); ++i) { stream << i->first << delimiter; LocalContextStorage & ssl_store_policy(*(i->second)); - stream << ssl_store_policy.max_memory / 1024 << delimiter; - stream << ssl_store_policy.memory_used / SSL_CTX_SIZE << delimiter; + stream << ssl_store_policy.memLimit() / 1024 << delimiter; + stream << ssl_store_policy.entries() << delimiter; stream << SSL_CTX_SIZE / 1024 << delimiter; - stream << ssl_store_policy.memory_used / 1024 << delimiter; - stream << (ssl_store_policy.max_memory - ssl_store_policy.memory_used) / 1024 << endString; + stream << ssl_store_policy.size() / 1024 << delimiter; + stream << ssl_store_policy.freeMem() / 1024 << endString; } stream << endString; stream.flush(); } -Ssl::LocalContextStorage::LocalContextStorage(size_t aMax_memory) - : max_memory(aMax_memory), memory_used(0) -{} - -Ssl::LocalContextStorage::~LocalContextStorage() -{ - for (QueueIterator i = lru_queue.begin(); i != lru_queue.end(); ++i) { - delete *i; - } -} - -SSL_CTX * Ssl::LocalContextStorage::add(const char * host_name, SSL_CTX * ssl_ctx) -{ - if (max_memory < SSL_CTX_SIZE) { - return NULL; - } - remove(host_name); - while (SSL_CTX_SIZE + memory_used > max_memory) { - purgeOne(); - } - lru_queue.push_front(new Item(ssl_ctx, host_name)); - storage.insert(MapPair(host_name, lru_queue.begin())); - memory_used += SSL_CTX_SIZE; - return ssl_ctx; -} - -SSL_CTX * Ssl::LocalContextStorage::find(char const * host_name) -{ - MapIterator i = storage.find(host_name); - if (i == storage.end()) { - return NULL; - } - lru_queue.push_front(*(i->second)); - lru_queue.erase(i->second); - i->second = lru_queue.begin(); - return (*lru_queue.begin())->ssl_ctx; -} - -void Ssl::LocalContextStorage::remove(char const * host_name) -{ - deleteAt(storage.find(host_name)); -} - -void Ssl::LocalContextStorage::purgeOne() -{ - QueueIterator i = lru_queue.end(); - --i; - if (i != lru_queue.end()) { - remove((*i)->host_name.c_str()); - } -} - -void Ssl::LocalContextStorage::deleteAt(LocalContextStorage::MapIterator i) -{ - if (i != storage.end()) { - - delete *(i->second); - lru_queue.erase(i->second); - storage.erase(i); - memory_used -= SSL_CTX_SIZE; - } -} - -void Ssl::LocalContextStorage::SetSize(size_t aMax_memory) -{ - max_memory = aMax_memory; -} - -Ssl::LocalContextStorage::Item::Item(SSL_CTX * aSsl_ctx, std::string const & aName) - : ssl_ctx(aSsl_ctx), host_name(aName) -{} - -Ssl::LocalContextStorage::Item::~Item() -{ - SSL_CTX_free(ssl_ctx); -} - /////////////////////////////////////////////////////// Ssl::GlobalContextStorage::GlobalContextStorage() : reconfiguring(true) { RegisterAction("cached_ssl_cert", "Statistic of cached generated ssl certificates", &CertificateStorageAction::Create, 0, 1); } Ssl::GlobalContextStorage::~GlobalContextStorage() { for (std::map::iterator i = storage.begin(); i != storage.end(); ++i) { delete i->second; } } void Ssl::GlobalContextStorage::addLocalStorage(Ip::Address const & address, size_t size_of_store) { assert(reconfiguring); configureStorage.insert(std::pair(address, size_of_store)); } @@ -149,34 +72,34 @@ assert (i != storage.end()); return *(i->second); } void Ssl::GlobalContextStorage::reconfigureStart() { reconfiguring = true; } void Ssl::GlobalContextStorage::reconfigureFinish() { if (reconfiguring) { reconfiguring = false; // remove or change old local storages. for (std::map::iterator i = storage.begin(); i != storage.end(); ++i) { std::map::iterator conf_i = configureStorage.find(i->first); if (conf_i == configureStorage.end()) { storage.erase(i); } else { - i->second->SetSize(conf_i->second); + i->second->setMemLimit(conf_i->second); } } // add new local storages. for (std::map::iterator conf_i = configureStorage.begin(); conf_i != configureStorage.end(); ++conf_i ) { if (storage.find(conf_i->first) == storage.end()) { - storage.insert(std::pair(conf_i->first, new LocalContextStorage(conf_i->second))); + storage.insert(std::pair(conf_i->first, new LocalContextStorage(-1, conf_i->second))); } } } } Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage; === modified file 'src/ssl/context_storage.h' --- src/ssl/context_storage.h 2012-10-04 11:10:17 +0000 +++ src/ssl/context_storage.h 2012-11-13 21:29:49 +0000 @@ -1,107 +1,63 @@ #ifndef SQUID_SSL_CONTEXT_STORAGE_H #define SQUID_SSL_CONTEXT_STORAGE_H #if USE_SSL #include "SquidTime.h" #include "CacheManager.h" #include "ip/Address.h" #include "mgr/Action.h" #include "mgr/Command.h" +#include "LruMap.h" +#include "ssl/gadgets.h" #if HAVE_MAP #include #endif #if HAVE_LIST #include #endif #include /// TODO: Replace on real size. #define SSL_CTX_SIZE 1024 namespace Ssl { /** Reports cached SSL certificate stats to Cache Manager. * TODO: Use "Report" functions instead friend class. */ class CertificateStorageAction : public Mgr::Action { public: CertificateStorageAction(const Mgr::Command::Pointer &cmd); static Pointer Create(const Mgr::Command::Pointer &cmd); virtual void dump (StoreEntry *sentry); /** * We do not support aggregation of information across workers * TODO: aggregate these stats */ virtual bool aggregatable() const { return false; } }; -/** - * Memory cache for store generated SSL context. Enforces total size limits - * using an LRU algorithm. - */ -class LocalContextStorage -{ - friend class CertificateStorageAction; -public: - /// Cache item is an (SSL_CTX, host name) tuple. - class Item - { - public: - Item(SSL_CTX * aSsl_ctx, std::string const & aName); - ~Item(); - public: - SSL_CTX * ssl_ctx; ///< The SSL context. - std::string host_name; ///< The host name of the SSL context. - }; - - typedef std::list Queue; - typedef Queue::iterator QueueIterator; - - /// host_name:queue_item mapping for fast lookups by host name - typedef std::map Map; - typedef Map::iterator MapIterator; - typedef std::pair MapPair; - - LocalContextStorage(size_t aMax_memory); - ~LocalContextStorage(); - /// Set maximum memory size for this storage. - void SetSize(size_t aMax_memory); - /// Return a pointer to the added ssl_ctx or NULL if fails (eg. max cache size equal 0). - SSL_CTX * add(char const * host_name, SSL_CTX * ssl_ctx); - /// Find SSL_CTX in storage by host name. Lru queue will be updated. - SSL_CTX * find(char const * host_name); - void remove(char const * host_name); ///< Delete the SSL context by hostname - -private: - void purgeOne(); ///< Delete oldest object. - /// Delete object by iterator. It is used in deletePurge() and remove(...) methods. - void deleteAt(MapIterator i); - - size_t max_memory; ///< Max cache size. - size_t memory_used; ///< Used cache size. - Map storage; ///< The hostnames/SSL_CTX * pairs - Queue lru_queue; ///< LRU cache index -}; +typedef LruMap LocalContextStorage; /// Class for storing/manipulating LocalContextStorage per local listening address/port. class GlobalContextStorage { friend class CertificateStorageAction; public: GlobalContextStorage(); ~GlobalContextStorage(); /// Create new SSL context storage for the local listening address/port. void addLocalStorage(Ip::Address const & address, size_t size_of_store); /// Return the local storage for the given listening address/port. LocalContextStorage & getLocalStorage(Ip::Address const & address); /// When reconfigring should be called this method. void reconfigureStart(); private: /// Called by getLocalStorage method void reconfigureFinish(); bool reconfiguring; ///< True if system reconfiguring now. /// Storage used on configure or reconfigure. std::map configureStorage; === modified file 'src/ssl/helper.cc' --- src/ssl/helper.cc 2012-11-13 18:13:50 +0000 +++ src/ssl/helper.cc 2012-11-14 10:03:53 +0000 @@ -1,30 +1,33 @@ #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 "ssl/cert_validate_message.h" #include "wordlist.h" #include "SquidConfig.h" +LruMap *Ssl::CertValidationHelper::HelperCache = NULL; + 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 @@ -127,64 +130,148 @@ 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); + + int ttl = 60; + size_t cache = 2048; { char *tmp = xstrdup(Ssl::TheConfig.ssl_crt_validator); char *tmp_begin = tmp; char * token = NULL; + bool parseParams = true; while ((token = strwordtok(NULL, &tmp))) { + if (parseParams) { + if (strncmp(token, "ttl=", 4) == 0) { + ttl = atoi(token + 4); + continue; + } else if (strncmp(token, "cache=", 6) == 0) { + cache = atoi(token + 6); + continue; + } else + parseParams = false; + } wordlistAdd(&ssl_crt_validator->cmdline, token); } safe_free(tmp_begin); } helperOpenServers(ssl_crt_validator); + + //WARNING: initializing static member in an object initialization method + assert(HelperCache == NULL); + HelperCache = new LruMap(ttl, cache); } 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; + + // CertValidationHelper::HelperCache is a static member, it is not good policy to + // reset it here. Will work because the current Ssl::CertValidationHelper is + // always the same static object. + delete HelperCache; + HelperCache = NULL; +} + +struct submitData { + std::string query; + Ssl::CertValidationHelper::CVHCB *callback; + void *data; + SSL *ssl; + CBDATA_CLASS2(submitData); +}; +CBDATA_CLASS_INIT(submitData); + +static void +sslCrtvdHandleReplyWrapper(void *data, const HelperReply &reply) +{ + Ssl::CertValidationMsg replyMsg(Ssl::CrtdMessage::REPLY); + Ssl::CertValidationResponse *validationResponse = new Ssl::CertValidationResponse; + std::string error; + + submitData *crtdvdData = static_cast(data); + STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(crtdvdData->ssl); + if (replyMsg.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK || + !replyMsg.parseResponse(*validationResponse, peerCerts, error) ) { + debugs(83, 5, HERE << "Reply from ssl_crtvd for " << " is incorrect"); + debugs(83, 5, HERE << "Certificate for " << " cannot be validated. ssl_crtvd response: " << replyMsg.getBody()); + validationResponse->resultCode = HelperReply::BrokenHelper; + } + else + validationResponse->resultCode = reply.result; + + crtdvdData->callback(crtdvdData->data, *validationResponse); + + if (Ssl::CertValidationHelper::HelperCache && + (validationResponse->resultCode == HelperReply::Okay || validationResponse->resultCode == HelperReply::Error)) { + Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query.c_str(), validationResponse); + } else + delete validationResponse; + + cbdataReferenceDone(crtdvdData->data); + SSL_free(crtdvdData->ssl); + delete crtdvdData; } -void Ssl::CertValidationHelper::sslSubmit(CrtdMessage const & message, HLPCB * callback, void * data) +void Ssl::CertValidationHelper::sslSubmit(Ssl::CertValidationRequest const &request, Ssl::CertValidationHelper::CVHCB * 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 servers not responding for 3 minutes"); debugs(83, 1, HERE << "Queue overload, rejecting"); - const char *errMsg = "BH error 45 Temporary network problem, please retry later"; - HelperReply failReply(errMsg,strlen(errMsg)); - callback(data, failReply); + Ssl::CertValidationResponse resp; + resp.resultCode = HelperReply::BrokenHelper; + callback(data, resp); return; } - first_warn = 0; - std::string msg = message.compose(); - msg += '\n'; - helperSubmit(ssl_crt_validator, msg.c_str(), callback, data); + + Ssl::CertValidationMsg message(Ssl::CrtdMessage::REQUEST); + message.setCode(Ssl::CertValidationMsg::code_cert_validate); + message.composeRequest(request); + debugs(83, 5, HERE << "SSL crtvd request: " << message.compose().c_str()); + + submitData *crtdvdData = new submitData; + crtdvdData->query = message.compose(); + crtdvdData->query += '\n'; + crtdvdData->callback = callback; + crtdvdData->data = cbdataReference(data); + crtdvdData->ssl = request.ssl; + CRYPTO_add(&crtdvdData->ssl->references,1,CRYPTO_LOCK_SSL); + Ssl::CertValidationResponse const*validationResponse; + + if (CertValidationHelper::HelperCache && + (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query.c_str()))) { + callback(data, *validationResponse); + cbdataReferenceDone(crtdvdData->data); + SSL_free(crtdvdData->ssl); + delete crtdvdData; + return; + } + helperSubmit(ssl_crt_validator, crtdvdData->query.c_str(), sslCrtvdHandleReplyWrapper, crtdvdData); } === modified file 'src/ssl/helper.h' --- src/ssl/helper.h 2012-11-13 18:13:50 +0000 +++ src/ssl/helper.h 2012-11-13 21:29:49 +0000 @@ -1,45 +1,52 @@ #ifndef SQUID_SSL_HELPER_H #define SQUID_SSL_HELPER_H #include "../helper.h" +#include "LruMap.h" +#include "ssl/cert_validate_message.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. */ 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. }; +class CertValidationRequest; +class CertValidationResponse; class CertValidationHelper { public: + typedef void CVHCB(void *, Ssl::CertValidationResponse const &); 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); + /// Submit crtd request message to external crtd server. + void sslSubmit(Ssl::CertValidationRequest const & request, CVHCB * callback, void *data); private: CertValidationHelper(); ~CertValidationHelper(); helper * ssl_crt_validator; ///< helper for management of ssl_crtd. +public: + static LruMap *HelperCache; ///< cache for cert validation helper }; } //namespace Ssl #endif // SQUID_SSL_HELPER_H === modified file 'src/tests/stub_libsslsquid.cc' --- src/tests/stub_libsslsquid.cc 2012-09-20 16:26:47 +0000 +++ src/tests/stub_libsslsquid.cc 2012-10-24 18:18:55 +0000 @@ -1,53 +1,43 @@ #include "squid.h" #if USE_SSL #include "fatal.h" /* Stub File for the ssl/libsslsquid.la convenience library */ #define STUB_API "ssl/libsslsquid.la" #include "tests/STUB.h" #include "ssl/Config.h" Ssl::Config::Config() { printf("Ssl::Config::Config No implemented\n"); } Ssl::Config::~Config() { printf("Ssl::Config::Config No implemented\n"); } Ssl::Config Ssl::TheConfig; #include "ssl/context_storage.h" //Ssl::CertificateStorageAction::CertificateStorageAction(const Mgr::Command::Pointer &cmd) STUB Ssl::CertificateStorageAction::Pointer Ssl::CertificateStorageAction::Create(const Mgr::Command::Pointer &cmd) STUB_RETSTATREF(Ssl::CertificateStorageAction::Pointer) 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; } +{ fatal(STUB_API " required"); static Ssl::LocalContextStorage v(0,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 *, 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;