=== modified file 'configure.in' --- configure.in 2010-11-06 23:34:12 +0000 +++ configure.in 2010-11-12 14:57:23 +0000 @@ -1513,6 +1513,21 @@ AM_CONDITIONAL([USE_DNSSERVER],[test "x$squid_opt_use_dnsserver" = "xyes" ]) +AM_CONDITIONAL(USE_SSL_CRTD, false) +AC_ARG_ENABLE(ssl-crtd, + AC_HELP_STRING([--enable-ssl-crtd], + [Prevent Squid from directly generation of SSL private key and + certificate request and instead enables the ssl_crtd processes.]), [ + SQUID_YESNO([$enableval], + [unrecogized argument to --enable-ssl-crtd: $enableval]) +]) + +if test "x$enable_ssl_crtd" = "xyes" -a "x$enable_ssl" = "xno" ; then + AC_MSG_ERROR([You need to enable ssl gatewaying support to use ssl_crtd feature. Try to use --enable-ssl. ]) +fi +SQUID_DEFINE_BOOL(USE_SSL_CRTD, ${enable_ssl_crtd:=no},[Use ssl_crtd daemon]) +AM_CONDITIONAL(USE_SSL_CRTD, [test "x$enable_ssl_crtd" = "xyes"]) + dnl Select Default hosts file location AC_ARG_ENABLE(default-hostsfile, AS_HELP_STRING([--enable-default-hostsfile=path], @@ -2187,6 +2202,7 @@ crypt.h \ cstdlib \ cstring \ + list \ ctype.h \ errno.h \ execinfo.h \ @@ -2199,6 +2215,7 @@ iosfwd \ iomanip \ iostream \ + fstream \ climits \ ip_compat.h \ ip_fil_compat.h \ @@ -2217,6 +2234,7 @@ map \ math.h \ memory.h \ + memory \ mount.h \ netdb.h \ netinet/in.h \ @@ -2228,6 +2246,7 @@ openssl/x509v3.h \ netinet/tcp.h \ openssl/engine.h \ + openssl/txt_db.h \ ostream \ paths.h \ poll.h \ @@ -3332,6 +3351,7 @@ src/ip/Makefile \ src/log/Makefile \ src/ipc/Makefile \ + src/ssl/Makefile \ src/mgr/Makefile \ contrib/Makefile \ snmplib/Makefile \ === modified file 'src/Makefile.am' --- src/Makefile.am 2010-11-06 14:58:44 +0000 +++ src/Makefile.am 2010-11-11 16:23:39 +0000 @@ -41,6 +41,15 @@ SUBDIRS = base comm eui acl fs repl auth ip icmp ident log ipc mgr +if ENABLE_SSL +SUBDIRS += ssl +SSL_LIBS = \ + ssl/libsslutil.la \ + ssl/libsslsquid.la +else +SSL_LOCAL_LIBS = +endif + if USE_ADAPTATION SUBDIRS += adaptation endif @@ -114,16 +123,6 @@ UNLINKD = endif -SSL_ALL_SOURCE = \ - ssl_support.cc \ - ssl_support.h - -if ENABLE_SSL -SSL_SOURCE = $(SSL_ALL_SOURCE) -else -SSL_SOURCE = -endif - WIN32_ALL_SOURCE = \ win32.cc \ WinSvc.cc @@ -430,7 +429,6 @@ SquidMath.h \ SquidMath.cc \ SquidNew.cc \ - $(SSL_SOURCE) \ stat.cc \ StatHist.cc \ String.cc \ @@ -517,8 +515,6 @@ LeakFinder.h \ $(SNMP_ALL_SOURCE) \ $(UNLINKDSOURCE) \ - $(SSL_ALL_SOURCE) \ - $(WIN32_ALL_SOURCE) \ $(LOADABLE_MODULES_SOURCES) \ DiskIO/DiskThreads/aiops.cc \ DiskIO/DiskThreads/aiops_win32.cc @@ -567,6 +563,7 @@ $(SNMPLIB) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -583,6 +580,7 @@ $(SNMPLIB) \ ${ADAPTATION_LIBS} \ $(ESI_LOCAL_LIBS) \ + $(SSL_LIBS) \ $(COMMON_LIBS) if USE_LOADABLE_MODULES @@ -761,12 +759,14 @@ DEFAULT_CONFIG_FILE = $(DEFAULT_CONFIG_DIR)/squid.conf DEFAULT_MIME_TABLE = $(DEFAULT_CONFIG_DIR)/mime.conf DEFAULT_DNSSERVER = $(libexecdir)/`echo dnsserver | sed '$(transform);s/$$/$(EXEEXT)/'` +DEFAULT_SSL_CRTD = $(libexecdir)/`echo ssl_crtd | sed '$(transform);s/$$/$(EXEEXT)/'` DEFAULT_LOG_PREFIX = $(DEFAULT_LOG_DIR) DEFAULT_CACHE_LOG = $(DEFAULT_LOG_PREFIX)/cache.log DEFAULT_ACCESS_LOG = $(DEFAULT_LOG_PREFIX)/access.log DEFAULT_STORE_LOG = $(DEFAULT_LOG_PREFIX)/store.log DEFAULT_NETDB_FILE = $(DEFAULT_LOG_PREFIX)/netdb.state DEFAULT_SWAP_DIR = $(localstatedir)/cache +DEFAULT_SSL_DB_DIR = $(localstatedir)/lib/ssl_db DEFAULT_PINGER = $(libexecdir)/`echo pinger | sed '$(transform);s/$$/$(EXEEXT)/'` DEFAULT_UNLINKD = $(libexecdir)/`echo unlinkd | sed '$(transform);s/$$/$(EXEEXT)/'` DEFAULT_LOGFILED = $(libexecdir)/`echo log_file_daemon | sed '$(transform);s/$$/$(EXEEXT)/'` @@ -833,6 +833,7 @@ -e "s%[@]DEFAULT_CACHE_EFFECTIVE_USER[@]%${CACHE_EFFECTIVE_USER}%g" \ -e "s%[@]DEFAULT_MIME_TABLE[@]%$(DEFAULT_MIME_TABLE)%g" \ -e "s%[@]DEFAULT_DNSSERVER[@]%$(DEFAULT_DNSSERVER)%g" \ + -e "s%[@]DEFAULT_SSL_CRTD[@]%$(DEFAULT_SSL_CRTD)%g" \ -e "s%[@]DEFAULT_UNLINKD[@]%$(DEFAULT_UNLINKD)%g" \ -e "s%[@]DEFAULT_PINGER[@]%$(DEFAULT_PINGER)%g" \ -e "s%[@]DEFAULT_DISKD[@]%$(DEFAULT_DISKD)%g" \ @@ -843,6 +844,7 @@ -e "s%[@]DEFAULT_PID_FILE[@]%$(DEFAULT_PID_FILE)%g" \ -e "s%[@]DEFAULT_NETDB_FILE[@]%$(DEFAULT_NETDB_FILE)%g" \ -e "s%[@]DEFAULT_SWAP_DIR[@]%$(DEFAULT_SWAP_DIR)%g" \ + -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_PREFIX[@]%$(DEFAULT_PREFIX)%g" \ @@ -1234,7 +1236,6 @@ RemovalPolicy.cc \ Server.cc \ $(SNMP_SOURCE) \ - $(SSL_SOURCE) \ SquidMath.h \ SquidMath.cc \ stat.cc \ @@ -1285,6 +1286,7 @@ $(REPL_OBJS) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -1438,7 +1440,6 @@ refresh.cc \ Server.cc \ $(SNMP_SOURCE) \ - $(SSL_SOURCE) \ SquidMath.h \ SquidMath.cc \ stat.cc \ @@ -1488,6 +1489,7 @@ $(REPL_OBJS) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -1601,7 +1603,6 @@ refresh.cc \ Server.cc \ $(SNMP_SOURCE) \ - $(SSL_SOURCE) \ SquidMath.h \ SquidMath.cc \ stat.cc \ @@ -1651,6 +1652,7 @@ $(REPL_OBJS) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -1755,7 +1757,6 @@ RemovalPolicy.cc \ Server.cc \ $(SNMP_SOURCE) \ - $(SSL_SOURCE) \ SquidMath.h \ SquidMath.cc \ stat.cc \ @@ -1809,6 +1810,7 @@ $(REPL_OBJS) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -1922,7 +1924,6 @@ RemovalPolicy.cc \ Server.cc \ $(SNMP_SOURCE) \ - $(SSL_SOURCE) \ SquidMath.h \ SquidMath.cc \ stat.cc \ @@ -1972,6 +1973,7 @@ $(REPL_OBJS) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -2339,7 +2341,6 @@ refresh.cc \ Server.cc \ $(SNMP_SOURCE) \ - $(SSL_SOURCE) \ SquidMath.h \ SquidMath.cc \ stat.cc \ @@ -2388,6 +2389,7 @@ $(REPL_OBJS) \ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ + $(SSL_LIBS) \ $(SNMPLIB) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ === modified file 'src/ProtoPort.cc' --- src/ProtoPort.cc 2009-12-31 02:35:01 +0000 +++ src/ProtoPort.cc 2010-11-11 16:01:09 +0000 @@ -4,11 +4,14 @@ #include "squid.h" #include "ProtoPort.h" +#if HAVE_LIMITS +#include +#endif http_port_list::http_port_list(const char *aProtocol) #if USE_SSL : - http(*this) + http(*this), dynamicCertMemCacheSize(std::numeric_limits::max()) #endif { protocol = xstrdup(aProtocol); @@ -31,6 +34,7 @@ safe_free(capath); safe_free(dhfile); safe_free(sslflags); + safe_free(sslContextSessionId); #endif } === modified file 'src/ProtoPort.h' --- src/ProtoPort.h 2010-04-17 02:29:04 +0000 +++ src/ProtoPort.h 2010-11-15 15:40:05 +0000 @@ -8,6 +8,10 @@ #include "cbdata.h" #include "comm/ListenStateData.h" +#if USE_SSL +#include "ssl/gadgets.h" +#endif + struct http_port_list { http_port_list(const char *aProtocol); ~http_port_list(); @@ -60,8 +64,13 @@ char *crlfile; char *dhfile; char *sslflags; - char *sslcontext; - SSL_CTX *sslContext; + char *sslContextSessionId; ///< "session id context" for staticSslContext + bool generateHostCertificates; ///< dynamically make host cert for sslBump + size_t dynamicCertMemCacheSize; ///< max size of generated certificates memory cache + + Ssl::SSL_CTX_Pointer staticSslContext; ///< for HTTPS accelerator or static sslBump + Ssl::X509_Pointer signingCert; ///< x509 certificate for signing generated certificates + Ssl::EVP_PKEY_Pointer signPkey; ///< private key for sighing generated certificates #endif CBDATA_CLASS2(http_port_list); === modified file 'src/acl/Certificate.h' --- src/acl/Certificate.h 2009-03-08 21:53:27 +0000 +++ src/acl/Certificate.h 2010-11-15 15:40:05 +0000 @@ -38,7 +38,7 @@ #include "acl/Acl.h" #include "acl/Data.h" #include "acl/Checklist.h" -#include "ssl_support.h" +#include "ssl/support.h" #include "acl/Strategised.h" /// \ingroup ACLAPI === modified file 'src/acl/CertificateData.h' --- src/acl/CertificateData.h 2009-03-31 12:39:30 +0000 +++ src/acl/CertificateData.h 2010-11-15 15:40:05 +0000 @@ -38,7 +38,7 @@ #include "splay.h" #include "acl/Acl.h" #include "acl/Data.h" -#include "ssl_support.h" +#include "ssl/support.h" #include "acl/StringData.h" /// \ingroup ACLAPI === modified file 'src/acl/SslErrorData.h' --- src/acl/SslErrorData.h 2009-03-08 19:34:36 +0000 +++ src/acl/SslErrorData.h 2010-11-09 15:37:39 +0000 @@ -8,7 +8,7 @@ #include "acl/Acl.h" #include "acl/Data.h" #include "CbDataList.h" -#include "ssl_support.h" +#include "ssl/support.h" class ACLSslErrorData : public ACLData { === modified file 'src/base/Makefile.am' --- src/base/Makefile.am 2010-10-07 07:53:45 +0000 +++ src/base/Makefile.am 2010-11-05 13:26:21 +0000 @@ -12,6 +12,7 @@ AsyncJobCalls.h \ AsyncCallQueue.cc \ AsyncCallQueue.h \ + TidyPointer.h \ CbcPointer.h \ InstanceId.h \ Subscription.h \ === added file 'src/base/TidyPointer.h' --- src/base/TidyPointer.h 1970-01-01 00:00:00 +0000 +++ src/base/TidyPointer.h 2010-11-10 11:51:32 +0000 @@ -0,0 +1,67 @@ +/* + * $Id$ + */ + +#ifndef SQUID_BASE_TIDYPOINTER_H +#define SQUID_BASE_TIDYPOINTER_H + +#include "config.h" + +/** + * A pointer that deletes the object it points to when the pointer's owner or + * context is gone. Similar to std::auto_ptr but without confusing assignment + * and with a customizable cleanup method. Prevents memory leaks in + * the presence of exceptions and processing short cuts. +*/ +template class TidyPointer +{ +public: + /// Delete callback. + typedef void DCB (T *t); + TidyPointer(T *t = NULL) + : raw(t), deAllocator(DeAllocator) {} +public: + bool operator !() const { return !raw; } + /// Returns raw and possibly NULL pointer + T *get() const { return raw; } + /// Address of the raw pointer, for pointer-setting functions + T **addr() { return &raw; } + /// Reset raw pointer - delete last one and save new one. + void reset(T *t) { + deletePointer(); + raw = t; + } + + /// Forget the raw pointer without freeing it. Become a nil pointer. + T *release() { + T *ret = raw; + raw = NULL; + return ret; + } + /// Deallocate raw pointer. + ~TidyPointer() { + deletePointer(); + } +private: + /// Forbidden copy constructor. + TidyPointer(TidyPointer const &); + /// Forbidden assigment operator. + TidyPointer & operator = (TidyPointer const &); + /// Deallocate raw pointer. Become a nil pointer. + void deletePointer() { + if (raw) { + deAllocator(raw); + } + raw = NULL; + } + T *raw; ///< pointer to T object or NULL + DCB *deAllocator; ///< cleanup function +}; + +/// DeAllocator for pointers that need free(3) from the std C library +template void tidyFree(T *p) +{ + xfree(p); +} + +#endif // SQUID_BASE_TIDYPOINTER_H === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2010-10-29 00:12:28 +0000 +++ src/cache_cf.cc 2010-11-15 22:06:58 +0000 @@ -46,6 +46,9 @@ #if USE_ECAP #include "adaptation/ecap/Config.h" #endif +#if USE_SSL +#include "ssl/Config.h" +#endif #include "auth/Config.h" #include "auth/Scheme.h" #include "ConfigParser.h" @@ -82,6 +85,10 @@ #include #endif +#if USE_SSL +#include "ssl/gadgets.h" +#endif + #if USE_ADAPTATION static void parse_adaptation_service_set_type(); static void parse_adaptation_service_chain_type(); @@ -146,6 +153,9 @@ static int parse_line(char *); static void parse_obsolete(const char *); static void parseBytesLine(size_t * bptr, const char *units); +#if USE_SSL +static void parseBytesOptionValue(size_t * bptr, const char *units, char const * value); +#endif #if !USE_DNSSERVERS static void parseBytesLineSigned(ssize_t * bptr, const char *units); #endif @@ -875,7 +885,13 @@ debugs(3, 1, "Initializing http_port " << s->http.s << " SSL context"); - s->sslContext = sslCreateServerContext(s->cert, s->key, s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslcontext); + 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::readCertAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->cert, s->key); } } @@ -886,7 +902,11 @@ for (s = Config.Sockaddr.https; s != NULL; s = (https_port_list *) s->http.next) { debugs(3, 1, "Initializing https_port " << s->http.s << " SSL context"); - s->sslContext = sslCreateServerContext(s->cert, s->key, s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslcontext); + 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)); } } @@ -1113,6 +1133,44 @@ } #endif +#if USE_SSL +/** + * Parse bytes from a string. + * Similar to the parseBytesLine function but parses the string value instead of + * the current token value. + */ +static void parseBytesOptionValue(size_t * bptr, const char *units, char const * value) +{ + int u; + if ((u = parseBytesUnits(units)) == 0) { + self_destruct(); + return; + } + + // Find number from string beginning. + char const * number_begin = value; + char const * number_end = value; + + while ((*number_end >= '0' && *number_end <= '9')) { + number_end++; + } + + String number; + number.limitInit(number_begin, number_end - number_begin); + + int d = xatoi(number.termedBuf()); + int m; + if ((m = parseBytesUnits(number_end)) == 0) { + self_destruct(); + return; + } + + *bptr = static_cast(m * d / u); + if (static_cast(*bptr) * 2 != m * d / u * 2) + self_destruct(); +} +#endif + static size_t parseBytesUnits(const char *unit) { @@ -3571,8 +3629,16 @@ safe_free(s->sslflags); s->sslflags = xstrdup(token + 9); } else if (strncmp(token, "sslcontext=", 11) == 0) { - safe_free(s->sslcontext); - s->sslcontext = xstrdup(token + 11); + safe_free(s->sslContextSessionId); + s->sslContextSessionId = xstrdup(token + 11); + } else if (strcmp(token, "generate-host-certificates") == 0) { + s->generateHostCertificates = true; + } else if (strcmp(token, "generate-host-certificates=on") == 0) { + s->generateHostCertificates = true; + } else if (strcmp(token, "generate-host-certificates=off") == 0) { + s->generateHostCertificates = false; + } else if (strncmp(token, "dynamic_cert_mem_cache_size=", 28) == 0) { + parseBytesOptionValue(&s->dynamicCertMemCacheSize, B_BYTES_STR, token + 28); #endif } else { self_destruct(); @@ -3638,7 +3704,7 @@ char *crlfile; char *dhfile; char *sslflags; - char *sslcontext; + char *sslContextSessionId; SSL_CTX *sslContext; #endif @@ -3762,8 +3828,14 @@ if (s->sslflags) storeAppendPrintf(e, " sslflags=%s", s->sslflags); - if (s->sslcontext) - storeAppendPrintf(e, " sslcontext=%s", s->sslcontext); + if (s->sslContextSessionId) + storeAppendPrintf(e, " sslcontext=%s", s->sslContextSessionId); + + if (s->generateHostCertificates) + storeAppendPrintf(e, " generate-host-certificates"); + + if (s->dynamicCertMemCacheSize != std::numeric_limits::max()) + storeAppendPrintf(e, "dynamic_cert_mem_cache_size=%lu%s\n", (unsigned long)s->dynamicCertMemCacheSize, B_BYTES_STR); #endif } === modified file 'src/cf.data.pre' --- src/cf.data.pre 2010-10-25 18:25:19 +0000 +++ src/cf.data.pre 2010-11-12 16:11:52 +0000 @@ -1390,6 +1390,24 @@ sslcontext= SSL session ID context identifier. + generate-host-certificates[=] + Dynamically create SSL server certificates for the + destination hosts of bumped CONNECT 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. An average XXX-bit certificate + consumes about XXX bytes of RAM. Other Options: @@ -1968,6 +1986,54 @@ DOC_END COMMENT_START + OPTIONS RELATING TO EXTERNAL SSL_CRTD + ----------------------------------------------------------------------------- +COMMENT_END + +NAME: sslcrtd_program +TYPE: eol +IFDEF: USE_SSL_CRTD +DEFAULT: @DEFAULT_SSL_CRTD@ -s @DEFAULT_SSL_DB_DIR@ -M 4MB +LOC: Ssl::TheConfig.ssl_crtd +DOC_START + Specify the location and options of the executable for ssl_crtd process. + @DEFAULT_SSL_CRTD@ program requires -s and -M parameters + For more information use: + @DEFAULT_SSL_CRTD@ -h +DOC_END + +NAME: sslcrtd_children +TYPE: HelperChildConfig +IFDEF: USE_SSL_CRTD +DEFAULT: 32 startup=5 idle=1 +LOC: Ssl::TheConfig.ssl_crtdChildren +DOC_START + The maximum number of processes spawn to service ssl server. + The maximum this may be safely set to is 32. + + The startup= and idle= options allow some measure of skew in your + tuning. + + startup=N + + Sets the minimum number of processes to spawn when Squid + starts or reconfigures. When set to zero the first request will + cause spawning of the first child process to handle it. + + Starting too few children temporary slows Squid under load while it + tries to spawn enough additional processes to cope with traffic. + + idle=N + + Sets a minimum of how many processes Squid is to try and keep available + at all times. When traffic begins to rise above what the existing + processes can handle this many more will be spawned up to the maximum + configured. A minimum setting of 1 is required. + + You must have at least one ssl_crtd process. +DOC_END + +COMMENT_START OPTIONS WHICH AFFECT THE NEIGHBOR SELECTION ALGORITHM ----------------------------------------------------------------------------- COMMENT_END === modified file 'src/client_side.cc' --- src/client_side.cc 2010-10-26 00:17:17 +0000 +++ src/client_side.cc 2010-11-15 15:40:05 +0000 @@ -115,6 +115,20 @@ #include "ClientInfo.h" #endif +#if USE_SSL +#include "ssl/context_storage.h" +#include "ssl/helper.h" +#include "ssl/gadgets.h" +#endif +#if USE_SSL_CRTD +#include "ssl/crtd_message.h" +#include "ssl/certificate_db.h" +#endif + +#if HAVE_LIMITS +#include +#endif + #if LINGERING_CLOSE #define comm_close comm_lingering_close #endif @@ -3346,7 +3360,7 @@ comm_err_t flag, int xerrno, void *data) { https_port_list *s = (https_port_list *)data; - SSL_CTX *sslContext = s->sslContext; + SSL_CTX *sslContext = s->staticSslContext.get(); if (flag != COMM_OK) { errno = xerrno; @@ -3396,24 +3410,109 @@ incoming_sockets_accepted++; } -bool -ConnStateData::switchToHttps() -{ - assert(!switchedToHttps_); - - //HTTPMSGLOCK(currentobject->http->request); - assert(areAllContextsForThisConnection()); - freeAllContexts(); - //currentobject->connIsFinished(); - - debugs(33, 5, HERE << "converting FD " << fd << " to SSL"); +void +ConnStateData::sslCrtdHandleReplyWrapper(void *data, char *reply) +{ + ConnStateData * state_data = (ConnStateData *)(data); + state_data->sslCrtdHandleReply(reply); +} + +void +ConnStateData::sslCrtdHandleReply(const char * reply) +{ + if (!reply) { + debugs(1, 1, HERE << "\"ssl_crtd\" helper return reply"); + } else { + Ssl::CrtdMessage reply_message; + if (reply_message.parse(reply, strlen(reply)) != Ssl::CrtdMessage::OK) { + debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslHostName << " is incorrect"); + } else { + if (reply_message.getCode() != "ok") { + debugs(33, 5, HERE << "Certificate for " << sslHostName << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); + } else { + debugs(33, 5, HERE << "Certificate for " << sslHostName << " was successfully recieved from ssl_crtd"); + getSslContextDone(Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str()), true); + return; + } + } + } + getSslContextDone(NULL); +} + +bool +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"); + Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); + SSL_CTX * dynCtx = ssl_ctx_cache.find(host); + 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); + } else { + debugs(33, 5, HERE << "Cached SSL certificate for " << host << " is out of date. Delete this certificate from cache"); + ssl_ctx_cache.remove(host); + } + } else { + debugs(33, 5, HERE << "SSL certificate for " << host << " haven't found in cache"); + } + +#ifdef USE_SSL_CRTD + debugs(33, 5, HERE << "Generating SSL certificate for " << host << " using ssl_crtd."); + Ssl::CrtdMessage request_message; + request_message.setCode(Ssl::CrtdMessage::code_new_certificate); + Ssl::CrtdMessage::BodyParams map; + map.insert(std::make_pair(Ssl::CrtdMessage::param_host, host)); + std::string bufferToWrite; + Ssl::writeCertAndPrivateKeyToMemory(port->signingCert, port->signPkey, bufferToWrite); + request_message.composeBody(map, bufferToWrite); + Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); + return true; +#else + debugs(33, 5, HERE << "Generating SSL certificate for " << host); + dynCtx = generateSslContext(host, port->signingCert, port->signPkey); + return getSslContextDone(dynCtx, true); +#endif //USE_SSL_CRTD + } + return getSslContextDone(NULL); +} + +bool +ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) +{ + // Try to add generated ssl context to storage. + if (port->generateHostCertificates && isNew) { + Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); + if (sslContext && sslHostName != "") { + if (!ssl_ctx_cache.add(sslHostName.termedBuf(), sslContext)) { + // If it is not in storage delete after using. Else storage deleted it. + fd_table[fd].dynamicSslContext = sslContext; + } + } else { + debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslHostName); + } + } + + // If generated ssl context = NULL, try to use static ssl context. + if (!sslContext) { + if (!port->staticSslContext) { + debugs(83, 1, "Closing SSL FD " << fd << " as lacking SSL context"); + comm_close(fd); + return false; + } else { + debugs(33, 5, HERE << "Using static ssl context."); + sslContext = port->staticSslContext.get(); + } + } // fake a ConnectionDetail object; XXX: make ConnState a ConnectionDetail? ConnectionDetail detail; detail.me = me; detail.peer = peer; - SSL_CTX *sslContext = port->sslContext; SSL *ssl = NULL; if (!(ssl = httpsCreate(fd, &detail, sslContext))) return false; @@ -3429,6 +3528,23 @@ return true; } +bool +ConnStateData::switchToHttps(const char *host) +{ + assert(!switchedToHttps_); + + sslHostName = host; + + //HTTPMSGLOCK(currentobject->http->request); + assert(areAllContextsForThisConnection()); + freeAllContexts(); + //currentobject->connIsFinished(); + + debugs(33, 5, HERE << "converting FD " << fd << " to SSL"); + + return getSslContextStart(); +} + #endif /* USE_SSL */ /// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed @@ -3476,14 +3592,21 @@ } #if USE_SSL - if (s->sslBump && s->sslContext == NULL) { + if (s->sslBump && + !s->staticSslContext && !s->generateHostCertificates) { debugs(1, 1, "Will not bump SSL at http_port " << s->http.s << " due to SSL initialization failure."); s->sslBump = 0; } - if (s->sslBump) + if (s->sslBump) { ++bumpCount; + // Create ssl_ctx cache for this port. + Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits::max() ? 4194304 : s->dynamicCertMemCacheSize); + } #endif +#if USE_SSL_CRTD + Ssl::Helper::GetInstance(); +#endif //USE_SSL_CRTD /* AYJ: 2009-12-27: bit bumpy. new ListenStateData(...) should be doing all the Comm:: stuff ... */ @@ -3545,7 +3668,7 @@ continue; } - if (s->sslContext == NULL) { + if (!s->staticSslContext) { debugs(1, 1, "Ignoring https_port " << s->http.s << " due to SSL initialization failure."); continue; @@ -3713,7 +3836,7 @@ CBDATA_CLASS_INIT(ConnStateData); -ConnStateData::ConnStateData() :AsyncJob("ConnStateData"), transparent_ (false), closing_ (false) +ConnStateData::ConnStateData() :AsyncJob("ConnStateData"), transparent_ (false), closing_ (false), switchedToHttps_(false) { pinning.fd = -1; pinning.pinned = false; === modified file 'src/client_side.h' --- src/client_side.h 2010-09-10 20:56:24 +0000 +++ src/client_side.h 2010-11-15 15:40:05 +0000 @@ -275,7 +275,20 @@ virtual void swanSong(); #if USE_SSL - bool switchToHttps(); + /// Start to create dynamic SSL_CTX for host or uses static port SSL context. + bool 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); + /// 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); bool switchedToHttps() const { return switchedToHttps_; } #else bool switchedToHttps() const { return false; } @@ -299,6 +312,7 @@ bool closing_; bool switchedToHttps_; + 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_request.cc' --- src/client_side_request.cc 2010-10-21 08:13:41 +0000 +++ src/client_side_request.cc 2010-11-15 15:22:27 +0000 @@ -1189,7 +1189,7 @@ return; } - getConn()->switchToHttps(); + getConn()->switchToHttps(request->GetHost()); } void === modified file 'src/comm.cc' --- src/comm.cc 2010-11-03 16:28:34 +0000 +++ src/comm.cc 2010-11-15 15:40:05 +0000 @@ -1540,6 +1540,10 @@ F->ssl = NULL; } + if (F->dynamicSslContext) { + SSL_CTX_free(F->dynamicSslContext); + F->dynamicSslContext = NULL; + } #endif fd_close(fd); /* update fdstat */ === modified file 'src/fde.h' --- src/fde.h 2010-10-25 21:49:58 +0000 +++ src/fde.h 2010-11-15 15:40:05 +0000 @@ -113,6 +113,7 @@ WRITE_HANDLER *write_method; #if USE_SSL SSL *ssl; + SSL_CTX *dynamicSslContext; ///< cached and then freed when fd is closed #endif #ifdef _SQUID_MSWIN_ struct { @@ -166,6 +167,7 @@ write_method = NULL; #if USE_SSL ssl = NULL; + dynamicSslContext = NULL; #endif #ifdef _SQUID_MSWIN_ win32.handle = NULL; === modified file 'src/helper.cc' --- src/helper.cc 2010-09-20 19:27:24 +0000 +++ src/helper.cc 2010-11-09 22:29:49 +0000 @@ -792,6 +792,55 @@ cbdataFree(srv); } +/// Calls back with a pointer to the buffer with the helper output +static void helperReturnBuffer(int request_number, helper_server * srv, helper * hlp, char * msg, char * msg_end) +{ + helper_request *r = srv->requests[request_number]; + if (r) { + HLPCB *callback = r->callback; + + srv->requests[request_number] = NULL; + + r->callback = NULL; + + void *cbdata = NULL; + if (cbdataReferenceValidDone(r->data, &cbdata)) + callback(cbdata, msg); + + srv->stats.pending--; + + hlp->stats.replies++; + + srv->answer_time = current_time; + + srv->dispatch_time = r->dispatch_time; + + hlp->stats.avg_svc_time = + Math::intAverage(hlp->stats.avg_svc_time, + tvSubMsec(r->dispatch_time, current_time), + hlp->stats.replies, REDIRECT_AV_FACTOR); + + helperRequestFree(r); + } else { + debugs(84, 1, "helperHandleRead: unexpected reply on channel " << + request_number << " from " << hlp->id_name << " #" << srv->index + 1 << + " '" << srv->rbuf << "'"); + } + srv->roffset -= (msg_end - srv->rbuf); + memmove(srv->rbuf, msg_end, srv->roffset + 1); + + if (!srv->flags.shutdown) { + helperKickQueue(hlp); + } else if (!srv->flags.closing && !srv->stats.pending) { + int wfd = srv->wfd; + srv->wfd = -1; + if (srv->rfd == wfd) + srv->rfd = -1; + srv->flags.closing=1; + comm_close(wfd); + return; + } +} static void helperHandleRead(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, void *data) @@ -834,69 +883,29 @@ srv->rbuf[0] = '\0'; } - while ((t = strchr(srv->rbuf, '\n'))) { - /* end of reply found */ - helper_request *r; - char *msg = srv->rbuf; - int i = 0; - debugs(84, 3, "helperHandleRead: end of reply found"); - - if (t > srv->rbuf && t[-1] == '\r') - t[-1] = '\0'; - - *t++ = '\0'; - - if (hlp->childs.concurrency) { - i = strtol(msg, &msg, 10); - - while (*msg && xisspace(*msg)) - msg++; - } - - r = srv->requests[i]; - - if (r) { - HLPCB *callback = r->callback; - void *cbdata; - - srv->requests[i] = NULL; - - r->callback = NULL; - - if (cbdataReferenceValidDone(r->data, &cbdata)) - callback(cbdata, msg); - - srv->stats.pending--; - - hlp->stats.replies++; - - srv->answer_time = current_time; - - srv->dispatch_time = r->dispatch_time; - - hlp->stats.avg_svc_time = Math::intAverage(hlp->stats.avg_svc_time, tvSubMsec(r->dispatch_time, current_time), hlp->stats.replies, REDIRECT_AV_FACTOR); - - helperRequestFree(r); - } else { - debugs(84, 1, "helperHandleRead: unexpected reply on channel " << - i << " from " << hlp->id_name << " #" << srv->index + 1 << - " '" << srv->rbuf << "'"); - - } - - srv->roffset -= (t - srv->rbuf); - memmove(srv->rbuf, t, srv->roffset + 1); - - if (!srv->flags.shutdown) { - helperKickQueue(hlp); - } else if (!srv->flags.closing && !srv->stats.pending) { - int wfd = srv->wfd; - srv->wfd = -1; - if (srv->rfd == wfd) - srv->rfd = -1; - srv->flags.closing=1; - comm_close(wfd); - return; + if (hlp->return_full_reply) { + debugs(84, 3, HERE << "Return entire buffer"); + helperReturnBuffer(0, srv, hlp, srv->rbuf, srv->rbuf + srv->roffset); + } else { + while ((t = strchr(srv->rbuf, '\n'))) { + /* end of reply found */ + char *msg = srv->rbuf; + int i = 0; + debugs(84, 3, "helperHandleRead: end of reply found"); + + if (t > srv->rbuf && t[-1] == '\r') + t[-1] = '\0'; + + *t++ = '\0'; + + if (hlp->childs.concurrency) { + i = strtol(msg, &msg, 10); + + while (*msg && xisspace(*msg)) + msg++; + } + + helperReturnBuffer(i, srv, hlp, msg, t); } } === modified file 'src/helper.h' --- src/helper.h 2010-05-02 19:32:42 +0000 +++ src/helper.h 2010-11-09 22:29:49 +0000 @@ -69,6 +69,8 @@ int queue_size; int avg_svc_time; } stats; + /// True if callback expects the whole helper output, as a c-string. + bool return_full_reply; private: CBDATA_CLASS2(helper); === modified file 'src/main.cc' --- src/main.cc 2010-11-01 05:44:28 +0000 +++ src/main.cc 2010-11-09 22:29:49 +0000 @@ -88,6 +88,15 @@ #include "LoadableModules.h" #endif +#if USE_SSL_CRTD +#include "ssl/helper.h" +#include "ssl/certificate_db.h" +#endif + +#if USE_SSL +#include "ssl/context_storage.h" +#endif + #if ICAP_CLIENT #include "adaptation/icap/Config.h" #endif @@ -733,7 +742,12 @@ idnsShutdown(); #endif - +#if USE_SSL_CRTD + Ssl::Helper::GetInstance()->Shutdown(); +#endif +#if USE_SSL + Ssl::TheGlobalContextStorage.reconfigureStart(); +#endif redirectShutdown(); authenticateReset(); externalAclShutdown(); @@ -819,6 +833,9 @@ idnsInit(); #endif +#if USE_SSL_CRTD + Ssl::Helper::GetInstance()->Init(); +#endif redirectInit(); authenticateInit(&Auth::TheConfig); @@ -1812,7 +1829,9 @@ idnsShutdown(); #endif - +#if USE_SSL_CRTD + Ssl::Helper::GetInstance()->Shutdown(); +#endif redirectShutdown(); externalAclShutdown(); icpConnectionClose(); === modified file 'src/squid.h' --- src/squid.h 2010-11-01 05:44:28 +0000 +++ src/squid.h 2010-11-09 22:29:49 +0000 @@ -155,7 +155,7 @@ #include "md5.h" #if USE_SSL -#include "ssl_support.h" +#include "ssl/support.h" #endif #if SQUID_SNMP #include "cache_snmp.h" === added directory 'src/ssl' === added file 'src/ssl/Config.cc' --- src/ssl/Config.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/Config.cc 2010-11-12 15:59:26 +0000 @@ -0,0 +1,21 @@ +/* + * $Id$ + */ + +#include "ssl/Config.h" + +Ssl::Config Ssl::TheConfig; + +Ssl::Config::Config(): +#if USE_SSL_CRTD +ssl_crtd(NULL) +#endif +{ +} + +Ssl::Config::~Config() +{ +#if USE_SSL_CRTD + xfree(ssl_crtd); +#endif +} === added file 'src/ssl/Config.h' --- src/ssl/Config.h 1970-01-01 00:00:00 +0000 +++ src/ssl/Config.h 2010-11-12 16:10:40 +0000 @@ -0,0 +1,32 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_CONFIG_H +#define SQUID_SSL_CONFIG_H + +#include "config.h" +#include "HelperChildConfig.h" + +namespace Ssl +{ + +class Config +{ +public: +#if USE_SSL_CRTD + char *ssl_crtd; ///< Name of external ssl_crtd application. + /// The number of processes spawn for ssl_crtd. + HelperChildConfig ssl_crtdChildren; +#endif + Config(); + ~Config(); +private: + Config(const Config &); // not implemented + Config &operator =(const Config &); // not implemented +}; + +extern Config TheConfig; + +} // namespace Ssl +#endif === added file 'src/ssl/Makefile.am' --- src/ssl/Makefile.am 1970-01-01 00:00:00 +0000 +++ src/ssl/Makefile.am 2010-11-15 15:40:05 +0000 @@ -0,0 +1,40 @@ +include $(top_srcdir)/src/Common.am +include $(top_srcdir)/src/TestHeaders.am + +noinst_LTLIBRARIES = libsslsquid.la libsslutil.la + +EXTRA_PROGRAMS = \ + ssl_crtd + +if USE_SSL_CRTD +SSL_CRTD = ssl_crtd +SSL_CRTD_SOURCE = \ + helper.cc \ + helper.h +else +SSL_CRTD = +SSL_CRTD_SOURCE = +endif + +libsslsquid_la_SOURCES = \ + context_storage.cc \ + context_storage.h \ + Config.cc \ + Config.h + +libsslutil_la_SOURCES = \ + support.cc \ + support.h \ + gadgets.cc \ + gadgets.h \ + crtd_message.cc \ + crtd_message.h \ + $(SSL_CRTD_SOURCE) + +libexec_PROGRAMS = \ + $(SSL_CRTD) + +if USE_SSL_CRTD +ssl_crtd_SOURCES = ssl_crtd.cc certificate_db.cc certificate_db.h +ssl_crtd_LDADD = @SSLLIB@ -lsslutil $(COMPAT_LIB) +endif === added file 'src/ssl/certificate_db.cc' --- src/ssl/certificate_db.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/certificate_db.cc 2010-11-12 11:43:46 +0000 @@ -0,0 +1,486 @@ +/* + * $Id$ + */ + +#include "config.h" +#include "ssl/certificate_db.h" +#if HAVE_FSTREAM +#include +#endif +#if HAVE_STDEXCEPT +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_FILE_H +#include +#endif +#if HAVE_FCNTL_H +#include +#endif + +Ssl::FileLocker::FileLocker(std::string const & filename) + : fd(-1) +{ +#if _SQUID_MSWIN_ + hFile = CreateFile(TEXT(filename.c_str()), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + LockFile(hFile, 0, 0, 1, 0); +#else + fd = open(filename.c_str(), 0); + if (fd != -1) + flock(fd, LOCK_EX); +#endif +} + +Ssl::FileLocker::~FileLocker() +{ +#ifdef _SQUID_MSWIN_ + if (hFile != INVALID_HANDLE_VALUE) { + UnlockFile(hFile, 0, 0, 1, 0); + CloseHandle(hFile); + } +#else + if (fd != -1) { + flock(fd, LOCK_UN); + close(fd); + } +#endif +} + +Ssl::CertificateDb::Row::Row() + : width(cnlNumber) +{ + row = new char *[width + 1]; + for (size_t i = 0; i < width + 1; i++) + row[i] = NULL; +} + +Ssl::CertificateDb::Row::~Row() +{ + if (row) { + for (size_t i = 0; i < width + 1; i++) { + delete[](row[i]); + } + delete[](row); + } +} + +void Ssl::CertificateDb::Row::reset() +{ + row = NULL; +} + +void Ssl::CertificateDb::Row::setValue(size_t cell, char const * value) +{ + assert(cell < width); + if (row[cell]) { + free(row[cell]); + } + if (value) { + row[cell] = static_cast(malloc(sizeof(char) * (strlen(value) + 1))); + memcpy(row[cell], value, sizeof(char) * (strlen(value) + 1)); + } else + row[cell] = NULL; +} + +char ** Ssl::CertificateDb::Row::getRow() +{ + return row; +} + +unsigned long Ssl::CertificateDb::index_serial_hash(const char **a) +{ + const char *n = a[Ssl::CertificateDb::cnlSerial]; + while (*n == '0') n++; + return lh_strhash(n); +} + +int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b) +{ + const char *aa, *bb; + for (aa = a[Ssl::CertificateDb::cnlSerial]; *aa == '0'; aa++); + for (bb = b[Ssl::CertificateDb::cnlSerial]; *bb == '0'; bb++); + return strcmp(aa, bb); +} + +unsigned long Ssl::CertificateDb::index_name_hash(const char **a) +{ + return(lh_strhash(a[Ssl::CertificateDb::cnlName])); +} + +int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b) +{ + 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"); +const size_t Ssl::CertificateDb::min_db_size(4096); + +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), + db(NULL), + max_db_size(aMax_db_size), + fs_block_size(aFs_block_size), + enabled_disk_store(true) +{ + if (db_path.empty() && !max_db_size) + enabled_disk_store = false; + else if ((db_path.empty() && max_db_size) || (!db_path.empty() && !max_db_size)) + throw std::runtime_error("ssl_crtd is missing the required parameter. There should be -s and -M parameters together."); + else + load(); +} + +bool Ssl::CertificateDb::find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) +{ + FileLocker db_locker(db_full); + load(); + return pure_find(host_name, cert, pkey); +} + +bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) +{ + FileLocker db_locker(db_full); + load(); + if (!db || !cert || !pkey || min_db_size > max_db_size) + return false; + Row row; + ASN1_INTEGER * ai = X509_get_serialNumber(cert.get()); + std::string serial_string; + Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(ai, NULL)); + { + TidyPointer hex_bn(BN_bn2hex(serial.get())); + serial_string = std::string(hex_bn.get()); + } + row.setValue(cnlSerial, serial_string.c_str()); + char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow()); + if (rrow != NULL) + return false; + + { + TidyPointer subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0)); + if (pure_find(subject.get(), cert, pkey)) + return true; + } + // check db size. + while (max_db_size < size()) { + if (!deleteInvalidCertificate()) + break; + } + + while (max_db_size < size()) { + deleteOldestCertificate(); + } + + row.setValue(cnlType, "V"); + 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"); + { + TidyPointer subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0)); + row.setValue(cnlName, subject.get()); + } + + if (!TXT_DB_insert(db.get(), row.getRow())) + return false; + + row.reset(); + std::string filename(cert_full + "/" + serial_string + ".pem"); + FileLocker cert_locker(filename); + if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str())) + return false; + addSize(filename); + + save(); + return true; +} + +BIGNUM * Ssl::CertificateDb::getCurrentSerialNumber() +{ + FileLocker serial_locker(serial_full); + // 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) +{ + 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); + +#ifdef _SQUID_MSWIN_ + if (mkdir(db_path.c_str())) +#else + if (mkdir(db_path.c_str(), 0777)) +#endif + throw std::runtime_error("Cannot create " + db_path); + +#ifdef _SQUID_MSWIN_ + if (mkdir(cert_full.c_str())) +#else + if (mkdir(cert_full.c_str(), 0777)) +#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; + else + throw std::runtime_error("Cannot open " + size_full + " to open"); + std::ofstream db(db_full.c_str()); + if (!db) + throw std::runtime_error("Cannot open " + db_full + " to open"); +} + +void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size) +{ + CertificateDb db(db_path, max_db_size, 0); +} + +std::string Ssl::CertificateDb::getSNString() const +{ + FileLocker serial_locker(serial_full); + 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) + return false; + + Row row; + row.setValue(cnlName, host_name.c_str()); + + char **rrow = TXT_DB_get_by_index(db.get(), cnlName, row.getRow()); + if (rrow == NULL) + return false; + + if (!sslDateIsInTheFuture(rrow[cnlExp_date])) { + deleteByHostname(rrow[cnlName]); + return false; + } + + // read cert and pkey from file. + std::string filename(cert_full + "/" + rrow[cnlSerial] + ".pem"); + FileLocker cert_locker(filename); + readCertAndPrivateKeyFromFiles(cert, pkey, filename.c_str(), NULL); + if (!cert || !pkey) + return false; + return true; +} + +size_t Ssl::CertificateDb::size() const +{ + FileLocker size_locker(size_full); + return readSize(); +} + +void Ssl::CertificateDb::addSize(std::string const & filename) +{ + FileLocker size_locker(size_full); + writeSize(readSize() + getFileSize(filename)); +} + +void Ssl::CertificateDb::subSize(std::string const & filename) +{ + FileLocker size_locker(size_full); + writeSize(readSize() - getFileSize(filename)); +} + +size_t Ssl::CertificateDb::readSize() const +{ + size_t db_size; + std::ifstream size_file(size_full.c_str()); + if (!size_file && enabled_disk_store) + throw std::runtime_error("cannot read \"" + size_full + "\" file"); + size_file >> db_size; + return db_size; +} + +void Ssl::CertificateDb::writeSize(size_t db_size) +{ + std::ofstream size_file(size_full.c_str()); + if (!size_file && enabled_disk_store) + throw std::runtime_error("cannot write \"" + size_full + "\" file"); + size_file << db_size; +} + +size_t Ssl::CertificateDb::getFileSize(std::string const & filename) +{ + std::ifstream file(filename.c_str(), std::ios::binary); + file.seekg(0, std::ios_base::end); + size_t file_size = file.tellg(); + return ((file_size + fs_block_size - 1) / fs_block_size) * fs_block_size; +} + +void Ssl::CertificateDb::load() +{ + // Load db from file. + Ssl::BIO_Pointer in(BIO_new(BIO_s_file())); + if (!in || BIO_read_filename(in.get(), db_full.c_str()) <= 0) + throw std::runtime_error("Uninitialized SSL certificate database directory: " + db_path + ". To initialize, run \"ssl_crtd -c -s " + db_path + "\"."); + + bool corrupt = false; + Ssl::TXT_DB_Pointer temp_db(TXT_DB_read(in.get(), cnlNumber)); + if (!temp_db) + corrupt = true; + + // Create indexes in db. + if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp))) + corrupt = true; + + if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp))) + corrupt = true; + + if (corrupt) + throw std::runtime_error("The SSL certificate database " + db_path + " is curruted. Please rebuild"); + + db.reset(temp_db.release()); +} + +void Ssl::CertificateDb::save() +{ + if (!db) + throw std::runtime_error("The certificates database is not loaded");; + + // To save the db to file, create a new BIO with BIO file methods. + Ssl::BIO_Pointer out(BIO_new(BIO_s_file())); + if (!out || !BIO_write_filename(out.get(), const_cast(db_full.c_str()))) + throw std::runtime_error("Failed to initialize " + db_full + " file for writing");; + + if (TXT_DB_write(out.get(), db.get()) < 0) + throw std::runtime_error("Failed to write " + db_full + " file"); +} + +bool Ssl::CertificateDb::deleteInvalidCertificate() +{ + if (!db) + return false; + + bool removed_one = false; + for (int i = 0; i < sk_num(db.get()->data); i++) { + const char ** current_row = ((const char **)sk_value(db.get()->data, i)); + + if (!sslDateIsInTheFuture(current_row[cnlExp_date])) { + std::string filename(cert_full + "/" + current_row[cnlSerial] + ".pem"); + FileLocker cert_locker(filename); + sk_delete(db.get()->data, i); + subSize(filename); + remove(filename.c_str()); + removed_one = true; + break; + } + } + + if (!removed_one) + return false; + return true; +} + +bool Ssl::CertificateDb::deleteOldestCertificate() +{ + if (!db) + return false; + + if (sk_num(db.get()->data) == 0) + return false; + + std::string filename(cert_full + "/" + ((const char **)sk_value(db.get()->data, 0))[cnlSerial] + ".pem"); + FileLocker cert_locker(filename); + sk_delete(db.get()->data, 0); + subSize(filename); + remove(filename.c_str()); + + return true; +} + +bool Ssl::CertificateDb::deleteByHostname(std::string const & host) +{ + if (!db) + return false; + + for (int i = 0; i < sk_num(db.get()->data); i++) { + const char ** current_row = ((const char **)sk_value(db.get()->data, i)); + if (host == current_row[cnlName]) { + std::string filename(cert_full + "/" + current_row[cnlSerial] + ".pem"); + FileLocker cert_locker(filename); + sk_delete(db.get()->data, i); + subSize(filename); + remove(filename.c_str()); + return true; + } + } + return false; +} + +bool Ssl::CertificateDb::IsEnabledDiskStore() const +{ + return enabled_disk_store; +} === added file 'src/ssl/certificate_db.h' --- src/ssl/certificate_db.h 1970-01-01 00:00:00 +0000 +++ src/ssl/certificate_db.h 2010-11-12 11:44:11 +0000 @@ -0,0 +1,139 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_CERTIFICATE_DB_H +#define SQUID_SSL_CERTIFICATE_DB_H + +#include "ssl/gadgets.h" +#include "ssl/support.h" +#if HAVE_STRING +#include +#endif + +namespace Ssl +{ +/// Cross platform file locker. +class FileLocker +{ +public: + /// Lock file + FileLocker(std::string const & aFilename); + /// Unlock file + ~FileLocker(); +private: +#ifdef _SQUID_MSWIN_ + HANDLE hFile; ///< Windows file handle. +#else + int fd; ///< Linux file descriptor. +#endif +}; + +/** + * Database class for storing SSL certificates and their private keys. + * A database consist by: + * - A disk file to store current serial number + * - A disk file to store the current database size + * - A disk file which is a normal TXT_DB openSSL database + * - A directory under which the certificates and their private keys stored. + * The database before used must initialized with CertificateDb::create static method. + */ +class CertificateDb +{ +public: + /// Names of db columns. + enum Columns { + cnlType = 0, + cnlExp_date, + cnlRev_date, + cnlSerial, + cnlFile, + cnlName, + cnlNumber + }; + + /// A wrapper for OpenSSL database row of TXT_DB database. + class Row + { + public: + /// Create row wrapper. + Row(); + /// Delete all row. + ~Row(); + void setValue(size_t number, char const * value); ///< Set cell's value in row + char ** getRow(); ///< Raw row + void reset(); ///< Abandon row and don't free memory + private: + char **row; ///< Raw row + size_t width; ///< Number of cells in the row + }; + + 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); + /// 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(); + /// Create and initialize a database under the db_path + static void create(std::string const & db_path, int serial); + /// 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. + void save(); ///< Save db to disk. + size_t size() const; ///< Get db size on disk in bytes. + /// Increase db size by the given file size and update size_file + void addSize(std::string const & filename); + /// Decrease db size by the given file size and update size_file + void subSize(std::string const & filename); + size_t readSize() const; ///< Read size from file size_file + void writeSize(size_t db_size); ///< Write size to file size_file. + size_t getFileSize(std::string const & filename); ///< get file size on disk. + /// Only find certificate in current db and return it. + bool pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey); + + bool deleteInvalidCertificate(); ///< Delete invalid certificate. + bool deleteOldestCertificate(); ///< Delete oldest certificate. + bool deleteByHostname(std::string const & host); ///< Delete using host name. + + /// Callback hash function for serials. Used to create TXT_DB index of serials. + static unsigned long index_serial_hash(const char **a); + /// Callback compare function for serials. Used to create TXT_DB index of serials. + static int index_serial_cmp(const char **a, const char **b); + /// Callback hash function for names. Used to create TXT_DB index of names.. + static unsigned long index_name_hash(const char **a); + /// Callback compare function for names. Used to create TXT_DB index of names.. + static int index_name_cmp(const char **a, const char **b); + + /// Definitions required by openSSL, to use the index_* functions defined above + ///with TXT_DB_create_index. + static IMPLEMENT_LHASH_HASH_FN(index_serial_hash,const char **) + static IMPLEMENT_LHASH_COMP_FN(index_serial_cmp,const char **) + static IMPLEMENT_LHASH_HASH_FN(index_name_hash,const char **) + static IMPLEMENT_LHASH_COMP_FN(index_name_cmp,const char **) + + 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. + /// Min size of disk db. If real size < min_db_size the db will be disabled. + 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. + + TXT_DB_Pointer db; ///< Database with certificates info. + const size_t max_db_size; ///< Max size of db. + const size_t fs_block_size; ///< File system block size. + + bool enabled_disk_store; ///< The storage on the disk is enabled. +}; + +} // namespace Ssl +#endif // SQUID_SSL_CERTIFICATE_DB_H === added file 'src/ssl/context_storage.cc' --- src/ssl/context_storage.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/context_storage.cc 2010-11-15 15:40:05 +0000 @@ -0,0 +1,182 @@ +/* + * $Id$ + */ + +#include "Store.h" +#include "StoreEntryStream.h" +#include "ssl/context_storage.h" +#include "mgr/Registration.h" +#if HAVE_LIMITS +#include +#endif + +Ssl::CertificateStorageAction::CertificateStorageAction(const Mgr::Command::Pointer &cmd) + : Mgr::Action(cmd) +{} + +Ssl::CertificateStorageAction::Pointer +Ssl::CertificateStorageAction::Create(const Mgr::Command::Pointer &cmd) +{ + return new CertificateStorageAction(cmd); +} + +void Ssl::CertificateStorageAction::dump (StoreEntry *sentry) +{ + StoreEntryStream stream(sentry); + const char delimiter = '\t'; + const char endString = '\n'; + // Page title. + stream << "Cached ssl certificates statistic.\n"; + // Title of statistic table. + stream << "Port" << delimiter << "Max mem(KB)" << delimiter << "Cert number" << delimiter << "KB/cert" << delimiter << "Mem used(KB)" << delimiter << "Mem free(KB)" << endString; + + // Add info for each port. + for (std::map::iterator i = TheGlobalContextStorage.storage.begin(); i != TheGlobalContextStorage.storage.end(); i++) { + stream << i->first << delimiter; + LocalContextStorage & ssl_store_policy(*(i->second)); + stream << ssl_store_policy.max_memory / 1024 << delimiter; + stream << ssl_store_policy.memory_used / SSL_CTX_SIZE << delimiter; + stream << SSL_CTX_SIZE / 1024 << delimiter; + stream << ssl_store_policy.memory_used / 1024 << delimiter; + stream << (ssl_store_policy.max_memory - ssl_store_policy.memory_used) / 1024 << endString; + } + stream << endString; + stream.flush(); +} + +Ssl::LocalContextStorage::LocalContextStorage(size_t aMax_memory) + : max_memory(aMax_memory), memory_used(0) +{} + +Ssl::LocalContextStorage::~LocalContextStorage() +{ + for (QueueIterator i = lru_queue.begin(); i != lru_queue.end(); i++) { + delete *i; + } +} + +SSL_CTX * Ssl::LocalContextStorage::add(const char * host_name, SSL_CTX * ssl_ctx) +{ + if (max_memory < SSL_CTX_SIZE) { + return NULL; + } + remove(host_name); + while (SSL_CTX_SIZE + memory_used > max_memory) { + purgeOne(); + } + lru_queue.push_front(new Item(ssl_ctx, host_name)); + storage.insert(MapPair(host_name, lru_queue.begin())); + memory_used += SSL_CTX_SIZE; + return ssl_ctx; +} + +SSL_CTX * Ssl::LocalContextStorage::find(char const * host_name) +{ + MapIterator i = storage.find(host_name); + if (i == storage.end()) { + return NULL; + } + lru_queue.push_front(*(i->second)); + lru_queue.erase(i->second); + i->second = lru_queue.begin(); + return (*lru_queue.begin())->ssl_ctx; +} + +void Ssl::LocalContextStorage::remove(char const * host_name) +{ + deleteAt(storage.find(host_name)); +} + +void Ssl::LocalContextStorage::purgeOne() +{ + QueueIterator i = lru_queue.end(); + i--; + if (i != lru_queue.end()) { + remove((*i)->host_name.c_str()); + } +} + +void Ssl::LocalContextStorage::deleteAt(LocalContextStorage::MapIterator i) +{ + if (i != storage.end()) { + + delete *(i->second); + lru_queue.erase(i->second); + storage.erase(i); + memory_used -= SSL_CTX_SIZE; + } +} + +void Ssl::LocalContextStorage::SetSize(size_t aMax_memory) +{ + max_memory = aMax_memory; +} + +Ssl::LocalContextStorage::Item::Item(SSL_CTX * aSsl_ctx, std::string const & aName) + : ssl_ctx(aSsl_ctx), host_name(aName) +{} + +Ssl::LocalContextStorage::Item::~Item() +{ + SSL_CTX_free(ssl_ctx); +} + +/////////////////////////////////////////////////////// + +Ssl::GlobalContextStorage::GlobalContextStorage() + : reconfiguring(true) +{ + RegisterAction("cached_ssl_cert", "Statistic of cached generated ssl certificates", &CertificateStorageAction::Create, 0, 1); +} + +Ssl::GlobalContextStorage::~GlobalContextStorage() +{ + for (std::map::iterator i = storage.begin(); i != storage.end(); i++) { + delete i->second; + } +} + +void Ssl::GlobalContextStorage::addLocalStorage(Ip::Address const & address, size_t size_of_store) +{ + assert(reconfiguring); + configureStorage.insert(std::pair(address, size_of_store)); +} + +Ssl::LocalContextStorage & Ssl::GlobalContextStorage::getLocalStorage(Ip::Address const & address) +{ + reconfigureFinish(); + std::map::iterator i = storage.find(address); + assert (i != storage.end()); + return *(i->second); +} + +void Ssl::GlobalContextStorage::reconfigureStart() +{ + reconfiguring = true; +} + +void Ssl::GlobalContextStorage::reconfigureFinish() +{ + if (reconfiguring) { + reconfiguring = false; + + // remove or change old local storages. + for (std::map::iterator i = storage.begin(); i != storage.end(); i++) { + std::map::iterator conf_i = configureStorage.find(i->first); + if (conf_i == configureStorage.end()) { + storage.erase(i); + } else { + i->second->SetSize(conf_i->second); + } + } + + // add new local storages. + for (std::map::iterator conf_i = configureStorage.begin(); conf_i != configureStorage.end(); conf_i++ ) { + if (storage.find(conf_i->first) == storage.end()) { + storage.insert(std::pair(conf_i->first, new LocalContextStorage(conf_i->second))); + } + } + } +} + +Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage; === added file 'src/ssl/context_storage.h' --- src/ssl/context_storage.h 1970-01-01 00:00:00 +0000 +++ src/ssl/context_storage.h 2010-11-15 15:40:05 +0000 @@ -0,0 +1,120 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_CONTEXT_STORAGE_H +#define SQUID_SSL_CONTEXT_STORAGE_H + +#if USE_SSL + +#include "SquidTime.h" +#include "CacheManager.h" +#include "mgr/Action.h" +#include "mgr/Command.h" +#if HAVE_MAP +#include +#endif +#if HAVE_LIST +#include +#endif + +/// TODO: Replace on real size. +#define SSL_CTX_SIZE 1024 + +namespace Ssl +{ + +/** Reports cached SSL certificate stats to Cache Manager. + * TODO: Use "Report" functions instead friend class. + */ +class CertificateStorageAction : public Mgr::Action +{ +public: + CertificateStorageAction(const Mgr::Command::Pointer &cmd); + static Pointer Create(const Mgr::Command::Pointer &cmd); + virtual void dump (StoreEntry *sentry); + /** + * We do not support aggregation of information across workers + * TODO: aggregate these stats + */ + virtual bool aggregatable() const { return false; } +}; + +/** + * Memory cache for store generated SSL context. Enforces total size limits + * using an LRU algorithm. + */ +class LocalContextStorage +{ + friend class CertificateStorageAction; +public: + /// Cache item is an (SSL_CTX, host name) tuple. + class Item + { + public: + Item(SSL_CTX * aSsl_ctx, std::string const & aName); + ~Item(); + public: + SSL_CTX * ssl_ctx; ///< The SSL context. + std::string host_name; ///< The host name of the SSL context. + }; + + typedef std::list Queue; + typedef Queue::iterator QueueIterator; + + /// host_name:queue_item mapping for fast lookups by host name + typedef std::map Map; + typedef Map::iterator MapIterator; + typedef std::pair MapPair; + + LocalContextStorage(size_t aMax_memory); + ~LocalContextStorage(); + /// Set maximum memory size for this storage. + void SetSize(size_t aMax_memory); + /// Return a pointer to the added ssl_ctx or NULL if fails (eg. max cache size equal 0). + SSL_CTX * add(char const * host_name, SSL_CTX * ssl_ctx); + /// Find SSL_CTX in storage by host name. Lru queue will be updated. + SSL_CTX * find(char const * host_name); + void remove(char const * host_name); ///< Delete the SSL context by hostname + +private: + void purgeOne(); ///< Delete oldest object. + /// Delete object by iterator. It is used in deletePurge() and remove(...) methods. + void deleteAt(MapIterator i); + + size_t max_memory; ///< Max cache size. + size_t memory_used; ///< Used cache size. + Map storage; ///< The hostnames/SSL_CTX * pairs + Queue lru_queue; ///< LRU cache index +}; + + +/// Class for storing/manipulating LocalContextStorage per local listening address/port. +class GlobalContextStorage +{ + friend class CertificateStorageAction; +public: + GlobalContextStorage(); + ~GlobalContextStorage(); + /// Create new SSL context storage for the local listening address/port. + void addLocalStorage(Ip::Address const & address, size_t size_of_store); + /// Return the local storage for the given listening address/port. + LocalContextStorage & getLocalStorage(Ip::Address const & address); + /// When reconfigring should be called this method. + void reconfigureStart(); +private: + /// Called by getLocalStorage method + void reconfigureFinish(); + bool reconfiguring; ///< True if system reconfiguring now. + /// Storage used on configure or reconfigure. + std::map configureStorage; + /// Map for storing all local ip address and their local storages. + std::map storage; +}; + +/// Global cache for store all SSL server certificates. +extern GlobalContextStorage TheGlobalContextStorage; +} //namespace Ssl +#endif // USE_SSL + +#endif // SQUID_SSL_CONTEXT_STORAGE_H === added file 'src/ssl/crtd_message.cc' --- src/ssl/crtd_message.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/crtd_message.cc 2010-11-12 11:46:32 +0000 @@ -0,0 +1,177 @@ +/* + * $Id$ + */ + +#include "config.h" +#include "ssl/crtd_message.h" +#if HAVE_CSTDLIB +#include +#endif +#if HAVE_CSTRING +#include +#endif + +Ssl::CrtdMessage::CrtdMessage() + : body_size(0), state(BEFORE_CODE) +{} + +Ssl::CrtdMessage::ParseResult Ssl::CrtdMessage::parse(const char * buffer, size_t len) +{ + char const *current_pos = buffer; + while (current_pos != buffer + len && state != END) { + switch (state) { + case BEFORE_CODE: { + if (xisspace(*current_pos)) { + current_pos++; + break; + } + if (xisalpha(*current_pos)) { + state = CODE; + break; + } + clear(); + return ERROR; + } + case CODE: { + if (xisalnum(*current_pos) || *current_pos == '_') { + current_block += *current_pos; + current_pos++; + break; + } + if (xisspace(*current_pos)) { + code = current_block; + current_block.clear(); + state = BEFORE_LENGTH; + break; + } + clear(); + return ERROR; + } + case BEFORE_LENGTH: { + if (xisspace(*current_pos)) { + current_pos++; + break; + } + if (xisdigit(*current_pos)) { + state = LENGTH; + break; + } + clear(); + return ERROR; + } + case LENGTH: { + if (xisdigit(*current_pos)) { + current_block += *current_pos; + current_pos++; + break; + } + if (xisspace(*current_pos)) { + body_size = atoi(current_block.c_str()); + current_block.clear(); + state = BEFORE_BODY; + break; + } + clear(); + return ERROR; + } + case BEFORE_BODY: { + if (body_size == 0) { + state = END; + break; + } + if (xisspace(*current_pos)) { + current_pos++; + break; + } else { + state = BODY; + break; + } + } + case BODY: { + size_t body_len = (static_cast(buffer + len - current_pos) >= body_size - current_block.length()) + ? body_size - current_block.length() + : static_cast(buffer + len - current_pos); + current_block += std::string(current_pos, body_len); + current_pos += body_len; + if (current_block.length() == body_size) { + body = current_block; + state = END; + } + if (current_block.length() > body_size) { + clear(); + return ERROR; + } + break; + } + case END: { + return OK; + } + } + } + if (state != END) return INCOMPLETE; + return OK; +} + +std::string const & Ssl::CrtdMessage::getBody() const { return body; } + +std::string const & Ssl::CrtdMessage::getCode() const { return code; } + +void Ssl::CrtdMessage::setBody(std::string const & aBody) { body = aBody; } + +void Ssl::CrtdMessage::setCode(std::string const & aCode) { code = aCode; } + + +std::string Ssl::CrtdMessage::compose() const +{ + if (code.empty()) return std::string(); + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%zd", body.length()); + return code + ' ' + buffer + ' ' + body + '\n'; +} + +void Ssl::CrtdMessage::clear() +{ + body_size = 0; + state = BEFORE_CODE; + body.clear(); + code.clear(); + current_block.clear(); +} + +void Ssl::CrtdMessage::parseBody(CrtdMessage::BodyParams & map, std::string & other_part) const +{ + other_part.clear(); + // Copy string for using it as temp buffer. + std::string temp_body(body.c_str(), body.length()); + char * buffer = const_cast(temp_body.c_str()); + char * token = strtok(buffer, "\r\n"); + while (token != NULL) { + std::string current_string(token); + size_t equal_pos = current_string.find('='); + if (equal_pos == std::string::npos) { + size_t offset_body_part = token - temp_body.c_str(); + other_part = std::string(body.c_str() + offset_body_part, body.length() - offset_body_part); + break; + } else { + std::string param(current_string.c_str(), current_string.c_str() + equal_pos); + std::string value(current_string.c_str() + equal_pos + 1); + map.insert(std::make_pair(param, value)); + } + token = strtok(NULL, "\r\n"); + } +} + +void Ssl::CrtdMessage::composeBody(CrtdMessage::BodyParams const & map, std::string const & other_part) +{ + body.clear(); + for (BodyParams::const_iterator i = map.begin(); i != map.end(); i++) { + if (i != map.begin()) + body += "\n"; + body += i->first + "=" + i->second; + } + if (!other_part.empty()) + body += other_part; +} + +const std::string Ssl::CrtdMessage::code_new_certificate("new_certificate"); +const std::string Ssl::CrtdMessage::param_host("host"); === added file 'src/ssl/crtd_message.h' --- src/ssl/crtd_message.h 1970-01-01 00:00:00 +0000 +++ src/ssl/crtd_message.h 2010-11-12 11:45:40 +0000 @@ -0,0 +1,86 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_CRTD_MESSAGE_H +#define SQUID_SSL_CRTD_MESSAGE_H + +#if HAVE_STRING +#include +#endif +#if HAVE_MAP +#include +#endif + +namespace Ssl +{ +/** + * This class is responsible for composing and parsing messages destined to, or comming + * from an ssl_crtd server. Format of these mesages is: + * + */ +class CrtdMessage +{ +public: + typedef std::map BodyParams; + /// Parse result codes. + enum ParseResult { + OK, + INCOMPLETE, + ERROR + }; + CrtdMessage(); + /**Parse buffer of length len + \retval OK if parsing completes + \retval INCOMPLETE if more data required + \retval ERROR if there is an error. + */ + ParseResult parse(const char * buffer, size_t len); + /// Current body. If parsing is not finished the method returns incompleted body. + std::string const & getBody() const; + /// Current response/request code. If parsing is not finished the method may return incompleted code. + std::string const & getCode() const; + void setBody(std::string const & aBody); ///< Set new body to encode. + void setCode(std::string const & aCode); ///< Set new request/reply code to compose. + std::string compose() const; ///< Compose current (request) code and body to string. + /// Reset the class. + void clear(); + /** + *Parse body data which has the form: \verbatim + param1=value1 + param2=value2 + The other multistring part of body. \endverbatim + * The parameters of the body stored to map and the remaining part to other_part + */ + void parseBody(BodyParams & map, std::string & other_part) const; + /** + *Compose parameters given by map with their values and the other part given by + * other_part to body data. The constructed body will have the form: \verbatim + param1=value1 + param2=value2 + The other multistring part of body. \endverbatim + */ + void composeBody(BodyParams const & map, std::string const & other_part); + /// String code for "new_certificate" messages + static const std::string code_new_certificate; + /// Parameter name for passing hostname + static const std::string param_host; +private: + enum ParseState { + BEFORE_CODE, + CODE, + BEFORE_LENGTH, + LENGTH, + BEFORE_BODY, + BODY, + END + }; + size_t body_size; ///< The body size if exist or 0. + ParseState state; ///< Parsing state. + std::string body; ///< Current body. + std::string code; ///< Current response/request code. + std::string current_block; ///< Current block buffer. +}; + +} //namespace Ssl +#endif // SQUID_SSL_CRTD_MESSAGE_H === added file 'src/ssl/gadgets.cc' --- src/ssl/gadgets.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/gadgets.cc 2010-11-11 17:19:15 +0000 @@ -0,0 +1,273 @@ +/* + * $Id$ + */ + +#include "config.h" +#include "ssl/gadgets.h" + +/** + \ingroup ServerProtocolSSLInternal + * Add CN to subject in request. + */ +static bool addCnToRequest(Ssl::X509_REQ_Pointer & request, char const * cn) +{ + Ssl::X509_NAME_Pointer name(X509_REQ_get_subject_name(request.get())); + if (!name) + return false; + if (!X509_NAME_add_entry_by_txt(name.get(), "CN", MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0)) + return false; + name.release(); + 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; +} + +void Ssl::BIO_free_wrapper(BIO * bio) +{ + BIO_free(bio); +} + +EVP_PKEY * Ssl::createSslPrivateKey() +{ + Ssl::EVP_PKEY_Pointer pkey(EVP_PKEY_new()); + + if (!pkey) + return NULL; + + Ssl::RSA_Pointer rsa(RSA_generate_key(1024, RSA_F4, NULL, NULL)); + + if (!rsa) + return NULL; + + if (!EVP_PKEY_assign_RSA(pkey.get(), (rsa.get()))) + return NULL; + + rsa.release(); + 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. + */ +static bool setSerialNumber(ASN1_INTEGER *ai, BIGNUM const* serial) +{ + if (!ai) + return false; + Ssl::BIGNUM_Pointer bn(BN_new()); + if (serial) { + bn.reset(BN_dup(serial)); + } else { + if (!bn) + return false; + + if (!BN_pseudo_rand(bn.get(), 64, 0, 0)) + return false; + } + + if (ai && !BN_to_ASN1_INTEGER(bn.get(), ai)) + return false; + 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(); + if (!pkey || !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; + + if (!PEM_write_bio_PrivateKey(bio.get(), pkey.get(), NULL, NULL, 0, NULL, NULL)) + return false; + + char *ptr = NULL; + long len = BIO_get_mem_data(bio.get(), &ptr); + if (!ptr) + return false; + + bufferToWrite = std::string(ptr, len); + return true; +} + +bool Ssl::writeCertAndPrivateKeyToFile(Ssl::X509_Pointer const & cert, Ssl::EVP_PKEY_Pointer const & pkey, char const * filename) +{ + if (!pkey || !cert) + return false; + + Ssl::BIO_Pointer bio(BIO_new(BIO_s_file_internal())); + if (!bio) + return false; + if (!BIO_write_filename(bio.get(), const_cast(filename))) + return false; + + if (!PEM_write_bio_X509(bio.get(), cert.get())) + return false; + + if (!PEM_write_bio_PrivateKey(bio.get(), pkey.get(), NULL, NULL, 0, NULL, NULL)) + return false; + + return true; +} + +bool Ssl::readCertAndPrivateKeyFromMemory(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, 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; + + EVP_PKEY * pkeyPtr = NULL; + pkey.reset(PEM_read_bio_PrivateKey(bio.get(), &pkeyPtr, 0, 0)); + if (!pkey) + return false; + + 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()); + 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)); + + if (!cert) + return false; + + return true; +} + +/** + \ingroup ServerProtocolSSLInternal + * Read certificate from file. + */ +static X509 * readSslX509Certificate(char const * certFilename) +{ + if (!certFilename) + return NULL; + Ssl::BIO_Pointer bio(BIO_new(BIO_s_file_internal())); + if (!bio) + return NULL; + if (!BIO_read_filename(bio.get(), certFilename)) + return NULL; + X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL); + return certificate; +} + +/** + \ingroup ServerProtocolSSLInternal + * Read private key from file. Make sure that this is not encrypted file. + */ +static EVP_PKEY * readSslPrivateKey(char const * keyFilename) +{ + if (!keyFilename) + return NULL; + Ssl::BIO_Pointer bio(BIO_new(BIO_s_file_internal())); + if (!bio) + return NULL; + if (!BIO_read_filename(bio.get(), keyFilename)) + return NULL; + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL); + return pkey; +} + +void Ssl::readCertAndPrivateKeyFromFiles(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, char const * certFilename, char const * keyFilename) +{ + if (keyFilename == NULL) + keyFilename = certFilename; + pkey.reset(readSslPrivateKey(keyFilename)); + cert.reset(readSslX509Certificate(certFilename)); + if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) { + pkey.reset(NULL); + cert.reset(NULL); + } +} + +bool Ssl::sslDateIsInTheFuture(char const * date) +{ + ASN1_UTCTIME tm; + tm.flags = 0; + tm.type = 23; + tm.data = (unsigned char *)date; + tm.length = strlen(date); + + return (X509_cmp_current_time(&tm) > 0); +} === added file 'src/ssl/gadgets.h' --- src/ssl/gadgets.h 1970-01-01 00:00:00 +0000 +++ src/ssl/gadgets.h 2010-11-15 15:40:05 +0000 @@ -0,0 +1,113 @@ +/* + * 2009/01/17 + */ + +#ifndef SQUID_SSL_GADGETS_H +#define SQUID_SSL_GADGETS_H + +#include "base/TidyPointer.h" + +#if HAVE_OPENSSL_SSL_H +#include +#endif +#if HAVE_OPENSSL_TXT_DB_H +#include +#endif +#if HAVE_STRING +#include +#endif + +namespace Ssl +{ +/** + \defgroup SslCrtdSslAPI ssl_crtd SSL api. + These functions must not depend on Squid runtime code such as debug() + because they are used by ssl_crtd. + */ + +/** + \ingroup SslCrtdSslAPI + * Function for BIO delete for Deleter template. +*/ +void BIO_free_wrapper(BIO * bio); + +/** + \ingroup SslCrtdSslAPI + * TidyPointer typedefs for common SSL objects + */ +typedef TidyPointer X509_Pointer; +typedef TidyPointer EVP_PKEY_Pointer; +typedef TidyPointer BIGNUM_Pointer; +typedef TidyPointer BIO_Pointer; +typedef TidyPointer ASN1_INT_Pointer; +typedef TidyPointer TXT_DB_Pointer; +typedef TidyPointer X509_NAME_Pointer; +typedef TidyPointer RSA_Pointer; +typedef TidyPointer X509_REQ_Pointer; +typedef TidyPointer SSL_CTX_Pointer; +typedef TidyPointer SSL_Pointer; + + +/** + \ingroup SslCrtdSslAPI + * Create 1024 bits rsa key. + */ +EVP_PKEY * createSslPrivateKey(); + +/** + \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 + * Write private key and SSL certificate to file. + */ +bool writeCertAndPrivateKeyToFile(X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey, char const * filename); + +/** + \ingroup SslCrtdSslAPI + * Write private key and SSL certificate to memory. + */ +bool readCertAndPrivateKeyFromMemory(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, char const * bufferToRead); + +/** + \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); + +/** + \ingroup SslCrtdSslAPI + * Decide on the kind of certificate and generate a CA- or self-signed one. + * 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); + +/** + \ingroup SslCrtdSslAPI + * Read certificate and private key from files. + * \param certFilename name of file with certificate. + * \param keyFilename name of file with private key. + */ +void readCertAndPrivateKeyFromFiles(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, char const * certFilename, char const * keyFilename); + +/** + \ingroup SslCrtdSslAPI + * Verify date. Date format it ASN1_UTCTIME. if there is out of date error, + * return false. +*/ +bool sslDateIsInTheFuture(char const * date); + +} // namespace Ssl +#endif // SQUID_SSL_GADGETS_H === added file 'src/ssl/helper.cc' --- src/ssl/helper.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/helper.cc 2010-11-12 16:11:30 +0000 @@ -0,0 +1,92 @@ +/* + * 2008/11/14 + */ + +#include "config.h" +#include "ssl/Config.h" +#include "ssl/helper.h" +#include "SquidTime.h" +#include "SwapDir.h" + +Ssl::Helper * Ssl::Helper::GetInstance() +{ + static Ssl::Helper sslHelper; + return &sslHelper; +} + +Ssl::Helper::Helper() +{ + Init(); +} + +Ssl::Helper::~Helper() +{ + Shutdown(); +} + +void Ssl::Helper::Init() +{ + if (ssl_crtd == NULL) + ssl_crtd = new helper("ssl_crtd"); + ssl_crtd->childs = Ssl::TheConfig.ssl_crtdChildren; + ssl_crtd->ipc_type = IPC_STREAM; + assert(ssl_crtd->cmdline == NULL); + { + char *tmp = xstrdup(Ssl::TheConfig.ssl_crtd); + char *tmp_begin = tmp; + char * token = NULL; + bool db_path_was_found = false; + bool block_size_was_found = false; + char buffer[20] = "2048"; + while ((token = strwordtok(NULL, &tmp))) { + wordlistAdd(&ssl_crtd->cmdline, token); + if (!strcmp(token, "-b")) + block_size_was_found = true; + if (!strcmp(token, "-s")) { + db_path_was_found = true; + } else if (db_path_was_found) { + db_path_was_found = false; + int fs_block_size = 0; + storeDirGetBlkSize(token, &fs_block_size); + snprintf(buffer, sizeof(buffer), "%i", fs_block_size); + } + } + if (!block_size_was_found) { + wordlistAdd(&ssl_crtd->cmdline, "-b"); + wordlistAdd(&ssl_crtd->cmdline, buffer); + } + safe_free(tmp_begin); + } + ssl_crtd->return_full_reply = true; + helperOpenServers(ssl_crtd); +} + +void Ssl::Helper::Shutdown() +{ + if (!ssl_crtd) + return; + helperShutdown(ssl_crtd); + wordlistDestroy(&ssl_crtd->cmdline); + if (!shutting_down) + return; + delete ssl_crtd; + ssl_crtd = NULL; +} + +void Ssl::Helper::sslSubmit(CrtdMessage const & message, HLPCB * callback, void * data) +{ + static time_t first_warn = 0; + + if (ssl_crtd->stats.queue_size >= (int)(ssl_crtd->childs.n_running * 2)) { + if (first_warn == 0) + first_warn = squid_curtime; + if (squid_curtime - first_warn > 3 * 60) + fatal("SSL servers not responding for 3 minutes"); + debugs(34, 1, HERE << "Queue overload, rejecting"); + callback(data, (char *)"error 45 Temporary network problem, please retry later"); + return; + } + + first_warn = 0; + helperSubmit(ssl_crtd, message.compose().c_str(), callback, data); +} === added file 'src/ssl/helper.h' --- src/ssl/helper.h 1970-01-01 00:00:00 +0000 +++ src/ssl/helper.h 2010-11-11 17:01:26 +0000 @@ -0,0 +1,34 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_HELPER_H +#define SQUID_SSL_HELPER_H + +#include "../helper.h" +#include "ssl/crtd_message.h" + +namespace Ssl +{ +/** + * Set of thread for ssl_crtd. This class is singleton. Use this class only + * over GetIntance() static method. This class use helper structure + * for threads management. + */ +class Helper +{ +public: + static Helper * GetInstance(); ///< Instance class. + void Init(); ///< Init helper structure. + void Shutdown(); ///< Shutdown helper structure. + /// Submit crtd message to external crtd server. + void sslSubmit(CrtdMessage const & message, HLPCB * callback, void *data); +private: + Helper(); + ~Helper(); + + helper * ssl_crtd; ///< helper for management of ssl_crtd. +}; + +} //namespace Ssl +#endif // SQUID_SSL_HELPER_H === added file 'src/ssl/ssl_crtd.cc' --- src/ssl/ssl_crtd.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/ssl_crtd.cc 2010-11-12 15:19:34 +0000 @@ -0,0 +1,351 @@ +/* + * $Id$ + */ + +#include "config.h" +#include "helpers/defines.h" +#include "ssl/gadgets.h" +#include "ssl/crtd_message.h" +#include "ssl/certificate_db.h" + +#if HAVE_CSTRING +#include +#endif +#if HAVE_SSTREAM +#include +#endif +#if HAVE_IOSTREAM +#include +#endif +#if HAVE_STDEXCEPT +#include +#endif +#if HAVE_STRING +#include +#endif +#if HAVE_GETOPT_H +#include +#endif + +/** + \defgroup ssl_crtd ssl_crtd + \ingroup ExternalPrograms + \par + Because the standart generation of ssl certificate for + sslBump feature, Squid must use external proccess to + actually make these calls. This process generate new ssl + certificates and worked with ssl certificates disk cache. + Typically there will be five ssl_crtd processes spawned + from Squid. Communication occurs via TCP sockets bound + to the loopback interface. The class in helper.h are + primally concerned with starting and stopping the ssl_crtd. + Reading and writing to and from the ssl_crtd occurs in the + \link IPCacheAPI IP\endlink and the dnsservers occurs in + the \link IPCacheAPI IP\endlink and \link FQDNCacheAPI + FQDN\endlink cache modules. + + \section ssl_crtdInterface Command Line Interface + \verbatim +usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size + -h Help + -v Version + -s ssl_storage_path Path to specific disk storage of ssl server + certificates. + -M storage_max_size max size of ssl certificates storage. + -b fs_block_size File system block size in bytes. Need for processing + natural size of certificate on disk. Default value is + 2048 bytes." + + After running write requests in the next format: + + There are two kind of request now: + new_certificate 14 host=host.dom + Create new private key and selfsigned certificate for "host.dom". + + new_certificate xxx host=host.dom + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + -----BEGIN RSA PRIVATE KEY----- + ... + -----END RSA PRIVATE KEY----- + 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 + -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 + */ + +static const char *const B_KBYTES_STR = "KB"; +static const char *const B_MBYTES_STR = "MB"; +static const char *const B_GBYTES_STR = "GB"; +static const char *const B_BYTES_STR = "B"; + +/** + \ingroup ssl_crtd + * Get current time. +*/ +time_t getCurrentTime(void) +{ + struct timeval current_time; +#if GETTIMEOFDAY_NO_TZP + gettimeofday(¤t_time); +#else + gettimeofday(¤t_time, NULL); +#endif + return current_time.tv_sec; +} + +/** + \ingroup ssl_crtd + * Parse bytes unit. It would be one of the next value: MB, GB, KB or B. + * This function is caseinsensitive. + */ +static size_t parseBytesUnits(const char * unit) +{ + if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)) || + !strncasecmp(unit, "", strlen(unit))) + return 1; + + if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR))) + return 1 << 10; + + if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR))) + return 1 << 20; + + if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR))) + return 1 << 30; + + return 0; +} + +/** + \ingroup ssl_crtd + * Parse uninterrapted string of bytes value. It looks like "4MB". + */ +static bool parseBytesOptionValue(size_t * bptr, char const * value) +{ + // Find number from string beginning. + char const * number_begin = value; + char const * number_end = value; + + while ((*number_end >= '0' && *number_end <= '9')) { + number_end++; + } + + std::string number(number_begin, number_end - number_begin); + std::istringstream in(number); + int d = 0; + if (!(in >> d)) + return false; + + int m; + if ((m = parseBytesUnits(number_end)) == 0) { + return false; + } + + *bptr = static_cast(m * d); + if (static_cast(*bptr * 2) != m * d * 2) + return false; + + return true; +} + +/** + \ingroup ssl_crtd + * Print help using response code. + */ +static void usage() +{ + std::string example_host_name = "host.dom"; + std::string request_string = Ssl::CrtdMessage::param_host + "=" + example_host_name; + std::stringstream request_string_size_stream; + request_string_size_stream << request_string.length(); + std::string help_string = + "usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size\n" + "\t-h Help\n" + "\t-v Version\n" + "\t-s ssl_storage_path Path to specific disk storage of ssl server\n" + "\t certificates.\n" + "\t-M storage_max_size max size of ssl certificates storage.\n" + "\t-b fs_block_size File system block size in bytes. Need for processing\n" + "\t natural size of certificate on disk. Default value is\n" + "\t 2048 bytes.\n" + "\n" + "After running write requests in the next format:\n" + "\n" + "There are two kind of request now:\n" + + Ssl::CrtdMessage::code_new_certificate + " " + request_string_size_stream.str() + " " + request_string + "\n" + + "\tCreate new private key and selfsigned certificate for \"host.dom\".\n" + + Ssl::CrtdMessage::code_new_certificate + " xxx " + request_string + "\n" + + "-----BEGIN CERTIFICATE-----\n" + "...\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN RSA PRIVATE KEY-----\n" + "...\n" + "-----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."; + std::cerr << help_string << std::endl; +} + +/** + \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) +{ + 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::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())) + throw std::runtime_error("Cannot create ssl certificate or private key."); + if (!db.addCertAndPrivateKey(cert, pkey) && db.IsEnabledDiskStore()) + throw std::runtime_error("Cannot add certificate to db."); + } + + std::string bufferToWrite; + if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite)) + throw std::runtime_error("Cannot write ssl certificate or/and private key to memory."); + + Ssl::CrtdMessage response_message; + response_message.setCode("ok"); + response_message.setBody(bufferToWrite); + + std::cout << response_message.compose(); + + return true; +} + +/** + \ingroup ssl_crtd + * This is the external ssl_crtd process. + */ +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) { + switch (c) { + case 'd': + debug_enabled = 1; + break; + case 'b': + if (!parseBytesOptionValue(&fs_block_size, optarg)) { + throw std::runtime_error("Error when parsing -b options value"); + } + break; + 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"); + } + break; + case 'v': + std::cout << "ssl_crtd version " << VERSION << std::endl; + exit(0); + break; + case 'c': + create_new_db = true; + break; + case 'g': + show_sn = true; + break; + case 'h': + usage(); + exit(0); + default: + exit(0); + } + } + + if (create_new_db) { + std::cout << "Initialization SSL db..." << std::endl; + Ssl::CertificateDb::create(db_path, serial); + 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); + } + // proccess request. + for (;;) { + char request[HELPER_INPUT_BUFFER]; + Ssl::CrtdMessage request_message; + Ssl::CrtdMessage::ParseResult parse_result = Ssl::CrtdMessage::INCOMPLETE; + + while (parse_result == Ssl::CrtdMessage::INCOMPLETE) { + if (fgets(request, HELPER_INPUT_BUFFER, stdin) == NULL) + return 1; + size_t gcount = strlen(request); + parse_result = request_message.parse(request, gcount); + } + + if (parse_result == Ssl::CrtdMessage::ERROR) { + throw std::runtime_error("Cannot parse request message."); + } else if (request_message.getCode() == Ssl::CrtdMessage::code_new_certificate) { + proccessNewRequest(request_message, db_path, max_db_size, fs_block_size); + } else { + throw std::runtime_error("Unknown request code: \"" + request_message.getCode() + "\"."); + } + std::cout.flush(); + } + } catch (std::runtime_error & error) { + std::cerr << argv[0] << ": " << error.what() << std::endl; + return 0; + } + return 0; +} === renamed file 'src/ssl_support.cc' => 'src/ssl/support.cc' --- src/ssl_support.cc 2010-03-31 15:59:21 +0000 +++ src/ssl/support.cc 2010-11-15 15:40:05 +0000 @@ -42,6 +42,7 @@ #include "fde.h" #include "acl/FilledChecklist.h" +#include "ssl/gadgets.h" /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals @@ -1206,4 +1207,58 @@ return str; } +/// \ingroup ServerProtocolSSLInternal +/// Create SSL context and apply ssl certificate and private key to it. +static SSL_CTX * createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey) +{ + Ssl::SSL_CTX_Pointer sslContext(SSL_CTX_new(SSLv23_server_method())); + + if (!SSL_CTX_use_certificate(sslContext.get(), x509.get())) + return NULL; + + if (!SSL_CTX_use_PrivateKey(sslContext.get(), pkey.get())) + return NULL; + return sslContext.release(); +} + +SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!readCertAndPrivateKeyFromMemory(cert, pkey, data)) + return NULL; + + if (!cert || !pkey) + return NULL; + + return createSSLContext(cert, pkey); +} + +SSL_CTX * Ssl::generateSslContext(char const *host, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!generateSslCertificateAndPrivateKey(host, signedX509, signedPkey, cert, pkey, NULL)) { + return NULL; + } + if (!cert) + return NULL; + + if (!pkey) + return NULL; + + return createSSLContext(cert, pkey); +} + +bool Ssl::verifySslCertificateDate(SSL_CTX * sslContext) +{ + // Temporary ssl for getting X509 certificate from SSL_CTX. + Ssl::SSL_Pointer ssl(SSL_new(sslContext)); + X509 * cert = SSL_get_certificate(ssl.get()); + 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; +} + #endif /* USE_SSL */ === renamed file 'src/ssl_support.h' => 'src/ssl/support.h' --- src/ssl_support.h 2010-02-03 12:36:21 +0000 +++ src/ssl/support.h 2010-11-15 15:40:05 +0000 @@ -36,6 +36,8 @@ #define SQUID_SSL_SUPPORT_H #include "config.h" +#include "ssl/gadgets.h" + #if HAVE_OPENSSL_SSL_H #include #endif @@ -92,6 +94,30 @@ ssl_error_t sslParseErrorString(const char *name); const char *sslFindErrorString(ssl_error_t value); +namespace Ssl +{ +/** + \ingroup ServerProtocolSSLAPI + * Decide on the kind of certificate and generate a CA- or self-signed one +*/ +SSL_CTX *generateSslContext(char const *host, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey); + +/** + \ingroup ServerProtocolSSLAPI + * Check date of certificate signature. If there is out of date error fucntion + * returns false, true otherwise. + */ +bool verifySslCertificateDate(SSL_CTX * sslContext); + +/** + \ingroup ServerProtocolSSLAPI + * Read private key and certificate from memory and generate SSL context + * using their. + */ +SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data); + +} //namespace Ssl + // Custom SSL errors; assumes all official errors are positive #define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1 // All SSL errors range: from smallest (negative) custom to largest SSL error