]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Port 3.2: Dynamic Ssl Certificate generation
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Sun, 17 Apr 2011 06:02:10 +0000 (18:02 +1200)
committerAmos Jeffries <squid3@treenet.co.nz>
Sun, 17 Apr 2011 06:02:10 +0000 (18:02 +1200)
Feature details at: http://wiki.squid-cache.org/Features/DynamicSslCert

40 files changed:
configure.ac
src/Makefile.am
src/ProtoPort.cc
src/ProtoPort.h
src/acl/SslErrorData.cc
src/acl/SslErrorData.h
src/base/Makefile.am
src/base/TidyPointer.h [new file with mode: 0644]
src/cache_cf.cc
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_request.cc
src/comm.cc
src/errorpage.cc
src/errorpage.h
src/fde.h
src/forward.cc
src/globals.h
src/helper.cc
src/helper.h
src/main.cc
src/ssl/Config.cc [new file with mode: 0644]
src/ssl/Config.h [new file with mode: 0644]
src/ssl/ErrorDetail.cc [new file with mode: 0644]
src/ssl/ErrorDetail.h [new file with mode: 0644]
src/ssl/Makefile.am [new file with mode: 0644]
src/ssl/certificate_db.cc [new file with mode: 0644]
src/ssl/certificate_db.h [new file with mode: 0644]
src/ssl/context_storage.cc [new file with mode: 0644]
src/ssl/context_storage.h [new file with mode: 0644]
src/ssl/crtd_message.cc [new file with mode: 0644]
src/ssl/crtd_message.h [new file with mode: 0644]
src/ssl/gadgets.cc [new file with mode: 0644]
src/ssl/gadgets.h [new file with mode: 0644]
src/ssl/helper.cc [new file with mode: 0644]
src/ssl/helper.h [new file with mode: 0644]
src/ssl/ssl_crtd.cc [new file with mode: 0644]
src/ssl_support.cc
src/ssl_support.h

index 78e037369dfa8add8c155b3bb25d65c23575a2e4..9718ea83ec0dfac0e31bbea256b301b0cb64677a 100644 (file)
@@ -1678,6 +1678,26 @@ if test "$use_dnsserver" = "yes"; then
     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 @@ AC_CHECK_HEADERS( \
        execinfo.h \
        fcntl.h \
        fnmatch.h \
+       fstream \
        getopt.h \
        glob.h \
        gnumalloc.h \
@@ -2272,12 +2293,16 @@ AC_CHECK_HEADERS( \
        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 @@ AC_CHECK_HEADERS( \
        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 @@ AC_CONFIG_FILES([\
        src/icmp/Makefile \
        src/ident/Makefile \
        src/ip/Makefile \
+       src/ssl/Makefile \
        contrib/Makefile \
        snmplib/Makefile \
        icons/Makefile \
index e1c0be4b6459f6a6d4a1f58352332ac0e377a47b..d9788927d4760923610a22641c97eb29ae6a9055 100644 (file)
@@ -112,8 +112,13 @@ SSL_ALL_SOURCE = \
 
 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 @@ squid_LDADD = \
        ${ADAPTATION_LIBS} \
        $(ESI_LIBS) \
        $(SSLLIB) \
+       $(SSL_LIBS) \
        -lmiscutil \
        $(EPOLL_LIBS) \
        $(MINGW_LIBS) \
@@ -715,6 +721,7 @@ DEFAULT_CONFIG_DIR  = $(sysconfdir)
 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_STORE_LOG       = $(DEFAULT_LOG_PREFIX)/store.log
 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 @@ cf.data: cf.data.pre Makefile
        -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 @@ cf.data: cf.data.pre Makefile
        -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 @@ tests_testCacheManager_LDADD = \
        $(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 @@ tests_testEvent_LDADD = \
        $(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 @@ tests_testEventLoop_LDADD = \
        $(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 @@ tests_test_http_range_LDADD = \
        $(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 @@ tests_testHttpRequest_LDADD = \
        $(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 @@ tests_testURL_LDADD = \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
+       $(SSL_LIBS) \
        $(XTRA_LIBS)
 tests_testURL_LDFLAGS = $(LIBADD_DL)
 tests_testURL_DEPENDENCIES =  $(top_builddir)/lib/libmiscutil.a \
index 999b0b17e283df516073348cbe12320a9ca08861..eb06ca7cbf69ea6293576ab7dfd2d8f54302032f 100644 (file)
@@ -6,11 +6,14 @@
 
 #include "squid.h"
 #include "ProtoPort.h"
+#if HAVE_LIMITS
+#include <limits>
+#endif
 
 http_port_list::http_port_list(const char *aProtocol)
 #if USE_SSL
         :
-        http(*this)
+    http(*this), dynamicCertMemCacheSize(std::numeric_limits<size_t>::max())
 #endif
 {
     protocol = xstrdup(aProtocol);
@@ -31,6 +34,7 @@ http_port_list::~http_port_list()
     safe_free(capath);
     safe_free(dhfile);
     safe_free(sslflags);
+    safe_free(sslContextSessionId);
 #endif
 }
 
index 15d6abf6486f49eeca520a51c46525ce8bcb773e..e280b670f95504ef7e43eef9f213c6f8ec904158 100644 (file)
@@ -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 @@ struct http_port_list {
     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);
index 3de564c1c8852c9a77f4cd47916765ffe2c47260..d565253f296c6579e6a6c93df5cd47e05cf06c47 100644 (file)
@@ -22,7 +22,7 @@ ACLSslErrorData::~ACLSslErrorData()
 }
 
 bool
-ACLSslErrorData::match(ssl_error_t toFind)
+ACLSslErrorData::match(Ssl::ssl_error_t toFind)
 {
     return values->findAndTune (toFind);
 }
@@ -30,17 +30,17 @@ ACLSslErrorData::match(ssl_error_t toFind)
 /* explicit instantiation required for some systems */
 /** \cond AUTODOCS-IGNORE */
 // AYJ: 2009-05-20 : Removing. clashes with template <int> instantiation for other ACLs.
-// template cbdata_type CbDataList<ssl_error_t>::CBDATA_CbDataList;
+// template cbdata_type CbDataList<Ssl::ssl_error_t>::CBDATA_CbDataList;
 /** \endcond */
 
 wordlist *
 ACLSslErrorData::dump()
 {
     wordlist *W = NULL;
-    CbDataList<ssl_error_t> *data = values;
+    CbDataList<Ssl::ssl_error_t> *data = values;
 
     while (data != NULL) {
-        wordlistAdd(&W, sslFindErrorString(data->element));
+        wordlistAdd(&W, Ssl::getErrorName(data->element));
         data = data->next;
     }
 
@@ -50,12 +50,12 @@ ACLSslErrorData::dump()
 void
 ACLSslErrorData::parse()
 {
-    CbDataList<ssl_error_t> **Tail;
+    CbDataList<Ssl::ssl_error_t> **Tail;
     char *t = NULL;
 
     for (Tail = &values; *Tail; Tail = &((*Tail)->next));
     while ((t = strtokFile())) {
-        CbDataList<ssl_error_t> *q = new CbDataList<ssl_error_t>(sslParseErrorString(t));
+        CbDataList<Ssl::ssl_error_t> *q = new CbDataList<Ssl::ssl_error_t>(Ssl::parseErrorString(t));
         *(Tail) = q;
         Tail = &q->next;
     }
@@ -67,7 +67,7 @@ ACLSslErrorData::empty() const
     return values == NULL;
 }
 
-ACLData<ssl_error_t> *
+ACLData<Ssl::ssl_error_t> *
 ACLSslErrorData::clone() const
 {
     /* Splay trees don't clone yet. */
index c26e17c383b258998ef31de83412bb5a91b04216..666f6d12d259fdfb1d5b78a73fe2f16de0dfe399 100644 (file)
@@ -9,8 +9,9 @@
 #include "acl/Data.h"
 #include "CbDataList.h"
 #include "ssl_support.h"
+#include "ssl/ErrorDetail.h"
 
-class ACLSslErrorData : public ACLData<ssl_error_t>
+class ACLSslErrorData : public ACLData<Ssl::ssl_error_t>
 {
 
 public:
@@ -20,13 +21,13 @@ public:
     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<ssl_error_t> *clone() const;
+    virtual ACLData<Ssl::ssl_error_t> *clone() const;
 
-    CbDataList<ssl_error_t> *values;
+    CbDataList<Ssl::ssl_error_t> *values;
 };
 
 MEMPROXY_CLASS_INLINE(ACLSslErrorData);
index 552de3c634c60ccccc3e92c7cc802582e8dcd6b4..4441d78ea5deb966f70f28e1d456a0a0c4c5743c 100644 (file)
@@ -12,4 +12,5 @@ libbase_la_SOURCES = \
        AsyncJobCalls.h \
        AsyncCallQueue.cc \
        AsyncCallQueue.h \
-       CbcPointer.h
+       CbcPointer.h \
+       TidyPointer.h
diff --git a/src/base/TidyPointer.h b/src/base/TidyPointer.h
new file mode 100644 (file)
index 0000000..4f50c85
--- /dev/null
@@ -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 <typename T, void (*DeAllocator)(T *t)> 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<T, DeAllocator> const &);
+    /// Forbidden assigment operator.
+    TidyPointer <T, DeAllocator> & operator = (TidyPointer<T, DeAllocator> 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<typename T> void tidyFree(T *p)
+{
+    xfree(p);
+}
+
+#endif // SQUID_BASE_TIDYPOINTER_H
index 2bed93d10cacbd2132d00c8a1212d21dd165c1b4..310ed112dc31c21947cf30b1a500cc54efab27e6 100644 (file)
 #include <glob.h>
 #endif
 
+#if HAVE_LIMITS
+#include <limits>
+#endif
+
 #if SQUID_SNMP
 #include "snmp.h"
 #endif
 #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 int parse_line(char *);
 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 @@ configDoConfigure(void)
 
             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 @@ configDoConfigure(void)
         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 @@ parseBytesLine(size_t * bptr, const char *units)
         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<size_t>(m * d / u);
+    if (static_cast<double>(*bptr) * 2 != m * d / u * 2)
+        self_destruct();
+}
+#endif
+
 static size_t
 parseBytesUnits(const char *unit)
 {
@@ -3191,8 +3251,16 @@ parse_http_port_option(http_port_list * s, char *token)
         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 @@ clone_http_port_list(http_port_list *a)
     char *crlfile;
     char *dhfile;
     char *sslflags;
-    char *sslcontext;
+    char *sslContextSessionId;
     SSL_CTX *sslContext;
 #endif
 
@@ -3415,8 +3483,14 @@ dump_generic_http_port(StoreEntry * e, const char *n, const http_port_list * s)
     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<size_t>::max())
+        storeAppendPrintf(e, "dynamic_cert_mem_cache_size=%lu%s\n", (unsigned long)s->dynamicCertMemCacheSize, B_BYTES_STR);
 #endif
 }
 
index b504918b3783e8ecaf1b941b55f2e4dce05c208f..ace7a45fa26ce2a64cb552b3746e8ac6427fd340 100644 (file)
@@ -1339,6 +1339,25 @@ DOC_START
 
           sslcontext=  SSL session ID context identifier.
 
+          generate-host-certificates[=<on|off>]
+                       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
@@ -1709,6 +1728,35 @@ DOC_START
        keys.
 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
  -----------------------------------------------------------------------------
index 9e66a24fbebd64f876ad60ac4e4179a2f1bf10f8..6e98d722cf99322b6d99ac7d4bc9bd9d1b096ba8 100644 (file)
 #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 <limits>
+#endif
+
 #if LINGERING_CLOSE
 #define comm_close comm_lingering_close
 #endif
@@ -3289,7 +3303,7 @@ httpsAccept(int sock, int newfd, ConnectionDetail *details,
             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;
@@ -3350,24 +3364,109 @@ httpsAccept(int sock, int newfd, ConnectionDetail *details,
     incoming_sockets_accepted++;
 }
 
-bool
-ConnStateData::switchToHttps()
+void
+ConnStateData::sslCrtdHandleReplyWrapper(void *data, char *reply)
 {
-    assert(!switchedToHttps_);
+    ConnStateData * state_data = (ConnStateData *)(data);
+    state_data->sslCrtdHandleReply(reply);
+}
 
-    //HTTPMSGLOCK(currentobject->http->request);
-    assert(areAllContextsForThisConnection());
-    freeAllContexts();
-    //currentobject->connIsFinished();
+void
+ConnStateData::sslCrtdHandleReply(const char * reply)
+{
+    if (!reply) {
+        debugs(1, 1, HERE << "\"ssl_crtd\" helper return <NULL> 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);
+}
 
-    debugs(33, 5, HERE << "converting FD " << fd << " to SSL");
+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;
@@ -3383,6 +3482,23 @@ ConnStateData::switchToHttps()
     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 */
 
 
@@ -3403,14 +3519,21 @@ clientHttpConnectionsOpen(void)
         }
 
 #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<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize);
+        }
 #endif
+#if USE_SSL_CRTD
+        Ssl::Helper::GetInstance();
+#endif //USE_SSL_CRTD
 
         enter_suid();
 
@@ -3462,7 +3585,7 @@ clientHttpsConnectionsOpen(void)
             continue;
         }
 
-        if (s->sslContext == NULL) {
+        if (!s->staticSslContext) {
             debugs(1, 1, "Ignoring https_port " << s->http.s <<
                    " due to SSL initialization failure.");
             continue;
@@ -3610,7 +3733,7 @@ clientAclChecklistCreate(const acl_access * acl, ClientHttpRequest * http)
 
 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;
index 3636317c456214d0bafd5fd21b94d07e444bf142..899541e59e090b2616f233f4c34ab8faaf14d92c 100644 (file)
@@ -259,7 +259,20 @@ public:
     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 @@ private:
     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
 };
index aee8a0cd7799f8f23537c3e0841fca4386ec7b88..56e935a27fff05b63d36d57611237172c22e0e28 100644 (file)
@@ -1183,7 +1183,7 @@ ClientHttpRequest::sslBumpEstablish(comm_err_t errflag)
         return;
     }
 
-    getConn()->switchToHttps();
+    getConn()->switchToHttps(request->GetHost());
 }
 
 void
index c74425e4a6dfe93f97f0d0203e4b412c20add16d..fdc1a54201826c3c09d669f3d09570405cc89e57 100644 (file)
@@ -1550,6 +1550,10 @@ comm_close_complete(int fd, void *data)
         F->ssl = NULL;
     }
 
+    if (F->dynamicSslContext) {
+        SSL_CTX_free(F->dynamicSslContext);
+        F->dynamicSslContext = NULL;
+    }
 #endif
     fd_close(fd);              /* update fdstat */
 
index cb5d50a07f25cc9c03ba53b5fe5ce641b05011b5..286e7d3fed18f6ab2c67ce578913d869607e514e 100644 (file)
@@ -508,6 +508,9 @@ errorStateFree(ErrorState * err)
     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 @@ ErrorState::Dump(MemBuf * mb)
 #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 @@ ErrorState::Convert(char token)
 
         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 @@ ErrorState::BuildHttpReply()
 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 @@ ErrorState::BuildContent()
         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 @@ ErrorState::BuildContent()
 
     assert((size_t)content->contentSize() == strlen(content->content()));
 
-#if USE_ERR_LOCALES
-    safe_free(freePage);
-#endif
-
     return content;
 }
index 653afaeda6f36f6ba1b295da65d9a889320ed959..880a4316222ccefea0c9c5ce93b69357268ed0b4 100644 (file)
@@ -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
@@ -98,10 +102,19 @@ private:
      */
     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 @@ public:
     char *request_hdrs;
     char *err_msg; /* Preformatted error message from the cache */
 
+#if USE_SSL
+    Ssl::ErrorDetail *detail;
+#endif
 private:
     CBDATA_CLASS2(ErrorState);
 };
index 3b503d2b51e0e903db4d9173b8c85ae3c590726f..eb4b6b5900e1347a4f1fa1039980c5755d1b0868 100644 (file)
--- a/src/fde.h
+++ b/src/fde.h
@@ -102,6 +102,7 @@ public:
     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 {
index c3a425e6525715e0be08b2d2c1da451d5a2c278b..deb3d4ec9b111458fd3c05b51a465dc6bc8507b0 100644 (file)
 #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 @@ FwdState::negotiateSSL(int fd)
             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) {
index 2acd0d50cdd6f04b937b632196b12e80bccea384..5145c5a4a4ecd6ab83d1adda6e500b3a347fc790 100644 (file)
@@ -172,6 +172,7 @@ extern "C" {
     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 */
index 31c89058de94dbf7bd2768ccb18a08b902ec4ffa..7d13a7c28f9022fb1e95fa1fa671818bbf73835e 100644 (file)
@@ -838,6 +838,55 @@ helperStatefulServerFree(int fd, void *data)
     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)
@@ -876,69 +925,29 @@ helperHandleRead(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, voi
         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;
+    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");
 
-            srv->dispatch_time = r->dispatch_time;
+            if (t > srv->rbuf && t[-1] == '\r')
+                t[-1] = '\0';
 
-            hlp->stats.avg_svc_time = Math::intAverage(hlp->stats.avg_svc_time, tvSubMsec(r->dispatch_time, current_time), hlp->stats.replies, REDIRECT_AV_FACTOR);
+            *t++ = '\0';
 
-            helperRequestFree(r);
-        } else {
-            debugs(84, 1, "helperHandleRead: unexpected reply on channel " <<
-                   i << " from " << hlp->id_name << " #" << srv->index + 1 <<
-                   " '" << srv->rbuf << "'");
+            if (hlp->concurrency) {
+                i = strtol(msg, &msg, 10);
 
-        }
+                while (*msg && xisspace(*msg))
+                    msg++;
+            }
 
-        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;
+            helperReturnBuffer(i, srv, hlp, msg, t);
         }
     }
 
index f860b9305e430f2d46454dc94bbd51f86eb13810..ecef39d5ff79fdc088a74f29de439b049ac5b81c 100644 (file)
@@ -73,6 +73,8 @@ struct _helper {
         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 {
index 941126d7806c125dcfd51f176851686c8acdab73..643073372dcf5b2c7448050e37e7eed50471b581 100644 (file)
 #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 @@ mainReconfigureStart(void)
 
     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 @@ mainReconfigureFinish(void *)
 
     idnsInit();
 #endif
+#if USE_SSL_CRTD
+    Ssl::Helper::GetInstance()->Init();
+#endif
 
     redirectInit();
     authenticateInit(&Config.authConfiguration);
@@ -1701,7 +1718,9 @@ SquidShutdown()
 
     idnsShutdown();
 #endif
-
+#if USE_SSL_CRTD
+    Ssl::Helper::GetInstance()->Shutdown();
+#endif
     redirectShutdown();
     externalAclShutdown();
     icpConnectionClose();
diff --git a/src/ssl/Config.cc b/src/ssl/Config.cc
new file mode 100644 (file)
index 0000000..17f6056
--- /dev/null
@@ -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
+}
diff --git a/src/ssl/Config.h b/src/ssl/Config.h
new file mode 100644 (file)
index 0000000..fd07e1f
--- /dev/null
@@ -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
diff --git a/src/ssl/ErrorDetail.cc b/src/ssl/ErrorDetail.cc
new file mode 100644 (file)
index 0000000..63a8e15
--- /dev/null
@@ -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()));
+    }
+}
diff --git a/src/ssl/ErrorDetail.h b/src/ssl/ErrorDetail.h
new file mode 100644 (file)
index 0000000..e3c6f50
--- /dev/null
@@ -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 <openssl/ssl.h>
+#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
diff --git a/src/ssl/Makefile.am b/src/ssl/Makefile.am
new file mode 100644 (file)
index 0000000..a87de47
--- /dev/null
@@ -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
diff --git a/src/ssl/certificate_db.cc b/src/ssl/certificate_db.cc
new file mode 100644 (file)
index 0000000..3f04781
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * $Id$
+ */
+
+#include "config.h"
+#include "util.h"
+#include "ssl/certificate_db.h"
+#if HAVE_FSTREAM
+#include <fstream>
+#endif
+#if HAVE_STDEXCEPT
+#include <stdexcept>
+#endif
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#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<char *>(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<char, tidyFree> 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<char, tidyFree> 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<char *>(tm->data), tm->length).c_str());
+    row.setValue(cnlFile, "unknown");
+    {
+        TidyPointer<char, tidyFree> 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<char *>(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<char *>(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<char *>(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;
+}
diff --git a/src/ssl/certificate_db.h b/src/ssl/certificate_db.h
new file mode 100644 (file)
index 0000000..f1cd252
--- /dev/null
@@ -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 <string>
+#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
diff --git a/src/ssl/context_storage.cc b/src/ssl/context_storage.cc
new file mode 100644 (file)
index 0000000..937e1ec
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * $Id$
+ */
+#include "config.h"
+#include "Store.h"
+#include "StoreEntryStream.h"
+#include "ssl/context_storage.h"
+#if HAVE_LIMITS
+#include <limits>
+#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<IpAddress, LocalContextStorage *>::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<IpAddress, LocalContextStorage *>::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<IpAddress, size_t>(address, size_of_store));
+}
+
+Ssl::LocalContextStorage & Ssl::GlobalContextStorage::getLocalStorage(IpAddress const & address)
+{
+    reconfigureFinish();
+    std::map<IpAddress, LocalContextStorage *>::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<IpAddress, LocalContextStorage *>::iterator i = storage.begin(); i != storage.end(); i++) {
+            std::map<IpAddress, size_t>::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<IpAddress, size_t>::iterator conf_i = configureStorage.begin(); conf_i != configureStorage.end(); conf_i++ ) {
+            if (storage.find(conf_i->first) == storage.end()) {
+                storage.insert(std::pair<IpAddress, LocalContextStorage *>(conf_i->first, new LocalContextStorage(conf_i->second)));
+            }
+        }
+    }
+}
+
+Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage;
diff --git a/src/ssl/context_storage.h b/src/ssl/context_storage.h
new file mode 100644 (file)
index 0000000..0b55888
--- /dev/null
@@ -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 <map>
+#endif
+#if HAVE_LIST
+#include <list>
+#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<Item *> Queue;
+    typedef Queue::iterator QueueIterator;
+
+    /// host_name:queue_item mapping for fast lookups by host name
+    typedef std::map<std::string, QueueIterator> Map;
+    typedef Map::iterator MapIterator;
+    typedef std::pair<std::string, QueueIterator> 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<IpAddress, size_t> configureStorage;
+    /// Map for storing all local ip address and their local storages.
+    std::map<IpAddress, LocalContextStorage *> storage;
+};
+
+/// Global cache for store all SSL server certificates.
+extern GlobalContextStorage TheGlobalContextStorage;
+} //namespace Ssl
+#endif // USE_SSL
+
+#endif // SQUID_SSL_CONTEXT_STORAGE_H
diff --git a/src/ssl/crtd_message.cc b/src/ssl/crtd_message.cc
new file mode 100644 (file)
index 0000000..9ae2808
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * $Id$
+ */
+
+#include "squid.h"
+#include "ssl/crtd_message.h"
+#if HAVE_CSTDLIB
+#include <cstdlib>
+#endif
+#if HAVE_CSTRING
+#include <cstring>
+#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<size_t>(buffer + len - current_pos) >= body_size - current_block.length())
+                              ? body_size - current_block.length()
+                              : static_cast<size_t>(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<char *>(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");
diff --git a/src/ssl/crtd_message.h b/src/ssl/crtd_message.h
new file mode 100644 (file)
index 0000000..6fade2a
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * $Id$
+ */
+
+#ifndef SQUID_SSL_CRTD_MESSAGE_H
+#define SQUID_SSL_CRTD_MESSAGE_H
+
+#if HAVE_STRING
+#include <string>
+#endif
+#if HAVE_MAP
+#include <map>
+#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:
+ *   <response/request code> <whitespace> <body length> <whitespace> <body>
+ */
+class CrtdMessage
+{
+public:
+    typedef std::map<std::string, std::string> 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
diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc
new file mode 100644 (file)
index 0000000..057d3c7
--- /dev/null
@@ -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<char *>(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);
+}
diff --git a/src/ssl/gadgets.h b/src/ssl/gadgets.h
new file mode 100644 (file)
index 0000000..11f2286
--- /dev/null
@@ -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 <openssl/ssl.h>
+#endif
+#if HAVE_OPENSSL_TXT_DB_H
+#include <openssl/txt_db.h>
+#endif
+#if HAVE_STRING
+#include <string>
+#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, X509_free_cpp> X509_Pointer;
+
+CtoCpp1(EVP_PKEY_free, EVP_PKEY *)
+typedef TidyPointer<EVP_PKEY, EVP_PKEY_free_cpp> EVP_PKEY_Pointer;
+
+CtoCpp1(BN_free, BIGNUM *)
+typedef TidyPointer<BIGNUM, BN_free_cpp> BIGNUM_Pointer;
+
+CtoCpp1(BIO_free, BIO *)
+typedef TidyPointer<BIO, BIO_free_cpp> BIO_Pointer;
+
+CtoCpp1(ASN1_INTEGER_free, ASN1_INTEGER *)
+typedef TidyPointer<ASN1_INTEGER, ASN1_INTEGER_free_cpp> ASN1_INT_Pointer;
+
+CtoCpp1(TXT_DB_free, TXT_DB *)
+typedef TidyPointer<TXT_DB, TXT_DB_free_cpp> TXT_DB_Pointer;
+
+CtoCpp1(X509_NAME_free, X509_NAME *)
+typedef TidyPointer<X509_NAME, X509_NAME_free_cpp> X509_NAME_Pointer;
+
+CtoCpp1(RSA_free, RSA *)
+typedef TidyPointer<RSA, RSA_free_cpp> RSA_Pointer;
+
+CtoCpp1(X509_REQ_free, X509_REQ *)
+typedef TidyPointer<X509_REQ, X509_REQ_free_cpp> X509_REQ_Pointer;
+
+CtoCpp1(SSL_CTX_free, SSL_CTX *)
+typedef TidyPointer<SSL_CTX, SSL_CTX_free_cpp> SSL_CTX_Pointer;
+
+CtoCpp1(SSL_free, SSL *)
+typedef TidyPointer<SSL, SSL_free_cpp> 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
diff --git a/src/ssl/helper.cc b/src/ssl/helper.cc
new file mode 100644 (file)
index 0000000..4d6f990
--- /dev/null
@@ -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);
+}
diff --git a/src/ssl/helper.h b/src/ssl/helper.h
new file mode 100644 (file)
index 0000000..278ce2a
--- /dev/null
@@ -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
diff --git a/src/ssl/ssl_crtd.cc b/src/ssl/ssl_crtd.cc
new file mode 100644 (file)
index 0000000..a357285
--- /dev/null
@@ -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 <cstring>
+#endif
+#if HAVE_SSTREAM
+#include <sstream>
+#endif
+#if HAVE_IOSTREAM
+#include <iostream>
+#endif
+#if HAVE_STDEXCEPT
+#include <stdexcept>
+#endif
+#if HAVE_STRING
+#include <string>
+#endif
+#if HAVE_GETOPT_H
+#include <getopt.h>
+#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:
+    <request code><whitespace><body_len><whitespace><body>
+    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(&current_time);
+#else
+    gettimeofday(&current_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<size_t>(m * d);
+    if (static_cast<long>(*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"
+        "<request code><whitespace><body_len><whitespace><body>\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;
+}
index ff1ed1158db91f1940f9deddd3323314b5e66ff5..974844bd7077aca9cf044a6e978445dfaebdc43c 100644 (file)
@@ -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 @@ ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
     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 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
     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 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
         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 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
             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 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
 
     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 @@ ssl_parse_flags(const char *flags)
     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 @@ ssl_freeAclChecklist(void *, void *ptr, CRYPTO_EX_DATA *,
     delete static_cast<ACLChecklist *>(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 <Ssl::ErrorDetail *>(ptr);
+    delete errDetail;
+}
+
 /// \ingroup ServerProtocolSSLInternal
 static void
 ssl_initialize(void)
@@ -630,6 +625,7 @@ ssl_initialize(void)
     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 @@ sslGetUserCertificateChainPEM(SSL *ssl)
     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 */
index 58b43217ca769b2d593227bad04db06b53985707..de3c907e4ee78a51b99935790a68cb4329005728 100644 (file)
@@ -36,6 +36,7 @@
 #define SQUID_SSL_SUPPORT_H
 
 #include "config.h"
+#include "ssl/gadgets.h"
 #if HAVE_OPENSSL_SSL_H
 #include <openssl/ssl.h>
 #endif
@@ -88,15 +89,50 @@ const char *sslGetUserCertificatePEM(SSL *ssl);
 /// \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);
+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);
 
-// 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
 
 #ifdef _SQUID_MSWIN_