DynCert feature port from Squid3 trunk r11308 to Squid 3.1 r10237 (v3.1.11). This is a Measurement Factory project. === modified file 'configure.ac' --- configure.ac 2011-02-08 04:05:03 +0000 +++ configure.ac 2011-03-26 14:46:24 +0000 @@ -1678,6 +1678,26 @@ AM_CONDITIONAL(USE_DNSSERVER, true) fi +AM_CONDITIONAL(USE_SSL_CRTD, false) +use_ssl_crtd= +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.]), [ + if test "x$enableval" = "xyes" ; then + use_ssl_crtd="yes" + fi +]) + +if test "x$use_ssl_crtd" = "xyes" -a -z "$USE_OPENSSL" ; then + AC_MSG_ERROR([You need to enable ssl gatewaying support to use ssl_crtd feature. Try to use --enable-ssl. ]) +fi + +if test "x$use_ssl_crtd" = "xyes"; then + AC_DEFINE(USE_SSL_CRTD, 1,[Use ssl_crtd daemon]) + AM_CONDITIONAL(USE_SSL_CRTD, true) +fi + dnl Select Default hosts file location AC_ARG_ENABLE(default-hostsfile, AS_HELP_STRING([--enable-default-hostsfile=path],[Select default location for hosts file. @@ -2257,6 +2277,7 @@ execinfo.h \ fcntl.h \ fnmatch.h \ + fstream \ getopt.h \ glob.h \ gnumalloc.h \ @@ -2272,12 +2293,16 @@ libc.h \ libgen.h \ limits.h \ + limits \ linux/posix_types.h \ linux/types.h \ + list \ machine/byte_swap.h \ malloc.h \ + map \ math.h \ memory.h \ + memory \ mount.h \ netdb.h \ netinet/in.h \ @@ -2286,6 +2311,7 @@ openssl/err.h \ openssl/md5.h \ openssl/ssl.h \ + openssl/txt_db.h \ openssl/x509v3.h \ netinet/tcp.h \ openssl/engine.h \ @@ -3934,6 +3960,7 @@ src/icmp/Makefile \ src/ident/Makefile \ src/ip/Makefile \ + src/ssl/Makefile \ contrib/Makefile \ snmplib/Makefile \ icons/Makefile \ === modified file 'src/Makefile.am' --- src/Makefile.am 2010-12-17 19:49:54 +0000 +++ src/Makefile.am 2011-03-26 10:02:41 +0000 @@ -112,8 +112,13 @@ if ENABLE_SSL SSL_SOURCE = $(SSL_ALL_SOURCE) +SUBDIRS += ssl +SSL_LIBS = \ + ssl/libsslutil.la \ + ssl/libsslsquid.la else SSL_SOURCE = +SSL_LIBS = endif WIN32_ALL_SOURCE = \ @@ -531,6 +536,7 @@ ${ADAPTATION_LIBS} \ $(ESI_LIBS) \ $(SSLLIB) \ + $(SSL_LIBS) \ -lmiscutil \ $(EPOLL_LIBS) \ $(MINGW_LIBS) \ @@ -715,6 +721,7 @@ 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 @@ -722,6 +729,7 @@ DEFAULT_PID_FILE = $(DEFAULT_PIDFILE) 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_DISKD = $(libexecdir)/`echo diskd | sed '$(transform);s/$$/$(EXEEXT)/'` @@ -769,6 +777,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" \ @@ -778,6 +787,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" \ @@ -1195,6 +1205,7 @@ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ + $(SSL_LIBS) \ $(XTRA_LIBS) tests_testCacheManager_LDFLAGS = $(LIBADD_DL) tests_testCacheManager_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @@ -1370,6 +1381,7 @@ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ + $(SSL_LIBS) \ $(XTRA_LIBS) tests_testEvent_LDFLAGS = $(LIBADD_DL) tests_testEvent_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @@ -1520,6 +1532,7 @@ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ + $(SSL_LIBS) \ $(XTRA_LIBS) tests_testEventLoop_LDFLAGS = $(LIBADD_DL) tests_testEventLoop_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @@ -1665,6 +1678,7 @@ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ + $(SSL_LIBS) \ $(XTRA_LIBS) tests_test_http_range_LDFLAGS = $(LIBADD_DL) tests_test_http_range_DEPENDENCIES = \ @@ -1815,6 +1829,7 @@ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ + $(SSL_LIBS) \ $(XTRA_LIBS) tests_testHttpRequest_LDFLAGS = $(LIBADD_DL) tests_testHttpRequest_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ @@ -2177,6 +2192,7 @@ $(SQUID_CPPUNIT_LIBS) \ $(SQUID_CPPUNIT_LA) \ $(SSLLIB) \ + $(SSL_LIBS) \ $(XTRA_LIBS) tests_testURL_LDFLAGS = $(LIBADD_DL) tests_testURL_DEPENDENCIES = $(top_builddir)/lib/libmiscutil.a \ === modified file 'src/ProtoPort.cc' --- src/ProtoPort.cc 2009-02-01 10:09:23 +0000 +++ src/ProtoPort.cc 2011-03-26 14:28:48 +0000 @@ -6,11 +6,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 2009-09-24 09:33:48 +0000 +++ src/ProtoPort.h 2011-03-26 14:29:20 +0000 @@ -6,6 +6,9 @@ //#include "typedefs.h" #include "cbdata.h" +#if USE_SSL +#include "ssl/gadgets.h" +#endif struct http_port_list { http_port_list(const char *aProtocol); @@ -52,8 +55,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/SslErrorData.cc' --- src/acl/SslErrorData.cc 2009-05-23 02:42:06 +0000 +++ src/acl/SslErrorData.cc 2011-03-27 15:55:47 +0000 @@ -22,7 +22,7 @@ } bool -ACLSslErrorData::match(ssl_error_t toFind) +ACLSslErrorData::match(Ssl::ssl_error_t toFind) { return values->findAndTune (toFind); } @@ -30,17 +30,17 @@ /* explicit instantiation required for some systems */ /** \cond AUTODOCS-IGNORE */ // AYJ: 2009-05-20 : Removing. clashes with template instantiation for other ACLs. -// template cbdata_type CbDataList::CBDATA_CbDataList; +// template cbdata_type CbDataList::CBDATA_CbDataList; /** \endcond */ wordlist * ACLSslErrorData::dump() { wordlist *W = NULL; - CbDataList *data = values; + CbDataList *data = values; while (data != NULL) { - wordlistAdd(&W, sslFindErrorString(data->element)); + wordlistAdd(&W, Ssl::getErrorName(data->element)); data = data->next; } @@ -50,12 +50,12 @@ void ACLSslErrorData::parse() { - CbDataList **Tail; + CbDataList **Tail; char *t = NULL; for (Tail = &values; *Tail; Tail = &((*Tail)->next)); while ((t = strtokFile())) { - CbDataList *q = new CbDataList(sslParseErrorString(t)); + CbDataList *q = new CbDataList(Ssl::parseErrorString(t)); *(Tail) = q; Tail = &q->next; } @@ -67,7 +67,7 @@ return values == NULL; } -ACLData * +ACLData * ACLSslErrorData::clone() const { /* Splay trees don't clone yet. */ === modified file 'src/acl/SslErrorData.h' --- src/acl/SslErrorData.h 2009-03-19 03:45:56 +0000 +++ src/acl/SslErrorData.h 2011-03-27 15:59:51 +0000 @@ -9,8 +9,9 @@ #include "acl/Data.h" #include "CbDataList.h" #include "ssl_support.h" +#include "ssl/ErrorDetail.h" -class ACLSslErrorData : public ACLData +class ACLSslErrorData : public ACLData { public: @@ -20,13 +21,13 @@ ACLSslErrorData(ACLSslErrorData const &); ACLSslErrorData &operator= (ACLSslErrorData const &); virtual ~ACLSslErrorData(); - bool match(ssl_error_t); + bool match(Ssl::ssl_error_t); wordlist *dump(); void parse(); bool empty() const; - virtual ACLData *clone() const; + virtual ACLData *clone() const; - CbDataList *values; + CbDataList *values; }; MEMPROXY_CLASS_INLINE(ACLSslErrorData); === modified file 'src/base/Makefile.am' --- src/base/Makefile.am 2010-08-27 16:16:29 +0000 +++ src/base/Makefile.am 2011-03-25 18:03:10 +0000 @@ -12,4 +12,5 @@ AsyncJobCalls.h \ AsyncCallQueue.cc \ AsyncCallQueue.h \ - CbcPointer.h + CbcPointer.h \ + TidyPointer.h === added file 'src/base/TidyPointer.h' --- src/base/TidyPointer.h 1970-01-01 00:00:00 +0000 +++ src/base/TidyPointer.h 2011-03-26 19:14:46 +0000 @@ -0,0 +1,64 @@ +/* + * $Id$ + */ + +#ifndef SQUID_BASE_TIDYPOINTER_H +#define SQUID_BASE_TIDYPOINTER_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) {} +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 +}; + +/// 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 2011-02-07 12:15:24 +0000 +++ src/cache_cf.cc 2011-03-26 14:45:52 +0000 @@ -57,6 +57,10 @@ #include #endif +#if HAVE_LIMITS +#include +#endif + #if SQUID_SNMP #include "snmp.h" #endif @@ -64,6 +68,11 @@ #include "esi/Parser.h" #endif +#if USE_SSL +#include "ssl/Config.h" +#include "ssl/gadgets.h" +#endif + #if USE_ADAPTATION #include "adaptation/Config.h" @@ -132,6 +141,9 @@ static void parse_obsolete(const char *); static void parseBytesLine(size_t * bptr, const char *units); static size_t parseBytesUnits(const char *unit); +#if USE_SSL +static void parseBytesOptionValue(size_t * bptr, const char *units, char const * value); +#endif static void free_all(void); void requirePathnameExists(const char *name, const char *path); static OBJH dump_config; @@ -721,7 +733,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); } } @@ -732,7 +750,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)); } } @@ -931,6 +953,44 @@ self_destruct(); } +#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) { @@ -3191,8 +3251,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); } else if (strcasecmp(token, "sslBump") == 0) { debugs(3, DBG_CRITICAL, "WARNING: '" << token << "' is deprecated " << "in http_port. Use 'ssl-bump' instead."); @@ -3269,7 +3337,7 @@ char *crlfile; char *dhfile; char *sslflags; - char *sslcontext; + char *sslContextSessionId; SSL_CTX *sslContext; #endif @@ -3415,8 +3483,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 2011-02-07 12:15:24 +0000 +++ src/cf.data.pre 2011-03-27 17:06:40 +0000 @@ -1339,6 +1339,25 @@ 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. + vport Accelerator with IP based virtual host support. vport=NN As above, but uses specified port number rather @@ -1710,6 +1729,35 @@ 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: int +IFDEF: USE_SSL_CRTD +DEFAULT: 5 +LOC: Ssl::TheConfig.ssl_crtd_n_running +DOC_START + The maximum number of processes spawn to service ssl server. + The maximum this may be safely set to is 32. + + 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 2011-03-22 12:04:26 +0000 +++ src/client_side.cc 2011-03-27 14:51:34 +0000 @@ -106,6 +106,20 @@ #include "ChunkedCodingParser.h" #include "rfc1738.h" +#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 @@ -3249,7 +3263,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_ERR_CLOSING) { return; @@ -3310,24 +3324,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 = Ssl::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; @@ -3343,6 +3442,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 */ @@ -3363,14 +3479,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 enter_suid(); @@ -3422,7 +3545,7 @@ continue; } - if (s->sslContext == NULL) { + if (!s->staticSslContext) { debugs(1, 1, "Ignoring https_port " << s->http.s << " due to SSL initialization failure."); continue; @@ -3570,7 +3693,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-12-20 11:41:54 +0000 +++ src/client_side.h 2011-03-26 14:58:59 +0000 @@ -259,7 +259,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; } @@ -282,6 +295,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-12-20 23:44:21 +0000 +++ src/client_side_request.cc 2011-03-27 09:37:03 +0000 @@ -1178,11 +1178,12 @@ return; if (errflag) { - getConn()->startClosing("CONNECT response failure in SslBump"); + debugs(85, 3, HERE << "CONNECT response failure in SslBump: " << errflag); + comm_close(getConn()->fd); return; } - getConn()->switchToHttps(); + getConn()->switchToHttps(request->GetHost()); } void === modified file 'src/comm.cc' --- src/comm.cc 2010-12-06 02:14:40 +0000 +++ src/comm.cc 2011-03-26 18:27:05 +0000 @@ -1550,6 +1550,10 @@ F->ssl = NULL; } + if (F->dynamicSslContext) { + SSL_CTX_free(F->dynamicSslContext); + F->dynamicSslContext = NULL; + } #endif fd_close(fd); /* update fdstat */ === modified file 'src/errorpage.cc' --- src/errorpage.cc 2011-02-01 01:55:04 +0000 +++ src/errorpage.cc 2011-03-27 16:21:20 +0000 @@ -508,6 +508,9 @@ if (err->err_language != Config.errorDefaultLanguage) #endif safe_free(err->err_language); +#if USE_SSL + delete err->detail; +#endif cbdataFree(err); } @@ -597,7 +600,7 @@ #define CVT_BUF_SZ 512 const char * -ErrorState::Convert(char token) +ErrorState::Convert(char token, bool allowRecursion) { static MemBuf mb; const char *p = NULL; /* takes priority over mb if set */ @@ -628,6 +631,25 @@ break; + case 'D': + if (!allowRecursion) + p = "%D"; // if recursion is not allowed, do not convert +#if USE_SSL + // currently only SSL error details implemented + else if (detail) { + const String &errDetail = detail->toString(); + if (errDetail.defined()) { + MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false); + mb.append(detail_mb->content(), detail_mb->contentSize()); + delete detail_mb; + do_quote = 0; + } + } +#endif + if (!mb.contentSize()) + mb.Printf("[No Error Detail]"); + break; + case 'e': mb.Printf("%d", xerrno); @@ -929,10 +951,7 @@ MemBuf * ErrorState::BuildContent() { - MemBuf *content = new MemBuf; const char *m = NULL; - const char *p; - const char *t; assert(page_id > ERR_NONE && page_id < error_page_count); @@ -1051,12 +1070,25 @@ debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file."); } + MemBuf *result = ConvertText(m, true); +#if USE_ERR_LOCALES + safe_free(freePage); +#endif + + return result; +} + +MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion) +{ + MemBuf *content = new MemBuf; + const char *p; + const char *m = text; assert(m); content->init(); while ((p = strchr(m, '%'))) { content->append(m, p - m); /* copy */ - t = Convert(*++p); /* convert */ + const char *t = Convert(*++p, allowRecursion); /* convert */ content->Printf("%s", t); /* copy */ m = p + 1; /* advance */ } @@ -1066,9 +1098,5 @@ assert((size_t)content->contentSize() == strlen(content->content())); -#if USE_ERR_LOCALES - safe_free(freePage); -#endif - return content; } === modified file 'src/errorpage.h' --- src/errorpage.h 2010-05-26 04:00:23 +0000 +++ src/errorpage.h 2011-03-27 16:04:07 +0000 @@ -37,6 +37,9 @@ #include "squid.h" #include "cbdata.h" #include "ip/IpAddress.h" +#if USE_SSL +#include "ssl/ErrorDetail.h" +#endif /** \defgroup ErrorPageAPI Error Pages API @@ -48,6 +51,7 @@ B - URL with FTP %2f hack x c - Squid error code x d - seconds elapsed since request received x + D - Error details x e - errno x E - strerror() x f - FTP request line x @@ -99,9 +103,18 @@ MemBuf *BuildContent(void); /** + * Convert the given template string into textual output + * + * \param text The string to be converted + * \param allowRecursion Whether to convert codes which output may contain codes + */ + MemBuf *ConvertText(const char *text, bool allowRecursion); + + /** * Convert an error template into an error page. + * \ allowRecursion True if the codes which do recursions should converted */ - const char *Convert(char token); + const char *Convert(char token, bool allowRecursion); /** * CacheManager / Debug dump of the ErrorState object. @@ -141,6 +154,9 @@ char *request_hdrs; char *err_msg; /* Preformatted error message from the cache */ +#if USE_SSL + Ssl::ErrorDetail *detail; +#endif private: CBDATA_CLASS2(ErrorState); }; === modified file 'src/fde.h' --- src/fde.h 2009-01-16 12:14:02 +0000 +++ src/fde.h 2011-03-26 18:27:05 +0000 @@ -102,6 +102,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 { === modified file 'src/forward.cc' --- src/forward.cc 2010-10-23 13:42:20 +0000 +++ src/forward.cc 2011-03-27 16:01:13 +0000 @@ -48,6 +48,10 @@ #include "icmp/net_db.h" #include "ip/IpIntercept.h" #include "ip/tools.h" +#if USE_SSL +#include "ssl_support.h" +#include "ssl/ErrorDetail.h" + #endif static PSC fwdStartCompleteWrapper; static PF fwdServerClosedWrapper; @@ -605,6 +609,14 @@ anErr->xerrno = EACCES; #endif + Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail); + if (errFromFailure != NULL){ + // The errFromFailure is attached to the ssl object + // and will be released when ssl object destroyed. + // Copy errFromFailure to a new Ssl::ErrorDetail object + anErr->detail = new Ssl::ErrorDetail(*errFromFailure); + } + fail(anErr); if (fs->_peer) { === modified file 'src/globals.h' --- src/globals.h 2010-12-17 19:46:13 +0000 +++ src/globals.h 2011-03-27 15:39:06 +0000 @@ -172,6 +172,7 @@ extern int ssl_ex_index_server; /* -1 */ extern int ssl_ctx_ex_index_dont_verify_domain; /* -1 */ extern int ssl_ex_index_cert_error_check; /* -1 */ + extern int ssl_ex_index_ssl_error_detail; /* -1 */ extern const char *external_acl_message; /* NULL */ extern int opt_send_signal; /* -1 */ === modified file 'src/helper.cc' --- src/helper.cc 2010-10-08 03:25:56 +0000 +++ src/helper.cc 2011-03-26 18:42:46 +0000 @@ -838,6 +838,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) @@ -880,69 +929,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->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->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 2009-08-12 11:51:23 +0000 +++ src/helper.h 2011-03-26 18:42:14 +0000 @@ -73,6 +73,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; }; struct _helper_stateful { === modified file 'src/main.cc' --- src/main.cc 2010-12-17 19:46:13 +0000 +++ src/main.cc 2011-03-26 18:33:01 +0000 @@ -80,6 +80,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 @@ -691,7 +700,12 @@ idnsShutdown(); #endif - +#if USE_SSL_CRTD + Ssl::Helper::GetInstance()->Shutdown(); +#endif +#if USE_SSL + Ssl::TheGlobalContextStorage.reconfigureStart(); +#endif redirectShutdown(); authenticateShutdown(); externalAclShutdown(); @@ -767,6 +781,9 @@ idnsInit(); #endif +#if USE_SSL_CRTD + Ssl::Helper::GetInstance()->Init(); +#endif redirectInit(); authenticateInit(&Config.authConfiguration); @@ -1701,7 +1718,9 @@ idnsShutdown(); #endif - +#if USE_SSL_CRTD + Ssl::Helper::GetInstance()->Shutdown(); +#endif redirectShutdown(); externalAclShutdown(); icpConnectionClose(); === 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 2011-03-26 11:12:16 +0000 @@ -0,0 +1,23 @@ +/* + * $Id$ + */ +#include "squid.h" +#include "ssl/Config.h" + +Ssl::Config Ssl::TheConfig; + +Ssl::Config::Config() +#if USE_SSL_CRTD + : + ssl_crtd(NULL), + ssl_crtd_n_running(5) +#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 2011-03-26 11:10:34 +0000 @@ -0,0 +1,29 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_CONFIG_H +#define SQUID_SSL_CONFIG_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. + int ssl_crtd_n_running; +#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/ErrorDetail.cc' --- src/ssl/ErrorDetail.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/ErrorDetail.cc 2011-03-27 15:18:43 +0000 @@ -0,0 +1,265 @@ +#include "squid.h" +#include "ssl/ErrorDetail.h" + +struct SslErrorDetailEntry { + Ssl::ssl_error_t value; + const char *name; + const char *detail; +}; + +static const char *SslErrorDetailDefaultStr = "SSL certificate validation error (%err_name): %ssl_subject"; +// TODO: optimize by replacing with std::map or similar +static SslErrorDetailEntry TheSslDetailMap[] = { + { SQUID_X509_V_ERR_DOMAIN_MISMATCH, + "SQUID_X509_V_ERR_DOMAIN_MISMATCH", + "%err_name: The hostname you are connecting to (%H), does not match any of the Certificate valid names: %ssl_cn"}, + { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, + "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT", + "%err_name: SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" }, + { X509_V_ERR_CERT_NOT_YET_VALID, + "X509_V_ERR_CERT_NOT_YET_VALID", + "%err_name: SSL Certficate is not valid before: %ssl_notbefore" }, + { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, + "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD", + "%err_name: SSL Certificate has invalid start date (the 'not before' field): %ssl_subject" }, + { X509_V_ERR_CERT_HAS_EXPIRED, + "X509_V_ERR_CERT_HAS_EXPIRED", + "%err_name: SSL Certificate expired on %ssl_notafter" }, + { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, + "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD", + "%err_name: SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject" }, + {X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, + "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT", + "%err_name: Self-signed SSL Certificate: %ssl_subject"}, + { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY", + "%err_name: SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" }, + { SSL_ERROR_NONE, "SSL_ERROR_NONE", "%err_name: No error" }, + {SSL_ERROR_NONE, NULL, NULL } +}; + +Ssl::ssl_error_t +Ssl::parseErrorString(const char *name) +{ + assert(name); + + for (int i = 0; TheSslDetailMap[i].name; ++i) { + if (strcmp(name, TheSslDetailMap[i].name) == 0) + return TheSslDetailMap[i].value; + } + + if (xisdigit(*name)) { + const long int value = strtol(name, NULL, 0); + if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX) + return value; + fatalf("Too small or too bug SSL error code '%s'", name); + } + + fatalf("Unknown SSL error name '%s'", name); + return SSL_ERROR_SSL; // not reached +} + +const char * +Ssl::getErrorName(Ssl::ssl_error_t value) +{ + + for (int i = 0; TheSslDetailMap[i].name; ++i) { + if (TheSslDetailMap[i].value == value) + return TheSslDetailMap[i].name; + } + + return NULL; +} + +static const char *getErrorDetail(Ssl::ssl_error_t value) +{ + for (int i = 0; TheSslDetailMap[i].name; ++i) { + if (TheSslDetailMap[i].value == value) + return TheSslDetailMap[i].detail; + } + + // we must always return something because ErrorDetail::buildDetail + // will hit an assertion + return SslErrorDetailDefaultStr; +} + +Ssl::ErrorDetail::err_frm_code Ssl::ErrorDetail::ErrorFormatingCodes[] = { + {"ssl_subject", &Ssl::ErrorDetail::subject}, + {"ssl_ca_name", &Ssl::ErrorDetail::ca_name}, + {"ssl_cn", &Ssl::ErrorDetail::cn}, + {"ssl_notbefore", &Ssl::ErrorDetail::notbefore}, + {"ssl_notafter", &Ssl::ErrorDetail::notafter}, + {"err_name", &Ssl::ErrorDetail::err_code}, + {NULL,NULL} +}; + +/** + * The subject of the current certification in text form + */ +const char *Ssl::ErrorDetail::subject() const +{ + if (!peer_cert) + return "[Not available]"; + + static char tmpBuffer[256]; // A temporary buffer + X509_NAME_oneline(X509_get_subject_name(peer_cert.get()), tmpBuffer, + sizeof(tmpBuffer)); + return tmpBuffer; +} + +// helper function to be used with Ssl::matchX509CommonNames +static int copy_cn(void *check_data, ASN1_STRING *cn_data) +{ + String *str = (String *)check_data; + if (!str) // no data? abort + return 0; + if (str->defined()) + str->append(", "); + str->append((const char *)cn_data->data, cn_data->length); + return 1; +} + +/** + * The list with certificates cn and alternate names + */ +const char *Ssl::ErrorDetail::cn() const +{ + if (!peer_cert) + return "[Not available]"; + + static String tmpStr; ///< A temporary string buffer + tmpStr.clean(); + Ssl::matchX509CommonNames(peer_cert.get(), &tmpStr, copy_cn); + return tmpStr.termedBuf(); +} + +/** + * The issuer name + */ +const char *Ssl::ErrorDetail::ca_name() const +{ + if (!peer_cert) + return "[Not available]"; + + static char tmpBuffer[256]; // A temporary buffer + X509_NAME_oneline(X509_get_issuer_name(peer_cert.get()), tmpBuffer, sizeof(tmpBuffer)); + return tmpBuffer; +} + +/** + * The certificate "not before" field + */ +const char *Ssl::ErrorDetail::notbefore() const +{ + if (!peer_cert) + return "[Not available]"; + + static char tmpBuffer[256]; // A temporary buffer + ASN1_UTCTIME * tm = X509_get_notBefore(peer_cert.get()); + Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); + return tmpBuffer; +} + +/** + * The certificate "not after" field + */ +const char *Ssl::ErrorDetail::notafter() const +{ + if (!peer_cert) + return "[Not available]"; + + static char tmpBuffer[256]; // A temporary buffer + ASN1_UTCTIME * tm = X509_get_notAfter(peer_cert.get()); + Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); + return tmpBuffer; +} + +/** + * The string representation of the error_no + */ +const char *Ssl::ErrorDetail::err_code() const +{ + static char tmpBuffer[64]; + const char *err = getErrorName(error_no); + if (!err) { + snprintf(tmpBuffer, 64, "%d", (int)error_no); + err = tmpBuffer; + } + return err; +} + +/** + * It converts the code to a string value. Currently the following + * formating codes are supported: + * %err_name: The name of the SSL error + * %ssl_cn: The comma-separated list of common and alternate names + * %ssl_subject: The certificate subject + * %ssl_ca_name: The certificate issuer name + * %ssl_notbefore: The certificate "not before" field + * %ssl_notafter: The certificate "not after" field + \retval the length of the code (the number of characters will be replaced by value) +*/ +int Ssl::ErrorDetail::convert(const char *code, const char **value) const +{ + *value = "-"; + for (int i=0; ErrorFormatingCodes[i].code!=NULL; i++) { + const int len = strlen(ErrorFormatingCodes[i].code); + if (strncmp(code,ErrorFormatingCodes[i].code, len)==0) { + ErrorDetail::fmt_action_t action = ErrorFormatingCodes[i].fmt_action; + *value = (this->*action)(); + return len; + } + } + return 0; +} + +/** + * It uses the convert method to build the string errDetailStr using + * a template message for the current SSL error. The template messages + * can also contain normal error pages formating codes. + * Currently the error template messages are hard-coded + */ +void Ssl::ErrorDetail::buildDetail() const +{ + char const *s = getErrorDetail(error_no); + char const *p; + char const *t; + int code_len = 0; + + assert(s); + while ((p = strchr(s, '%'))) { + errDetailStr.append(s, p - s); + code_len = convert(++p, &t); + if (code_len) + errDetailStr.append(t); + else + errDetailStr.append("%"); + s = p + code_len; + } + errDetailStr.append(s, strlen(s)); +} + +const String &Ssl::ErrorDetail::toString() const +{ + if (!errDetailStr.defined()) + buildDetail(); + return errDetailStr; +} + +/* We may do not want to use X509_dup but instead + internal SSL locking: + CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); + peer_cert.reset(cert); +*/ +Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no) +{ + peer_cert.reset(X509_dup(cert)); +} + +Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail) +{ + error_no = anErrDetail.error_no; + if (anErrDetail.peer_cert.get()) { + peer_cert.reset(X509_dup(anErrDetail.peer_cert.get())); + } +} === added file 'src/ssl/ErrorDetail.h' --- src/ssl/ErrorDetail.h 1970-01-01 00:00:00 +0000 +++ src/ssl/ErrorDetail.h 2011-03-28 06:21:42 +0000 @@ -0,0 +1,76 @@ +#ifndef _SQUID_SSL_ERROR_DETAIL_H +#define _SQUID_SSL_ERROR_DETAIL_H + +#include "ssl_support.h" +#include "ssl/gadgets.h" +#include "SquidString.h" + +#if HAVE_OPENSSL_SSL_H +#include +#endif + +// 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 +#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH +#define SQUID_SSL_ERROR_MAX INT_MAX + +namespace Ssl +{ +/// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE +typedef int ssl_error_t; + +/** + \ingroup ServerProtocolSSLAPI + * The ssl_error_t representation of the error described by "name". + */ +ssl_error_t parseErrorString(const char *name); + +/** + \ingroup ServerProtocolSSLAPI + * The string representation of the SSL error "value" + */ +const char *getErrorName(ssl_error_t value); + +/** + \ingroup ServerProtocolSSLAPI + * Used to pass SSL error details to the error pages returned to the + * end user. + */ +class ErrorDetail +{ +public: + ErrorDetail(ssl_error_t err_no, X509 *cert); + ErrorDetail(ErrorDetail const &); + const String &toString() const; ///< An error detail string to embed in squid error pages + +private: + typedef const char * (ErrorDetail::*fmt_action_t)() const; + /** + * Holds a formating code and its conversion method + */ + class err_frm_code + { + public: + const char *code; ///< The formating code + fmt_action_t fmt_action; ///< A pointer to the conversion method + }; + static err_frm_code ErrorFormatingCodes[]; ///< The supported formating codes + + const char *subject() const; + const char *ca_name() const; + const char *cn() const; + const char *notbefore() const; + const char *notafter() const; + const char *err_code() const; + + int convert(const char *code, const char **value) const; + void buildDetail() const; + + mutable String errDetailStr; ///< Caches the error detail message + ssl_error_t error_no; ///< The error code + X509_Pointer peer_cert; ///< A pointer to the peer certificate +}; + +}//namespace Ssl +#endif === added file 'src/ssl/Makefile.am' --- src/ssl/Makefile.am 1970-01-01 00:00:00 +0000 +++ src/ssl/Makefile.am 2011-03-27 15:19:37 +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 \ + ErrorDetail.cc \ + ErrorDetail.h + +libsslutil_la_SOURCES = \ + 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/libcompat.la ../../lib/libmiscutil.a +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 2011-03-26 14:09:22 +0000 @@ -0,0 +1,487 @@ +/* + * $Id$ + */ + +#include "config.h" +#include "util.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(xmalloc(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 2011-03-26 11:26:47 +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 2011-03-26 10:58:11 +0000 @@ -0,0 +1,176 @@ +/* + * $Id$ + */ +#include "config.h" +#include "Store.h" +#include "StoreEntryStream.h" +#include "ssl/context_storage.h" +#if HAVE_LIMITS +#include +#endif + +Ssl::CertificateStorageAction::CertificateStorageAction() + : CacheManagerAction("cached_ssl_cert", "Statistic of cached generated ssl certificates", 1, 1) +{} + +void Ssl::CertificateStorageAction::run (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); + CacheManager::GetInstance()->registerAction(new CertificateStorageAction); +} + +Ssl::GlobalContextStorage::~GlobalContextStorage() +{ + for (std::map::iterator i = storage.begin(); i != storage.end(); i++) { + delete i->second; + } +} + +void Ssl::GlobalContextStorage::addLocalStorage(IpAddress const & address, size_t size_of_store) +{ + assert(reconfiguring); + configureStorage.insert(std::pair(address, size_of_store)); +} + +Ssl::LocalContextStorage & Ssl::GlobalContextStorage::getLocalStorage(IpAddress 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 2011-03-26 11:12:45 +0000 @@ -0,0 +1,113 @@ +/* + * $Id$ + */ + +#ifndef SQUID_SSL_CONTEXT_STORAGE_H +#define SQUID_SSL_CONTEXT_STORAGE_H + +#if USE_SSL + +#include "SquidTime.h" +#include "CacheManager.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 CacheManagerAction +{ +public: + CertificateStorageAction(); + virtual void run (StoreEntry *sentry); +}; + +/** + * 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(IpAddress const & address, size_t size_of_store); + /// Return the local storage for the given listening address/port. + LocalContextStorage & getLocalStorage(IpAddress 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 2011-03-26 11:13:21 +0000 @@ -0,0 +1,177 @@ +/* + * $Id$ + */ + +#include "squid.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 += '\n' + 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 2011-03-25 18:10:22 +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 2011-03-25 18:10:22 +0000 @@ -0,0 +1,272 @@ +/* + * $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; + + // The second argument of the X509_NAME_add_entry_by_txt declared as + // "char *" on some OS. Use cn_name to avoid compile warnings. + static char cn_name[3] = "CN"; + if (!X509_NAME_add_entry_by_txt(name.get(), cn_name, 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; +} + +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 2011-03-25 18:10:22 +0000 @@ -0,0 +1,135 @@ +/* + * 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. + */ + +// Macro to be used to define the C++ equivalent function of an extern "C" +// function. The C++ function suffixed with the _cpp extension +#define CtoCpp1(function, argument) \ + extern "C++" inline void function ## _cpp(argument a) { \ + function(a); \ + } + +/** + \ingroup SslCrtdSslAPI + * TidyPointer typedefs for common SSL objects + */ +CtoCpp1(X509_free, X509 *) +typedef TidyPointer X509_Pointer; + +CtoCpp1(EVP_PKEY_free, EVP_PKEY *) +typedef TidyPointer EVP_PKEY_Pointer; + +CtoCpp1(BN_free, BIGNUM *) +typedef TidyPointer BIGNUM_Pointer; + +CtoCpp1(BIO_free, BIO *) +typedef TidyPointer BIO_Pointer; + +CtoCpp1(ASN1_INTEGER_free, ASN1_INTEGER *) +typedef TidyPointer ASN1_INT_Pointer; + +CtoCpp1(TXT_DB_free, TXT_DB *) +typedef TidyPointer TXT_DB_Pointer; + +CtoCpp1(X509_NAME_free, X509_NAME *) +typedef TidyPointer X509_NAME_Pointer; + +CtoCpp1(RSA_free, RSA *) +typedef TidyPointer RSA_Pointer; + +CtoCpp1(X509_REQ_free, X509_REQ *) +typedef TidyPointer X509_REQ_Pointer; + +CtoCpp1(SSL_CTX_free, SSL_CTX *) +typedef TidyPointer SSL_CTX_Pointer; + +CtoCpp1(SSL_free, SSL *) +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 2011-03-27 14:03:23 +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 = helperCreate("ssl_crtd"); + ssl_crtd->n_to_start = Ssl::TheConfig.ssl_crtd_n_running; + 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; + helperFree(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->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 2011-03-25 18:10:22 +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 2011-03-26 11:31:27 +0000 @@ -0,0 +1,356 @@ +/* + * $Id$ + */ + +#include "config.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 + */ + +#ifndef HELPER_INPUT_BUFFER +#define HELPER_INPUT_BUFFER 8192 +#endif + +int debug_enabled = 0; + +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; +} === modified file 'src/ssl_support.cc' --- src/ssl_support.cc 2010-02-28 19:49:30 +0000 +++ src/ssl_support.cc 2011-03-27 15:53:14 +0000 @@ -42,6 +42,9 @@ #include "fde.h" #include "acl/FilledChecklist.h" +#include "ssl/ErrorDetail.h" +#include "ssl_support.h" +#include "ssl/gadgets.h" /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals @@ -135,6 +138,68 @@ return rsa; } +int Ssl::asn1timeToString(ASN1_TIME *tm, char *buf, int len) +{ + BIO *bio; + int write = 0; + bio = BIO_new(BIO_s_mem()); + if (bio) { + if (ASN1_TIME_print(bio, tm)) + write = BIO_read(bio, buf, len-1); + BIO_free(bio); + } + buf[write]='\0'; + return write; +} + +int Ssl::matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)) +{ + assert(peer_cert); + + X509_NAME *name = X509_get_subject_name(peer_cert); + + for (int i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); i >= 0; i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) { + + ASN1_STRING *cn_data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); + + if ( (*check_func)(check_data, cn_data) == 0) + return 1; + } + + STACK_OF(GENERAL_NAME) * altnames; + altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL); + + if (altnames) { + int numalts = sk_GENERAL_NAME_num(altnames); + for (int i = 0; i < numalts; i++) { + const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i); + if (check->type != GEN_DNS) { + continue; + } + ASN1_STRING *cn_data = check->d.dNSName; + + if ( (*check_func)(check_data, cn_data) == 0) + return 1; + } + sk_GENERAL_NAME_pop_free(altnames, GENERAL_NAME_free); + } + return 0; +} + +static int check_domain( void *check_data, ASN1_STRING *cn_data) +{ + char cn[1024]; + const char *server = (const char *)check_data; + + if (cn_data->length > (int)sizeof(cn) - 1) { + return 1; //if does not fit our buffer just ignore + } + memcpy(cn, cn_data->data, cn_data->length); + cn[cn_data->length] = '\0'; + debugs(83, 4, "Verifying server domain " << server << " to certificate name/subjectAltName " << cn); + return matchDomainName(server, cn[0] == '*' ? cn + 1 : cn); +} + /// \ingroup ServerProtocolSSLInternal static int ssl_verify_cb(int ok, X509_STORE_CTX * ctx) @@ -146,6 +211,7 @@ void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain); ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check); X509 *peer_cert = ctx->cert; + Ssl::ssl_error_t error_no = SSL_ERROR_NONE; X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer, sizeof(buffer)); @@ -154,65 +220,22 @@ debugs(83, 5, "SSL Certificate signature OK: " << buffer); if (server) { - int i; - int found = 0; - char cn[1024]; - - STACK_OF(GENERAL_NAME) * altnames; - altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL); - if (altnames) { - int numalts = sk_GENERAL_NAME_num(altnames); - debugs(83, 3, "Verifying server domain " << server << " to certificate subjectAltName"); - for (i = 0; i < numalts; i++) { - const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i); - if (check->type != GEN_DNS) { - continue; - } - ASN1_STRING *data = check->d.dNSName; - if (data->length > (int)sizeof(cn) - 1) { - continue; - } - memcpy(cn, data->data, data->length); - cn[data->length] = '\0'; - debugs(83, 4, "Verifying server domain " << server << " to certificate name " << cn); - if (matchDomainName(server, cn[0] == '*' ? cn + 1 : cn) == 0) { - found = 1; - break; - } - } - } - - X509_NAME *name = X509_get_subject_name(peer_cert); - debugs(83, 3, "Verifying server domain " << server << " to certificate dn " << buffer); - - for (i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); i >= 0; i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) { - ASN1_STRING *data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); - - if (data->length > (int)sizeof(cn) - 1) - continue; - - memcpy(cn, data->data, data->length); - - cn[data->length] = '\0'; - - debugs(83, 4, "Verifying server domain " << server << " to certificate cn " << cn); - - if (matchDomainName(server, cn[0] == '*' ? cn + 1 : cn) == 0) { - found = 1; - break; - } - } + int found = Ssl::matchX509CommonNames(peer_cert, (void *)server, check_domain); if (!found) { debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << buffer << " does not match domainname " << server); ok = 0; + error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH; + if (check) Filled(check)->ssl_error = SQUID_X509_V_ERR_DOMAIN_MISMATCH; } } } else { + error_no = ctx->error; switch (ctx->error) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: debugs(83, 5, "SSL Certficate error: CA not known: " << buffer); break; @@ -235,6 +258,10 @@ debugs(83, 5, "SSL Certificate has invalid \'not after\' field: " << buffer); break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + debugs(83, 5, "SSL Certificate is self signed: " << buffer); + break; + default: debugs(83, 1, "SSL unknown certificate error " << ctx->error << " in " << buffer); break; @@ -255,6 +282,14 @@ if (!dont_verify_domain && server) {} + if (error_no != SSL_ERROR_NONE && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) { + Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(error_no, peer_cert); + if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) { + debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer); + delete errDetail; + } + } + return ok; } @@ -524,55 +559,6 @@ return fl; } -struct SslErrorMapEntry { - const char *name; - ssl_error_t value; -}; - -static SslErrorMapEntry TheSslErrorMap[] = { - { "SQUID_X509_V_ERR_DOMAIN_MISMATCH", SQUID_X509_V_ERR_DOMAIN_MISMATCH }, - { "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT", X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT }, - { "X509_V_ERR_CERT_NOT_YET_VALID", X509_V_ERR_CERT_NOT_YET_VALID }, - { "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD", X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD }, - { "X509_V_ERR_CERT_HAS_EXPIRED", X509_V_ERR_CERT_HAS_EXPIRED }, - { "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD", X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD }, - { "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY", X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY }, - { "SSL_ERROR_NONE", SSL_ERROR_NONE }, - { NULL, SSL_ERROR_NONE } -}; - -ssl_error_t -sslParseErrorString(const char *name) -{ - assert(name); - - for (int i = 0; TheSslErrorMap[i].name; ++i) { - if (strcmp(name, TheSslErrorMap[i].name) == 0) - return TheSslErrorMap[i].value; - } - - if (xisdigit(*name)) { - const long int value = strtol(name, NULL, 0); - if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX) - return value; - fatalf("Too small or too bug SSL error code '%s'", name); - } - - fatalf("Unknown SSL error name '%s'", name); - return SSL_ERROR_SSL; // not reached -} - -const char * -sslFindErrorString(ssl_error_t value) -{ - for (int i = 0; TheSslErrorMap[i].name; ++i) { - if (TheSslErrorMap[i].value == value) - return TheSslErrorMap[i].name; - } - - return NULL; -} - // "dup" function for SSL_get_ex_new_index("cert_err_check") static int ssl_dupAclChecklist(CRYPTO_EX_DATA *, CRYPTO_EX_DATA *, void *, @@ -592,6 +578,15 @@ delete static_cast(ptr); // may be NULL } +// "free" function for SSL_get_ex_new_index("ssl_error_detail") +static void +ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *, + int, long, void *) +{ + Ssl::ErrorDetail *errDetail = static_cast (ptr); + delete errDetail; +} + /// \ingroup ServerProtocolSSLInternal static void ssl_initialize(void) @@ -630,6 +625,7 @@ ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", NULL, NULL, NULL); ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL); ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist); + ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail); } /// \ingroup ServerProtocolSSLInternal @@ -1206,4 +1202,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 */ === modified file 'src/ssl_support.h' --- src/ssl_support.h 2010-02-12 12:22:25 +0000 +++ src/ssl_support.h 2011-03-27 15:41:39 +0000 @@ -36,6 +36,7 @@ #define SQUID_SSL_SUPPORT_H #include "config.h" +#include "ssl/gadgets.h" #if HAVE_OPENSSL_SSL_H #include #endif @@ -88,15 +89,50 @@ /// \ingroup ServerProtocolSSLAPI const char *sslGetUserCertificateChainPEM(SSL *ssl); -typedef int ssl_error_t; -ssl_error_t sslParseErrorString(const char *name); -const char *sslFindErrorString(ssl_error_t value); - -// 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 -#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH -#define SQUID_SSL_ERROR_MAX INT_MAX +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); + +/** + \ingroup ServerProtocolSSLAPI + * Iterates over the X509 common and alternate names and to see if matches with given data + * using the check_func. + \param peer_cert The X509 cert to check + \param check_data The data with which the X509 CNs compared + \param check_func The function used to match X509 CNs. The CN data passed as ASN1_STRING data + \return 1 if any of the certificate CN matches, 0 if none matches. + */ +int matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)); + +/** + \ingroup ServerProtocolSSLAPI + * Convert a given ASN1_TIME to a string form. + \param tm the time in ASN1_TIME form + \param buf the buffer to write the output + \param len write at most len bytes + \return The number of bytes written + */ +int asn1timeToString(ASN1_TIME *tm, char *buf, int len); + +} //namespace Ssl #ifdef _SQUID_MSWIN_ === modified file 'test-suite/testheaders.sh' --- test-suite/testheaders.sh 2009-11-22 20:09:33 +0000 +++ test-suite/testheaders.sh 2011-03-26 19:35:21 +0000 @@ -21,6 +21,7 @@ hdr=`echo "${f}" | sed s/.h//` if [ ! -e ./testHeaderDeps_${hdr}.o -o ${dir}/${f} -nt ./testHeaderDeps_${hdr}.o ]; then ( echo "/* This file is AUTOMATICALLY GENERATED. DO NOT ALTER IT */" + echo "#include \"config.h\"" echo "#include \"${dir}/${f}\" " echo "int main( int argc, char* argv[] ) { return 0; } " ) >./testHeaderDeps_${hdr}.cc