From fb2178bb51a9028bbb9d848e669fd02f380134d4 Mon Sep 17 00:00:00 2001 From: Christos Tsantilas Date: Sat, 21 Jan 2012 12:11:43 +0200 Subject: [PATCH] sslproxy_cert_adapt squid.conf option This patch add the sslproxy_cert_adapt option to squid.conf which gives to squid administrators the required functionality to "fix" a known broken certificate using acls. Currently only the "Not After", "Not Before" and "Common Name" fields of a certificate can be modified/fixed. The sslproxy_cert_adapt option has the form: sslproxy_cert_adapt acl ... where is one of the setValidAfter, setValidBefore and setCommonName. setValidAfter: sets the "Not After" property to the signing cert's "Not After" property. setValidBefore: sets the "Not Before" property to the signing cert's "Not After" property. setCommonName: sets certificate Subject.CN property to the host name from specified as a CN parameter (setCommonName{CN}) or, if no explicit CN parameter was specified, extracted from the CONNECT request When the acl(s) match, the corresponding adaptation algorithm is applied to the fake/generated certificate. Otherwise, the default mimicking action takes place. --- src/AclRegs.cc | 2 +- src/acl/FilledChecklist.cc | 6 --- src/acl/FilledChecklist.h | 5 +- src/acl/SslError.cc | 2 +- src/acl/SslError.h | 5 +- src/acl/SslErrorData.cc | 11 ++-- src/acl/SslErrorData.h | 7 +-- src/cache_cf.cc | 81 ++++++++++++++++++++++++++++ src/cf.data.depend | 1 + src/cf.data.pre | 29 ++++++++++ src/client_side.cc | 108 ++++++++++++++++++++++++++++--------- src/client_side.h | 10 +++- src/forward.cc | 9 +++- src/globals.h | 1 + src/ssl/certificate_db.cc | 8 +-- src/ssl/certificate_db.h | 2 +- src/ssl/crtd_message.cc | 4 ++ src/ssl/crtd_message.h | 6 +++ src/ssl/gadgets.cc | 59 +++++++++++++++++--- src/ssl/gadgets.h | 27 +++++++++- src/ssl/ssl_crtd.cc | 18 ++++++- src/ssl/support.cc | 30 +++++++++-- src/ssl/support.h | 6 ++- src/structs.h | 10 ++++ src/typedefs.h | 4 ++ 25 files changed, 389 insertions(+), 62 deletions(-) diff --git a/src/AclRegs.cc b/src/AclRegs.cc index ae4faabb4a..6a03e6baaa 100644 --- a/src/AclRegs.cc +++ b/src/AclRegs.cc @@ -137,7 +137,7 @@ ACLStrategised ACLUrlPort::RegistryEntry_(new ACLIntRange, ACLUrlPortStrate #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 (sslGetUserAttribute), ACLCertificateStrategy::Instance(), "user_cert"); ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEntry_, "ca_cert"); diff --git a/src/acl/FilledChecklist.cc b/src/acl/FilledChecklist.cc index e061e8c638..084bcf749b 100644 --- a/src/acl/FilledChecklist.cc +++ b/src/acl/FilledChecklist.cc @@ -62,9 +62,6 @@ ACLFilledChecklist::ACLFilledChecklist() : #endif #if SQUID_SNMP snmp_community(NULL), -#endif -#if USE_SSL - ssl_error(0), #endif extacl_entry (NULL), conn_(NULL), @@ -174,9 +171,6 @@ ACLFilledChecklist::ACLFilledChecklist(const acl_access *A, HttpRequest *http_re #endif #if SQUID_SNMP snmp_community(NULL), -#endif -#if USE_SSL - ssl_error(0), #endif extacl_entry (NULL), conn_(NULL), diff --git a/src/acl/FilledChecklist.h b/src/acl/FilledChecklist.h index 9ffccbcb61..3ec833128b 100644 --- a/src/acl/FilledChecklist.h +++ b/src/acl/FilledChecklist.h @@ -5,6 +5,9 @@ #if USE_AUTH #include "auth/UserRequest.h" #endif +#if USE_SSL +#include "ssl/support.h" +#endif class ExternalACLEntry; class ConnStateData; @@ -63,7 +66,7 @@ public: #endif #if USE_SSL - int ssl_error; + Ssl::Errors sslErrorList; #endif ExternalACLEntry *extacl_entry; diff --git a/src/acl/SslError.cc b/src/acl/SslError.cc index 33041e6d17..b50ed75e64 100644 --- a/src/acl/SslError.cc +++ b/src/acl/SslError.cc @@ -11,7 +11,7 @@ int ACLSslErrorStrategy::match (ACLData * &data, ACLFilledChecklist *checklist) { - return data->match (checklist->ssl_error); + return data->match (checklist->sslErrorList); } ACLSslErrorStrategy * diff --git a/src/acl/SslError.h b/src/acl/SslError.h index e18cf1c7bc..0fff38076b 100644 --- a/src/acl/SslError.h +++ b/src/acl/SslError.h @@ -7,8 +7,9 @@ #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: @@ -31,7 +32,7 @@ class ACLSslError private: static ACL::Prototype RegistryProtoype; - static ACLStrategised RegistryEntry_; + static ACLStrategised RegistryEntry_; }; #endif /* SQUID_ACLSSL_ERROR_H */ diff --git a/src/acl/SslErrorData.cc b/src/acl/SslErrorData.cc index 820ed646af..ad2a0043f7 100644 --- a/src/acl/SslErrorData.cc +++ b/src/acl/SslErrorData.cc @@ -22,9 +22,14 @@ ACLSslErrorData::~ACLSslErrorData() } bool -ACLSslErrorData::match(Ssl::ssl_error_t toFind) +ACLSslErrorData::match(Ssl::Errors const &toFind) { - return values->findAndTune (toFind); + typedef std::vector::const_iterator SEsI; + for (SEsI it = toFind.begin() ; it != toFind.end(); it++ ) { + if (values->findAndTune (*it)) + return true; + } + return false; } /* explicit instantiation required for some systems */ @@ -67,7 +72,7 @@ ACLSslErrorData::empty() const return values == NULL; } -ACLData * +ACLSslErrorData * ACLSslErrorData::clone() const { /* Splay trees don't clone yet. */ diff --git a/src/acl/SslErrorData.h b/src/acl/SslErrorData.h index 1f21a7ceae..0673049d3a 100644 --- a/src/acl/SslErrorData.h +++ b/src/acl/SslErrorData.h @@ -10,8 +10,9 @@ #include "CbDataList.h" #include "ssl/support.h" #include "ssl/ErrorDetail.h" +#include -class ACLSslErrorData : public ACLData +class ACLSslErrorData : public ACLData { public: @@ -21,11 +22,11 @@ public: ACLSslErrorData(ACLSslErrorData const &); ACLSslErrorData &operator= (ACLSslErrorData const &); virtual ~ACLSslErrorData(); - bool match(Ssl::ssl_error_t); + bool match(Ssl::Errors const &); wordlist *dump(); void parse(); bool empty() const; - virtual ACLData *clone() const; + virtual ACLSslErrorData *clone() const; CbDataList *values; }; diff --git a/src/cache_cf.cc b/src/cache_cf.cc index add7c03dd2..189aa0ffe4 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -197,6 +197,9 @@ static void free_http_port_list(http_port_list **); static void parse_https_port_list(https_port_list **); static void dump_https_port_list(StoreEntry *, const char *, const https_port_list *); static void free_https_port_list(https_port_list **); +static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt); +static void dump_sslproxy_cert_adapt(StoreEntry *entry, const char *name, sslproxy_cert_adapt *cert_adapt); +static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt); #if 0 static int check_null_https_port_list(const https_port_list *); #endif @@ -4521,4 +4524,82 @@ static void free_icap_service_failure_limit(Adaptation::Icap::Config *cfg) cfg->service_failure_limit = 0; } +#if USE_SSL +static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt) +{ + char *al; + sslproxy_cert_adapt *ca = (sslproxy_cert_adapt *) xcalloc(1, sizeof(sslproxy_cert_adapt)); + if ((al = strtok(NULL, w_space)) == NULL) { + self_destruct(); + return; + } + + const char *param; + if ( char *s = strchr(al, '{')) { + *s = '\0'; // terminate the al string + s++; + param = s; + s = strchr(s, '}'); + if (!s) { + self_destruct(); + return; + } + *s = '\0'; + } + else + param = NULL; + + if (strcmp(al, Ssl::CertAdaptAlgorithmStr[Ssl::algSetValidAfter]) == 0) { + ca->alg = Ssl::algSetValidAfter; + ca->param = strdup("on"); + } + else if (strcmp(al, Ssl::CertAdaptAlgorithmStr[Ssl::algSetValidBefore]) == 0) { + ca->alg = Ssl::algSetValidBefore; + ca->param = strdup("on"); + } + else if (strcmp(al, Ssl::CertAdaptAlgorithmStr[Ssl::algSetCommonName]) == 0) { + ca->alg = Ssl::algSetCommonName; + if (param) { + ca->param = strdup(param); + } + } else { + debugs(3, DBG_CRITICAL, "FATAL: sslproxy_cert_adapt: unknown cert adaptation algorithm: " << al); + self_destruct(); + return; + } + + aclParseAclList(LegacyParser, &ca->aclList); + + while(*cert_adapt) + cert_adapt = &(*cert_adapt)->next; + + *cert_adapt = ca; +} + +static void dump_sslproxy_cert_adapt(StoreEntry *entry, const char *name, sslproxy_cert_adapt *cert_adapt) +{ + for (sslproxy_cert_adapt *ca = cert_adapt; ca != NULL; ca = ca->next) { + storeAppendPrintf(entry, "%s ", name); + storeAppendPrintf(entry, "%s{%s} ", Ssl::sslCertAdaptAlgoritm(ca->alg), ca->param); + if (ca->aclList) + dump_acl_list(entry, ca->aclList); + storeAppendPrintf(entry, "\n"); + } +} + +static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt) +{ + while(*cert_adapt) { + sslproxy_cert_adapt *ca = *cert_adapt; + *cert_adapt = ca->next; + safe_free(ca->param); + + if (ca->aclList) + aclDestroyAclList(&ca->aclList); + + safe_free(ca); + } +} +#endif + #endif diff --git a/src/cf.data.depend b/src/cf.data.depend index 9409d54712..478390aef9 100644 --- a/src/cf.data.depend +++ b/src/cf.data.depend @@ -69,3 +69,4 @@ wccp2_amethod wccp2_service wccp2_service_info wordlist +sslproxy_cert_adapt acl diff --git a/src/cf.data.pre b/src/cf.data.pre index e400801cec..0b45881252 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -2052,6 +2052,35 @@ DOC_START Default setting: sslproxy_cert_error deny all DOC_END +NAME: sslproxy_cert_adapt +IFDEF: USE_SSL +DEFAULT: none +TYPE: sslproxy_cert_adapt +LOC: Config.ssl_client.cert_adapt +DOC_START + + sslproxy_cert_adapt acl ... + + The following certificate adaptation algorithms supported: + setValidAfter + sets the "Not After" property to the "Not After" propery of + the ca certificate used to sign generated certificates + setValidBefore + sets the "Not Before" property to the "Not Before" property of + the ca certificate used to sign generated certificates + setCommonName + sets certificate Subject.CN property to the + host name from specified as a CN parameter (setCommonName{CN}) + or, if no explicit CN parameter was specified, extracted from + the CONNECT request. It is a misconfiguration to use setName + without an explicit parameter for intercepted or tproxied SSL + transactions. + + When the acl(s) match, the corresponding adaptation algorithm is + applied to the fake/generated certificate. Otherwise, the + default mimicking action takes place. +DOC_END + NAME: sslpassword_program IFDEF: USE_SSL DEFAULT: none diff --git a/src/client_side.cc b/src/client_side.cc index 7eb28aceda..35f33e3bfc 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -3615,12 +3615,12 @@ ConnStateData::sslCrtdHandleReply(const char * reply) } else { Ssl::CrtdMessage reply_message; if (reply_message.parse(reply, strlen(reply)) != Ssl::CrtdMessage::OK) { - debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslHostName << " is incorrect"); + debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslConnectHostOrIp << " is incorrect"); } else { if (reply_message.getCode() != "OK") { - debugs(33, 5, HERE << "Certificate for " << sslHostName << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); + debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); } else { - debugs(33, 5, HERE << "Certificate for " << sslHostName << " was successfully recieved from ssl_crtd"); + debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd"); SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str()); getSslContextDone(ctx, true); return; @@ -3630,34 +3630,91 @@ ConnStateData::sslCrtdHandleReply(const char * reply) getSslContextDone(NULL); } +void ConnStateData::buildSslCertAdaptParams(Ssl::CrtdMessage::BodyParams &certAdaptParams) +{ + // Build a key to use for storing/retrieving certificates to cache + sslBumpCertKey = sslCommonName.defined() ? sslCommonName : sslConnectHostOrIp; + + // fake certificate adaptation requires bump-server-first mode + if (!bumpServerFirstErrorEntry()) + return; + + HttpRequest *fakeRequest = new HttpRequest(); + fakeRequest->SetHost(sslConnectHostOrIp.termedBuf()); + fakeRequest->port = clientConnection->local.GetPort(); + fakeRequest->protocol = AnyP::PROTO_HTTPS; + + ACLFilledChecklist checklist(NULL, fakeRequest, + clientConnection != NULL ? clientConnection->rfc931 : dash_str); + checklist.conn(this); + checklist.src_addr = clientConnection->remote; + checklist.my_addr = clientConnection->local; + checklist.sslErrorList = bumpSslErrorNoList; + + for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) { + if (ca->aclList && checklist.fastCheck(ca->aclList) == ACCESS_ALLOWED) { + const char *alg = Ssl::CertAdaptAlgorithmStr[ca->alg]; + const char *param = ca->param; + + // if already set ignore + if (certAdaptParams.find(alg) != certAdaptParams.end()) + continue; + + // if not param defined for Common Name adaptation use hostname from + // the CONNECT request + if (!param && ca->alg == Ssl::algSetCommonName) + param = sslConnectHostOrIp.termedBuf(); + + assert(alg && param); + debugs(33, 5, HERE << "Matches certificate adaptation aglorithm: " << + alg << " param: " << param); + + // append to the certificate adaptation parameters + certAdaptParams.insert( std::make_pair(alg, param)); + + // And also build the key used to store certificate to cache. + sslBumpCertKey.append("+"); + sslBumpCertKey.append(alg); + sslBumpCertKey.append("="); + sslBumpCertKey.append(param); + } + } +} + void ConnStateData::getSslContextStart() { - char const * host = sslHostName.termedBuf(); - if (port->generateHostCertificates && host && strcmp(host, "") != 0) { - debugs(33, 5, HERE << "Finding SSL certificate for " << host << " in cache"); + if (port->generateHostCertificates) { + Ssl::CrtdMessage::BodyParams certAdaptParams; + buildSslCertAdaptParams(certAdaptParams); + 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(host); + SSL_CTX * dynCtx = ssl_ctx_cache.find(sslBumpCertKey.termedBuf()); if (dynCtx) { - debugs(33, 5, HERE << "SSL certificate for " << host << " have found in cache"); + debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache"); if (Ssl::verifySslCertificate(dynCtx, bumpServerCert.get())) { - debugs(33, 5, HERE << "Cached SSL certificate for " << host << " is valid"); + debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid"); getSslContextDone(dynCtx); return; } else { - debugs(33, 5, HERE << "Cached SSL certificate for " << host << " is out of date. Delete this certificate from cache"); - ssl_ctx_cache.remove(host); + debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); + ssl_ctx_cache.remove(sslBumpCertKey.termedBuf()); } } else { - debugs(33, 5, HERE << "SSL certificate for " << host << " haven't found in cache"); + debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } + char const * host = sslCommonName.defined() ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf(); #if USE_SSL_CRTD debugs(33, 5, HERE << "Generating SSL certificate for " << host << " using ssl_crtd."); Ssl::CrtdMessage request_message; request_message.setCode(Ssl::CrtdMessage::code_new_certificate); Ssl::CrtdMessage::BodyParams map; map.insert(std::make_pair(Ssl::CrtdMessage::param_host, host)); + /*Append parameters for cert adaptation*/ + map.insert(certAdaptParams.begin(), certAdaptParams.end()); std::string bufferToWrite; Ssl::writeCertAndPrivateKeyToMemory(port->signingCert, port->signPkey, bufferToWrite); if (bumpServerCert.get()) { @@ -3665,11 +3722,12 @@ ConnStateData::getSslContextStart() debugs(33, 5, HERE << "Append Mimic Certificate to body request: " << bufferToWrite); } request_message.composeBody(map, bufferToWrite); + debugs(33, 5, HERE << "SSL crtd request: " << request_message.compose().c_str()); Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); return; #else debugs(33, 5, HERE << "Generating SSL certificate for " << host); - dynCtx = Ssl::generateSslContext(host, bumpServerCert, port->signingCert, port->signPkey); + dynCtx = Ssl::generateSslContext(host, bumpServerCert, port->signingCert, port->signPkey, certAdaptParams); getSslContextDone(dynCtx, true); return; #endif //USE_SSL_CRTD @@ -3686,13 +3744,14 @@ ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); - if (sslContext && sslHostName != "") { - if (!ssl_ctx_cache.add(sslHostName.termedBuf(), sslContext)) { + assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); + if (sslContext) { + if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), 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 " << sslHostName); + debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslConnectHostOrIp); } } @@ -3725,7 +3784,8 @@ ConnStateData::switchToHttps(const char *host, const int port) { assert(!switchedToHttps_); - sslHostName = host; + sslConnectHostOrIp = host; + sslCommonName = host; //HTTPMSGLOCK(currentobject->http->request); assert(areAllContextsForThisConnection()); @@ -3741,7 +3801,7 @@ ConnStateData::switchToHttps(const char *host, const int port) const bool alwaysBumpServerFirst = true; if (alwaysBumpServerFirst) { Must(!httpsPeeker.set()); - httpsPeeker = new Ssl::ServerPeeker(this, sslHostName.termedBuf(), port); + httpsPeeker = new Ssl::ServerPeeker(this, sslConnectHostOrIp.termedBuf(), port); bumpErrorEntry = httpsPeeker->storeEntry(); Must(bumpErrorEntry); bumpErrorEntry->lock(); @@ -3750,7 +3810,7 @@ ConnStateData::switchToHttps(const char *host, const int port) return; } - // otherwise, use sslHostName + // otherwise, use sslConnectHostOrIp getSslContextStart(); } @@ -3764,16 +3824,16 @@ ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) assert(ssl); Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl)); assert(serverCert.get() != NULL); - sslHostName = Ssl::CommonHostName(serverCert.get()); - assert(sslHostName.defined()); - debugs(33, 5, HERE << "found HTTPS server " << sslHostName << " at bumped " << + sslCommonName = Ssl::CommonHostName(serverCert.get()); + assert(sslCommonName.defined()); + debugs(33, 5, HERE << "found HTTPS server CN " << sslCommonName << " at bumped " << *serverConnection); pinConnection(serverConnection, NULL, NULL, false); - debugs(33, 5, HERE << "bumped HTTPS server: " << sslHostName); + debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp); } else - debugs(33, 5, HERE << "Error while bumped HTTPS server: " << sslHostName); + debugs(33, 5, HERE << "Error while bumped HTTPS server: " << sslConnectHostOrIp); if (httpsPeeker.valid()) httpsPeeker->noteHttpsPeeked(serverConnection); diff --git a/src/client_side.h b/src/client_side.h index 8df9c59de7..b0d4591dad 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -335,6 +335,10 @@ public: StoreEntry *bumpServerFirstErrorEntry() const {return bumpErrorEntry;} void setBumpServerCert(X509 *serverCert) {bumpServerCert.reset(serverCert);} X509 *getBumpServerCert() {return bumpServerCert.get();} + void setBumpSslErrorList(Ssl::Errors &errNoList) {bumpSslErrorNoList = errNoList;} + /// Fill the certAdaptParams with the required data for certificate adaptation + /// and create the key for storing/retrieve the certificate to/from the cache + void buildSslCertAdaptParams(Ssl::CrtdMessage::BodyParams &certAdaptParams); bool serveDelayedError(ClientSocketContext *context); #else bool switchedToHttps() const { return false; } @@ -360,12 +364,16 @@ private: #if USE_SSL bool switchedToHttps_; - String sslHostName; ///< Host name for SSL certificate generation + /// The SSL server host name appears in CONNECT request or the server ip address for the intercepted requests + String sslConnectHostOrIp; ///< The SSL server host name as passed in the CONNECT request + String sslCommonName; ///< CN name for SSL certificate generation + String sslBumpCertKey; ///< Key to use to store/retrieve generated certificate /// a job that connects to the HTTPS server to get its SSL certificate CbcPointer httpsPeeker; StoreEntry *bumpErrorEntry; Ssl::X509_Pointer bumpServerCert; + Ssl::Errors bumpSslErrorNoList; ///< The list of SSL certificate errors which ignored #endif AsyncCall::Pointer reader; ///< set when we are reading diff --git a/src/forward.cc b/src/forward.cc index 5f940df8a0..d372803a03 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -668,6 +668,10 @@ FwdState::negotiateSSL(int fd) // Get the server certificate from ErrorDetail object and store it // to connection manager request->clientConnectionManager->setBumpServerCert(X509_dup(srvX509)); + + // if there is a list of ssl errors, pass it to connection manager + if (Ssl::Errors *errNoList = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_sslerrno))) + request->clientConnectionManager->setBumpSslErrorList(*errNoList); } HttpRequest *fakeRequest = NULL; @@ -697,8 +701,11 @@ FwdState::negotiateSSL(int fd) } } - if (request->clientConnectionManager.valid()) + if (request->clientConnectionManager.valid()) { request->clientConnectionManager->setBumpServerCert(SSL_get_peer_certificate(ssl)); + if (Ssl::Errors *errNoList = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_sslerrno))) + request->clientConnectionManager->setBumpSslErrorList(*errNoList); + } if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) { if (serverConnection()->getPeer()->sslSession) diff --git a/src/globals.h b/src/globals.h index 048be61382..c590922e91 100644 --- a/src/globals.h +++ b/src/globals.h @@ -156,6 +156,7 @@ extern "C" { extern int ssl_ex_index_cert_error_check; /* -1 */ extern int ssl_ex_index_ssl_error_detail; /* -1 */ extern int ssl_ex_index_ssl_peeked_cert; /* -1 */ + extern int ssl_ex_index_ssl_error_sslerrno; /* -1 */ extern const char *external_acl_message; /* NULL */ extern int opt_send_signal; /* -1 */ diff --git a/src/ssl/certificate_db.cc b/src/ssl/certificate_db.cc index c2237f1874..d7f2955c03 100644 --- a/src/ssl/certificate_db.cc +++ b/src/ssl/certificate_db.cc @@ -203,7 +203,7 @@ bool Ssl::CertificateDb::find(std::string const & host_name, Ssl::X509_Pointer & return pure_find(host_name, cert, pkey); } -bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) +bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName) { const Locker locker(dbLock, Here); load(); @@ -224,7 +224,7 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP { TidyPointer subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0)); - if (pure_find(subject.get(), cert, pkey)) + if (pure_find(useName.empty() ? subject.get() : useName, cert, pkey)) return true; } // check db size. @@ -241,7 +241,9 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP ASN1_UTCTIME * tm = X509_get_notAfter(cert.get()); row.setValue(cnlExp_date, std::string(reinterpret_cast(tm->data), tm->length).c_str()); row.setValue(cnlFile, "unknown"); - { + if (!useName.empty()) + row.setValue(cnlName, useName.c_str()); + else { TidyPointer subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0)); row.setValue(cnlName, subject.get()); } diff --git a/src/ssl/certificate_db.h b/src/ssl/certificate_db.h index 3035251431..1d247c9cc9 100644 --- a/src/ssl/certificate_db.h +++ b/src/ssl/certificate_db.h @@ -96,7 +96,7 @@ public: /// Find certificate and private key for host name bool find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey); /// Save certificate to disk. - bool addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey); + bool addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName); /// Get a serial number to use for generating a new certificate. BIGNUM * getCurrentSerialNumber(); /// Create and initialize a database under the db_path diff --git a/src/ssl/crtd_message.cc b/src/ssl/crtd_message.cc index 1ade0a7cea..f23c1aa2bb 100644 --- a/src/ssl/crtd_message.cc +++ b/src/ssl/crtd_message.cc @@ -3,6 +3,7 @@ */ #include "config.h" +#include "ssl/gadgets.h" #include "ssl/crtd_message.h" #if HAVE_CSTDLIB #include @@ -175,3 +176,6 @@ void Ssl::CrtdMessage::composeBody(CrtdMessage::BodyParams const & map, std::str const std::string Ssl::CrtdMessage::code_new_certificate("new_certificate"); const std::string Ssl::CrtdMessage::param_host("host"); +const std::string Ssl::CrtdMessage::param_SetValidAfter(Ssl::CertAdaptAlgorithmStr[algSetValidAfter]); +const std::string Ssl::CrtdMessage::param_SetValidBefore(Ssl::CertAdaptAlgorithmStr[algSetValidBefore]); +const std::string Ssl::CrtdMessage::param_SetCommonName(Ssl::CertAdaptAlgorithmStr[algSetCommonName]); diff --git a/src/ssl/crtd_message.h b/src/ssl/crtd_message.h index 6fade2a26c..5891c3f207 100644 --- a/src/ssl/crtd_message.h +++ b/src/ssl/crtd_message.h @@ -65,6 +65,12 @@ public: 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; private: enum ParseState { BEFORE_CODE, diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc index 4c08d1ce28..bed17c327b 100644 --- a/src/ssl/gadgets.cc +++ b/src/ssl/gadgets.cc @@ -256,7 +256,31 @@ bool Ssl::generateSslCertificateAndPrivateKey(char const *host, Ssl::X509_Pointe return true; } -static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & caCert, Ssl::X509_Pointer const &certToMimic) +// Replace certs common name with the given +static bool replaceCommonName(Ssl::X509_Pointer & cert, const char *cn) +{ + X509_NAME *name = X509_get_subject_name(cert.get()); + if (!name) + return false; + // Remove the CN part: + int loc = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + X509_NAME_ENTRY *tmp = X509_NAME_get_entry(name, loc); + X509_NAME_delete_entry(name, loc); + X509_NAME_ENTRY_free(tmp); + + // Add a new CN + return X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_ASC, + (unsigned char *)cn, -1, -1, 0); +} + +const char *Ssl::CertAdaptAlgorithmStr[] = { + "setValidAfter", + "setValidBefore", + "setCommonName", + NULL +}; + +static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & caCert, Ssl::X509_Pointer const &certToMimic, Ssl::CrtdMessage::BodyParams const &exceptions) { // not an Ssl::X509_NAME_Pointer because X509_REQ_get_subject_name() // returns a pointer to the existing subject name. Nothing to clean here. @@ -266,27 +290,46 @@ static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & // X509_set_subject_name will call X509_dup for name X509_set_subject_name(cert.get(), name); + Ssl::CrtdMessage::BodyParams::const_iterator it; + it = exceptions.find(sslCertAdaptAlgoritm(Ssl::algSetCommonName)); + if (it != exceptions.end()) { + // In this case the CN of the certificate given by the exceptions + // and should be replaced + const char *cn = it->second.c_str(); + if (cn && !replaceCommonName(cert, cn)) + return false; + } // We should get caCert notBefore and notAfter fields and do not allow // notBefore/notAfter values from certToMimic before/after notBefore/notAfter // fields from caCert. // Currently there is not any way in openssl tollkit to compare two ASN1_TIME // objects. - ASN1_TIME *aTime; - if ((aTime = X509_get_notBefore(certToMimic.get())) || (aTime = X509_get_notBefore(caCert.get())) ) { + ASN1_TIME *aTime = NULL; + it = exceptions.find(sslCertAdaptAlgoritm(Ssl::algSetValidBefore)); + if (it == exceptions.end() || strcasecmp(it->second.c_str(), "on") != 0) + aTime = X509_get_notBefore(certToMimic.get()); + if (!aTime) + aTime = X509_get_notBefore(caCert.get()); + + if (aTime) { if (!X509_set_notBefore(cert.get(), aTime)) return false; } else if (!X509_gmtime_adj(X509_get_notBefore(cert.get()), (-2)*24*60*60)) return false; - if ((aTime = X509_get_notAfter(certToMimic.get())) || (aTime = X509_get_notAfter(caCert.get())) ) { + aTime = NULL; + it = exceptions.find(sslCertAdaptAlgoritm(Ssl::algSetValidAfter)); + if (it == exceptions.end() || strcasecmp(it->second.c_str(), "on") != 0) + aTime = X509_get_notAfter(certToMimic.get()); + if (!aTime) + aTime = X509_get_notAfter(caCert.get()); + if (aTime) { if (!X509_set_notAfter(cert.get(), aTime)) return NULL; } else if (!X509_gmtime_adj(X509_get_notAfter(cert.get()), 60*60*24*356*3)) return NULL; - - unsigned char *alStr; int alLen; alStr = X509_alias_get0(certToMimic.get(), &alLen); @@ -303,7 +346,7 @@ static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & return true; } -bool Ssl::generateSslCertificate(Ssl::X509_Pointer const &certToMimic, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkey, BIGNUM const * serial) +bool Ssl::generateSslCertificate(Ssl::X509_Pointer const &certToMimic, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkey, BIGNUM const * serial, Ssl::CrtdMessage::BodyParams const &mimicExceptions) { if (!certToMimic.get()) return false; @@ -323,7 +366,7 @@ bool Ssl::generateSslCertificate(Ssl::X509_Pointer const &certToMimic, Ssl::X509 return false; // inherit properties from certToMimic - if (!mimicCertificate(cert, signedX509, certToMimic)) + if (!mimicCertificate(cert, signedX509, certToMimic, mimicExceptions)) return false; // Set issuer name, from CA or our subject name for self signed cert diff --git a/src/ssl/gadgets.h b/src/ssl/gadgets.h index 97f60a129a..e8cf163fe7 100644 --- a/src/ssl/gadgets.h +++ b/src/ssl/gadgets.h @@ -6,6 +6,7 @@ #define SQUID_SSL_GADGETS_H #include "base/TidyPointer.h" +#include "ssl/crtd_message.h" #if HAVE_OPENSSL_SSL_H #include @@ -131,6 +132,30 @@ X509 * signRequest(X509_REQ_Pointer const & request, X509_Pointer const & x509, */ bool generateSslCertificateAndPrivateKey(char const *host, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const* serial); +/** + \ingroup SslCrtdSslAPI + * Supported certificate adaptation algorithms + */ +enum CertAdaptAlgorithm {algSetValidAfter = 0, algSetValidBefore, algSetCommonName, algEnd}; + +/** + \ingroup SslCrtdSslAPI + * Short names for certificate adaptation algorithms + */ +extern const char *CertAdaptAlgorithmStr[]; + +/** + \ingroup SslCrtdSslAPI + * Return the short name of the adaptation algorithm "alg" + */ +inline const char *sslCertAdaptAlgoritm(int alg) +{ + if (alg >=0 && alg < Ssl::algEnd) + return Ssl::CertAdaptAlgorithmStr[alg]; + + return NULL; +} + /** \ingroup SslCrtdSslAPI * Decide on the kind of certificate and generate a CA- or self-signed one. @@ -138,7 +163,7 @@ bool generateSslCertificateAndPrivateKey(char const *host, X509_Pointer const & * Return generated certificate and private key in resultX509 and resultPkey * variables. */ -bool generateSslCertificate(X509_Pointer const &certToMimic, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const * serial); +bool generateSslCertificate(X509_Pointer const &certToMimic, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const * serial, CrtdMessage::BodyParams const & mimicExceptions); /** \ingroup SslCrtdSslAPI diff --git a/src/ssl/ssl_crtd.cc b/src/ssl/ssl_crtd.cc index 1057284404..6ba0194448 100644 --- a/src/ssl/ssl_crtd.cc +++ b/src/ssl/ssl_crtd.cc @@ -243,6 +243,20 @@ static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::st if (cert_subject.empty()) cert_subject = "/CN=" + host; + i = map.find(Ssl::CrtdMessage::param_SetValidAfter); + if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) + cert_subject.append("+SetValidAfter=on"); + + i = map.find(Ssl::CrtdMessage::param_SetValidBefore); + if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) + cert_subject.append("+SetValidBefore=on"); + + i = map.find(Ssl::CrtdMessage::param_SetCommonName); + if (i != map.end()) { + cert_subject.append("+SetCommonName="); + cert_subject.append(i->second); + } + db.find(cert_subject, cert, pkey); if (cert.get() && certToMimic.get()) { @@ -262,13 +276,13 @@ static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::st Ssl::BIGNUM_Pointer serial(db.getCurrentSerialNumber()); if (certToMimic.get()) { - Ssl::generateSslCertificate(certToMimic, certToSign, pkeyToSign, cert, pkey, serial.get()); + Ssl::generateSslCertificate(certToMimic, certToSign, pkeyToSign, cert, pkey, serial.get(), map); } else if (!Ssl::generateSslCertificateAndPrivateKey(host.c_str(), certToSign, pkeyToSign, cert, pkey, serial.get())) throw std::runtime_error("Cannot create ssl certificate or private key."); - if (!db.addCertAndPrivateKey(cert, pkey) && db.IsEnabledDiskStore()) + if (!db.addCertAndPrivateKey(cert, pkey, cert_subject) && db.IsEnabledDiskStore()) throw std::runtime_error("Cannot add certificate to db."); } diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 8cb0d5e5bd..f50b9fb0bd 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -246,13 +246,28 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx) } if (!ok) { + Ssl::Errors *errNoList = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_sslerrno)); + if (!errNoList) { + errNoList = new Ssl::Errors; + if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_sslerrno, (void *)errNoList)) { + debugs(83, 2, "Failed to set ssl error_no in ssl_verify_cb: Certificate " << buffer); + delete errNoList; + errNoList = NULL; + } + } + + if (errNoList) // Append the err no to the SSL errors lists. + errNoList->push_back(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) { - Filled(check)->ssl_error = error_no; + ACLFilledChecklist *filledCheck = Filled(check); + filledCheck->sslErrorList.clear(); + filledCheck->sslErrorList.push_back(error_no); if (check->fastCheck() == ACCESS_ALLOWED) { debugs(83, 3, "bypassing SSL error " << error_no << " in " << buffer); ok = 1; @@ -580,6 +595,14 @@ ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *, delete errDetail; } +static void +ssl_free_SslErrNoList(void *, void *ptr, CRYPTO_EX_DATA *, + int, long, void *) +{ + Ssl::Errors *errNo = static_cast (ptr); + delete errNo; +} + // "free" function for X509 certificates static void ssl_free_X509(void *, void *ptr, CRYPTO_EX_DATA *, @@ -629,6 +652,7 @@ ssl_initialize(void) ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist); ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail); ssl_ex_index_ssl_peeked_cert = SSL_get_ex_new_index(0, (void *) "ssl_peeked_cert", NULL, NULL, &ssl_free_X509); + ssl_ex_index_ssl_error_sslerrno = SSL_get_ex_new_index(0, (void *) "ssl_error_sslerrno", NULL, NULL, &ssl_free_SslErrNoList); } /// \ingroup ServerProtocolSSLInternal @@ -1234,12 +1258,12 @@ SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data) return createSSLContext(cert, pkey); } -SSL_CTX * Ssl::generateSslContext(char const *host, Ssl::X509_Pointer const & mimicCert, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey) +SSL_CTX * Ssl::generateSslContext(char const *host, Ssl::X509_Pointer const & mimicCert, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::CrtdMessage::BodyParams const &mimicExceptions) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; if (mimicCert .get()) { - if (!generateSslCertificate(mimicCert, signedX509, signedPkey, cert, pkey, NULL)) + if (!generateSslCertificate(mimicCert, signedX509, signedPkey, cert, pkey, NULL, mimicExceptions)) return NULL; } else if (!generateSslCertificateAndPrivateKey(host, signedX509, signedPkey, cert, pkey, NULL)) { diff --git a/src/ssl/support.h b/src/ssl/support.h index e2b25b15ed..4e36848548 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -67,6 +67,10 @@ namespace Ssl { /// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE typedef int ssl_error_t; + +/// \ingroup ServerProtocolSSLAPI +/// SSL error codes in the order they were encountered +typedef std::vector Errors; } //namespace Ssl /// \ingroup ServerProtocolSSLAPI @@ -109,7 +113,7 @@ namespace Ssl \ingroup ServerProtocolSSLAPI * Decide on the kind of certificate and generate a CA- or self-signed one */ -SSL_CTX *generateSslContext(char const *host, Ssl::X509_Pointer const & mimicCert, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey); + SSL_CTX *generateSslContext(char const *host, Ssl::X509_Pointer const & mimicCert, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, CrtdMessage::BodyParams const & mimicExceptions); /** \ingroup ServerProtocolSSLAPI diff --git a/src/structs.h b/src/structs.h index 84952a2ebc..5e7525fc28 100644 --- a/src/structs.h +++ b/src/structs.h @@ -629,6 +629,7 @@ struct SquidConfig { char *flags; acl_access *cert_error; SSL_CTX *sslContext; + sslproxy_cert_adapt *cert_adapt; } ssl_client; #endif @@ -1301,6 +1302,15 @@ struct _store_rebuild_data { int zero_object_sz; }; +#if USE_SSL +struct _sslproxy_cert_adapt { + int alg; + char *param; + ACLList *aclList; + sslproxy_cert_adapt *next; +}; +#endif + class Logfile; #include "format/Format.h" diff --git a/src/typedefs.h b/src/typedefs.h index 1acfdb664f..ac61115715 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -114,6 +114,10 @@ typedef struct _link_list link_list; typedef struct _customlog customlog; +#if USE_SSL +typedef struct _sslproxy_cert_adapt sslproxy_cert_adapt; +#endif + #if SQUID_SNMP typedef variable_list *(oid_ParseFn) (variable_list *, snint *); -- 2.47.2