------------------------------------------------------------ revno: 12219 [merge] revision-id: chtsanti@users.sourceforge.net-20120718174554-7ic3sj9hv6v8xkvm parent: chtsanti@users.sourceforge.net-20120718174051-vela9spxqf40113h parent: chtsanti@users.sourceforge.net-20120718162147-r02wzp20mhlvdche committer: Christos Tsantilas branch nick: trunk timestamp: Wed 2012-07-18 20:45:54 +0300 message: author: Alex Rousskov , Christos Tsantilas SslBump: Support bump-ssl-server-first and mimic SSL server certificates. Summary: These changes allow Squid working in SslBump mode to peek at the origin server certificate and mimic peeked server certificate properties in the generated fake certificate, all prior to establishing a secure connection with the client: http://wiki.squid-cache.org/Features/BumpSslServerFirst http://wiki.squid-cache.org/Features/MimicSslServerCert The changes are required to bump intercepted SSL connections without excessive browser warnings. The changes allow to disable bumping of some intercepted SSL connections, forcing Squid to go into a TCP tunnel mode for those connections. The changes also empower end user to examine and either honor or bypass most origin SSL server certificate errors. Prior to these changes, the responsibility for ignoring certificate validation errors belonged exclusively to Squid, necessarily leaving users in the dark if errors are ignored/bypassed. Squid can still be configured to emulate old bump-ssl-client-first behavior. However, a manual revision of ssl_bump options is required during upgrade because ssl_bump no longer supports an implicit "negate the last one" rule (and it is risky to let Squid guess what the admin true intent was or mix old- and new-style rules). Finally, fake certificate generation has been significantly improved. The new code guarantees that all identically configured Squids receiving identical origin server certificates will generate identical fake certificates, even if those Squid instances are running on different hosts, at different times, and do not communicate with each other. Such stable, reproducible certificates are required for distributed, scalable, or fail-safe Squid deployment. Overall, the changes are meant to make SslBump more powerful and safer. The code has been tested in several independent labs. Specific major changes are highlighted below: Make bumping algorithm selectable using ACLs. Even though bump-server-first is an overall better method, bumping the client first is useful for backward compatibility and possibly for serving internal Squid objects (such as icons inside Squid error pages). The following example bumps special and most other requests only, using the old bump-client-first approach for the special requests only: ssl_bump client-first specialOnes ssl_bump server-first mostOthers ssl_bump none all It allow use the old ssl_bump syntax: ssl_bump allow/deny acl ... but warns the user to update squid configuration. Added sslproxy_cert_adapt squid.conf option to overwrite default mimicking behavior when generating SSL certificates. See squid.conf.documented. Added sslproxy_cert_sign squid.conf option to control how generated SSL certificates are signed. See squid.conf.documented. Added ssl::certHasExpired, ssl::certNotYetValid, ssl::certDomainMismatch, ssl::certUntrusted, and ssl::certSelfSign predefined ACLs to squid.conf. Do not require http[s]_port's key option to be set if cert option is given. The fixed behavior for bumped connections now matches squid.conf docs. Generate stable fake certificates by using signing and true certificate hashes as the serial number and by using the configured CA private key for all fake certificates. Use minimal, trusted certificate for serving SSL errors to the client instead of trying to mimic the broken true certificate (which results in double error for the user: browser error dialog plus Squid error page). To mimic "untrusted" true certificates, generate an untrusted CA certificate from the configured trusted CA certificate. This both reduces configuration effort (compared to a configuration option) and results in identical untrusted fake certificates given identical Squid configurations. Intelligent handling of CONNECT denials: Do not connect to origin servers unless CONNECT is successfully authenticated. Delay errors.Added sslproxy_cert_sign squid.conf option to control how generated SSL certificates are signed. See squid.conf.documented. Provide '%I' error page formatting code with enough information to avoid displaying '[unknown]' on SQUID_X509_V_ERR_DOMAIN_MISMATCH errors. Set logged status code (% bool +CbDataList::push_back_unique(C const &toAdd) +{ + CbDataList *last; + for (last = this; last->next; last = last->next) { + if (last->element == toAdd) + return false; + } + + last->next = new CbDataList(toAdd); + return true; +} + +template +CbDataList * +CbDataList::tail() +{ + CbDataList *last; + for (last = this; last->next; last = last->next); + return last; +} + + +template +bool CbDataList::find (C const &toFind) const { CbDataList const *node = NULL; === modified file 'src/AccessLogEntry.cc' --- src/AccessLogEntry.cc 2012-07-17 14:11:24 +0000 +++ src/AccessLogEntry.cc 2012-07-18 16:21:47 +0000 @@ -1,6 +1,14 @@ #include "squid.h" #include "AccessLogEntry.h" #include "HttpRequest.h" +#include "ssl/support.h" + +#if USE_SSL +AccessLogEntry::SslDetails::SslDetails(): user(NULL), bumpMode(::Ssl::bumpEnd) +{ +} +#endif /* USE_SSL */ + void AccessLogEntry::getLogClientIp(char *buf, size_t bufsz) const === modified file 'src/AccessLogEntry.h' --- src/AccessLogEntry.h 2012-07-18 00:12:18 +0000 +++ src/AccessLogEntry.h 2012-07-18 16:21:47 +0000 @@ -118,6 +118,17 @@ const char *opcode; } htcp; +#if USE_SSL + /// logging information specific to the SSL protocol + class SslDetails { + public: + SslDetails(); + + const char *user; ///< emailAddress from the SSL client certificate + int bumpMode; ///< whether and how the request was SslBumped + } ssl; +#endif + /** \brief This subclass holds log info for Squid internal stats * \todo Inner class declarations should be moved outside * \todo some details relevant to particular protocols need shuffling to other sub-classes === modified file 'src/AclRegs.cc' --- src/AclRegs.cc 2012-01-20 18:55:04 +0000 +++ src/AclRegs.cc 2012-02-24 13:17:24 +0000 @@ -137,7 +137,7 @@ #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"); === modified file 'src/ClientRequestContext.h' --- src/ClientRequestContext.h 2012-05-08 01:13:51 +0000 +++ src/ClientRequestContext.h 2012-06-19 21:51:49 +0000 @@ -47,7 +47,7 @@ */ bool sslBumpAccessCheck(); /// The callback function for ssl-bump access check list - void sslBumpAccessCheckDone(bool doSslBump); + void sslBumpAccessCheckDone(const allow_t &answer); #endif ClientHttpRequest *http; @@ -68,6 +68,8 @@ #if USE_SSL bool sslBumpCheckDone; #endif + ErrorState *error; ///< saved error page for centralized/delayed processing + bool readNextRequest; ///< whether Squid should read after error handling private: CBDATA_CLASS(ClientRequestContext); === modified file 'src/HttpRequest.cc' --- src/HttpRequest.cc 2012-07-17 14:25:06 +0000 +++ src/HttpRequest.cc 2012-07-18 16:21:47 +0000 @@ -531,6 +531,14 @@ errDetail = aDetail; } +void +HttpRequest::clearError() +{ + debugs(11, 7, HERE << "old error details: " << errType << '/' << errDetail); + errType = ERR_NONE; + errDetail = ERR_DETAIL_NONE; +} + const char *HttpRequest::packableURI(bool full_uri) const { if (full_uri) === modified file 'src/HttpRequest.h' --- src/HttpRequest.h 2012-07-18 00:12:18 +0000 +++ src/HttpRequest.h 2012-07-18 16:21:47 +0000 @@ -102,6 +102,7 @@ debugs(23, 3, "HttpRequest::SetHost() given IP: " << host_addr); host_is_numeric = 1; } + safe_free(canonical); // force its re-build }; inline const char* GetHost(void) const { return host; }; inline int GetHostIsNumeric(void) const { return host_is_numeric; }; @@ -123,6 +124,8 @@ /// sets error detail if no earlier detail was available void detailError(err_type aType, int aDetail); + /// clear error details, useful for retries/repeats + void clearError(); protected: void clean(); === modified file 'src/Makefile.am' --- src/Makefile.am 2012-07-17 14:25:06 +0000 +++ src/Makefile.am 2012-07-18 16:21:47 +0000 @@ -953,6 +953,7 @@ -e "s%[@]DEFAULT_SSL_DB_DIR[@]%$(DEFAULT_SSL_DB_DIR)%g" \ -e "s%[@]DEFAULT_ICON_DIR[@]%$(DEFAULT_ICON_DIR)%g" \ -e "s%[@]DEFAULT_CONFIG_DIR[@]%$(DEFAULT_CONFIG_DIR)%g" \ + -e "s%[@]DEFAULT_ERROR_DIR[@]%$(DEFAULT_ERROR_DIR)%g" \ -e "s%[@]DEFAULT_PREFIX[@]%$(DEFAULT_PREFIX)%g" \ -e "s%[@]DEFAULT_HOSTS[@]%$(DEFAULT_HOSTS)%g" \ -e "s%[@]SQUID[@]%SQUID\ $(VERSION)%g" \ === modified file 'src/Server.cc' --- src/Server.cc 2012-06-18 23:08:56 +0000 +++ src/Server.cc 2012-06-19 16:08:52 +0000 @@ -828,7 +828,7 @@ if (entry->isEmpty()) { debugs(11,9, HERE << "creating ICAP error entry after ICAP failure"); ErrorState *err = new ErrorState(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); - err->xerrno = ERR_DETAIL_ICAP_RESPMOD_EARLY; + err->detailError(ERR_DETAIL_ICAP_RESPMOD_EARLY); fwd->fail(err); fwd->dontRetry(true); } else if (request) { // update logged info directly @@ -862,7 +862,7 @@ page_id = ERR_ACCESS_DENIED; ErrorState *err = new ErrorState(page_id, HTTP_FORBIDDEN, request); - err->xerrno = ERR_DETAIL_RESPMOD_BLOCK_EARLY; + err->detailError(ERR_DETAIL_RESPMOD_BLOCK_EARLY); fwd->fail(err); fwd->dontRetry(true); @@ -901,7 +901,6 @@ ServerStateData::sendBodyIsTooLargeError() { ErrorState *err = new ErrorState(ERR_TOO_BIG, HTTP_FORBIDDEN, request); - err->xerrno = errno; fwd->fail(err); fwd->dontRetry(true); abortTransaction("Virgin body too large."); === modified file 'src/acl/Acl.h' --- src/acl/Acl.h 2012-05-08 01:21:10 +0000 +++ src/acl/Acl.h 2012-05-14 10:37:40 +0000 @@ -116,7 +116,32 @@ // Authentication ACL result states ACCESS_AUTH_REQUIRED, // Missing Credentials -} allow_t; +} aclMatchCode; + +/// \ingroup ACLAPI +/// ACL check answer; TODO: Rename to Acl::Answer +class allow_t { +public: + // not explicit: allow "aclMatchCode to allow_t" conversions (for now) + allow_t(const aclMatchCode aCode): code(aCode), kind(0) {} + + allow_t(): code(ACCESS_DUNNO), kind(0) {} + + bool operator ==(const aclMatchCode aCode) const { + return code == aCode; + } + + bool operator !=(const aclMatchCode aCode) const { + return !(*this == aCode); + } + + operator aclMatchCode() const { + return code; + } + + aclMatchCode code; ///< ACCESS_* code + int kind; ///< which custom access list verb matched +}; inline std::ostream & operator <<(std::ostream &o, const allow_t a) === modified file 'src/acl/Checklist.h' --- src/acl/Checklist.h 2012-06-17 01:19:07 +0000 +++ src/acl/Checklist.h 2012-06-28 18:26:44 +0000 @@ -181,9 +181,10 @@ virtual bool hasRequest() const = 0; virtual bool hasReply() const = 0; -protected: - virtual void checkCallback(allow_t answer); private: + /// Calls non-blocking check callback with the answer and destroys self. + void checkCallback(allow_t answer); + void checkAccessList(); void checkForAsync(); === modified file 'src/acl/FilledChecklist.cc' --- src/acl/FilledChecklist.cc 2012-01-20 18:55:04 +0000 +++ src/acl/FilledChecklist.cc 2012-06-28 18:26:44 +0000 @@ -12,31 +12,6 @@ CBDATA_CLASS_INIT(ACLFilledChecklist); -void -ACLFilledChecklist::checkCallback(allow_t answer) -{ - debugs(28, 5, HERE << this << " answer=" << answer); - -#if USE_AUTH - /* During reconfigure, we can end up not finishing call - * sequences into the auth code */ - - if (auth_user_request != NULL) { - /* the filled_checklist lock */ - auth_user_request = NULL; - // It might have been connection based - // In the case of sslBump we need to preserve authentication info - // XXX: need to re-evaluate this. ACL tests should not be playing with - // XXX: wider scoped TCP connection state, even if the helper lookup is stuck. - if (conn() && !conn()->switchedToHttps()) { - conn()->auth_user_request = NULL; - } - } -#endif - - ACLChecklist::checkCallback(answer); // may delete us -} - void * ACLFilledChecklist::operator new (size_t size) @@ -67,7 +42,7 @@ snmp_community(NULL), #endif #if USE_SSL - ssl_error(0), + sslErrors(NULL), #endif extacl_entry (NULL), conn_(NULL), @@ -97,6 +72,10 @@ cbdataReferenceDone(conn_); +#if USE_SSL + cbdataReferenceDone(sslErrors); +#endif + debugs(28, 4, HERE << "ACLFilledChecklist destroyed " << this); } @@ -179,7 +158,7 @@ snmp_community(NULL), #endif #if USE_SSL - ssl_error(0), + sslErrors(NULL), #endif extacl_entry (NULL), conn_(NULL), === modified file 'src/acl/FilledChecklist.h' --- src/acl/FilledChecklist.h 2011-12-30 01:24:57 +0000 +++ src/acl/FilledChecklist.h 2012-06-28 18:26:44 +0000 @@ -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,15 +66,13 @@ #endif #if USE_SSL - int ssl_error; + /// SSL [certificate validation] errors, in undefined order + Ssl::Errors *sslErrors; #endif ExternalACLEntry *extacl_entry; private: - virtual void checkCallback(allow_t answer); - -private: CBDATA_CLASS(ACLFilledChecklist); ConnStateData * conn_; /**< hack for ident and NTLM */ === modified file 'src/acl/SslError.cc' --- src/acl/SslError.cc 2012-01-20 18:55:04 +0000 +++ src/acl/SslError.cc 2012-06-19 21:51:49 +0000 @@ -11,7 +11,7 @@ int ACLSslErrorStrategy::match (ACLData * &data, ACLFilledChecklist *checklist) { - return data->match (checklist->ssl_error); + return data->match (checklist->sslErrors); } ACLSslErrorStrategy * === modified file 'src/acl/SslError.h' --- src/acl/SslError.h 2009-03-08 21:53:27 +0000 +++ src/acl/SslError.h 2012-01-24 10:03:18 +0000 @@ -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 @@ 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-01-20 18:55:04 +0000 +++ src/acl/SslErrorData.cc 2012-06-19 21:51:49 +0000 @@ -22,22 +22,26 @@ } bool -ACLSslErrorData::match(Ssl::ssl_error_t toFind) +ACLSslErrorData::match(const Ssl::Errors *toFind) { - return values->findAndTune (toFind); + for (const Ssl::Errors *err = toFind; err; err = err->next ) { + if (values->findAndTune(err->element)) + 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 CbDataList::CBDATA_CbDataList; +// template cbdata_type Ssl::Errors::CBDATA_CbDataList; /** \endcond */ wordlist * ACLSslErrorData::dump() { wordlist *W = NULL; - CbDataList *data = values; + Ssl::Errors *data = values; while (data != NULL) { wordlistAdd(&W, Ssl::GetErrorName(data->element)); @@ -50,14 +54,14 @@ void ACLSslErrorData::parse() { - CbDataList **Tail; + Ssl::Errors **Tail; char *t = NULL; for (Tail = &values; *Tail; Tail = &((*Tail)->next)); while ((t = strtokFile())) { - CbDataList *q = new CbDataList(Ssl::ParseErrorString(t)); + Ssl::Errors *q = Ssl::ParseErrorString(t); *(Tail) = q; - Tail = &q->next; + Tail = &q->tail()->next; } } @@ -67,7 +71,7 @@ return values == NULL; } -ACLData * +ACLSslErrorData * ACLSslErrorData::clone() const { /* Splay trees don't clone yet. */ === modified file 'src/acl/SslErrorData.h' --- src/acl/SslErrorData.h 2010-12-14 12:17:34 +0000 +++ src/acl/SslErrorData.h 2012-01-24 10:03:18 +0000 @@ -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,13 +22,13 @@ ACLSslErrorData(ACLSslErrorData const &); ACLSslErrorData &operator= (ACLSslErrorData const &); virtual ~ACLSslErrorData(); - bool match(Ssl::ssl_error_t); + bool match(const Ssl::Errors *); wordlist *dump(); void parse(); bool empty() const; - virtual ACLData *clone() const; + virtual ACLSslErrorData *clone() const; - CbDataList *values; + Ssl::Errors *values; }; MEMPROXY_CLASS_INLINE(ACLSslErrorData); === modified file 'src/adaptation/Iterator.cc' --- src/adaptation/Iterator.cc 2012-01-20 18:55:04 +0000 +++ src/adaptation/Iterator.cc 2012-04-20 17:14:56 +0000 @@ -57,6 +57,12 @@ return; } + HttpRequest *request = dynamic_cast(theMsg); + if (!request) + request = theCause; + assert(request); + request->clearError(); + if (iterations > Adaptation::Config::service_iteration_limit) { debugs(93,DBG_CRITICAL, "Adaptation iterations limit (" << Adaptation::Config::service_iteration_limit << ") exceeded:\n" << === modified file 'src/adaptation/icap/Launcher.cc' --- src/adaptation/icap/Launcher.cc 2012-01-20 18:55:04 +0000 +++ src/adaptation/icap/Launcher.cc 2012-04-20 17:14:56 +0000 @@ -43,8 +43,10 @@ debugs(93,4, HERE << "launching " << xkind << " xaction #" << theLaunches); Adaptation::Icap::Xaction *x = createXaction(); x->attempts = theLaunches; - if (theLaunches > 1) + if (theLaunches > 1) { + x->clearError(); x->disableRetries(); + } if (theLaunches >= TheConfig.repeat_limit) x->disableRepeats("over icap_retry_limit"); theXaction = initiateAdaptation(x); === modified file 'src/adaptation/icap/ModXact.cc' --- src/adaptation/icap/ModXact.cc 2012-07-17 14:11:24 +0000 +++ src/adaptation/icap/ModXact.cc 2012-07-18 16:21:47 +0000 @@ -1923,6 +1923,17 @@ request->detailError(ERR_ICAP_FAILURE, errDetail); } +void Adaptation::Icap::ModXact::clearError() +{ + HttpRequest *request = dynamic_cast(adapted.header); + // if no adapted request, update virgin (and inherit its properties later) + if (!request) + request = const_cast(&virginRequest()); + + if (request) + request->clearError(); +} + /* Adaptation::Icap::ModXactLauncher */ Adaptation::Icap::ModXactLauncher::ModXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, Adaptation::ServicePointer aService): === modified file 'src/adaptation/icap/ModXact.h' --- src/adaptation/icap/ModXact.h 2011-05-13 10:38:28 +0000 +++ src/adaptation/icap/ModXact.h 2012-06-19 21:51:49 +0000 @@ -168,6 +168,8 @@ /// record error detail in the virgin request if possible virtual void detailError(int errDetail); + // Icap::Xaction API + virtual void clearError(); private: virtual void start(); === modified file 'src/adaptation/icap/Xaction.h' --- src/adaptation/icap/Xaction.h 2012-07-17 14:11:24 +0000 +++ src/adaptation/icap/Xaction.h 2012-07-18 16:21:47 +0000 @@ -134,6 +134,8 @@ // custom exception handling and end-of-call checks virtual void callException(const std::exception &e); virtual void callEnd(); + /// clear stored error details, if any; used for retries/repeats + virtual void clearError() {} void dnsLookupDone(const ipcache_addrs *ia); protected: === modified file 'src/anyp/PortCfg.cc' --- src/anyp/PortCfg.cc 2012-04-25 05:29:20 +0000 +++ src/anyp/PortCfg.cc 2012-05-14 10:37:40 +0000 @@ -4,6 +4,9 @@ #if HAVE_LIMITS #include #endif +#if USE_SSL +#include "ssl/support.h" +#endif CBDATA_NAMESPACED_CLASS_INIT(AnyP, PortCfg); @@ -89,3 +92,42 @@ return b; } + +#if USE_SSL +void AnyP::PortCfg::configureSslServerContext() +{ + staticSslContext.reset( + sslCreateServerContext(cert, key, + version, cipher, options, sslflags, clientca, + cafile, capath, crlfile, dhfile, + sslContextSessionId)); + + if (!staticSslContext) { + char buf[128]; + fatalf("%s_port %s initialization error", protocol, s.ToURL(buf, sizeof(buf))); + } + + if (!sslBump) + return; + + if (cert) + Ssl::readCertChainAndPrivateKeyFromFiles(signingCert, signPkey, certsToChain, cert, key); + + if (!signingCert) { + char buf[128]; + fatalf("No valid signing SSL certificate configured for %s_port %s", protocol, s.ToURL(buf, sizeof(buf))); + } + + if (!signPkey) + debugs(3, DBG_IMPORTANT, "No SSL private key configured for " << protocol << "_port " << s); + + Ssl::generateUntrustedCert(untrustedSigningCert, untrustedSignPkey, + signingCert, signPkey); + + if (!untrustedSigningCert) { + char buf[128]; + fatalf("Unable to generate signing SSL certificate for untrusted sites for %s_port %s", protocol, s.ToURL(buf, sizeof(buf))); + } +} +#endif + === modified file 'src/anyp/PortCfg.h' --- src/anyp/PortCfg.h 2012-04-25 05:29:20 +0000 +++ src/anyp/PortCfg.h 2012-06-19 21:51:49 +0000 @@ -15,6 +15,10 @@ PortCfg(const char *aProtocol); ~PortCfg(); AnyP::PortCfg *clone() const; +#if USE_SSL + /// creates, configures, and validates SSL context and related port options + void configureSslServerContext(); +#endif PortCfg *next; @@ -70,6 +74,8 @@ Ssl::X509_Pointer signingCert; ///< x509 certificate for signing generated certificates Ssl::EVP_PKEY_Pointer signPkey; ///< private key for sighing generated certificates Ssl::X509_STACK_Pointer certsToChain; ///< x509 certificates to send with the generated cert + Ssl::X509_Pointer untrustedSigningCert; ///< x509 certificate for signing untrusted generated certificates + Ssl::EVP_PKEY_Pointer untrustedSignPkey; ///< private key for signing untrusted generated certificates #endif CBDATA_CLASS2(PortCfg); // namespaced === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2012-07-17 14:25:06 +0000 +++ src/cache_cf.cc 2012-07-18 16:21:47 +0000 @@ -55,6 +55,7 @@ #include "auth/Config.h" #include "auth/Scheme.h" #endif +#include "base/RunnersRegistry.h" #include "ConfigParser.h" #include "CpuAffinityMap.h" #include "DiskIO/DiskIOModule.h" @@ -159,6 +160,7 @@ static void parse_string(char **); static void default_all(void); static void defaults_if_none(void); +static void defaults_postscriptum(void); static int parse_line(char *); static void parse_obsolete(const char *); static void parseBytesLine(size_t * bptr, const char *units); @@ -202,6 +204,18 @@ static void dump_PortCfg(StoreEntry *, const char *, const AnyP::PortCfg *); static void free_PortCfg(AnyP::PortCfg **); +#if USE_SSL +static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign); +static void dump_sslproxy_cert_sign(StoreEntry *entry, const char *name, sslproxy_cert_sign *cert_sign); +static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign); +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); +static void parse_sslproxy_ssl_bump(acl_access **ssl_bump); +static void dump_sslproxy_ssl_bump(StoreEntry *entry, const char *name, acl_access *ssl_bump); +static void free_sslproxy_ssl_bump(acl_access **ssl_bump); +#endif /* USE_SSL */ + static void parse_b_size_t(size_t * var); static void parse_b_int64_t(int64_t * var); @@ -566,6 +580,8 @@ defaults_if_none(); + defaults_postscriptum(); + /* * We must call configDoConfigure() before leave_suid() because * configDoConfigure() is where we turn username strings into @@ -889,28 +905,16 @@ } for (AnyP::PortCfg *s = Config.Sockaddr.http; s != NULL; s = s->next) { - if (!s->cert && !s->key) + if (!s->sslBump) continue; debugs(3, 1, "Initializing http_port " << s->s << " SSL context"); - - s->staticSslContext.reset( - sslCreateServerContext(s->cert, s->key, - s->version, s->cipher, s->options, s->sslflags, s->clientca, - s->cafile, s->capath, s->crlfile, s->dhfile, - s->sslContextSessionId)); - - Ssl::readCertChainAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->certsToChain, s->cert, s->key); + s->configureSslServerContext(); } for (AnyP::PortCfg *s = Config.Sockaddr.https; s != NULL; s = s->next) { debugs(3, 1, "Initializing https_port " << s->s << " SSL context"); - - s->staticSslContext.reset( - sslCreateServerContext(s->cert, s->key, - s->version, s->cipher, s->options, s->sslflags, s->clientca, - s->cafile, s->capath, s->crlfile, s->dhfile, - s->sslContextSessionId)); + s->configureSslServerContext(); } #endif @@ -3741,6 +3745,21 @@ parse_port_option(s, token); } +#if USE_SSL + if (strcasecmp(protocol, "https") == 0) { + /* ssl-bump on https_port configuration requires either tproxy or intercept, and vice versa */ + const bool hijacked = s->spoof_client_ip || s->intercepted; + if (s->sslBump && !hijacked) { + debugs(3, DBG_CRITICAL, "FATAL: ssl-bump on https_port requires tproxy/intercept which is missing."); + self_destruct(); + } + if (hijacked && !s->sslBump) { + debugs(3, DBG_CRITICAL, "FATAL: tproxy/intercept on https_port requires ssl-bump which is missing."); + self_destruct(); + } + } +#endif + if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && s->s.IsAnyAddr()) { // clone the port options from *s to *(s->next) s->next = cbdataReference(s->clone()); @@ -4319,6 +4338,262 @@ cfg->oldest_service_failure = 0; cfg->service_failure_limit = 0; } +#endif + +#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) { + if (strlen(param) > 64) { + debugs(3, DBG_CRITICAL, "FATAL: sslproxy_cert_adapt: setCommonName{" <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); + } +} + +static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign) +{ + char *al; + sslproxy_cert_sign *cs = (sslproxy_cert_sign *) xcalloc(1, sizeof(sslproxy_cert_sign)); + if ((al = strtok(NULL, w_space)) == NULL) { + self_destruct(); + return; + } + + if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignTrusted]) == 0) + cs->alg = Ssl::algSignTrusted; + else if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignUntrusted]) == 0) + cs->alg = Ssl::algSignUntrusted; + else if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignSelf]) == 0) + cs->alg = Ssl::algSignSelf; + else { + debugs(3, DBG_CRITICAL, "FATAL: sslproxy_cert_sign: unknown cert signing algorithm: " << al); + self_destruct(); + return; + } + + aclParseAclList(LegacyParser, &cs->aclList); + + while(*cert_sign) + cert_sign = &(*cert_sign)->next; + + *cert_sign = cs; +} + +static void dump_sslproxy_cert_sign(StoreEntry *entry, const char *name, sslproxy_cert_sign *cert_sign) +{ + sslproxy_cert_sign *cs; + for (cs = cert_sign; cs != NULL; cs = cs->next) { + storeAppendPrintf(entry, "%s ", name); + storeAppendPrintf(entry, "%s ", Ssl::certSignAlgorithm(cs->alg)); + if (cs->aclList) + dump_acl_list(entry, cs->aclList); + storeAppendPrintf(entry, "\n"); + } +} + +static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign) +{ + while(*cert_sign) { + sslproxy_cert_sign *cs = *cert_sign; + *cert_sign = cs->next; + + if (cs->aclList) + aclDestroyAclList(&cs->aclList); + + safe_free(cs); + } +} + +class sslBumpCfgRr: public ::RegisteredRunner +{ +public: + static Ssl::BumpMode lastDeprecatedRule; + /* RegisteredRunner API */ + virtual void run(const RunnerRegistry &); +}; + +Ssl::BumpMode sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpEnd; + +RunnerRegistrationEntry(rrFinalizeConfig, sslBumpCfgRr); + +void sslBumpCfgRr::run(const RunnerRegistry &r) +{ + if (lastDeprecatedRule != Ssl::bumpEnd) { + assert( lastDeprecatedRule == Ssl::bumpClientFirst || lastDeprecatedRule == Ssl::bumpNone); + static char buf[1024]; + if (lastDeprecatedRule == Ssl::bumpClientFirst) { + strcpy(buf, "ssl_bump deny all"); + debugs(3, DBG_CRITICAL, "WARNING: auto-converting deprecated implicit " + "\"ssl_bump deny all\" to \"ssl_bump none all\". New ssl_bump configurations " + "must not use implicit rules. Update your ssl_bump rules."); + } else { + strcpy(buf, "ssl_bump allow all"); + debugs(3, DBG_CRITICAL, "SECURITY NOTICE: auto-converting deprecated implicit " + "\"ssl_bump allow all\" to \"ssl_bump client-first all\" which is usually " + "inferior to the newer server-first bumping mode. New ssl_bump" + " configurations must not use implicit rules. Update your ssl_bump rules."); + } + parse_line(buf); + } +} + +static void parse_sslproxy_ssl_bump(acl_access **ssl_bump) +{ + typedef const char *BumpCfgStyle; + BumpCfgStyle bcsNone = NULL; + BumpCfgStyle bcsNew = "new client/server-first/none"; + BumpCfgStyle bcsOld = "deprecated allow/deny"; + static BumpCfgStyle bumpCfgStyleLast = bcsNone; + BumpCfgStyle bumpCfgStyleNow = bcsNone; + char *bm; + if ((bm = strtok(NULL, w_space)) == NULL) { + self_destruct(); + return; + } + + // if this is the first rule proccessed + if (*ssl_bump == NULL) { + bumpCfgStyleLast = bcsNone; + sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpEnd; + } + + acl_access *A = new acl_access; + A->allow = allow_t(ACCESS_ALLOWED); + + if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpClientFirst]) == 0) { + A->allow.kind = Ssl::bumpClientFirst; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) { + A->allow.kind = Ssl::bumpServerFirst; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) { + A->allow.kind = Ssl::bumpNone; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, "allow") == 0) { + debugs(3, DBG_CRITICAL, "SECURITY NOTICE: auto-converting deprecated " + "\"ssl_bump allow \" to \"ssl_bump client-first \" which " + "is usually inferior to the newer server-first " + "bumping mode. Update your ssl_bump rules."); + A->allow.kind = Ssl::bumpClientFirst; + bumpCfgStyleNow = bcsOld; + sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpClientFirst; + } else if (strcmp(bm, "deny") == 0) { + debugs(3, DBG_CRITICAL, "WARNING: auto-converting deprecated " + "\"ssl_bump deny \" to \"ssl_bump none \". Update " + "your ssl_bump rules."); + A->allow.kind = Ssl::bumpNone; + bumpCfgStyleNow = bcsOld; + sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpNone; + } else { + debugs(3, DBG_CRITICAL, "FATAL: unknown ssl_bump mode: " << bm); + self_destruct(); + return; + } + + if (bumpCfgStyleLast != bcsNone && bumpCfgStyleNow != bumpCfgStyleLast) { + debugs(3, DBG_CRITICAL, "FATAL: do not mix " << bumpCfgStyleNow << " actions with " << + bumpCfgStyleLast << " actions. Update your ssl_bump rules."); + self_destruct(); + return; + } + + bumpCfgStyleLast = bumpCfgStyleNow; + + aclParseAclList(LegacyParser, &A->aclList); + + acl_access *B, **T; + for (B = *ssl_bump, T = ssl_bump; B; T = &B->next, B = B->next); + *T = A; +} + +static void dump_sslproxy_ssl_bump(StoreEntry *entry, const char *name, acl_access *ssl_bump) +{ + acl_access *sb; + for (sb = ssl_bump; sb != NULL; sb = sb->next) { + storeAppendPrintf(entry, "%s ", name); + storeAppendPrintf(entry, "%s ", Ssl::bumpMode(sb->allow.kind)); + if (sb->aclList) + dump_acl_list(entry, sb->aclList); + storeAppendPrintf(entry, "\n"); + } +} + +static void free_sslproxy_ssl_bump(acl_access **ssl_bump) +{ + free_acl_access(ssl_bump); +} #endif === modified file 'src/cf.data.depend' --- src/cf.data.depend 2012-07-17 14:25:06 +0000 +++ src/cf.data.depend 2012-07-18 16:21:47 +0000 @@ -69,3 +69,6 @@ wccp2_service wccp2_service_info wordlist +sslproxy_ssl_bump acl +sslproxy_cert_sign acl +sslproxy_cert_adapt acl === modified file 'src/cf.data.pre' --- src/cf.data.pre 2012-07-17 14:25:06 +0000 +++ src/cf.data.pre 2012-07-18 16:21:47 +0000 @@ -647,6 +647,13 @@ NAME: acl TYPE: acl LOC: Config.aclList +IF USE_SSL +DEFAULT: ssl::certHasExpired ssl_error X509_V_ERR_CERT_HAS_EXPIRED +DEFAULT: ssl::certNotYetValid ssl_error X509_V_ERR_CERT_NOT_YET_VALID +DEFAULT: ssl::certDomainMismatch ssl_error SQUID_X509_V_ERR_DOMAIN_MISMATCH +DEFAULT: ssl::certUntrusted ssl_error X509_V_ERR_INVALID_CA X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY X509_V_ERR_CERT_UNTRUSTED +DEFAULT: ssl::certSelfSigned ssl_error X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT +ENDIF DEFAULT: all src all DEFAULT: manager url_regex -i ^cache_object:// +i ^https?://[^/]+/squid-internal-mgr/ DEFAULT: localhost src 127.0.0.1/32 ::1 @@ -871,6 +878,29 @@ # effect in rules that affect the reply data stream such as # http_reply_access. +IF USE_SSL + acl aclname ssl_error errorname + # match against SSL certificate validation error [fast] + # + # For valid error names see in @DEFAULT_ERROR_DIR@/templates/error-details.txt + # template file. + # + # The following can be used as shortcuts for certificate properties: + # [ssl::]certHasExpired: the "not after" field is in the past + # [ssl::]certNotYetValid: the "not before" field is in the future + # [ssl::]certUntrusted: The certificate issuer is not to be trusted. + # [ssl::]certSelfSigned: The certificate is self signed. + # [ssl::]certDomainMismatch: The certificate CN domain does not + # match the name the name of the host we are connecting to. + # + # The ssl::certHasExpired, ssl::certNotYetValid, ssl::certDomainMismatch, + # ssl::certUntrusted, and ssl::certSelfSigned can also be used as + # predefined ACLs, just like the 'all' ACL. + # + # NOTE: The ssl_error ACL is only supported with sslproxy_cert_error, + # sslproxy_cert_sign, and sslproxy_cert_adapt options. +ENDIF + Examples: acl macaddress arp 09:00:2b:23:45:67 acl myexample dst_as 1241 @@ -1316,14 +1346,14 @@ accel Accelerator / reverse proxy mode - ssl-bump Intercept each CONNECT request matching ssl_bump ACL, + ssl-bump For each CONNECT request allowed by ssl_bump ACLs, establish secure connection with the client and with - the server, decrypt HTTP messages as they pass through + the server, decrypt HTTPS messages as they pass through Squid, and treat them as unencrypted HTTP messages, becoming the man-in-the-middle. The ssl_bump option is required to fully enable - the SslBump feature. + bumping of CONNECT requests. Omitting the mode flag causes default forward proxy mode to be used. @@ -1386,8 +1416,7 @@ dynamic_cert_mem_cache_size=SIZE Approximate total RAM size spent on cached generated certificates. If set to zero, caching is disabled. The - default value is 4MB. An average XXX-bit certificate - consumes about XXX bytes of RAM. + default value is 4MB. TLS / SSL Options: @@ -1529,6 +1558,117 @@ You may specify multiple socket addresses on multiple lines, each with their own SSL certificate and/or options. + Modes: + + accel Accelerator / reverse proxy mode + + intercept Support for IP-Layer interception of + outgoing requests without browser settings. + NP: disables authentication and IPv6 on the port. + + tproxy Support Linux TPROXY for spoofing outgoing + connections using the client IP address. + NP: disables authentication and maybe IPv6 on the port. + + ssl-bump For each intercepted connection allowed by ssl_bump + ACLs, establish a secure connection with the client and with + the server, decrypt HTTPS messages as they pass through + Squid, and treat them as unencrypted HTTP messages, + becoming the man-in-the-middle. + + An "ssl_bump server-first" match is required to + fully enable bumping of intercepted SSL connections. + + Requires tproxy or intercept. + + Omitting the mode flag causes default forward proxy mode to be used. + + + See http_port for a list of generic options + + + SSL Options: + + cert= Path to SSL certificate (PEM format). + + key= Path to SSL private key file (PEM format) + if not specified, the certificate file is + assumed to be a combined certificate and + key file. + + version= The version of SSL/TLS supported + 1 automatic (default) + 2 SSLv2 only + 3 SSLv3 only + 4 TLSv1 only + + cipher= Colon separated list of supported ciphers. + + options= Various SSL engine options. The most important + being: + NO_SSLv2 Disallow the use of SSLv2 + NO_SSLv3 Disallow the use of SSLv3 + NO_TLSv1 Disallow the use of TLSv1 + SINGLE_DH_USE Always create a new key when using + temporary/ephemeral DH key exchanges + See src/ssl_support.c or OpenSSL SSL_CTX_set_options + documentation for a complete list of options. + + clientca= File containing the list of CAs to use when + requesting a client certificate. + + cafile= File containing additional CA certificates to + use when verifying client certificates. If unset + clientca will be used. + + capath= Directory containing additional CA certificates + and CRL lists to use when verifying client certificates. + + crlfile= File of additional CRL lists to use when verifying + the client certificate, in addition to CRLs stored in + the capath. Implies VERIFY_CRL flag below. + + dhparams= File containing DH parameters for temporary/ephemeral + DH key exchanges. + + sslflags= Various flags modifying the use of SSL: + DELAYED_AUTH + Don't request client certificates + immediately, but wait until acl processing + requires a certificate (not yet implemented). + NO_DEFAULT_CA + Don't use the default CA lists built in + to OpenSSL. + NO_SESSION_REUSE + Don't allow for session reuse. Each connection + will result in a new SSL session. + VERIFY_CRL + Verify CRL lists when accepting client + certificates. + VERIFY_CRL_ALL + Verify CRL lists for all certificates in the + client certificate chain. + + sslcontext= SSL session ID context identifier. + + generate-host-certificates[=] + Dynamically create SSL server certificates for the + destination hosts of bumped SSL requests.When + enabled, the cert and key options are used to sign + generated certificates. Otherwise generated + certificate will be selfsigned. + If there is CA certificate life time of generated + certificate equals lifetime of CA certificate. If + generated certificate is selfsigned lifetime is three + years. + This option is enabled by default when SslBump is used. + See the sslBump option above for more information. + + dynamic_cert_mem_cache_size=SIZE + Approximate total RAM size spent on cached generated + certificates. If set to zero, caching is disabled. The + default value is 4MB. + See http_port for a list of available options. DOC_END @@ -1944,32 +2084,60 @@ NAME: ssl_bump IFDEF: USE_SSL -TYPE: acl_access +TYPE: sslproxy_ssl_bump LOC: Config.accessList.ssl_bump DEFAULT: none DOC_START - This ACL controls which CONNECT requests to an http_port - marked with an sslBump flag are actually "bumped". Please - see the sslBump flag of an http_port option for more details - about decoding proxied SSL connections. - - By default, no requests are bumped. - - See also: http_port ssl-bump - + This option is consulted when a CONNECT request is received on + an http_port (or a new connection is intercepted at an + https_port), provided that port was configured with an ssl-bump + flag. The subsequent data on the connection is either treated as + HTTPS and decrypted OR tunneled at TCP level without decryption, + depending on the first bumping "mode" which ACLs match. + + ssl_bump [!]acl ... + + The following bumping modes are supported: + + client-first + Allow bumping of the connection. Establish a secure connection + with the client first, then connect to the server. This old mode + does not allow Squid to mimic server SSL certificate and does + not work with intercepted SSL connections. + + server-first + Allow bumping of the connection. Establish a secure connection + with the server first, then establish a secure connection with + the client, using a mimicked server certificate. Works with both + CONNECT requests and intercepted SSL connections. + + none + Become a TCP tunnel without decoding the connection. + Works with both CONNECT requests and intercepted SSL + connections. This is the default behavior when no + ssl_bump option is given or no ssl_bump ACLs match. + + By default, no connections are bumped. + + The first matching ssl_bump option wins. If no ACLs match, the + connection is not bumped. Unlike most allow/deny ACL lists, ssl_bump + does not have an implicit "negate the last given option" rule. You + must make that rule explicit if you convert old ssl_bump allow/deny + rules that rely on such an implicit rule. + This clause supports both fast and slow acl types. See http://wiki.squid-cache.org/SquidFaq/SquidAcl for details. - - # Example: Bump all requests except those originating from localhost and - # those going to webax.com or example.com sites. - - acl localhost src 127.0.0.1/32 - acl broken_sites dstdomain .webax.com + See also: http_port ssl-bump, https_port ssl-bump + + + # Example: Bump all requests except those originating from + # localhost and those going to example.com. + acl broken_sites dstdomain .example.com - ssl_bump deny localhost - ssl_bump deny broken_sites - ssl_bump allow all + ssl_bump none localhost + ssl_bump none broken_sites + ssl_bump server-first all DOC_END NAME: sslproxy_flags @@ -2015,6 +2183,89 @@ Default setting: sslproxy_cert_error deny all DOC_END +NAME: sslproxy_cert_sign +IFDEF: USE_SSL +DEFAULT: none +POSTSCRIPTUM: signUntrusted ssl::certUntrusted +POSTSCRIPTUM: signSelf ssl::certSelfSigned +POSTSCRIPTUM: signTrusted all +TYPE: sslproxy_cert_sign +LOC: Config.ssl_client.cert_sign +DOC_START + + sslproxy_cert_sign acl ... + + The following certificate signing algorithms are supported: + signTrusted + Sign using the configured CA certificate which is usually + placed in and trusted by end-user browsers. This is the + default for trusted origin server certificates. + signUntrusted + Sign to guarantee an X509_V_ERR_CERT_UNTRUSTED browser error. + This is the default for untrusted origin server certificates + that are not self-signed (see ssl::certUntrusted). + signSelf + Sign using a self-signed certificate with the right CN to + generate a X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT error in the + browser. This is the default for self-signed origin server + certificates (see ssl::certSelfSigned). + + This clause only supports fast acl types. + + When sslproxy_cert_sign acl(s) match, Squid uses the corresponding + signing algorithm to generate the certificate and ignores all + subsequent sslproxy_cert_sign options (the first match wins). If no + acl(s) match, the default signing algorithm is determined by errors + detected when obtaining and validating the origin server certificate. + + WARNING: SQUID_X509_V_ERR_DOMAIN_MISMATCH and ssl:certDomainMismatch can + be used with sslproxy_cert_adapt, but if and only if Squid is bumping a + CONNECT request that carries a domain name. In all other cases (CONNECT + to an IP address or an intercepted SSL connection), Squid cannot detect + the domain mismatch at certificate generation time when + bump-server-first is used. +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 are supported: + setValidAfter + Sets the "Not After" property to the "Not After" property 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 or setCommonName{CN} + Sets Subject.CN property to the host name specified as a + CN parameter or, if no explicit CN parameter was specified, + extracted from the CONNECT request. It is a misconfiguration + to use setCommonName without an explicit parameter for + intercepted or tproxied SSL connections. + + This clause only supports fast acl types. + + Squid first groups sslproxy_cert_adapt options by adaptation algorithm. + Within a group, when sslproxy_cert_adapt acl(s) match, Squid uses the + corresponding adaptation algorithm to generate the certificate and + ignores all subsequent sslproxy_cert_adapt options in that algorithm's + group (i.e., the first match wins within each algorithm group). If no + acl(s) match, the default mimicking action takes place. + + WARNING: SQUID_X509_V_ERR_DOMAIN_MISMATCH and ssl:certDomainMismatch can + be used with sslproxy_cert_adapt, but if and only if Squid is bumping a + CONNECT request that carries a domain name. In all other cases (CONNECT + to an IP address or an intercepted SSL connection), Squid cannot detect + the domain mismatch at certificate generation time when + bump-server-first is used. +DOC_END + NAME: sslpassword_program IFDEF: USE_SSL DEFAULT: none @@ -3091,6 +3342,24 @@ Ss Squid request status (TCP_MISS etc) Sh Squid hierarchy status (DEFAULT_PARENT etc) + SSL-related format codes: + + ssl::bump_mode SslBump decision for the transaction: + + For CONNECT requests that initiated bumping of + a connection and for any request received on + an already bumped connection, Squid logs the + corresponding SslBump mode ("server-first" or + "client-first"). See the ssl_bump option for + more information about these modes. + + A "none" token is logged for requests that + triggered "ssl_bump" ACL evaluation matching + either a "none" rule or no rules at all. + + In all other cases, a single dash ("-") is + logged. + If ICAP is enabled, the following code becomes available (as well as ICAP log codes documented with the icap_log option): === modified file 'src/cf_gen.cc' --- src/cf_gen.cc 2011-08-22 06:36:16 +0000 +++ src/cf_gen.cc 2012-07-18 15:19:04 +0000 @@ -64,6 +64,7 @@ #include #include #include +#include #include "cf_gen_defines.cci" @@ -99,6 +100,9 @@ /// An error will be printed during build if they clash. LineList if_none; + /// Default config lines to parse and add to any prior settings. + LineList postscriptum; + /// Text description to use in documentation for the default. /// If unset the preset or if-none values will be displayed. LineList docs; @@ -151,6 +155,8 @@ static void gen_free(const EntryList &, std::ostream&); static void gen_conf(const EntryList &, std::ostream&, bool verbose_output); static void gen_default_if_none(const EntryList &, std::ostream&); +static void gen_default_postscriptum(const EntryList &, std::ostream&); +static bool isDefined(const std::string &name); static void checkDepend(const std::string &directive, const char *name, const TypeList &types, const EntryList &entries) @@ -198,6 +204,7 @@ char *ptr = NULL; char buff[MAX_LINE]; std::ifstream fp; + std::stack IFDEFS; if (argc != 3) usage(argv[0]); @@ -252,6 +259,21 @@ if ((t = strchr(buff, '\n'))) *t = '\0'; + if(strncmp(buff, "IF ", 3) == 0) { + if ((ptr = strtok(buff + 3, WS)) == NULL) { + std::cerr << "Missing IF parameter on line" << linenum << std::endl; + exit(1); + } + IFDEFS.push(ptr); + continue; + } else if (strcmp(buff, "ENDIF") == 0) { + if (IFDEFS.size() == 0) { + std::cerr << "ENDIF without IF before on line " << linenum << std::endl; + exit(1); + } + IFDEFS.pop(); + } + else if (!IFDEFS.size() || isDefined(IFDEFS.top())) switch (state) { case sSTART: @@ -314,6 +336,13 @@ ptr++; curr.defaults.if_none.push_back(ptr); + } else if (!strncmp(buff, "POSTSCRIPTUM:", 13)) { + ptr = buff + 13; + + while (isspace((unsigned char)*ptr)) + ptr++; + + curr.defaults.postscriptum.push_back(ptr); } else if (!strncmp(buff, "DEFAULT_DOC:", 12)) { ptr = buff + 12; @@ -424,6 +453,8 @@ gen_default_if_none(entries, fout); + gen_default_postscriptum(entries, fout); + gen_parse(entries, fout); gen_dump(entries, fout); @@ -552,6 +583,36 @@ fout << "}" << std::endl << std::endl; } +/// append configuration options specified by POSTSCRIPTUM lines +static void +gen_default_postscriptum(const EntryList &head, std::ostream &fout) +{ + fout << "static void" << std::endl << + "defaults_postscriptum(void)" << std::endl << + "{" << std::endl; + + for (EntryList::const_iterator entry = head.begin(); entry != head.end(); ++entry) { + assert(entry->name.size()); + + if (!entry->loc.size()) + continue; + + if (entry->defaults.postscriptum.empty()) + continue; + + if (entry->ifdef.size()) + fout << "#if " << entry->ifdef << std::endl; + + for (LineList::const_iterator l = entry->defaults.postscriptum.begin(); l != entry->defaults.postscriptum.end(); ++l) + fout << " default_line(\"" << entry->name << " " << *l <<"\");" << std::endl; + + if (entry->ifdef.size()) + fout << "#endif" << std::endl; + } + + fout << "}" << std::endl << std::endl; +} + void Entry::genParseAlias(const std::string &aName, std::ostream &fout) const { === modified file 'src/client_side.cc' --- src/client_side.cc 2012-07-17 14:25:06 +0000 +++ src/client_side.cc 2012-07-18 16:21:47 +0000 @@ -105,8 +105,10 @@ #include "comm/Loops.h" #include "comm/Write.h" #include "comm/TcpAcceptor.h" +#include "errorpage.h" #include "eui/Config.h" #include "fde.h" +#include "forward.h" #include "HttpHdrContRange.h" #include "HttpReply.h" #include "HttpRequest.h" @@ -123,6 +125,7 @@ #if USE_SSL #include "ssl/context_storage.h" #include "ssl/helper.h" +#include "ssl/ServerBump.h" #include "ssl/support.h" #include "ssl/gadgets.h" #endif @@ -808,6 +811,10 @@ if (bodyPipe != NULL) stopProducingFor(bodyPipe, false); + +#if USE_SSL + delete sslServerBump; +#endif } /** @@ -2441,6 +2448,100 @@ readSomeData(); } +void +ConnStateData::quitAfterError(HttpRequest *request) +{ + // From HTTP p.o.v., we do not have to close after every error detected + // at the client-side, but many such errors do require closure and the + // client-side code is bad at handling errors so we play it safe. + if (request) + request->flags.proxy_keepalive = 0; + flags.readMore = false; + debugs(33,4, HERE << "Will close after error: " << clientConnection); +} + +#if USE_SSL +bool ConnStateData::serveDelayedError(ClientSocketContext *context) +{ + ClientHttpRequest *http = context->http; + + if (!sslServerBump) + return false; + + assert(sslServerBump->entry); + // Did we create an error entry while processing CONNECT? + if (!sslServerBump->entry->isEmpty()) { + quitAfterError(http->request); + + // Get the saved error entry and send it to the client by replacing the + // ClientHttpRequest store entry with it. + clientStreamNode *node = context->getClientReplyContext(); + 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 = HTTPMSGLOCK(http->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()) { + HttpRequest *request = http->request; + if (!Ssl::checkX509ServerValidity(sslServerBump->serverCert.get(), request->GetHost())) { + debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << + "does not match domainname " << request->GetHost()); + + ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str); + check.sslErrors = new Ssl::Errors(SQUID_X509_V_ERR_DOMAIN_MISMATCH); + if (Comm::IsConnOpen(pinning.serverConnection)) + check.fd(pinning.serverConnection->fd); + const bool 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_SERVICE_UNAVAILABLE, request); + err->src_addr = clientConnection->remote; + Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail( + SQUID_X509_V_ERR_DOMAIN_MISMATCH, + sslServerBump->serverCert.get(), NULL); + err->detail = errDetail; + // Save the original request for logging purposes. + if (!context->http->al->request) + context->http->al->request = HTTPMSGLOCK(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, HttpVersion http_ver) { @@ -2459,6 +2560,7 @@ if (context->flags.parsed_ok == 0) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 2, "clientProcessRequest: Invalid Request"); + conn->quitAfterError(NULL); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); @@ -2477,13 +2579,13 @@ } assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Invalid URL: " << http->uri); + conn->quitAfterError(request); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); @@ -2491,7 +2593,6 @@ repContext->setReplyToError(ERR_INVALID_URL, HTTP_BAD_REQUEST, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } @@ -2503,6 +2604,7 @@ clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp)); + conn->quitAfterError(request); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); @@ -2511,7 +2613,6 @@ conn->clientConnection->remote, NULL, HttpParserHdrBuf(hp), NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } @@ -2521,6 +2622,7 @@ if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp)); + conn->quitAfterError(request); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); @@ -2528,7 +2630,6 @@ repContext->setReplyToError(ERR_INVALID_REQ, HTTP_BAD_REQUEST, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } @@ -2537,8 +2638,11 @@ request->flags.accelerated = http->flags.accel; request->flags.sslBumped = conn->switchedToHttps(); + request->flags.canRePin = request->flags.sslBumped && conn->pinning.pinned; request->flags.ignore_cc = conn->port->ignore_cc; - request->flags.no_direct = request->flags.accelerated ? !conn->port->allow_direct : 0; + // TODO: decouple http->flags.accel from request->flags.sslBumped + request->flags.no_direct = (request->flags.accelerated && !request->flags.sslBumped) ? + !conn->port->allow_direct : 0; #if USE_AUTH if (request->flags.sslBumped) { if (conn->auth_user_request != NULL) @@ -2599,13 +2703,13 @@ (request->header.getInt64(HDR_MAX_FORWARDS) == 0); if (!urlCheckRequest(request) || mustReplyToOptions || unsupportedTe) { clientStreamNode *node = context->getClientReplyContext(); + conn->quitAfterError(request); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_UNSUP_REQ, HTTP_NOT_IMPLEMENTED, request->method, NULL, conn->clientConnection->remote, request, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } @@ -2614,12 +2718,12 @@ clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); + conn->quitAfterError(request); repContext->setReplyToError(ERR_INVALID_REQ, HTTP_LENGTH_REQUIRED, request->method, NULL, conn->clientConnection->remote, request, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } @@ -2630,11 +2734,11 @@ clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); + conn->quitAfterError(request); repContext->setReplyToError(ERR_INVALID_REQ, HTTP_EXPECTATION_FAILED, request->method, http->uri, conn->clientConnection->remote, request, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } } @@ -2648,6 +2752,11 @@ conn->flags.readMore = false; } +#if USE_SSL + if (conn->switchedToHttps() && conn->serveDelayedError(context)) + goto finish; +#endif + /* Do we expect a request-body? */ expectBody = chunked || request->content_length > 0; if (!context->mayUseConnection() && expectBody) { @@ -2664,12 +2773,12 @@ clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); + conn->quitAfterError(request); repContext->setReplyToError(ERR_TOO_BIG, HTTP_REQUEST_ENTITY_TOO_LARGE, METHOD_NONE, NULL, conn->clientConnection->remote, http->request, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - conn->flags.readMore = false; goto finish; } @@ -3425,6 +3534,86 @@ conn->readSomeData(); } +/** + * If SSL_CTX is given, starts reading the SSL handshake. + * Otherwise, calls switchToHttps to generate a dynamic SSL_CTX. + */ +static void +httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext, Ssl::BumpMode bumpMode) +{ + SSL *ssl = NULL; + assert(connState); + const Comm::ConnectionPointer &details = connState->clientConnection; + + if (sslContext && !(ssl = httpsCreate(details, sslContext))) + return; + + typedef CommCbMemFunT TimeoutDialer; + AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer, + connState, ConnStateData::requestTimeout); + commSetConnTimeout(details, Config.Timeout.request, timeoutCall); + + if (ssl) + Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); + else { + char buf[MAX_IPSTRLEN]; + assert(bumpMode != Ssl::bumpNone && bumpMode != Ssl::bumpEnd); + HttpRequest *fakeRequest = new HttpRequest; + fakeRequest->SetHost(details->local.NtoA(buf, sizeof(buf))); + fakeRequest->port = details->local.GetPort(); + fakeRequest->clientConnectionManager = connState; + fakeRequest->client_addr = connState->clientConnection->remote; +#if FOLLOW_X_FORWARDED_FOR + fakeRequest->indirect_client_addr = connState->clientConnection->remote; +#endif + fakeRequest->my_addr = connState->clientConnection->local; + fakeRequest->flags.spoof_client_ip = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; + fakeRequest->flags.intercepted = ((connState->clientConnection->flags & COMM_INTERCEPTION) != 0); + debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX"); + connState->switchToHttps(fakeRequest, bumpMode); + } +} + +/** + * A callback function to use with the ACLFilledChecklist callback. + * In the case of ACCES_ALLOWED answer initializes a bumped SSL connection, + * else reverts the connection to tunnel mode. + */ +static void +httpsSslBumpAccessCheckDone(allow_t answer, void *data) +{ + ConnStateData *connState = (ConnStateData *) data; + + // if the connection is closed or closing, just return. + if (!connState->isOpen()) + return; + + // Require both a match and a positive bump mode to work around exceptional + // cases where ACL code may return ACCESS_ALLOWED with zero answer.kind. + if (answer == ACCESS_ALLOWED && answer.kind != Ssl::bumpNone) { + debugs(33, 2, HERE << "sslBump needed for " << connState->clientConnection); + connState->sslBumpMode = static_cast(answer.kind); + httpsEstablish(connState, NULL, (Ssl::BumpMode)answer.kind); + } else { + debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection); + connState->sslBumpMode = Ssl::bumpNone; + + // fake a CONNECT request to force connState to tunnel + static char ip[MAX_IPSTRLEN]; + static char reqStr[MAX_IPSTRLEN + 80]; + connState->clientConnection->local.NtoA(ip, sizeof(ip)); + snprintf(reqStr, sizeof(reqStr), "CONNECT %s:%d HTTP/1.1\r\nHost: %s\r\n\r\n", ip, connState->clientConnection->local.GetPort(), ip); + bool ret = connState->handleReadData(reqStr, strlen(reqStr)); + if (ret) + ret = connState->clientParseRequests(); + + if (!ret) { + debugs(33, 2, HERE << "Failed to start fake CONNECT request for ssl bumped connection: " << connState->clientConnection); + connState->clientConnection->close(); + } + } +} + /** handle a new HTTPS connection */ static void httpsAccept(const CommAcceptCbParams ¶ms) @@ -3437,11 +3626,6 @@ return; } - SSL_CTX *sslContext = s->staticSslContext.get(); - SSL *ssl = NULL; - if (!(ssl = httpsCreate(params.conn, sslContext))) - return; - debugs(33, 4, HERE << params.conn << " accepted, starting SSL negotiation."); fd_note(params.conn->fd, "client https connect"); @@ -3454,12 +3638,32 @@ // Socket is ready, setup the connection manager to start using it ConnStateData *connState = connStateCreate(params.conn, s); - typedef CommCbMemFunT TimeoutDialer; - AsyncCall::Pointer timeoutCall = JobCallback(33, 5, - TimeoutDialer, connState, ConnStateData::requestTimeout); - commSetConnTimeout(params.conn, Config.Timeout.request, timeoutCall); + if (s->sslBump) { + debugs(33, 5, "httpsAccept: accept transparent connection: " << params.conn); - Comm::SetSelect(params.conn->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); + if (!Config.accessList.ssl_bump) { + httpsSslBumpAccessCheckDone(ACCESS_DENIED, connState); + return; + } + + // Create a fake HTTP request for ssl_bump ACL check, + // using tproxy/intercept provided destination IP and port. + HttpRequest *request = new HttpRequest(); + static char ip[MAX_IPSTRLEN]; + assert(params.conn->flags & (COMM_TRANSPARENT | COMM_INTERCEPTION)); + request->SetHost(params.conn->local.NtoA(ip, sizeof(ip))); + request->port = params.conn->local.GetPort(); + request->myportname = s->name; + + ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL); + acl_checklist->src_addr = params.conn->remote; + acl_checklist->my_addr = s->s; + acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, connState); + return; + } else { + SSL_CTX *sslContext = s->staticSslContext.get(); + httpsEstablish(connState, sslContext, Ssl::bumpNone); + } } void @@ -3477,12 +3681,12 @@ } 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; @@ -3492,63 +3696,171 @@ getSslContextDone(NULL); } -bool +void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties) +{ + certProperties.commonName = sslCommonName.defined() ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf(); + + // fake certificate adaptation requires bump-server-first mode + if (!sslServerBump) { + assert(port->signingCert.get()); + certProperties.signWithX509.resetAndLock(port->signingCert.get()); + if (port->signPkey.get()) + certProperties.signWithPkey.resetAndLock(port->signPkey.get()); + certProperties.signAlgorithm = Ssl::algSignTrusted; + return; + } + + // In case of an error while connecting to the secure server, use a fake + // trusted certificate, with no mimicked fields and no adaptation + // algorithms. There is nothing we can mimic so we want to minimize the + // number of warnings the user will have to see to get to the error page. + assert(sslServerBump->entry); + if (sslServerBump->entry->isEmpty()) { + if (X509 *mimicCert = sslServerBump->serverCert.get()) + certProperties.mimicCert.resetAndLock(mimicCert); + + ACLFilledChecklist checklist(NULL, sslServerBump->request, + clientConnection != NULL ? clientConnection->rfc931 : dash_str); + checklist.conn(this); + checklist.sslErrors = cbdataReference(sslServerBump->sslErrors); + + for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) { + // If the algorithm already set, then ignore it. + if ((ca->alg == Ssl::algSetCommonName && certProperties.setCommonName) || + (ca->alg == Ssl::algSetValidAfter && certProperties.setValidAfter) || + (ca->alg == Ssl::algSetValidBefore && certProperties.setValidBefore) ) + continue; + + if (ca->aclList && checklist.fastCheck(ca->aclList) == ACCESS_ALLOWED) { + const char *alg = Ssl::CertAdaptAlgorithmStr[ca->alg]; + const char *param = ca->param; + + // For parameterless CN adaptation, use hostname from the + // CONNECT request. + if (ca->alg == Ssl::algSetCommonName) { + if (!param) + param = sslConnectHostOrIp.termedBuf(); + certProperties.commonName = param; + certProperties.setCommonName = true; + } + else if(ca->alg == Ssl::algSetValidAfter) + certProperties.setValidAfter = true; + else if(ca->alg == Ssl::algSetValidBefore) + certProperties.setValidBefore = true; + + debugs(33, 5, HERE << "Matches certificate adaptation aglorithm: " << + alg << " param: " << (param ? param : "-")); + } + } + + certProperties.signAlgorithm = Ssl::algSignEnd; + for (sslproxy_cert_sign *sg = Config.ssl_client.cert_sign; sg != NULL; sg = sg->next) { + if (sg->aclList && checklist.fastCheck(sg->aclList) == ACCESS_ALLOWED) { + certProperties.signAlgorithm = (Ssl::CertSignAlgorithm)sg->alg; + break; + } + } + } else {// if (!sslServerBump->entry->isEmpty()) + // Use trusted certificate for a Squid-generated error + // or the user would have to add a security exception + // just to see the error page. We will close the connection + // so that the trust is not extended to non-Squid content. + certProperties.signAlgorithm = Ssl::algSignTrusted; + } + + assert(certProperties.signAlgorithm != Ssl::algSignEnd); + + if (certProperties.signAlgorithm == Ssl::algSignUntrusted) { + assert(port->untrustedSigningCert.get()); + certProperties.signWithX509.resetAndLock(port->untrustedSigningCert.get()); + certProperties.signWithPkey.resetAndLock(port->untrustedSignPkey.get()); + } + else { + assert(port->signingCert.get()); + certProperties.signWithX509.resetAndLock(port->signingCert.get()); + + if (port->signPkey.get()) + certProperties.signWithPkey.resetAndLock(port->signPkey.get()); + } + signAlgorithm = certProperties.signAlgorithm; +} + +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"); + 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(host); + SSL_CTX * dynCtx = ssl_ctx_cache.find(sslBumpCertKey.termedBuf()); if (dynCtx) { - debugs(33, 5, HERE << "SSL certificate for " << host << " have found in cache"); - if (Ssl::verifySslCertificateDate(dynCtx)) { - debugs(33, 5, HERE << "Cached SSL certificate for " << host << " is valid"); - return getSslContextDone(dynCtx); + 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 " << 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"); } #if USE_SSL_CRTD - debugs(33, 5, HERE << "Generating SSL certificate for " << host << " using ssl_crtd."); + try { + debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName << " 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)); - std::string bufferToWrite; - Ssl::writeCertAndPrivateKeyToMemory(port->signingCert, port->signPkey, bufferToWrite); - request_message.composeBody(map, bufferToWrite); + 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 true; -#else - debugs(33, 5, HERE << "Generating SSL certificate for " << host); - dynCtx = Ssl::generateSslContext(host, port->signingCert, port->signPkey); - return getSslContextDone(dynCtx, true); -#endif //USE_SSL_CRTD + 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. + } +#endif // USE_SSL_CRTD + + debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); + dynCtx = Ssl::generateSslContext(certProperties); + getSslContextDone(dynCtx, true); + return; } - return getSslContextDone(NULL); + getSslContextDone(NULL); } -bool +void ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) { // Try to add generated ssl context to storage. if (port->generateHostCertificates && isNew) { - Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); + 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)); - 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); } } @@ -3557,7 +3869,7 @@ if (!port->staticSslContext) { debugs(83, 1, "Closing SSL " << clientConnection->remote << " as lacking SSL context"); clientConnection->close(); - return false; + return; } else { debugs(33, 5, HERE << "Using static ssl context."); sslContext = port->staticSslContext.get(); @@ -3566,7 +3878,7 @@ SSL *ssl = NULL; if (!(ssl = httpsCreate(clientConnection, sslContext))) - return false; + return; // commSetConnTimeout() was called for this request before we switched. @@ -3574,26 +3886,73 @@ Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0); switchedToHttps_ = true; - return true; } -bool -ConnStateData::switchToHttps(const char *host) +void +ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) { assert(!switchedToHttps_); - sslHostName = host; - - //HTTPMSGLOCK(currentobject->http->request); - assert(areAllContextsForThisConnection()); - freeAllContexts(); - //currentobject->connIsFinished(); + sslConnectHostOrIp = request->GetHost(); + sslCommonName = request->GetHost(); // We are going to read new request flags.readMore = true; debugs(33, 5, HERE << "converting " << clientConnection << " to SSL"); - return getSslContextStart(); + // If sslServerBump is set, then we have decided to deny CONNECT + // and now want to switch to SSL to send the error to the client + // without even peeking at the origin server certificate. + if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) { + request->flags.sslPeek = 1; + sslServerBump = new Ssl::ServerBump(request); + + // will call httpsPeeked() with certificate and connection, eventually + FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request); + return; + } + + // otherwise, use sslConnectHostOrIp + getSslContextStart(); +} + +void +ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) +{ + Must(sslServerBump != NULL); + + if (Comm::IsConnOpen(serverConnection)) { + SSL *ssl = fd_table[serverConnection->fd].ssl; + assert(ssl); + Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl)); + assert(serverCert.get() != NULL); + sslCommonName = Ssl::CommonHostName(serverCert.get()); + debugs(33, 5, HERE << "HTTPS server CN: " << sslCommonName << + " bumped: " << *serverConnection); + + pinConnection(serverConnection, NULL, NULL, false); + + debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp); + } else { + debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp); + Ip::Address intendedDest; + intendedDest = sslConnectHostOrIp.termedBuf(); + const bool isConnectRequest = !port->spoof_client_ip && !port->intercepted; + + // Squid serves its own error page and closes, so we want + // a CN that causes no additional browser errors. Possible + // only when bumping CONNECT with a user-typed address. + if (intendedDest.IsAnyAddr() || isConnectRequest) + sslCommonName = sslConnectHostOrIp; + else if (sslServerBump->serverCert.get()) + sslCommonName = Ssl::CommonHostName(sslServerBump->serverCert.get()); + + // copy error detail from bump-server-first request to CONNECT request + if (currentobject != NULL && currentobject->http != NULL && currentobject->http->request) + currentobject->http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail); + } + + getSslContextStart(); } #endif /* USE_SSL */ @@ -3693,6 +4052,22 @@ continue; } + // TODO: merge with similar code in clientHttpConnectionsOpen() + if (s->sslBump && !Config.accessList.ssl_bump) { + debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->s); + s->sslBump = 0; + } + + if (s->sslBump && !s->staticSslContext && !s->generateHostCertificates) { + debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->s << " due to SSL initialization failure."); + s->sslBump = 0; + } + + if (s->sslBump) { + // Create ssl_ctx cache for this port. + Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits::max() ? 4194304 : s->dynamicCertMemCacheSize); + } + // Fill out a Comm::Connection which IPC will open as a listener for us s->listenConn = new Comm::Connection; s->listenConn->local = s->s; @@ -3870,7 +4245,11 @@ ConnStateData::ConnStateData() : AsyncJob("ConnStateData"), +#if USE_SSL + sslBumpMode(Ssl::bumpEnd), switchedToHttps_(false), + sslServerBump(NULL), +#endif stoppedSending_(NULL), stoppedReceiving_(NULL) { @@ -4026,11 +4405,16 @@ clientConnection->close(); } -/* This is a comm call normally scheduled by comm_close() */ +/// Our close handler called by Comm when the pinned connection is closed void ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) { - unpinConnection(); + // FwdState might repin a failed connection sooner than this close + // callback is called for the failed connection. + if (pinning.serverConnection == io.conn) { + pinning.closeHandler = NULL; // Comm unregisters handlers before calling + unpinConnection(); + } } void @@ -4041,33 +4425,49 @@ if (Comm::IsConnOpen(pinning.serverConnection)) { if (pinning.serverConnection->fd == pinServer->fd) return; + } - unpinConnection(); // clears fields ready for re-use. Prevent close() scheduling our close handler. - pinning.serverConnection->close(); - } else - unpinConnection(); // clears fields ready for re-use. + unpinConnection(); // closes pinned connection, if any, and resets fields pinning.serverConnection = pinServer; - pinning.host = xstrdup(request->GetHost()); - pinning.port = request->port; + + debugs(33, 3, HERE << pinning.serverConnection); + + // when pinning an SSL bumped connection, the request may be NULL + const char *pinnedHost = "[unknown]"; + if (request) { + pinning.host = xstrdup(request->GetHost()); + pinning.port = request->port; + pinnedHost = pinning.host; + } else { + pinning.port = pinServer->remote.GetPort(); + } pinning.pinned = true; if (aPeer) pinning.peer = cbdataReference(aPeer); pinning.auth = auth; char stmp[MAX_IPSTRLEN]; snprintf(desc, FD_DESC_SZ, "%s pinned connection for %s (%d)", - (auth || !aPeer) ? request->GetHost() : aPeer->name, clientConnection->remote.ToURL(stmp,MAX_IPSTRLEN), clientConnection->fd); + (auth || !aPeer) ? pinnedHost : aPeer->name, + clientConnection->remote.ToURL(stmp,MAX_IPSTRLEN), + clientConnection->fd); fd_note(pinning.serverConnection->fd, desc); typedef CommCbMemFunT Dialer; pinning.closeHandler = JobCallback(33, 5, Dialer, this, ConnStateData::clientPinnedConnectionClosed); + // remember the pinned connection so that cb does not unpin a fresher one + typedef CommCloseCbParams Params; + Params ¶ms = GetCommParams(pinning.closeHandler); + params.conn = pinning.serverConnection; comm_add_close_handler(pinning.serverConnection->fd, pinning.closeHandler); } const Comm::ConnectionPointer ConnStateData::validatePinnedConnection(HttpRequest *request, const struct peer *aPeer) { + debugs(33, 7, HERE << pinning.serverConnection); + bool valid = true; if (!Comm::IsConnOpen(pinning.serverConnection)) valid = false; @@ -4095,15 +4495,21 @@ void ConnStateData::unpinConnection() { + debugs(33, 3, HERE << pinning.serverConnection); + if (pinning.peer) cbdataReferenceDone(pinning.peer); + if (Comm::IsConnOpen(pinning.serverConnection)) { if (pinning.closeHandler != NULL) { comm_remove_close_handler(pinning.serverConnection->fd, pinning.closeHandler); pinning.closeHandler = NULL; } /// also close the server side socket, we should not use it for any future requests... + // TODO: do not close if called from our close handler? pinning.serverConnection->close(); + } + safe_free(pinning.host); /* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host === modified file 'src/client_side.h' --- src/client_side.h 2012-04-25 05:29:20 +0000 +++ src/client_side.h 2012-07-18 15:19:04 +0000 @@ -44,6 +44,9 @@ #include "HttpParser.h" #include "RefCount.h" #include "StoreIOBuffer.h" +#if USE_SSL +#include "ssl/support.h" +#endif class ConnStateData; class ClientHttpRequest; @@ -160,7 +163,11 @@ class ConnectionDetail; - +#if USE_SSL +namespace Ssl { + class ServerBump; +} +#endif /** * Manages a connection to a client. * @@ -312,22 +319,47 @@ virtual bool doneAll() const { return BodyProducer::doneAll() && false;} virtual void swanSong(); + /// Changes state so that we close the connection and quit after serving + /// the client-side-detected error response instead of getting stuck. + void quitAfterError(HttpRequest *request); // meant to be private + #if USE_SSL + /// called by FwdState when it is done bumping the server + void httpsPeeked(Comm::ConnectionPointer serverConnection); + /// Start to create dynamic SSL_CTX for host or uses static port SSL context. - bool getSslContextStart(); + void getSslContextStart(); /** * Done create dynamic ssl certificate. * * \param[in] isNew if generated certificate is new, so we need to add this certificate to storage. */ - bool getSslContextDone(SSL_CTX * sslContext, bool isNew = false); + void getSslContextDone(SSL_CTX * sslContext, bool isNew = false); /// Callback function. It is called when squid receive message from ssl_crtd. static void sslCrtdHandleReplyWrapper(void *data, char *reply); /// Proccess response from ssl_crtd. void sslCrtdHandleReply(const char * reply); - bool switchToHttps(const char *host); + void switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode); bool switchedToHttps() const { return switchedToHttps_; } + Ssl::ServerBump *serverBump() {return sslServerBump;} + inline void setServerBump(Ssl::ServerBump *srvBump) { + if (!sslServerBump) + sslServerBump = srvBump; + else + assert(sslServerBump == srvBump); + } + /// Fill the certAdaptParams with the required data for certificate adaptation + /// and create the key for storing/retrieve the certificate to/from the cache + void buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties); + /// Called when the client sends the first request on a bumped connection. + /// Returns false if no [delayed] error should be written to the client. + /// Otherwise, writes the error to the client and returns true. Also checks + /// for SQUID_X509_V_ERR_DOMAIN_MISMATCH on bumped requests. + bool serveDelayedError(ClientSocketContext *context); + + Ssl::BumpMode sslBumpMode; ///< ssl_bump decision (Ssl::bumpEnd if n/a). + #else bool switchedToHttps() const { return false; } #endif @@ -349,14 +381,23 @@ // XXX: CBDATA plays with public/private and leaves the following 'private' fields all public... :( CBDATA_CLASS2(ConnStateData); +#if USE_SSL bool switchedToHttps_; + /// 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 + + /// HTTPS server cert. fetching state for bump-ssl-server-first + Ssl::ServerBump *sslServerBump; + Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use +#endif /// the reason why we no longer write the response or nil const char *stoppedSending_; /// the reason why we no longer read the request or nil const char *stoppedReceiving_; - String sslHostName; ///< Host name for SSL certificate generation AsyncCall::Pointer reader; ///< set when we are reading BodyPipe::Pointer bodyPipe; // set when we are reading request body }; === modified file 'src/client_side_reply.cc' --- src/client_side_reply.cc 2012-07-17 14:11:24 +0000 +++ src/client_side_reply.cc 2012-07-18 16:21:47 +0000 @@ -113,21 +113,39 @@ if (unparsedrequest) errstate->request_hdrs = xstrdup(unparsedrequest); - if (status == HTTP_NOT_IMPLEMENTED && http->request) +#if USE_AUTH + errstate->auth_user_request = auth_user_request; +#endif + setReplyToError(method, errstate); +} + +void clientReplyContext::setReplyToError(const HttpRequestMethod& method, ErrorState *errstate) +{ + if (errstate->httpStatus == HTTP_NOT_IMPLEMENTED && http->request) /* prevent confusion over whether we default to persistent or not */ http->request->flags.proxy_keepalive = 0; http->al->http.code = errstate->httpStatus; createStoreEntry(method, request_flags()); -#if USE_AUTH - errstate->auth_user_request = auth_user_request; -#endif assert(errstate->callback_data == NULL); errorAppendEntry(http->storeEntry(), errstate); /* Now the caller reads to get this */ } +void clientReplyContext::setReplyToStoreEntry(StoreEntry *entry) +{ + entry->lock(); // removeClientStoreReference() unlocks + sc = storeClientListAdd(entry, this); +#if USE_DELAY_POOLS + sc->setDelayId(DelayId::DelayClient(http)); +#endif + reqofs = 0; + reqsize = 0; + flags.storelogiccomplete = 1; + http->storeEntry(entry); +} + void clientReplyContext::removeStoreReference(store_client ** scp, StoreEntry ** ep) @@ -1456,6 +1474,10 @@ } else if (fdUsageHigh()&& !request->flags.must_keepalive) { debugs(88, 3, "clientBuildReplyHeader: Not many unused FDs, can't keep-alive"); request->flags.proxy_keepalive = 0; + } else if (request->flags.sslBumped && !reply->persistent()) { + // We do not really have to close, but we pretend we are a tunnel. + debugs(88, 3, "clientBuildReplyHeader: bumped reply forces close"); + request->flags.proxy_keepalive = 0; } // Decide if we send chunked reply === modified file 'src/client_side_reply.h' --- src/client_side_reply.h 2011-12-30 01:24:57 +0000 +++ src/client_side_reply.h 2012-06-19 21:51:49 +0000 @@ -71,12 +71,17 @@ void identifyFoundObject(StoreEntry *entry); int storeOKTransferDone() const; int storeNotOKTransferDone() const; + /// replaces current response store entry with the given one + void setReplyToStoreEntry(StoreEntry *e); + /// builds error using clientBuildError() and calls setReplyToError() below void setReplyToError(err_type, http_status, const HttpRequestMethod&, char const *, Ip::Address &, HttpRequest *, const char *, #if USE_AUTH Auth::UserRequest::Pointer); #else void * unused); #endif + /// creates a store entry for the reply and appends err to it + void setReplyToError(const HttpRequestMethod& method, ErrorState *err); void createStoreEntry(const HttpRequestMethod& m, request_flags flags); void removeStoreReference(store_client ** scp, StoreEntry ** ep); void removeClientStoreReference(store_client **scp, ClientHttpRequest *http); === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2012-07-17 14:25:06 +0000 +++ src/client_side_request.cc 2012-07-18 16:21:47 +0000 @@ -66,6 +66,7 @@ #include "comm/Connection.h" #include "comm/Write.h" #include "compat/inet_pton.h" +#include "errorpage.h" #include "fde.h" #include "format/Token.h" #include "HttpHdrCc.h" @@ -79,6 +80,7 @@ #include "err_detail_type.h" #if USE_SSL #include "ssl/support.h" +#include "ssl/ServerBump.h" #endif @@ -92,6 +94,8 @@ static void clientFollowXForwardedForCheck(allow_t answer, void *data); #endif /* FOLLOW_X_FORWARDED_FOR */ +extern ErrorState *clientBuildError(err_type, http_status, char const *url, Ip::Address &, HttpRequest *); + CBDATA_CLASS_INIT(ClientRequestContext); void * @@ -135,10 +139,11 @@ if (http) cbdataReferenceDone(http); + delete error; debugs(85,3, HERE << this << " ClientRequestContext destructed"); } -ClientRequestContext::ClientRequestContext(ClientHttpRequest *anHttp) : http(cbdataReference(anHttp)), acl_checklist (NULL), redirect_state (REDIRECT_NONE) +ClientRequestContext::ClientRequestContext(ClientHttpRequest *anHttp) : http(cbdataReference(anHttp)), acl_checklist (NULL), redirect_state (REDIRECT_NONE), error(NULL), readNextRequest(false) { http_access_done = false; redirect_done = false; @@ -189,7 +194,7 @@ request_satisfaction_mode = false; #endif #if USE_SSL - sslBumpNeed = needUnknown; + sslBumpNeed_ = Ssl::bumpEnd; #endif } @@ -815,26 +820,19 @@ page_id = ERR_ACCESS_DENIED; } - clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; - clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); Ip::Address tmpnoaddr; tmpnoaddr.SetNoAddr(); - repContext->setReplyToError(page_id, status, - http->request->method, NULL, - http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmpnoaddr, - http->request, - NULL, -#if USE_AUTH - http->getConn() != NULL && http->getConn()->auth_user_request != NULL ? - http->getConn()->auth_user_request : http->request->auth_user_request); -#else - NULL); -#endif - http->getConn()->flags.readMore = true; // resume any pipeline reads. - node = (clientStreamNode *)http->client_stream.tail->data; - clientStreamRead(node, http, node->readBuffer); - return; + error = clientBuildError(page_id, status, + NULL, + http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmpnoaddr, + http->request + ); + + error->auth_user_request = + http->getConn() != NULL && http->getConn()->auth_user_request != NULL ? + http->getConn()->auth_user_request : http->request->auth_user_request; + + readNextRequest = true; } /* ACCESS_ALLOWED continues here ... */ @@ -1286,17 +1284,40 @@ bool ClientRequestContext::sslBumpAccessCheck() { - if (http->request->method == METHOD_CONNECT && - Config.accessList.ssl_bump && http->getConn()->port->sslBump) { - debugs(85, 5, HERE << "SslBump possible, checking ACL"); - - ACLFilledChecklist *acl_checklist = clientAclChecklistCreate(Config.accessList.ssl_bump, http); - acl_checklist->nonBlockingCheck(sslBumpAccessCheckDoneWrapper, this); - return true; - } else { - http->sslBumpNeeded(false); - return false; - } + // If SSL connection tunneling or bumping decision has been made, obey it. + const Ssl::BumpMode bumpMode = http->getConn()->sslBumpMode; + if (bumpMode != Ssl::bumpEnd) { + debugs(85, 5, HERE << "SslBump already decided (" << bumpMode << + "), " << "ignoring ssl_bump for " << http->getConn()); + http->al->ssl.bumpMode = bumpMode; // inherited from bumped connection + return false; + } + + // If we have not decided yet, decide whether to bump now. + + // Bumping here can only start with a CONNECT request on a bumping port + // (bumping of intercepted SSL conns is decided before we get 1st request). + // We also do not bump redirected CONNECT requests. + if (http->request->method != METHOD_CONNECT || http->redirect.status || + !Config.accessList.ssl_bump || !http->getConn()->port->sslBump) { + http->al->ssl.bumpMode = Ssl::bumpEnd; // SslBump does not apply; log - + debugs(85, 5, HERE << "cannot SslBump this request"); + return false; + } + + // Do not bump during authentication: clients would not proxy-authenticate + // if we delay a 407 response and respond with 200 OK to CONNECT. + if (error && error->httpStatus == HTTP_PROXY_AUTHENTICATION_REQUIRED) { + http->al->ssl.bumpMode = Ssl::bumpEnd; // SslBump does not apply; log - + debugs(85, 5, HERE << "no SslBump during proxy authentication"); + return false; + } + + debugs(85, 5, HERE << "SslBump possible, checking ACL"); + + ACLFilledChecklist *acl_checklist = clientAclChecklistCreate(Config.accessList.ssl_bump, http); + acl_checklist->nonBlockingCheck(sslBumpAccessCheckDoneWrapper, this); + return true; } /** @@ -1310,13 +1331,20 @@ if (!calloutContext->httpStateIsValid()) return; - calloutContext->sslBumpAccessCheckDone(answer == ACCESS_ALLOWED); + calloutContext->sslBumpAccessCheckDone(answer); } void -ClientRequestContext::sslBumpAccessCheckDone(bool doSslBump) +ClientRequestContext::sslBumpAccessCheckDone(const allow_t &answer) { - http->sslBumpNeeded(doSslBump); + if (!httpStateIsValid()) + return; + + const Ssl::BumpMode bumpMode = answer == ACCESS_ALLOWED ? + static_cast(answer.kind) : Ssl::bumpNone; + http->sslBumpNeed(bumpMode); // for processRequest() to bump if needed + http->al->ssl.bumpMode = bumpMode; // for logging + http->doCallouts(); } #endif @@ -1364,18 +1392,11 @@ #if USE_SSL -bool -ClientHttpRequest::sslBumpNeeded() const -{ - assert(sslBumpNeed != needUnknown); - return (sslBumpNeed == needConfirmed); -} - void -ClientHttpRequest::sslBumpNeeded(bool isNeeded) +ClientHttpRequest::sslBumpNeed(Ssl::BumpMode mode) { - debugs(83, 3, HERE << "sslBump required: "<< (isNeeded ? "Yes" : "No")); - sslBumpNeed = (isNeeded ? needConfirmed : needNot); + debugs(83, 3, HERE << "sslBump required: "<< Ssl::bumpMode(mode)); + sslBumpNeed_ = mode; } // called when comm_write has completed @@ -1402,21 +1423,28 @@ return; } + // We lack HttpReply which logRequest() uses to log the status code. + // TODO: Use HttpReply instead of the "200 Connection established" string. + al->http.code = 200; + #if USE_AUTH // Preserve authentication info for the ssl-bumped request if (request->auth_user_request != NULL) getConn()->auth_user_request = request->auth_user_request; #endif - getConn()->switchToHttps(request->GetHost()); + + assert(sslBumpNeeded()); + getConn()->switchToHttps(request, sslBumpNeed_); } void ClientHttpRequest::sslBumpStart() { - debugs(85, 5, HERE << "Confirming CONNECT tunnel on FD " << getConn()->clientConnection); + debugs(85, 5, HERE << "Confirming " << Ssl::bumpMode(sslBumpNeed_) << + "-bumped CONNECT tunnel on FD " << getConn()->clientConnection); + getConn()->sslBumpMode = sslBumpNeed_; + // send an HTTP 200 response to kick client SSL negotiation - debugs(33, 7, HERE << "Confirming CONNECT tunnel on FD " << getConn()->clientConnection); - // TODO: Unify with tunnel.cc and add a Server(?) header static const char *const conn_established = "HTTP/1.1 200 Connection established\r\n\r\n"; AsyncCall::Pointer call = commCbCall(85, 5, "ClientSocketContext::sslBumpEstablish", @@ -1502,65 +1530,67 @@ if (!calloutContext->http->al->request) calloutContext->http->al->request = HTTPMSGLOCK(request); + if (!calloutContext->error) { // CVE-2009-0801: verify the Host: header is consistent with other known details. - if (!calloutContext->host_header_verify_done) { - debugs(83, 3, HERE << "Doing calloutContext->hostHeaderVerify()"); - calloutContext->host_header_verify_done = true; - calloutContext->hostHeaderVerify(); - return; - } + if (!calloutContext->host_header_verify_done) { + debugs(83, 3, HERE << "Doing calloutContext->hostHeaderVerify()"); + calloutContext->host_header_verify_done = true; + calloutContext->hostHeaderVerify(); + return; + } - if (!calloutContext->http_access_done) { - debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck()"); - calloutContext->http_access_done = true; - calloutContext->clientAccessCheck(); - return; - } + if (!calloutContext->http_access_done) { + debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck()"); + calloutContext->http_access_done = true; + calloutContext->clientAccessCheck(); + return; + } #if USE_ADAPTATION - if (!calloutContext->adaptation_acl_check_done) { - calloutContext->adaptation_acl_check_done = true; - if (Adaptation::AccessCheck::Start( + if (!calloutContext->adaptation_acl_check_done) { + calloutContext->adaptation_acl_check_done = true; + if (Adaptation::AccessCheck::Start( Adaptation::methodReqmod, Adaptation::pointPreCache, request, NULL, this)) - return; // will call callback - } + return; // will call callback + } #endif - if (!calloutContext->redirect_done) { - calloutContext->redirect_done = true; - assert(calloutContext->redirect_state == REDIRECT_NONE); - - if (Config.Program.redirect) { - debugs(83, 3, HERE << "Doing calloutContext->clientRedirectStart()"); - calloutContext->redirect_state = REDIRECT_PENDING; - calloutContext->clientRedirectStart(); - return; - } - } - - if (!calloutContext->adapted_http_access_done) { - debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck2()"); - calloutContext->adapted_http_access_done = true; - calloutContext->clientAccessCheck2(); - return; - } - - if (!calloutContext->interpreted_req_hdrs) { - debugs(83, 3, HERE << "Doing clientInterpretRequestHeaders()"); - calloutContext->interpreted_req_hdrs = 1; - clientInterpretRequestHeaders(this); - } - - if (!calloutContext->no_cache_done) { - calloutContext->no_cache_done = true; - - if (Config.accessList.noCache && request->flags.cachable) { - debugs(83, 3, HERE << "Doing calloutContext->checkNoCache()"); - calloutContext->checkNoCache(); - return; - } - } + if (!calloutContext->redirect_done) { + calloutContext->redirect_done = true; + assert(calloutContext->redirect_state == REDIRECT_NONE); + + if (Config.Program.redirect) { + debugs(83, 3, HERE << "Doing calloutContext->clientRedirectStart()"); + calloutContext->redirect_state = REDIRECT_PENDING; + calloutContext->clientRedirectStart(); + return; + } + } + + if (!calloutContext->adapted_http_access_done) { + debugs(83, 3, HERE << "Doing calloutContext->clientAccessCheck2()"); + calloutContext->adapted_http_access_done = true; + calloutContext->clientAccessCheck2(); + return; + } + + if (!calloutContext->interpreted_req_hdrs) { + debugs(83, 3, HERE << "Doing clientInterpretRequestHeaders()"); + calloutContext->interpreted_req_hdrs = 1; + clientInterpretRequestHeaders(this); + } + + if (!calloutContext->no_cache_done) { + calloutContext->no_cache_done = true; + + if (Config.accessList.noCache && request->flags.cachable) { + debugs(83, 3, HERE << "Doing calloutContext->checkNoCache()"); + calloutContext->checkNoCache(); + return; + } + } + } // if !calloutContext->error if (!calloutContext->tosToClientDone) { calloutContext->tosToClientDone = true; @@ -1587,6 +1617,8 @@ } #if USE_SSL + // We need to check for SslBump even if the calloutContext->error is set + // because bumping may require delaying the error until after CONNECT. if (!calloutContext->sslBumpCheckDone) { calloutContext->sslBumpCheckDone = true; if (calloutContext->sslBumpAccessCheck()) @@ -1595,6 +1627,36 @@ } #endif + if (calloutContext->error) { + const char *uri = urlCanonical(request); + StoreEntry *e= storeCreateEntry(uri, uri, request->flags, request->method); +#if USE_SSL + if (sslBumpNeeded()) { + // set final error but delay sending until we bump + Ssl::ServerBump *srvBump = new Ssl::ServerBump(request, e); + errorAppendEntry(e, calloutContext->error); + calloutContext->error = NULL; + getConn()->setServerBump(srvBump); + e->unlock(); + } else +#endif + { + // send the error to the client now + clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data; + clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); + assert (repContext); + repContext->setReplyToStoreEntry(e); + errorAppendEntry(e, calloutContext->error); + calloutContext->error = NULL; + if (calloutContext->readNextRequest) + getConn()->flags.readMore = true; // resume any pipeline reads. + node = (clientStreamNode *)client_stream.tail->data; + clientStreamRead(node, this, node->readBuffer); + e->unlock(); + return; + } + } + cbdataReferenceDone(calloutContext->http); delete calloutContext; calloutContext = NULL; @@ -1823,24 +1885,25 @@ // The original author of the code also wanted to pass an errno to // setReplyToError, but it seems unlikely that the errno reflects the // true cause of the error at this point, so I did not pass it. - Ip::Address noAddr; - noAddr.SetNoAddr(); - ConnStateData * c = getConn(); - repContext->setReplyToError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, - request->method, NULL, - (c != NULL ? c->clientConnection->remote : noAddr), request, NULL, + if (calloutContext) { + Ip::Address noAddr; + noAddr.SetNoAddr(); + ConnStateData * c = getConn(); + calloutContext->error = clientBuildError(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, + NULL, + c != NULL ? c->clientConnection->remote : noAddr, + request + ); #if USE_AUTH - (c != NULL && c->auth_user_request != NULL ? - c->auth_user_request : request->auth_user_request)); -#else - NULL); + calloutContext->error->auth_user_request = + c != NULL && c->auth_user_request != NULL ? c->auth_user_request : request->auth_user_request; #endif - - request->detailError(ERR_ICAP_FAILURE, errDetail); - c->flags.readMore = true; - c->expectNoForwarding(); - node = (clientStreamNode *)client_stream.tail->data; - clientStreamRead(node, this, node->readBuffer); + calloutContext->error->detailError(errDetail); + calloutContext->readNextRequest = true; + c->expectNoForwarding(); + doCallouts(); + } + //else if(calloutContext == NULL) is it possible? } #endif === modified file 'src/client_side_request.h' --- src/client_side_request.h 2012-07-17 14:11:24 +0000 +++ src/client_side_request.h 2012-07-18 16:21:47 +0000 @@ -155,14 +155,16 @@ ConnStateData * conn_; #if USE_SSL - /// whether the request needs to be bumped - enum { needUnknown, needConfirmed, needNot } sslBumpNeed; + /// whether (and how) the request needs to be bumped + Ssl::BumpMode sslBumpNeed_; public: - /// return true if the request needs to be bumped - bool sslBumpNeeded() const; + /// returns raw sslBump mode value + Ssl::BumpMode sslBumpNeed() const { return sslBumpNeed_; } + /// returns true if and only if the request needs to be bumped + bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst; } /// set the sslBumpNeeded state - void sslBumpNeeded(bool isNeeded); + void sslBumpNeed(Ssl::BumpMode mode); void sslBumpStart(); void sslBumpEstablish(comm_err_t errflag); #endif === modified file 'src/errorpage.cc' --- src/errorpage.cc 2012-06-27 19:15:28 +0000 +++ src/errorpage.cc 2012-07-18 16:21:47 +0000 @@ -579,10 +579,11 @@ callback(NULL), callback_data(NULL), request_hdrs(NULL), - err_msg(NULL) + err_msg(NULL), #if USE_SSL - , detail(NULL) + detail(NULL), #endif + detailCode(ERR_DETAIL_NONE) { memset(&flags, 0, sizeof(flags)); memset(&ftp, 0, sizeof(ftp)); @@ -593,7 +594,6 @@ if (req != NULL) { request = HTTPMSGLOCK(req); src_addr = req->client_addr; - request->detailError(type, ERR_DETAIL_NONE); } } @@ -644,13 +644,6 @@ HttpReply *rep; debugs(4, 3, HERE << conn << ", err=" << err); assert(Comm::IsConnOpen(conn)); - /* - * ugh, this is how we make sure error codes get back to - * the client side for logging and error tracking. - */ - - if (err->request) - err->request->detailError(err->type, err->xerrno); /* moved in front of errorBuildBuf @?@ */ err->flags.flag_cbdata = 1; @@ -1221,6 +1214,22 @@ /* do not memBufClean() or delete the content, it was absorbed by httpBody */ } + // Make sure error codes get back to the client side for logging and + // error tracking. + if (request) { + int edc = ERR_DETAIL_NONE; // error detail code +#if USE_SSL + if (detail) + edc = detail->errorNo(); + else +#endif + if (detailCode) + edc = detailCode; + else + edc = xerrno; + request->detailError(type, edc); + } + return rep; } === modified file 'src/errorpage.h' --- src/errorpage.h 2012-02-05 11:24:07 +0000 +++ src/errorpage.h 2012-07-18 15:19:04 +0000 @@ -40,6 +40,7 @@ #endif #include "cbdata.h" #include "comm/forward.h" +#include "err_detail_type.h" #include "ip/Address.h" #include "MemBuf.h" #if USE_SSL @@ -104,6 +105,9 @@ */ HttpReply *BuildHttpReply(void); + /// set error type-specific detail code + void detailError(int dCode) {detailCode = dCode;} + private: /** * Locates error page template to be used for this error @@ -182,6 +186,9 @@ #if USE_SSL Ssl::ErrorDetail *detail; #endif + /// type-specific detail about the transaction error; + /// overwrites xerrno; overwritten by detail, if any. + int detailCode; private: CBDATA_CLASS2(ErrorState); }; === modified file 'src/format/ByteCode.h' --- src/format/ByteCode.h 2012-07-17 14:25:06 +0000 +++ src/format/ByteCode.h 2012-07-18 16:21:47 +0000 @@ -191,6 +191,7 @@ #endif #if USE_SSL + LFT_SSL_BUMP_MODE, LFT_SSL_USER_CERT_SUBJECT, LFT_SSL_USER_CERT_ISSUER, #endif === modified file 'src/format/Format.cc' --- src/format/Format.cc 2012-07-17 14:25:06 +0000 +++ src/format/Format.cc 2012-07-18 16:21:47 +0000 @@ -1006,6 +1006,13 @@ break; #if USE_SSL + case LFT_SSL_BUMP_MODE: { + const Ssl::BumpMode mode = static_cast(al->ssl.bumpMode); + // for Ssl::bumpEnd, Ssl::bumpMode() returns NULL and we log '-' + out = Ssl::bumpMode(mode); + break; + } + case LFT_SSL_USER_CERT_SUBJECT: if (X509 *cert = al->cache.sslClientCert.get()) { if (X509_NAME *subject = X509_get_subject_name(cert)) { === modified file 'src/format/Token.cc' --- src/format/Token.cc 2012-07-17 14:25:06 +0000 +++ src/format/Token.cc 2012-07-18 16:21:47 +0000 @@ -189,6 +189,7 @@ #if USE_SSL // SSL (ssl::) tokens static TokenTableEntry TokenTableSsl[] = { + {"bump_mode", LFT_SSL_BUMP_MODE}, {">cert_subject", LFT_SSL_USER_CERT_SUBJECT}, {">cert_issuer", LFT_SSL_USER_CERT_ISSUER}, {NULL, LFT_NONE} @@ -209,7 +210,6 @@ #if ICAP_CLIENT TheConfig.registerTokens(String("icap"),::Format::TokenTableIcap); #endif - #if USE_SSL TheConfig.registerTokens(String("ssl"),::Format::TokenTableSsl); #endif === modified file 'src/forward.cc' --- src/forward.cc 2012-07-18 17:40:51 +0000 +++ src/forward.cc 2012-07-18 17:45:54 +0000 @@ -35,6 +35,7 @@ #include "forward.h" #include "acl/FilledChecklist.h" #include "acl/Gadgets.h" +#include "anyp/PortCfg.h" #include "CacheManager.h" #include "comm/Connection.h" #include "comm/ConnOpener.h" @@ -59,6 +60,7 @@ #if USE_SSL #include "ssl/support.h" #include "ssl/ErrorDetail.h" +#include "ssl/ServerBump.h" #endif static PSC fwdPeerSelectionCompleteWrapper; @@ -327,15 +329,23 @@ // this server link regardless of what happens when connecting to it. // IF sucessfuly connected this top destination will become the serverConnection(). request->hier.note(serverDestinations[0], request->GetHost()); + request->clearError(); connectStart(); } else { debugs(17, 3, HERE << "Connection failed: " << entry->url()); if (!err) { ErrorState *anErr = new ErrorState(ERR_CANNOT_FORWARD, HTTP_INTERNAL_SERVER_ERROR, request); - anErr->xerrno = errno; fail(anErr); } // else use actual error from last connection attempt +#if USE_SSL + if (request->flags.sslPeek && request->clientConnectionManager.valid()) { + errorAppendEntry(entry, err); // will free err + err = NULL; + CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, + ConnStateData::httpsPeeked, Comm::ConnectionPointer(NULL)); + } +#endif self = NULL; // refcounted } } @@ -355,13 +365,6 @@ debugs(17, 5, HERE << "pconn race happened"); pconnRace = raceHappened; } - -#if USE_SSL - if (errorState->type == ERR_SECURE_CONNECT_FAIL && errorState->detail) - request->detailError(errorState->type, errorState->detail->errorNo()); - else -#endif - request->detailError(errorState->type, errorState->xerrno); } /** @@ -645,26 +648,52 @@ // falling through to complete error handling default: - ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL); - anErr->xerrno = sysErrNo; - + // TODO: move into a method before merge + Ssl::ErrorDetail *errDetails; Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail); if (errFromFailure != NULL) { // The errFromFailure is attached to the ssl object // and will be released when ssl object destroyed. - // Copy errFromFailure to a new Ssl::ErrorDetail object - anErr->detail = new Ssl::ErrorDetail(*errFromFailure); + // Copy errFromFailure to a new Ssl::ErrorDetail object. + errDetails = new Ssl::ErrorDetail(*errFromFailure); } else { - // clientCert can be be NULL - X509 *client_cert = SSL_get_peer_certificate(ssl); - anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, client_cert); - if (client_cert) - X509_free(client_cert); + // 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) - anErr->detail->setLibError(ssl_lib_error); - + 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))) + 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->spoof_client_ip && + !request->clientConnectionManager->port->intercepted; + 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()) { @@ -675,6 +704,17 @@ 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))) + serverBump->sslErrors = cbdataReference(errs); + } + } if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) { if (serverConnection()->getPeer()->sslSession) @@ -706,7 +746,7 @@ if ((ssl = SSL_new(sslContext)) == NULL) { debugs(83, 1, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL) ); ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); - anErr->xerrno = errno; + // TODO: create Ssl::ErrorDetail with OpenSSL-supplied error code fail(anErr); self = NULL; // refcounted return; @@ -732,11 +772,20 @@ SSL_set_session(ssl, peer->sslSession); } else { - SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)request->GetHost()); + // While we are peeking at the certificate, we may not know the server + // name that the client will request (after interception or CONNECT) + // unless it was the CONNECT request with a user-typed address. + const char *hostname = request->GetHost(); + const bool hostnameIsIp = request->GetHostIsNumeric(); + const bool isConnectRequest = !request->clientConnectionManager->port->spoof_client_ip && + !request->clientConnectionManager->port->intercepted; + if (!request->flags.sslPeek || isConnectRequest) + SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname); - // We need to set SNI TLS extension only in the case we are - // connecting direct to origin server - Ssl::setClientSNI(ssl, request->GetHost()); + // Use SNI TLS extension only when we connect directly + // to the origin server and we know the server host name. + if (!hostnameIsIp) + Ssl::setClientSNI(ssl, hostname); } // Create the ACL check list now, while we have access to more info. @@ -747,6 +796,15 @@ SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check); } + // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE + X509 *peeked_cert; + if (request->clientConnectionManager.valid() && + request->clientConnectionManager->serverBump() && + (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) { + CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509); + SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert); + } + fd_table[fd].ssl = ssl; fd_table[fd].read_method = &ssl_read_method; fd_table[fd].write_method = &ssl_write_method; @@ -784,10 +842,21 @@ if (serverConnection()->getPeer()) peerConnectSucceded(serverConnection()->getPeer()); + // some requests benefit from pinning but do not require it and can "repin" + const bool rePin = request->flags.canRePin && + request->clientConnectionManager.valid(); + if (rePin) { + debugs(17, 3, HERE << "repinning " << serverConn); + request->clientConnectionManager->pinConnection(serverConn, + request, serverConn->getPeer(), request->flags.auth); + request->flags.pinned = 1; + } + #if USE_SSL - if (!request->flags.pinned) { + if (!request->flags.pinned || rePin) { if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl) || - (!serverConnection()->getPeer() && request->protocol == AnyP::PROTO_HTTPS)) { + (!serverConnection()->getPeer() && request->protocol == AnyP::PROTO_HTTPS) || + request->flags.sslPeek) { initiateSSL(); return; } @@ -879,16 +948,22 @@ request->flags.pinned = 1; if (pinned_connection->pinnedAuth()) request->flags.auth = 1; + comm_add_close_handler(serverConn->fd, fwdServerClosedWrapper, this); // the server may close the pinned connection before this request pconnRace = racePossible; dispatch(); return; } - /* Failure. Fall back on next path */ - debugs(17,2,HERE << " Pinned connection " << pinned_connection << " not valid."); - serverDestinations.shift(); - startConnectionOrFail(); - return; + /* Failure. Fall back on next path unless we can re-pin */ + debugs(17,2,HERE << "Pinned connection failed: " << pinned_connection); + if (pconnRace != raceHappened || !request->flags.canRePin) { + serverDestinations.shift(); + pconnRace = raceImpossible; + startConnectionOrFail(); + return; + } + debugs(17,3, HERE << "There was a pconn race. Will try to repin."); + // and fall through to regular handling } // Use pconn to avoid opening a new connection. @@ -1013,12 +1088,23 @@ } #endif +#if USE_SSL + if (request->flags.sslPeek) { + CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, + ConnStateData::httpsPeeked, serverConnection()); + unregister(serverConn); // async call owns it now + complete(); // destroys us + return; + } +#endif + if (serverConnection()->getPeer() != NULL) { serverConnection()->getPeer()->stats.fetches++; request->peer_login = serverConnection()->getPeer()->login; request->peer_domain = serverConnection()->getPeer()->domain; httpStart(this); } else { + assert(!request->flags.sslPeek); request->peer_login = NULL; request->peer_domain = NULL; === modified file 'src/ftp.cc' --- src/ftp.cc 2012-06-22 03:49:38 +0000 +++ src/ftp.cc 2012-07-18 16:21:47 +0000 @@ -3580,9 +3580,6 @@ http_code = HTTP_INTERNAL_SERVER_ERROR; } - if (ftpState->request) - ftpState->request->detailError(err_code, code); - ErrorState err(err_code, http_code, ftpState->request); if (ftpState->old_request) @@ -3597,6 +3594,9 @@ else err.ftp.reply = xstrdup(""); + // TODO: interpret as FTP-specific error code + err.detailError(code); + ftpState->entry->replaceHttpReply( err.BuildHttpReply() ); ftpSendQuit(ftpState); === modified file 'src/globals.h' --- src/globals.h 2012-04-25 05:29:20 +0000 +++ src/globals.h 2012-06-19 21:51:49 +0000 @@ -152,6 +152,8 @@ extern int ssl_ctx_ex_index_dont_verify_domain; /* -1 */ extern int ssl_ex_index_cert_error_check; /* -1 */ extern int ssl_ex_index_ssl_error_detail; /* -1 */ + extern int ssl_ex_index_ssl_peeked_cert; /* -1 */ + extern int ssl_ex_index_ssl_errors; /* -1 */ extern const char *external_acl_message; /* NULL */ extern int opt_send_signal; /* -1 */ === modified file 'src/gopher.cc' --- src/gopher.cc 2012-01-20 18:55:04 +0000 +++ src/gopher.cc 2012-04-20 17:14:56 +0000 @@ -758,7 +758,6 @@ return; } - errno = 0; #if USE_DELAY_POOLS read_sz = delayId.bytesWanted(1, read_sz); #endif @@ -796,13 +795,13 @@ if (flag != COMM_OK) { debugs(50, 1, "gopherReadReply: error reading: " << xstrerror()); - if (ignoreErrno(errno)) { + if (ignoreErrno(xerrno)) { AsyncCall::Pointer call = commCbCall(5,4, "gopherReadReply", CommIoCbPtrFun(gopherReadReply, gopherState)); comm_read(conn, buf, read_sz, call); } else { ErrorState *err = new ErrorState(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR, gopherState->fwd->request); - err->xerrno = errno; + err->xerrno = xerrno; gopherState->fwd->fail(err); gopherState->serverConn->close(); } @@ -852,7 +851,7 @@ if (errflag) { ErrorState *err; err = new ErrorState(ERR_WRITE_ERROR, HTTP_SERVICE_UNAVAILABLE, gopherState->fwd->request); - err->xerrno = errno; + err->xerrno = xerrno; err->port = gopherState->fwd->request->port; err->url = xstrdup(entry->url()); gopherState->fwd->fail(err); === modified file 'src/http.cc' --- src/http.cc 2012-07-17 14:25:06 +0000 +++ src/http.cc 2012-07-18 16:21:47 +0000 @@ -2291,7 +2291,7 @@ // should not matter because either client-side will provide its own or // there will be no response at all (e.g., if the the client has left). ErrorState *err = new ErrorState(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, fwd->request); - err->xerrno = ERR_DETAIL_SRV_REQMOD_REQ_BODY; + err->detailError(ERR_DETAIL_SRV_REQMOD_REQ_BODY); fwd->fail(err); } === modified file 'src/ssl/ErrorDetail.cc' --- src/ssl/ErrorDetail.cc 2012-01-20 18:55:04 +0000 +++ src/ssl/ErrorDetail.cc 2012-06-19 21:51:49 +0000 @@ -16,6 +16,8 @@ SslErrors TheSslErrors; static SslErrorEntry TheSslErrorArray[] = { + {SQUID_X509_V_ERR_CERT_CHANGE, + "SQUID_X509_V_ERR_CERT_CHANGE"}, {SQUID_ERR_SSL_HANDSHAKE, "SQUID_ERR_SSL_HANDSHAKE"}, {SQUID_X509_V_ERR_DOMAIN_MISMATCH, @@ -88,6 +90,44 @@ {SSL_ERROR_NONE, NULL} }; +struct SslErrorAlias { + const char *name; + const Ssl::ssl_error_t *errors; +}; + +static const Ssl::ssl_error_t hasExpired[] = {X509_V_ERR_CERT_HAS_EXPIRED, SSL_ERROR_NONE}; +static const Ssl::ssl_error_t notYetValid[] = {X509_V_ERR_CERT_NOT_YET_VALID, SSL_ERROR_NONE}; +static const Ssl::ssl_error_t domainMismatch[] = {SQUID_X509_V_ERR_DOMAIN_MISMATCH, SSL_ERROR_NONE}; +static const Ssl::ssl_error_t certUntrusted[] = {X509_V_ERR_INVALID_CA, + X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, + X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + X509_V_ERR_CERT_UNTRUSTED, SSL_ERROR_NONE}; +static const Ssl::ssl_error_t certSelfSigned[] = {X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, SSL_ERROR_NONE}; + +// The list of error name shortcuts for use with ssl_error acls. +// The keys without the "ssl::" scope prefix allow shorter error +// names within the SSL options scope. This is easier than +// carefully stripping the scope prefix in Ssl::ParseErrorString(). +static SslErrorAlias TheSslErrorShortcutsArray[] = { + {"ssl::certHasExpired", hasExpired}, + {"certHasExpired", hasExpired}, + {"ssl::certNotYetValid", notYetValid}, + {"certNotYetValid", notYetValid}, + {"ssl::certDomainMismatch", domainMismatch}, + {"certDomainMismatch", domainMismatch}, + {"ssl::certUntrusted", certUntrusted}, + {"certUntrusted", certUntrusted}, + {"ssl::certSelfSigned", certSelfSigned}, + {"certSelfSigned", certSelfSigned}, + {NULL, NULL} +}; + +// Use std::map to optimize search. +typedef std::map SslErrorShortcuts; +SslErrorShortcuts TheSslErrorShortcuts; + static void loadSslErrorMap() { assert(TheSslErrors.empty()); @@ -96,6 +136,13 @@ } } +static void loadSslErrorShortcutsMap() +{ + assert(TheSslErrorShortcuts.empty()); + for (int i = 0; TheSslErrorShortcutsArray[i].name; i++) + TheSslErrorShortcuts[TheSslErrorShortcutsArray[i].name] = TheSslErrorShortcutsArray[i].errors; +} + Ssl::ssl_error_t Ssl::GetErrorCode(const char *name) { for (int i = 0; TheSslErrorArray[i].name != NULL; i++) { @@ -105,24 +152,38 @@ return SSL_ERROR_NONE; } -Ssl::ssl_error_t +Ssl::Errors * Ssl::ParseErrorString(const char *name) { assert(name); const Ssl::ssl_error_t ssl_error = GetErrorCode(name); if (ssl_error != SSL_ERROR_NONE) - return ssl_error; + return new Ssl::Errors(ssl_error); if (xisdigit(*name)) { const long int value = strtol(name, NULL, 0); if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX) - return value; + return new Ssl::Errors(value); fatalf("Too small or too bug SSL error code '%s'", name); } + if (TheSslErrorShortcuts.empty()) + loadSslErrorShortcutsMap(); + + const SslErrorShortcuts::const_iterator it = TheSslErrorShortcuts.find(name); + if (it != TheSslErrorShortcuts.end()) { + // Should not be empty... + assert(it->second[0] != SSL_ERROR_NONE); + Ssl::Errors *errors = new Ssl::Errors(it->second[0]); + for (int i =1; it->second[i] != SSL_ERROR_NONE; i++) { + errors->push_back_unique(it->second[i]); + } + return errors; + } + fatalf("Unknown SSL error name '%s'", name); - return SSL_ERROR_SSL; // not reached + return NULL; // not reached } const char *Ssl::GetErrorName(Ssl::ssl_error_t value) @@ -160,11 +221,11 @@ */ const char *Ssl::ErrorDetail::subject() const { - if (!peer_cert) + if (!broken_cert) return "[Not available]"; static char tmpBuffer[256]; // A temporary buffer - X509_NAME_oneline(X509_get_subject_name(peer_cert.get()), tmpBuffer, + X509_NAME_oneline(X509_get_subject_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer)); return tmpBuffer; } @@ -186,12 +247,12 @@ */ const char *Ssl::ErrorDetail::cn() const { - if (!peer_cert) + if (!broken_cert) return "[Not available]"; static String tmpStr; ///< A temporary string buffer tmpStr.clean(); - Ssl::matchX509CommonNames(peer_cert.get(), &tmpStr, copy_cn); + Ssl::matchX509CommonNames(broken_cert.get(), &tmpStr, copy_cn); return tmpStr.termedBuf(); } @@ -200,11 +261,11 @@ */ const char *Ssl::ErrorDetail::ca_name() const { - if (!peer_cert) + if (!broken_cert) return "[Not available]"; static char tmpBuffer[256]; // A temporary buffer - X509_NAME_oneline(X509_get_issuer_name(peer_cert.get()), tmpBuffer, sizeof(tmpBuffer)); + X509_NAME_oneline(X509_get_issuer_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer)); return tmpBuffer; } @@ -213,11 +274,11 @@ */ const char *Ssl::ErrorDetail::notbefore() const { - if (!peer_cert) + if (!broken_cert) return "[Not available]"; static char tmpBuffer[256]; // A temporary buffer - ASN1_UTCTIME * tm = X509_get_notBefore(peer_cert.get()); + ASN1_UTCTIME * tm = X509_get_notBefore(broken_cert.get()); Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); return tmpBuffer; } @@ -227,11 +288,11 @@ */ const char *Ssl::ErrorDetail::notafter() const { - if (!peer_cert) + if (!broken_cert) return "[Not available]"; static char tmpBuffer[256]; // A temporary buffer - ASN1_UTCTIME * tm = X509_get_notAfter(peer_cert.get()); + ASN1_UTCTIME * tm = X509_get_notAfter(broken_cert.get()); Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); return tmpBuffer; } @@ -279,16 +340,20 @@ } /** - * It converts the code to a string value. Currently the following - * formating codes are supported: + * Converts the code to a string value. Supported formating codes are: + * + * Error meta information: * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*) * %ssl_error_descr: A short description of the SSL error + * %ssl_lib_error: human-readable low-level error string by ERR_error_string(3SSL) + * + * Certificate information extracted from broken (not necessarily peer!) cert * %ssl_cn: The comma-separated list of common and alternate names * %ssl_subject: The certificate subject * %ssl_ca_name: The certificate issuer name * %ssl_notbefore: The certificate "not before" field * %ssl_notafter: The certificate "not after" field - * %ssl_lib_error: human-readable low-level error string by ERR_error_string(3SSL) + * \retval the length of the code (the number of characters will be replaced by value) */ int Ssl::ErrorDetail::convert(const char *code, const char **value) const @@ -344,15 +409,15 @@ return errDetailStr; } -/* We may do not want to use X509_dup but instead - internal SSL locking: - CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); - peer_cert.reset(cert); -*/ -Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no), lib_error_no(SSL_ERROR_NONE) +Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert, X509 *broken): error_no (err_no), lib_error_no(SSL_ERROR_NONE) { if (cert) - peer_cert.reset(X509_dup(cert)); + peer_cert.resetAndLock(cert); + + if (broken) + broken_cert.resetAndLock(broken); + else + broken_cert.resetAndLock(cert); detailEntry.error_no = SSL_ERROR_NONE; } @@ -363,7 +428,11 @@ request = anErrDetail.request; if (anErrDetail.peer_cert.get()) { - peer_cert.reset(X509_dup(anErrDetail.peer_cert.get())); + peer_cert.resetAndLock(anErrDetail.peer_cert.get()); + } + + if (anErrDetail.broken_cert.get()) { + broken_cert.resetAndLock(anErrDetail.broken_cert.get()); } detailEntry = anErrDetail.detailEntry; === modified file 'src/ssl/ErrorDetail.h' --- src/ssl/ErrorDetail.h 2011-11-18 16:40:10 +0000 +++ src/ssl/ErrorDetail.h 2012-06-19 21:51:49 +0000 @@ -15,10 +15,12 @@ { /** \ingroup ServerProtocolSSLAPI - * The ssl_error_t representation of the error described by "name". - * This function also parses numeric arguments. + * Converts user-friendly error "name" into an Ssl::Errors list. + * The resulting list may have one or more elements, and needs to be + * released by the caller. + * This function can handle numeric error numbers as well as names. */ -ssl_error_t ParseErrorString(const char *name); +Ssl::Errors *ParseErrorString(const char *name); /** \ingroup ServerProtocolSSLAPI @@ -46,7 +48,8 @@ class ErrorDetail { public: - ErrorDetail(ssl_error_t err_no, X509 *cert); + // if broken certificate is nil, the peer certificate is broken + ErrorDetail(ssl_error_t err_no, X509 *peer, X509 *broken); ErrorDetail(ErrorDetail const &); const String &toString() const; ///< An error detail string to embed in squid error pages void useRequest(HttpRequest *aRequest) { if (aRequest != NULL) request = aRequest;} @@ -56,7 +59,10 @@ ssl_error_t errorNo() const {return error_no;} ///Sets the low-level error returned by OpenSSL ERR_get_error() void setLibError(unsigned long lib_err_no) {lib_error_no = lib_err_no;} - + /// the peer certificate + X509 *peerCert() { return peer_cert.get(); } + /// peer or intermediate certificate that failed validation + X509 *brokenCert() {return broken_cert.get(); } private: typedef const char * (ErrorDetail::*fmt_action_t)() const; /** @@ -86,6 +92,7 @@ ssl_error_t error_no; ///< The error code unsigned long lib_error_no; ///< low-level error returned by OpenSSL ERR_get_error(3SSL) X509_Pointer peer_cert; ///< A pointer to the peer certificate + X509_Pointer broken_cert; ///< A pointer to the broken certificate (peer or intermediate) mutable ErrorDetailEntry detailEntry; HttpRequest::Pointer request; }; === modified file 'src/ssl/Makefile.am' --- src/ssl/Makefile.am 2012-06-08 11:33:21 +0000 +++ src/ssl/Makefile.am 2012-06-19 16:08:52 +0000 @@ -31,6 +31,8 @@ ErrorDetail.h \ ErrorDetailManager.cc \ ErrorDetailManager.h \ + ServerBump.cc \ + ServerBump.h \ support.cc \ support.h \ \ === added file 'src/ssl/ServerBump.cc' --- src/ssl/ServerBump.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/ServerBump.cc 2012-06-19 21:51:49 +0000 @@ -0,0 +1,45 @@ +/* + * $Id$ + * + * DEBUG: section 33 Client-side Routines + * + */ + +#include "squid.h" + +#include "client_side.h" +#include "forward.h" +#include "ssl/ServerBump.h" +#include "Store.h" + + +CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerBump); + + +Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e): + request(fakeRequest), + sslErrors(NULL) +{ + debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port); + const char *uri = urlCanonical(request); + if (e) { + entry = e; + entry->lock(); + } else + entry = storeCreateEntry(uri, uri, request->flags, request->method); + // We do not need to be a client because the error contents will be used + // later, but an entry without any client will trim all its contents away. + sc = storeClientListAdd(entry, this); +} + +Ssl::ServerBump::~ServerBump() +{ + debugs(33, 4, HERE << "destroying"); + if (entry) { + debugs(33, 4, HERE << *entry); + storeUnregister(sc, entry, this); + entry->unlock(); + } + cbdataReferenceDone(sslErrors); +} + === added file 'src/ssl/ServerBump.h' --- src/ssl/ServerBump.h 1970-01-01 00:00:00 +0000 +++ src/ssl/ServerBump.h 2012-06-19 21:51:49 +0000 @@ -0,0 +1,39 @@ +#ifndef _SQUID_SSL_PEEKER_H +#define _SQUID_SSL_PEEKER_H + +#include "base/AsyncJob.h" +#include "base/CbcPointer.h" +#include "comm/forward.h" +#include "HttpRequest.h" +#include "ip/Address.h" + +class ConnStateData; + +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 + +private: + store_client *sc; ///< dummy client to prevent entry trimming + + CBDATA_CLASS2(ServerBump); +}; + +} // namespace Ssl + +#endif === modified file 'src/ssl/certificate_db.cc' --- src/ssl/certificate_db.cc 2012-06-16 02:17:14 +0000 +++ src/ssl/certificate_db.cc 2012-06-19 21:51:49 +0000 @@ -171,14 +171,12 @@ return(strcmp(a[Ssl::CertificateDb::cnlName], b[CertificateDb::cnlName])); } -const std::string Ssl::CertificateDb::serial_file("serial"); const std::string Ssl::CertificateDb::db_file("index.txt"); const std::string Ssl::CertificateDb::cert_dir("certs"); const std::string Ssl::CertificateDb::size_file("size"); Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size) : db_path(aDb_path), - serial_full(aDb_path + "/" + serial_file), db_full(aDb_path + "/" + db_file), cert_full(aDb_path + "/" + cert_dir), size_full(aDb_path + "/" + size_file), @@ -186,7 +184,6 @@ max_db_size(aMax_db_size), fs_block_size(aFs_block_size), dbLock(db_full), - dbSerialLock(serial_full), enabled_disk_store(true) { if (db_path.empty() && !max_db_size) @@ -202,7 +199,21 @@ return pure_find(host_name, cert, pkey); } -bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) +bool Ssl::CertificateDb::purgeCert(std::string const & key) +{ + const Locker locker(dbLock, Here); + load(); + if (!db) + return false; + + if (!deleteByHostname(key)) + return false; + + save(); + return true; +} + +bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName) { const Locker locker(dbLock, Here); load(); @@ -218,12 +229,14 @@ } row.setValue(cnlSerial, serial_string.c_str()); char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow()); + // We are creating certificates with unique serial numbers. If the serial + // number is found in the database, the same certificate is already stored. if (rrow != NULL) - return false; + return true; { 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; } @@ -244,7 +257,9 @@ 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()); } @@ -262,56 +277,10 @@ return true; } -BIGNUM * Ssl::CertificateDb::getCurrentSerialNumber() -{ - const Locker locker(dbSerialLock, Here); - // load serial number from file. - Ssl::BIO_Pointer file(BIO_new(BIO_s_file())); - if (!file) - return NULL; - - if (BIO_rw_filename(file.get(), const_cast(serial_full.c_str())) <= 0) - return NULL; - - Ssl::ASN1_INT_Pointer serial_ai(ASN1_INTEGER_new()); - if (!serial_ai) - return NULL; - - char buffer[1024]; - if (!a2i_ASN1_INTEGER(file.get(), serial_ai.get(), buffer, sizeof(buffer))) - return NULL; - - Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(serial_ai.get(), NULL)); - - if (!serial) - return NULL; - - // increase serial number. - Ssl::BIGNUM_Pointer increased_serial(BN_dup(serial.get())); - if (!increased_serial) - return NULL; - - BN_add_word(increased_serial.get(), 1); - - // save increased serial number. - if (BIO_seek(file.get(), 0)) - return NULL; - - Ssl::ASN1_INT_Pointer increased_serial_ai(BN_to_ASN1_INTEGER(increased_serial.get(), NULL)); - if (!increased_serial_ai) - return NULL; - - i2a_ASN1_INTEGER(file.get(), increased_serial_ai.get()); - BIO_puts(file.get(),"\n"); - - return serial.release(); -} - -void Ssl::CertificateDb::create(std::string const & db_path, int serial) +void Ssl::CertificateDb::create(std::string const & db_path) { if (db_path == "") throw std::runtime_error("Path to db is empty"); - std::string serial_full(db_path + "/" + serial_file); std::string db_full(db_path + "/" + db_file); std::string cert_full(db_path + "/" + cert_dir); std::string size_full(db_path + "/" + size_file); @@ -330,18 +299,6 @@ #endif throw std::runtime_error("Cannot create " + cert_full); - Ssl::ASN1_INT_Pointer i(ASN1_INTEGER_new()); - ASN1_INTEGER_set(i.get(), serial); - - Ssl::BIO_Pointer file(BIO_new(BIO_s_file())); - if (!file) - throw std::runtime_error("SSL error"); - - if (BIO_write_filename(file.get(), const_cast(serial_full.c_str())) <= 0) - throw std::runtime_error("Cannot open " + cert_full + " to open"); - - i2a_ASN1_INTEGER(file.get(), i.get()); - std::ofstream size(size_full.c_str()); if (size) size << 0; @@ -358,17 +315,6 @@ db.load(); } -std::string Ssl::CertificateDb::getSNString() const -{ - const Locker locker(dbSerialLock, Here); - std::ifstream file(serial_full.c_str()); - if (!file) - return ""; - std::string serial; - file >> serial; - return serial; -} - bool Ssl::CertificateDb::pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) { if (!db) === modified file 'src/ssl/certificate_db.h' --- src/ssl/certificate_db.h 2012-03-08 13:03:19 +0000 +++ src/ssl/certificate_db.h 2012-03-27 16:52:31 +0000 @@ -94,15 +94,14 @@ CertificateDb(std::string const & db_path, size_t aMax_db_size, size_t aFs_block_size); /// Find certificate and private key for host name bool find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey); + /// Delete a certificate from database + bool purgeCert(std::string const & key); /// Save certificate to disk. - bool addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey); - /// Get a serial number to use for generating a new certificate. - BIGNUM * getCurrentSerialNumber(); + bool addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName); /// Create and initialize a database under the db_path - static void create(std::string const & db_path, int serial); + static void create(std::string const & db_path); /// Check the database stored under the db_path. static void check(std::string const & db_path, size_t max_db_size); - std::string getSNString() const; ///< Get serial number as string. bool IsEnabledDiskStore() const; ///< Check enabled of dist store. private: void load(); ///< Load db from disk. @@ -154,7 +153,6 @@ static IMPLEMENT_LHASH_COMP_FN(index_name_cmp,const char **) #endif - static const std::string serial_file; ///< Base name of the file to store serial number. static const std::string db_file; ///< Base name of the database index file. static const std::string cert_dir; ///< Base name of the directory to store the certs. static const std::string size_file; ///< Base name of the file to store db size. @@ -162,7 +160,6 @@ static const size_t min_db_size; const std::string db_path; ///< The database directory. - const std::string serial_full; ///< Full path of the file to store serial number. const std::string db_full; ///< Full path of the database index file. const std::string cert_full; ///< Full path of the directory to store the certs. const std::string size_full; ///< Full path of the file to store the db size. @@ -171,7 +168,6 @@ const size_t max_db_size; ///< Max size of db. const size_t fs_block_size; ///< File system block size. mutable Lock dbLock; ///< protects the database file - mutable Lock dbSerialLock; ///< protects the serial number file bool enabled_disk_store; ///< The storage on the disk is enabled. }; === modified file 'src/ssl/crtd_message.cc' --- src/ssl/crtd_message.cc 2012-01-20 18:55:04 +0000 +++ src/ssl/crtd_message.cc 2012-03-08 04:02:40 +0000 @@ -3,6 +3,7 @@ */ #include "squid.h" +#include "ssl/gadgets.h" #include "ssl/crtd_message.h" #if HAVE_CSTDLIB #include @@ -10,6 +11,9 @@ #if HAVE_CSTRING #include #endif +#if HAVE_STDEXCEPT +#include +#endif Ssl::CrtdMessage::CrtdMessage() : body_size(0), state(BEFORE_CODE) @@ -173,5 +177,86 @@ body += '\n' + other_part; } + +bool Ssl::CrtdMessage::parseRequest(Ssl::CertificateProperties &certProperties, std::string &error) +{ + Ssl::CrtdMessage::BodyParams map; + std::string certs_part; + parseBody(map, certs_part); + Ssl::CrtdMessage::BodyParams::iterator i = map.find(Ssl::CrtdMessage::param_host); + if (i == map.end()) { + error = "Cannot find \"host\" parameter in request message"; + return false; + } + certProperties.commonName = i->second; + + i = map.find(Ssl::CrtdMessage::param_SetValidAfter); + if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) + certProperties.setValidAfter = true; + + i = map.find(Ssl::CrtdMessage::param_SetValidBefore); + if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) + certProperties.setValidBefore = true; + + i = map.find(Ssl::CrtdMessage::param_SetCommonName); + if (i != map.end()) { + // use this as Common Name instead of the hostname + // defined with host or Common Name from mimic cert + certProperties.commonName = i->second; + certProperties.setCommonName = true; + } + + i = map.find(Ssl::CrtdMessage::param_Sign); + if (i != map.end()) { + if ((certProperties.signAlgorithm = Ssl::certSignAlgorithmId(i->second.c_str())) == Ssl::algSignEnd) { + error = "Wrong signing algoritm: " + i->second; + return false; + } + } + else + certProperties.signAlgorithm = Ssl::algSignTrusted; + + if (!Ssl::readCertAndPrivateKeyFromMemory(certProperties.signWithX509, certProperties.signWithPkey, certs_part.c_str())) { + error = "Broken signing certificate!"; + return false; + } + + static const std::string CERT_BEGIN_STR("-----BEGIN CERTIFICATE"); + size_t pos; + if ((pos = certs_part.find(CERT_BEGIN_STR)) != std::string::npos) { + pos += CERT_BEGIN_STR.length(); + if ((pos= certs_part.find(CERT_BEGIN_STR, pos)) != std::string::npos) + Ssl::readCertFromMemory(certProperties.mimicCert, certs_part.c_str() + pos); + } + return true; +} + +void Ssl::CrtdMessage::composeRequest(Ssl::CertificateProperties const &certProperties) +{ + body.clear(); + body = Ssl::CrtdMessage::param_host + "=" + certProperties.commonName; + if (certProperties.setCommonName) + body += "\n" + Ssl::CrtdMessage::param_SetCommonName + "=" + certProperties.commonName; + if (certProperties.setValidAfter) + body += "\n" + Ssl::CrtdMessage::param_SetValidAfter + "=on"; + if (certProperties.setValidBefore) + body += "\n" + Ssl::CrtdMessage::param_SetValidBefore + "=on"; + if(certProperties.signAlgorithm != Ssl::algSignEnd) + body += "\n" + Ssl::CrtdMessage::param_Sign + "=" + certSignAlgorithm(certProperties.signAlgorithm); + + std::string certsPart; + if (!Ssl::writeCertAndPrivateKeyToMemory(certProperties.signWithX509, certProperties.signWithPkey, certsPart)) + throw std::runtime_error("Ssl::writeCertAndPrivateKeyToMemory()"); + if (certProperties.mimicCert.get()) { + if (!Ssl::appendCertToMemory(certProperties.mimicCert, certsPart)) + throw std::runtime_error("Ssl::appendCertToMemory()"); + } + body += "\n" + certsPart; +} + 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]); +const std::string Ssl::CrtdMessage::param_Sign("Sign"); === modified file 'src/ssl/crtd_message.h' --- src/ssl/crtd_message.h 2010-11-18 08:01:53 +0000 +++ src/ssl/crtd_message.h 2012-06-19 21:51:49 +0000 @@ -14,6 +14,8 @@ namespace Ssl { +class CertificateProperties; + /** * This class is responsible for composing and parsing messages destined to, or comming * from an ssl_crtd server. Format of these mesages is: @@ -61,10 +63,23 @@ The other multistring part of body. \endverbatim */ void composeBody(BodyParams const & map, std::string const & other_part); + + /// orchestrates entire request parsing + bool parseRequest(Ssl::CertificateProperties &, std::string &error); + void composeRequest(Ssl::CertificateProperties const &); // throws + /// String code for "new_certificate" messages static const std::string code_new_certificate; /// Parameter name for passing hostname static const std::string param_host; + /// Parameter name for passing SetValidAfter cert adaptation variable + static const std::string param_SetValidAfter; + /// Parameter name for passing SetValidBefore cert adaptation variable + static const std::string param_SetValidBefore; + /// Parameter name for passing SetCommonName cert adaptation variable + static const std::string param_SetCommonName; + /// Parameter name for passing signing algorithm + static const std::string param_Sign; private: enum ParseState { BEFORE_CODE, === modified file 'src/ssl/gadgets.cc' --- src/ssl/gadgets.cc 2012-02-20 18:07:29 +0000 +++ src/ssl/gadgets.cc 2012-06-02 00:21:53 +0000 @@ -8,44 +8,6 @@ #include #endif -/** - \ingroup ServerProtocolSSLInternal - * Add CN to subject in request. - */ -static bool addCnToRequest(Ssl::X509_REQ_Pointer & request, char const * cn) -{ - // not an Ssl::X509_NAME_Pointer because X509_REQ_get_subject_name() - // returns a pointer to the existing subject name. Nothing to clean here. - X509_NAME *name = X509_REQ_get_subject_name(request.get()); - if (!name) - return false; - - // The second argument of the X509_NAME_add_entry_by_txt declared as - // "char *" on some OS. Use cn_name to avoid compile warnings. - static char cn_name[3] = "CN"; - if (!X509_NAME_add_entry_by_txt(name, cn_name, MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0)) - return false; - - return true; -} - -/** - \ingroup ServerProtocolSSLInternal - * Make request on sign using private key and hostname. - */ -static bool makeRequest(Ssl::X509_REQ_Pointer & request, Ssl::EVP_PKEY_Pointer const & pkey, char const * host) -{ - if (!X509_REQ_set_version(request.get(), 0L)) - return false; - - if (!addCnToRequest(request, host)) - return false; - - if (!X509_REQ_set_pubkey(request.get(), pkey.get())) - return false; - return true; -} - EVP_PKEY * Ssl::createSslPrivateKey() { Ssl::EVP_PKEY_Pointer pkey(EVP_PKEY_new()); @@ -65,18 +27,6 @@ return pkey.release(); } -X509_REQ * Ssl::createNewX509Request(Ssl::EVP_PKEY_Pointer const & pkey, const char * hostname) -{ - Ssl::X509_REQ_Pointer request(X509_REQ_new()); - - if (!request) - return NULL; - - if (!makeRequest(request, pkey, hostname)) - return NULL; - return request.release(); -} - /** \ingroup ServerProtocolSSLInternal * Set serial random serial number or set random serial number. @@ -101,41 +51,6 @@ return true; } -X509 * Ssl::signRequest(Ssl::X509_REQ_Pointer const & request, Ssl::X509_Pointer const & x509, Ssl::EVP_PKEY_Pointer const & pkey, ASN1_TIME * timeNotAfter, BIGNUM const * serial) -{ - Ssl::X509_Pointer cert(X509_new()); - if (!cert) - return NULL; - - if (!setSerialNumber(X509_get_serialNumber(cert.get()), serial)) - return NULL; - - if (!X509_set_issuer_name(cert.get(), x509.get() ? X509_get_subject_name(x509.get()) : X509_REQ_get_subject_name(request.get()))) - return NULL; - - if (!X509_gmtime_adj(X509_get_notBefore(cert.get()), (-2)*24*60*60)) - return NULL; - - if (timeNotAfter) { - if (!X509_set_notAfter(cert.get(), timeNotAfter)) - return NULL; - } else if (!X509_gmtime_adj(X509_get_notAfter(cert.get()), 60*60*24*356*3)) - return NULL; - - if (!X509_set_subject_name(cert.get(), X509_REQ_get_subject_name(request.get()))) - return NULL; - - Ssl::EVP_PKEY_Pointer tmppkey(X509_REQ_get_pubkey(request.get())); - - if (!tmppkey || !X509_set_pubkey(cert.get(), tmppkey.get())) - return NULL; - - if (!X509_sign(cert.get(), pkey.get(), EVP_sha1())) - return NULL; - - return cert.release(); -} - bool Ssl::writeCertAndPrivateKeyToMemory(Ssl::X509_Pointer const & cert, Ssl::EVP_PKEY_Pointer const & pkey, std::string & bufferToWrite) { bufferToWrite.clear(); @@ -160,6 +75,30 @@ return true; } +bool Ssl::appendCertToMemory(Ssl::X509_Pointer const & cert, std::string & bufferToWrite) +{ + if (!cert) + return false; + + BIO_Pointer bio(BIO_new(BIO_s_mem())); + if (!bio) + return false; + + if (!PEM_write_bio_X509 (bio.get(), cert.get())) + return false; + + char *ptr = NULL; + long len = BIO_get_mem_data(bio.get(), &ptr); + if (!ptr) + return false; + + if (!bufferToWrite.empty()) + bufferToWrite.append(" "); // add a space... + + bufferToWrite.append(ptr, len); + return true; +} + bool Ssl::writeCertAndPrivateKeyToFile(Ssl::X509_Pointer const & cert, Ssl::EVP_PKEY_Pointer const & pkey, char const * filename) { if (!pkey || !cert) @@ -198,25 +137,333 @@ return true; } -bool Ssl::generateSslCertificateAndPrivateKey(char const *host, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, BIGNUM const * serial) -{ - pkey.reset(createSslPrivateKey()); +bool Ssl::readCertFromMemory(X509_Pointer & cert, char const * bufferToRead) +{ + Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); + BIO_puts(bio.get(), bufferToRead); + + X509 * certPtr = NULL; + cert.reset(PEM_read_bio_X509(bio.get(), &certPtr, 0, 0)); + if (!cert) + return false; + + return true; +} + +// According to RFC 5280 (Section A.1), the common name length in a certificate +// can be at most 64 characters +static const size_t MaxCnLen = 64; + +// Replace certs common name with the given +static bool replaceCommonName(Ssl::X509_Pointer & cert, std::string const &rawCn) +{ + std::string cn = rawCn; + + if (cn.length() > MaxCnLen) { + // In the case the length od CN is more than the maximum supported size + // try to use the first upper level domain. + size_t pos = 0; + do { + pos = cn.find('.', pos + 1); + } while(pos != std::string::npos && (cn.length() - pos + 2) > MaxCnLen); + + // If no short domain found or this domain is a toplevel domain + // we failed to find a good cn name. + if (pos == std::string::npos || cn.find('.', pos + 1) == std::string::npos) + return false; + + std::string fixedCn(1, '*'); + fixedCn.append(cn.c_str() + pos); + cn = fixedCn; + } + + // Assume [] surround an IPv6 address and strip them because browsers such + // as Firefox, Chromium, and Safari prefer bare IPv6 addresses in CNs. + if (cn.length() > 2 && *cn.begin() == '[' && *cn.rbegin() == ']') + cn = cn.substr(1, cn.size()-2); + + 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); + if (loc >=0) { + 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.c_str()), -1, -1, 0); +} + +const char *Ssl::CertSignAlgorithmStr[] = { + "signTrusted", + "signUntrusted", + "signSelf", + NULL +}; + +const char *Ssl::CertAdaptAlgorithmStr[] = { + "setValidAfter", + "setValidBefore", + "setCommonName", + NULL +}; + +Ssl::CertificateProperties::CertificateProperties(): + setValidAfter(false), + setValidBefore(false), + setCommonName(false), + signAlgorithm(Ssl::algSignEnd) +{} + +std::string & Ssl::CertificateProperties::dbKey() const +{ + static std::string certKey; + certKey.clear(); + certKey.reserve(4096); + if (mimicCert.get()) { + char buf[1024]; + certKey.append(X509_NAME_oneline(X509_get_subject_name(mimicCert.get()), buf, sizeof(buf))); + } + + if (certKey.empty()) { + certKey.append("/CN=", 4); + certKey.append(commonName); + } + + if (setValidAfter) + certKey.append("+SetValidAfter=on", 17); + + if (setValidBefore) + certKey.append("+SetValidBefore=on", 18); + + if (setCommonName) { + certKey.append("+SetCommonName=", 15); + certKey.append(commonName); + } + + if (signAlgorithm != Ssl::algSignEnd) { + certKey.append("+Sign=", 6); + certKey.append(certSignAlgorithm(signAlgorithm)); + } + + return certKey; +} + +static bool buildCertificate(Ssl::X509_Pointer & cert, Ssl::CertificateProperties const &properties) +{ + // not an Ssl::X509_NAME_Pointer because X509_REQ_get_subject_name() + // returns a pointer to the existing subject name. Nothing to clean here. + if (properties.mimicCert.get()) { + // Leave subject empty if we cannot extract it from true cert. + if (X509_NAME *name = X509_get_subject_name(properties.mimicCert.get())) { + // X509_set_subject_name will call X509_dup for name + X509_set_subject_name(cert.get(), name); + } + } + + if (properties.setCommonName || !properties.mimicCert.get()) { + // In this case the CN of the certificate given by the user + // Ignore errors: it is better to make a certificate with no CN + // than to quit ssl_crtd because we cannot make a certificate. + // Most errors are caused by user input such as huge domain names. + (void)replaceCommonName(cert, properties.commonName); + } + + // 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 = NULL; + if (!properties.setValidBefore && properties.mimicCert.get()) + aTime = X509_get_notBefore(properties.mimicCert.get()); + if (!aTime && properties.signWithX509.get()) + aTime = X509_get_notBefore(properties.signWithX509.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; + + aTime = NULL; + if (!properties.setValidAfter && properties.mimicCert.get()) + aTime = X509_get_notAfter(properties.mimicCert.get()); + if (!aTime && properties.signWithX509.get()) + aTime = X509_get_notAfter(properties.signWithX509.get()); + if (aTime) { + if (!X509_set_notAfter(cert.get(), aTime)) + return false; + } else if (!X509_gmtime_adj(X509_get_notAfter(cert.get()), 60*60*24*356*3)) + return false; + + // mimic the alias and possibly subjectAltName + if (properties.mimicCert.get()) { + unsigned char *alStr; + int alLen; + alStr = X509_alias_get0(properties.mimicCert.get(), &alLen); + if (alStr) { + X509_alias_set1(cert.get(), alStr, alLen); + } + + // Mimic subjectAltName unless we used a configured CN: browsers reject + // certificates with CN unrelated to subjectAltNames. + if (!properties.setCommonName) { + int pos=X509_get_ext_by_NID (properties.mimicCert.get(), OBJ_sn2nid("subjectAltName"), -1); + X509_EXTENSION *ext=X509_get_ext(properties.mimicCert.get(), pos); + if (ext) { + X509_add_ext(cert.get(), ext, -1); + /* According the RFC 5280 using extensions requires version 3 + certificate. + Set version value to 2 for version 3 certificates. + */ + X509_set_version(cert.get(), 2); + } + } + } + + return true; +} + +static bool generateFakeSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkeyToStore, Ssl::CertificateProperties const &properties, Ssl::BIGNUM_Pointer const &serial) +{ + Ssl::EVP_PKEY_Pointer pkey; + // Use signing certificates private key as generated certificate private key + if (properties.signWithPkey.get()) + pkey.resetAndLock(properties.signWithPkey.get()); + else // if not exist generate one + pkey.reset(Ssl::createSslPrivateKey()); + if (!pkey) return false; - Ssl::X509_REQ_Pointer request(createNewX509Request(pkey, host)); - if (!request) - return false; - - if (signedX509.get() && signedPkey.get()) - cert.reset(signRequest(request, signedX509, signedPkey, X509_get_notAfter(signedX509.get()), serial)); - else - cert.reset(signRequest(request, signedX509, pkey, NULL, serial)); - + Ssl::X509_Pointer cert(X509_new()); if (!cert) return false; - return true; + // Set pub key and serial given by the caller + if (!X509_set_pubkey(cert.get(), pkey.get())) + return false; + if (!setSerialNumber(X509_get_serialNumber(cert.get()), serial.get())) + return false; + + // Fill the certificate with the required properties + if (!buildCertificate(cert, properties)) + return false; + + int ret = 0; + // Set issuer name, from CA or our subject name for self signed cert + if (properties.signAlgorithm != Ssl::algSignSelf && properties.signWithX509.get()) + ret = X509_set_issuer_name(cert.get(), X509_get_subject_name(properties.signWithX509.get())); + else // Self signed certificate, set issuer to self + ret = X509_set_issuer_name(cert.get(), X509_get_subject_name(cert.get())); + if (!ret) + return false; + + /*Now sign the request */ + if (properties.signAlgorithm != Ssl::algSignSelf && properties.signWithPkey.get()) + ret = X509_sign(cert.get(), properties.signWithPkey.get(), EVP_sha1()); + else //else sign with self key (self signed request) + ret = X509_sign(cert.get(), pkey.get(), EVP_sha1()); + + if (!ret) + return false; + + certToStore.reset(cert.release()); + pkeyToStore.reset(pkey.release()); + return true; +} + +static BIGNUM *createCertSerial(unsigned char *md, unsigned int n) +{ + + assert(n == 20); //for sha1 n is 20 (for md5 n is 16) + + BIGNUM *serial = NULL; + serial = BN_bin2bn(md, n, NULL); + + // if the serial is "0" set it to '1' + if (BN_is_zero(serial)) + BN_one(serial); + + // serial size does not exceed 20 bytes + assert(BN_num_bits(serial) <= 160); + + // According the RFC 5280, serial is an 20 bytes ASN.1 INTEGER (a signed big integer) + // and the maximum value for X.509 certificate serial number is 2^159-1 and + // the minimum 0. If the first bit of the serial is '1' ( eg 2^160-1), + // will result to a negative integer. + // To handle this, if the produced serial is greater than 2^159-1 + // truncate the last bit + if (BN_is_bit_set(serial, 159)) + BN_clear_bit(serial, 159); + + return serial; +} + +/// Return the SHA1 digest of the DER encoded version of the certificate +/// stored in a BIGNUM +static BIGNUM *x509Digest(Ssl::X509_Pointer const & cert) +{ + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + + if (!X509_digest(cert.get(),EVP_sha1(),md,&n)) + return NULL; + + return createCertSerial(md, n); +} + +static BIGNUM *x509Pubkeydigest(Ssl::X509_Pointer const & cert) +{ + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + + if (!X509_pubkey_digest(cert.get(),EVP_sha1(),md,&n)) + return NULL; + + return createCertSerial(md, n); +} + +/// Generate a unique serial number based on a Ssl::CertificateProperties object +/// for a new generated certificate +static bool createSerial(Ssl::BIGNUM_Pointer &serial, Ssl::CertificateProperties const &properties) +{ + Ssl::EVP_PKEY_Pointer fakePkey; + Ssl::X509_Pointer fakeCert; + + serial.reset(x509Pubkeydigest(properties.signWithX509)); + if (!serial.get()) { + serial.reset(BN_new()); + BN_is_zero(serial.get()); + } + + if (!generateFakeSslCertificate(fakeCert, fakePkey, properties, serial)) + return false; + + // The x509Fingerprint return an SHA1 hash. + // both SHA1 hash and maximum serial number size are 20 bytes. + BIGNUM *r = x509Digest(fakeCert); + if (!r) + return false; + + serial.reset(r); + return true; +} + +bool Ssl::generateSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkeyToStore, Ssl::CertificateProperties const &properties) +{ + Ssl::BIGNUM_Pointer serial; + + if (!createSerial(serial, properties)) + return false; + + return generateFakeSslCertificate(certToStore, pkeyToStore, properties, serial); } /** @@ -271,3 +518,150 @@ return (X509_cmp_current_time(&tm) > 0); } + +/// Print the time represented by a ASN1_TIME struct to a string using GeneralizedTime format +static bool asn1timeToGeneralizedTimeStr(ASN1_TIME *aTime, char *buf, int bufLen) +{ + // ASN1_Time holds time to UTCTime or GeneralizedTime form. + // UTCTime has the form YYMMDDHHMMSS[Z | [+|-]offset] + // GeneralizedTime has the form YYYYMMDDHHMMSS[Z | [+|-] offset] + + // length should have space for data plus 2 extra bytes for the two extra year fields + // plus the '\0' char. + if ((aTime->length + 3) > bufLen) + return false; + + char *str; + if (aTime->type == V_ASN1_UTCTIME) { + if (aTime->data[0] > '5') { // RFC 2459, section 4.1.2.5.1 + buf[0] = '1'; + buf[1] = '9'; + } else { + buf[0] = '2'; + buf[1] = '0'; + } + str = buf +2; + } + else // if (aTime->type == V_ASN1_GENERALIZEDTIME) + str = buf; + + memcpy(str, aTime->data, aTime->length); + str[aTime->length] = '\0'; + return true; +} + +static int asn1time_cmp(ASN1_TIME *asnTime1, ASN1_TIME *asnTime2) +{ + char strTime1[64], strTime2[64]; + if (!asn1timeToGeneralizedTimeStr(asnTime1, strTime1, sizeof(strTime1))) + return -1; + if (!asn1timeToGeneralizedTimeStr(asnTime2, strTime2, sizeof(strTime2))) + return -1; + + return strcmp(strTime1, strTime2); +} + +bool Ssl::certificateMatchesProperties(X509 *cert, CertificateProperties const &properties) +{ + assert(cert); + + // For non self-signed certificates we have to check if the signing certificate changed + if (properties.signAlgorithm != Ssl::algSignSelf) { + assert(properties.signWithX509.get()); + if (X509_check_issued(properties.signWithX509.get(), cert) != X509_V_OK) + return false; + } + + X509 *cert2 = properties.mimicCert.get(); + // If there is not certificate to mimic stop here + if (!cert2) + return true; + + if (!properties.setCommonName) { + X509_NAME *cert1_name = X509_get_subject_name(cert); + X509_NAME *cert2_name = X509_get_subject_name(cert2); + if (X509_NAME_cmp(cert1_name, cert2_name) != 0) + return false; + } + else if (properties.commonName != CommonHostName(cert)) + return false; + + if (!properties.setValidBefore) { + ASN1_TIME *aTime = X509_get_notBefore(cert); + ASN1_TIME *bTime = X509_get_notBefore(cert2); + if (asn1time_cmp(aTime, bTime) != 0) + return false; + } else if (X509_cmp_current_time(X509_get_notBefore(cert)) >= 0) { + // notBefore does not exist (=0) or it is in the future (>0) + return false; + } + + if (!properties.setValidAfter) { + ASN1_TIME *aTime = X509_get_notAfter(cert); + ASN1_TIME *bTime = X509_get_notAfter(cert2); + if (asn1time_cmp(aTime, bTime) != 0) + return false; + } else if (X509_cmp_current_time(X509_get_notAfter(cert)) <= 0) { + // notAfter does not exist (0) or it is in the past (<0) + return false; + } + + + char *alStr1; + int alLen; + alStr1 = (char *)X509_alias_get0(cert, &alLen); + char *alStr2 = (char *)X509_alias_get0(cert2, &alLen); + if ((!alStr1 && alStr2) || (alStr1 && !alStr2) || + (alStr1 && alStr2 && strcmp(alStr1, alStr2)) != 0) + return false; + + // Compare subjectAltName extension + STACK_OF(GENERAL_NAME) * cert1_altnames; + cert1_altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + STACK_OF(GENERAL_NAME) * cert2_altnames; + cert2_altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(cert2, NID_subject_alt_name, NULL, NULL); + bool match = true; + if (cert1_altnames) { + int numalts = sk_GENERAL_NAME_num(cert1_altnames); + for (int i = 0; match && i < numalts; i++) { + const GENERAL_NAME *aName = sk_GENERAL_NAME_value(cert1_altnames, i); + match = sk_GENERAL_NAME_find(cert2_altnames, aName); + } + } + else if (cert2_altnames) + match = false; + + sk_GENERAL_NAME_pop_free(cert1_altnames, GENERAL_NAME_free); + sk_GENERAL_NAME_pop_free(cert2_altnames, GENERAL_NAME_free); + + return match; +} + +static const char *getSubjectEntry(X509 *x509, int nid) +{ + static char name[1024] = ""; // stores common name (CN) + + if (!x509) + return NULL; + + // TODO: What if the entry is a UTF8String? See X509_NAME_get_index_by_NID(3ssl). + const int nameLen = X509_NAME_get_text_by_NID( + X509_get_subject_name(x509), + nid, name, sizeof(name)); + + if (nameLen > 0) + return name; + + return NULL; +} + +const char *Ssl::CommonHostName(X509 *x509) +{ + return getSubjectEntry(x509, NID_commonName); +} + +const char *Ssl::getOrganization(X509 *x509) +{ + return getSubjectEntry(x509, NID_organizationName); +} + === modified file 'src/ssl/gadgets.h' --- src/ssl/gadgets.h 2012-02-20 18:07:29 +0000 +++ src/ssl/gadgets.h 2012-04-05 16:38:31 +0000 @@ -6,6 +6,7 @@ #define SQUID_SSL_GADGETS_H #include "base/TidyPointer.h" +#include "ssl/crtd_message.h" #if HAVE_OPENSSL_SSL_H #include @@ -25,6 +26,28 @@ because they are used by ssl_crtd. */ +/** + \ingroup SslCrtdSslAPI + * Add SSL locking (a.k.a. reference counting) to TidyPointer + */ +template +class LockingPointer: public TidyPointer +{ +public: + typedef TidyPointer Parent; + + LockingPointer(T *t = NULL): Parent(t) { + } + + void resetAndLock(T *t) { + if (t != this->get()) { + reset(t); + if (t) + CRYPTO_add(&t->references, 1, lock); + } + } +}; + // Macro to be used to define the C++ equivalent function of an extern "C" // function. The C++ function suffixed with the _cpp extension #define CtoCpp1(function, argument) \ @@ -37,13 +60,13 @@ * TidyPointer typedefs for common SSL objects */ CtoCpp1(X509_free, X509 *) -typedef TidyPointer X509_Pointer; +typedef LockingPointer X509_Pointer; CtoCpp1(sk_X509_free, STACK_OF(X509) *) typedef TidyPointer X509_STACK_Pointer; CtoCpp1(EVP_PKEY_free, EVP_PKEY *) -typedef TidyPointer EVP_PKEY_Pointer; +typedef LockingPointer EVP_PKEY_Pointer; CtoCpp1(BN_free, BIGNUM *) typedef TidyPointer BIGNUM_Pointer; @@ -81,18 +104,18 @@ /** \ingroup SslCrtdSslAPI - * Create request on certificate for a host. - */ -X509_REQ * createNewX509Request(EVP_PKEY_Pointer const & pkey, const char * hostname); - -/** - \ingroup SslCrtdSslAPI * Write private key and SSL certificate to memory. */ bool writeCertAndPrivateKeyToMemory(X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey, std::string & bufferToWrite); /** \ingroup SslCrtdSslAPI + * Append SSL certificate to bufferToWrite. + */ +bool appendCertToMemory(X509_Pointer const & cert, std::string & bufferToWrite); + +/** + \ingroup SslCrtdSslAPI * Write private key and SSL certificate to file. */ bool writeCertAndPrivateKeyToFile(X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey, char const * filename); @@ -105,19 +128,103 @@ /** \ingroup SslCrtdSslAPI - * Sign SSL request. - * \param x509 if this param equals NULL, returning certificate will be selfsigned. - * \return X509 Signed certificate. - */ -X509 * signRequest(X509_REQ_Pointer const & request, X509_Pointer const & x509, EVP_PKEY_Pointer const & pkey, ASN1_TIME * timeNotAfter, BIGNUM const * serial); + * Read SSL certificate from memory. + */ +bool readCertFromMemory(X509_Pointer & cert, char const * bufferToRead); + +/** + \ingroup SslCrtdSslAPI + * Supported certificate signing algorithms + */ +enum CertSignAlgorithm {algSignTrusted = 0, algSignUntrusted, algSignSelf, algSignEnd}; + +/** + \ingroup SslCrtdSslAPI + * Short names for certificate signing algorithms + */ + +extern const char *CertSignAlgorithmStr[]; + +/** + \ingroup SslCrtdSslAPI + * Return the short name of the signing algorithm "sg" + */ +inline const char *certSignAlgorithm(int sg) +{ + if (sg >=0 && sg < Ssl::algSignEnd) + return Ssl::CertSignAlgorithmStr[sg]; + + return NULL; +} + +/** + \ingroup SslCrtdSslAPI + * Return the id of the signing algorithm "sg" + */ +inline CertSignAlgorithm certSignAlgorithmId(const char *sg) +{ + for (int i = 0; i < algSignEnd && Ssl::CertSignAlgorithmStr[i] != NULL; i++) + if (strcmp(Ssl::CertSignAlgorithmStr[i], sg) == 0) + return (CertSignAlgorithm)i; + + return algSignEnd; +} + +/** + \ingroup SslCrtdSslAPI + * Supported certificate adaptation algorithms + */ +enum CertAdaptAlgorithm {algSetValidAfter = 0, algSetValidBefore, algSetCommonName, algSetEnd}; + +/** + \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::algSetEnd) + return Ssl::CertAdaptAlgorithmStr[alg]; + + return NULL; +} + +/** + \ingroup SslCrtdSslAPI + * Simple struct to pass certificate generation parameters to generateSslCertificate function. + */ +class CertificateProperties { +public: + CertificateProperties(); + X509_Pointer mimicCert; ///< Certificate to mimic + X509_Pointer signWithX509; ///< Certificate to sign the generated request + EVP_PKEY_Pointer signWithPkey; ///< The key of the signing certificate + bool setValidAfter; ///< Do not mimic "Not Valid After" field + bool setValidBefore; ///< Do not mimic "Not Valid Before" field + bool setCommonName; ///< Replace the CN field of the mimicing subject with the given + std::string commonName; ///< A CN to use for the generated certificate + CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use + /// Returns certificate database primary key. New fake certificates + /// purge old fake certificates with the same key. + std::string & dbKey() const; +private: + CertificateProperties(CertificateProperties &); + CertificateProperties &operator =(CertificateProperties const &); +}; /** \ingroup SslCrtdSslAPI * Decide on the kind of certificate and generate a CA- or self-signed one. + * The generated certificate will inherite properties from certToMimic * Return generated certificate and private key in resultX509 and resultPkey * variables. */ -bool generateSslCertificateAndPrivateKey(char const *host, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const* serial); +bool generateSslCertificate(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, CertificateProperties const &properties); /** \ingroup SslCrtdSslAPI @@ -140,5 +247,27 @@ */ bool sslDateIsInTheFuture(char const * date); +/** + \ingroup SslCrtdSslAPI + * Check if the major fields of a certificates matches the properties given by + * a CertficateProperties object + \return true if the certificates matches false otherwise. +*/ +bool certificateMatchesProperties(X509 *peer_cert, CertificateProperties const &properties); + +/** + \ingroup ServerProtocolSSLAPI + * Returns CN from the certificate, suitable for use as a host name. + * Uses static memory to temporary store the extracted name. +*/ +const char *CommonHostName(X509 *x509); + +/** + \ingroup ServerProtocolSSLAPI + * Returns Organization from the certificate. + * Uses static memory to temporary store the extracted name. +*/ +const char *getOrganization(X509 *x509); + } // namespace Ssl #endif // SQUID_SSL_GADGETS_H === modified file 'src/ssl/helper.cc' --- src/ssl/helper.cc 2012-04-25 21:08:51 +0000 +++ src/ssl/helper.cc 2012-06-07 17:46:16 +0000 @@ -28,15 +28,13 @@ { assert(ssl_crtd == NULL); - bool useSslBump = false; - for (AnyP::PortCfg *s = ::Config.Sockaddr.http; s; s = s->next) { - if (s->sslBump) { - useSslBump = true; - break; - } - } - - if (!useSslBump) + // 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_crtd = new helper("ssl_crtd"); === modified file 'src/ssl/ssl_crtd.cc' --- src/ssl/ssl_crtd.cc 2012-01-20 18:55:04 +0000 +++ src/ssl/ssl_crtd.cc 2012-06-19 21:51:49 +0000 @@ -72,14 +72,9 @@ Create new private key and certificate request for "host.dom". Sign new request by received certificate and private key. -usage: ssl_crtd -c -s ssl_store_path\n -n new_serial_number +usage: ssl_crtd -c -s ssl_store_path\n -c Init ssl db directories and exit. - -n new_serial_number HEX serial number to use when initializing db. - The default value of serial number is - the number of seconds since Epoch minus 1200000000 -usage: ssl_crtd -g -s ssl_store_path - -g Show current serial number and exit. \endverbatim */ @@ -195,13 +190,8 @@ "-----END RSA PRIVATE KEY-----\n" "\tCreate new private key and certificate request for \"host.dom\"\n" "\tSign new request by received certificate and private key.\n" - "usage: ssl_crtd -c -s ssl_store_path -n new_serial_number\n" - "\t-c Init ssl db directories and exit.\n" - "\t-n new_serial_number HEX serial number to use when initializing db.\n" - "\t The default value of serial number is\n" - "\t the number of seconds since Epoch minus 1200000000\n" - "usage: ssl_crtd -g -s ssl_store_path\n" - "\t-g Show current serial number and exit."; + "usage: ssl_crtd -c -s ssl_store_path\n" + "\t-c Init ssl db directories and exit.\n"; std::cerr << help_string << std::endl; } @@ -209,33 +199,36 @@ \ingroup ssl_crtd * Proccess new request message. */ -static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size) +static bool proccessNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size) { - Ssl::CrtdMessage::BodyParams map; - std::string body_part; - request_message.parseBody(map, body_part); - - Ssl::CrtdMessage::BodyParams::iterator i = map.find(Ssl::CrtdMessage::param_host); - if (i == map.end()) - throw std::runtime_error("Cannot find \"" + Ssl::CrtdMessage::param_host + "\" parameter in request message."); - std::string host = i->second; + Ssl::CertificateProperties certProperties; + std::string error; + if (!request_message.parseRequest(certProperties, error)) + throw std::runtime_error("Error while parsing the crtd request: " + error); Ssl::CertificateDb db(db_path, max_db_size, fs_block_size); Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; - db.find("/CN=" + host, cert, pkey); - - if (!cert || !pkey) { - Ssl::X509_Pointer certToSign; - Ssl::EVP_PKEY_Pointer pkeyToSign; - Ssl::readCertAndPrivateKeyFromMemory(certToSign, pkeyToSign, body_part.c_str()); - - Ssl::BIGNUM_Pointer serial(db.getCurrentSerialNumber()); - - if (!Ssl::generateSslCertificateAndPrivateKey(host.c_str(), certToSign, pkeyToSign, cert, pkey, serial.get())) + std::string &cert_subject = certProperties.dbKey(); + + db.find(cert_subject, cert, pkey); + + if (cert.get()) { + if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) { + // The certificate changed (renewed or other reason). + // Generete a new one with the updated fields. + cert.reset(NULL); + pkey.reset(NULL); + db.purgeCert(cert_subject); + } + } + + if (!cert || !pkey) { + if (!Ssl::generateSslCertificate(cert, pkey, certProperties)) 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."); } @@ -260,12 +253,10 @@ int main(int argc, char *argv[]) { try { - int serial = (getCurrentTime() - 1200000000); size_t max_db_size = 0; size_t fs_block_size = 2048; char c; bool create_new_db = false; - bool show_sn = false; std::string db_path; // proccess options. while ((c = getopt(argc, argv, "dcghvs:M:b:n:")) != -1) { @@ -281,11 +272,6 @@ case 's': db_path = optarg; break; - case 'n': { - std::stringstream sn_stream(optarg); - sn_stream >> std::hex >> serial; - break; - } case 'M': if (!parseBytesOptionValue(&max_db_size, optarg)) { throw std::runtime_error("Error when parsing -M options value"); @@ -298,9 +284,6 @@ case 'c': create_new_db = true; break; - case 'g': - show_sn = true; - break; case 'h': usage(); exit(0); @@ -311,16 +294,11 @@ if (create_new_db) { std::cout << "Initialization SSL db..." << std::endl; - Ssl::CertificateDb::create(db_path, serial); + Ssl::CertificateDb::create(db_path); std::cout << "Done" << std::endl; exit(0); } - if (show_sn) { - Ssl::CertificateDb db(db_path, 4096, 0); - std::cout << db.getSNString() << std::endl; - exit(0); - } { Ssl::CertificateDb::check(db_path, max_db_size); } === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2012-05-06 01:29:22 +0000 +++ src/ssl/support.cc 2012-06-19 21:51:49 +0000 @@ -46,6 +46,13 @@ #include "ssl/support.h" #include "ssl/gadgets.h" +const char *Ssl::BumpModeStr[] = { + "none", + "client-first", + "server-first", + NULL +}; + /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals \ingroup ServerProtocolSSLAPI @@ -200,6 +207,11 @@ return matchDomainName(server, cn[0] == '*' ? cn + 1 : cn); } +bool Ssl::checkX509ServerValidity(X509 *cert, const char *server) +{ + return matchX509CommonNames(cert, (void *)server, check_domain); +} + /// \ingroup ServerProtocolSSLInternal static int ssl_verify_cb(int ok, X509_STORE_CTX * ctx) @@ -213,6 +225,7 @@ const char *server = (const char *)SSL_get_ex_data(ssl, ssl_ex_index_server); void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain); ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check); + X509 *peeked_cert = (X509 *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_peeked_cert); X509 *peer_cert = ctx->cert; X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer, @@ -222,9 +235,7 @@ debugs(83, 5, "SSL Certificate signature OK: " << buffer); if (server) { - int found = Ssl::matchX509CommonNames(peer_cert, (void *)server, check_domain); - - if (!found) { + 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; @@ -232,20 +243,45 @@ } } + 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)); + if (!errs) { + errs = new Ssl::Errors(error_no); + 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); + 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); + assert(!filledCheck->sslErrors); + filledCheck->sslErrors = new Ssl::Errors(error_no); if (check->fastCheck() == ACCESS_ALLOWED) { debugs(83, 3, "bypassing SSL error " << error_no << " in " << buffer); ok = 1; } else { debugs(83, 5, "confirming SSL error " << error_no); } + delete filledCheck->sslErrors; + filledCheck->sslErrors = NULL; } } @@ -262,7 +298,7 @@ } Ssl::ErrorDetail *errDetail = - new Ssl::ErrorDetail(error_no, broken_cert); + new Ssl::ErrorDetail(error_no, peer_cert, broken_cert); if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) { debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer); @@ -577,6 +613,23 @@ delete errDetail; } +static void +ssl_free_SslErrors(void *, void *ptr, CRYPTO_EX_DATA *, + int, long, void *) +{ + Ssl::Errors *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) @@ -616,6 +669,8 @@ ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL); ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist); ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail); + ssl_ex_index_ssl_peeked_cert = SSL_get_ex_new_index(0, (void *) "ssl_peeked_cert", NULL, NULL, &ssl_free_X509); + ssl_ex_index_ssl_errors = SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors); } /// \ingroup ServerProtocolSSLInternal @@ -668,6 +723,11 @@ if (!CAfile) CAfile = clientCA; + if (!certfile) { + debugs(83, DBG_CRITICAL, "ERROR: No certificate file"); + return NULL; + } + switch (version) { case 2: @@ -722,8 +782,8 @@ if (sslContext == NULL) { ssl_error = ERR_get_error(); - fatalf("Failed to allocate SSL context: %s\n", - ERR_error_string(ssl_error, NULL)); + debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate SSL context: " << ERR_error_string(ssl_error, NULL)); + return NULL; } SSL_CTX_set_options(sslContext, ssl_parse_options(options)); @@ -747,8 +807,9 @@ if (!SSL_CTX_set_cipher_list(sslContext, cipher)) { ssl_error = ERR_get_error(); - fatalf("Failed to set SSL cipher suite '%s': %s\n", - cipher, ERR_error_string(ssl_error, NULL)); + debugs(83, DBG_CRITICAL, "ERROR: Failed to set SSL cipher suite '" << cipher << "': " << ERR_error_string(ssl_error, NULL)); + SSL_CTX_free(sslContext); + return NULL; } } @@ -1261,13 +1322,13 @@ return createSSLContext(cert, pkey); } -SSL_CTX * Ssl::generateSslContext(char const *host, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey) +SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; - if (!generateSslCertificateAndPrivateKey(host, signedX509, signedPkey, cert, pkey, NULL)) { + if (!generateSslCertificate(cert, pkey, properties)) return NULL; - } + if (!cert) return NULL; @@ -1277,7 +1338,7 @@ return createSSLContext(cert, pkey); } -bool Ssl::verifySslCertificateDate(SSL_CTX * sslContext) +bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties) { // Temporary ssl for getting X509 certificate from SSL_CTX. Ssl::SSL_Pointer ssl(SSL_new(sslContext)); @@ -1285,7 +1346,10 @@ ASN1_TIME * time_notBefore = X509_get_notBefore(cert); ASN1_TIME * time_notAfter = X509_get_notAfter(cert); bool ret = (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0); - return ret; + if (!ret) + return false; + + return certificateMatchesProperties(cert, properties); } bool @@ -1376,4 +1440,29 @@ } } +bool Ssl::generateUntrustedCert(X509_Pointer &untrustedCert, EVP_PKEY_Pointer &untrustedPkey, X509_Pointer const &cert, EVP_PKEY_Pointer const & pkey) +{ + // Generate the self-signed certificate, using a hard-coded subject prefix + 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); +} + #endif /* USE_SSL */ === modified file 'src/ssl/support.h' --- src/ssl/support.h 2011-11-22 12:00:59 +0000 +++ src/ssl/support.h 2012-06-19 21:51:49 +0000 @@ -35,6 +35,7 @@ #ifndef SQUID_SSL_SUPPORT_H #define SQUID_SSL_SUPPORT_H +#include "CbDataList.h" #include "ssl/gadgets.h" #if HAVE_OPENSSL_SSL_H @@ -56,16 +57,20 @@ */ // 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_ERR_SSL_HANDSHAKE +#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_CERT_CHANGE #define SQUID_SSL_ERROR_MAX INT_MAX 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; + } //namespace Ssl /// \ingroup ServerProtocolSSLAPI @@ -106,16 +111,45 @@ { /** \ingroup ServerProtocolSSLAPI + * Supported ssl-bump modes + */ +enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpEnd}; + +/** + \ingroup ServerProtocolSSLAPI + * Short names for ssl-bump modes + */ +extern const char *BumpModeStr[]; + +/** + \ingroup ServerProtocolSSLAPI + * Return the short name of the ssl-bump mode "bm" + */ +inline const char *bumpMode(int bm) +{ + return (0 <= bm && bm < Ssl::bumpEnd) ? Ssl::BumpModeStr[bm] : NULL; +} + +/** + \ingroup ServerProtocolSSLAPI + * Generate a certificate to be used as untrusted signing certificate, based on a trusted CA +*/ +bool generateUntrustedCert(X509_Pointer & untrustedCert, EVP_PKEY_Pointer & untrustedPkey, X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey); + +/** + \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 & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey); +SSL_CTX * generateSslContext(CertificateProperties const &properties); /** \ingroup ServerProtocolSSLAPI - * Check date of certificate signature. If there is out of date error fucntion - * returns false, true otherwise. + * Check if the certificate of the given context is still valid + \param sslContext The context to check + \param properties Check if the context certificate matches the given properties + \return true if the contexts certificate is valid, false otherwise */ -bool verifySslCertificateDate(SSL_CTX * sslContext); +bool verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties); /** \ingroup ServerProtocolSSLAPI @@ -152,6 +186,15 @@ /** \ingroup ServerProtocolSSLAPI + * Check if the certificate is valid for a server + \param cert The X509 cert to check. + \param server The server name. + \return true if the certificate is valid for the server or false otherwise. + */ +bool checkX509ServerValidity(X509 *cert, const char *server); + +/** + \ingroup ServerProtocolSSLAPI * Convert a given ASN1_TIME to a string form. \param tm the time in ASN1_TIME form \param buf the buffer to write the output === modified file 'src/structs.h' --- src/structs.h 2012-07-17 14:25:06 +0000 +++ src/structs.h 2012-07-18 16:21:47 +0000 @@ -622,6 +622,8 @@ char *flags; acl_access *cert_error; SSL_CTX *sslContext; + sslproxy_cert_sign *cert_sign; + sslproxy_cert_adapt *cert_adapt; } ssl_client; #endif @@ -958,9 +960,7 @@ struct request_flags { - request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0), - hostVerified(0), - spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),chunked_reply(0),stream_error(0),sslBumped(0),destinationIPLookedUp_(0) { + request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0),hostVerified(0),spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),pinned(0),canRePin(0),chunked_reply(0),stream_error(0),sslPeek(0),sslBumped(0),destinationIPLookedUp_(0) { #if USE_HTTP_VIOLATIONS nocache_hack = 0; #endif @@ -999,10 +999,12 @@ unsigned int connection_auth_disabled:1; /** Connection oriented auth can not be supported */ unsigned int connection_proxy_auth:1; /** Request wants connection oriented auth */ unsigned int pinned:1; /* Request sent on a pinned connection */ + unsigned int canRePin:1; ///< OK to reopen a failed pinned connection unsigned int auth_sent:1; /* Authentication forwarded */ unsigned int no_direct:1; /* Deny direct forwarding unless overriden by always_direct. Used in accelerator mode */ unsigned int chunked_reply:1; /**< Reply with chunked transfer encoding */ unsigned int stream_error:1; /**< Whether stream error has occured */ + unsigned int sslPeek:1; ///< internal ssl-bump request to get server cert unsigned int sslBumped:1; /**< ssl-bumped request*/ // When adding new flags, please update cloneAdaptationImmune() as needed. @@ -1090,6 +1092,21 @@ int zero_object_sz; }; +#if USE_SSL +struct _sslproxy_cert_sign { + int alg; + ACLList *aclList; + sslproxy_cert_sign *next; +}; + +struct _sslproxy_cert_adapt { + int alg; + char *param; + ACLList *aclList; + sslproxy_cert_adapt *next; +}; +#endif + class Logfile; #include "format/Format.h" === modified file 'src/tunnel.cc' --- src/tunnel.cc 2012-01-20 18:55:04 +0000 +++ src/tunnel.cc 2012-06-14 21:48:10 +0000 @@ -481,10 +481,23 @@ } /** + * Set the HTTP status for this request and sets the read handlers for client + * and server side connections. + */ +static void +tunnelStartShoveling(TunnelStateData *tunnelState) +{ + *tunnelState->status_ptr = HTTP_OK; + if (cbdataReferenceValid(tunnelState)) { + tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer); + tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); + } +} + +/** * All the pieces we need to write to client and/or server connection * have been written. - * - Set the HTTP status for this request. - * - Start the blind pump. + * Call the tunnelStartShoveling to start the blind pump. */ static void tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t flag, int xerrno, void *data) @@ -498,11 +511,7 @@ return; } - *tunnelState->status_ptr = HTTP_OK; - if (cbdataReferenceValid(tunnelState)) { - tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer); - tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient); - } + tunnelStartShoveling(tunnelState); } /* @@ -513,9 +522,14 @@ { TunnelStateData *tunnelState = (TunnelStateData *)data; debugs(26, 3, HERE << server << ", tunnelState=" << tunnelState); - AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", - CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); - Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL); + + if (tunnelState->request && (tunnelState->request->flags.spoof_client_ip || tunnelState->request->flags.intercepted)) + tunnelStartShoveling(tunnelState); // ssl-bumped connection, be quiet + else { + AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", + CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); + Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL); + } } static void === modified file 'src/typedefs.h' --- src/typedefs.h 2012-03-04 22:24:58 +0000 +++ src/typedefs.h 2012-03-07 23:33:53 +0000 @@ -91,6 +91,12 @@ typedef struct _customlog customlog; +#if USE_SSL +typedef struct _sslproxy_cert_sign sslproxy_cert_sign; + +typedef struct _sslproxy_cert_adapt sslproxy_cert_adapt; +#endif + #if SQUID_SNMP typedef variable_list *(oid_ParseFn) (variable_list *, snint *); === modified file 'src/whois.cc' --- src/whois.cc 2012-01-20 18:55:04 +0000 +++ src/whois.cc 2012-04-20 17:14:56 +0000 @@ -159,7 +159,7 @@ comm_read(conn, aBuffer, BUFSIZ, call); } else { ErrorState *err = new ErrorState(ERR_READ_ERROR, HTTP_INTERNAL_SERVER_ERROR, fwd->request); - err->xerrno = errno; + err->xerrno = xerrno; fwd->fail(err); conn->close(); }