]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/security/Session.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / security / Session.cc
index e8c24e8d1426da91387630a4f224f0698acebfa2..3499e980b74c4548522a0b6e1fcbb53cb65f7240 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
@@ -11,7 +11,9 @@
 #include "squid.h"
 #include "anyp/PortCfg.h"
 #include "base/RunnersRegistry.h"
+#include "CachePeer.h"
 #include "Debug.h"
+#include "fd.h"
 #include "fde.h"
 #include "ipc/MemMap.h"
 #include "security/Session.h"
 #define SSL_SESSION_ID_SIZE 32
 #define SSL_SESSION_MAX_SIZE 10*1024
 
+#if USE_OPENSSL
+static Ipc::MemMap *SessionCache = nullptr;
+static const char *SessionCacheName = "tls_session_cache";
+#endif
+
+#if USE_OPENSSL || USE_GNUTLS
+static int
+tls_read_method(int fd, char *buf, int len)
+{
+    auto session = fd_table[fd].ssl.get();
+    debugs(83, 3, "started for session=" << (void*)session);
+
+#if DONT_DO_THIS && USE_OPENSSL
+    if (!SSL_is_init_finished(session)) {
+        errno = ENOTCONN;
+        return -1;
+    }
+#endif
+
+#if USE_OPENSSL
+    int i = SSL_read(session, buf, len);
+#elif USE_GNUTLS
+    int i = gnutls_record_recv(session, buf, len);
+#endif
+
+    if (i > 0) {
+        debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
+        (void)VALGRIND_MAKE_MEM_DEFINED(buf, i);
+    }
+
+#if USE_OPENSSL
+    if (i > 0 && SSL_pending(session) > 0) {
+#elif USE_GNUTLS
+    if (i > 0 && gnutls_record_check_pending(session) > 0) {
+#endif
+        debugs(83, 2, "TLS FD " << fd << " is pending");
+        fd_table[fd].flags.read_pending = true;
+    } else
+        fd_table[fd].flags.read_pending = false;
+
+    return i;
+}
+
+static int
+tls_write_method(int fd, const char *buf, int len)
+{
+    auto session = fd_table[fd].ssl.get();
+    debugs(83, 3, "started for session=" << (void*)session);
+
+#if USE_OPENSSL
+    if (!SSL_is_init_finished(session)) {
+        errno = ENOTCONN;
+        return -1;
+    }
+#endif
+
+#if USE_OPENSSL
+    int i = SSL_write(session, buf, len);
+#elif USE_GNUTLS
+    int i = gnutls_record_send(session, buf, len);
+#endif
+
+    if (i > 0) {
+        debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
+    }
+    return i;
+}
+#endif
+
+#if USE_OPENSSL
+Security::SessionPointer
+Security::NewSessionObject(const Security::ContextPointer &ctx)
+{
+    Security::SessionPointer session(SSL_new(ctx.get()), [](SSL *p) {
+        debugs(83, 5, "SSL_free session=" << (void*)p);
+        SSL_free(p);
+    });
+    debugs(83, 5, "SSL_new session=" << (void*)session.get());
+    return session;
+}
+#endif
+
 static bool
-CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::Io::Type type, const char *squidCtx)
+CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::PeerOptions &opts, Security::Io::Type type, const char *squidCtx)
 {
     if (!Comm::IsConnOpen(conn)) {
         debugs(83, DBG_IMPORTANT, "Gone connection");
         return false;
     }
 
+#if USE_OPENSSL || USE_GNUTLS
+
     const char *errAction = "with no TLS/SSL library";
     int errCode = 0;
 #if USE_OPENSSL
-    Security::SessionPointer ssl(SSL_new(ctx.get()));
-    if (ssl) {
+    Security::SessionPointer session(Security::NewSessionObject(ctx));
+    if (!session) {
+        errCode = ERR_get_error();
+        errAction = "failed to allocate handle";
+        debugs(83, DBG_IMPORTANT, "TLS error: " << errAction << ": " << Security::ErrorString(errCode));
+    }
+#elif USE_GNUTLS
+    gnutls_session_t tmp;
+    errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
+    Security::SessionPointer session(tmp, [](gnutls_session_t p) {
+        debugs(83, 5, "gnutls_deinit session=" << (void*)p);
+        gnutls_deinit(p);
+    });
+    debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
+    if (errCode != GNUTLS_E_SUCCESS) {
+        session.reset();
+        errAction = "failed to initialize session";
+        debugs(83, DBG_IMPORTANT, "TLS error: " << errAction << ": " << Security::ErrorString(errCode));
+    }
+#endif
+
+    if (session) {
         const int fd = conn->fd;
+
+#if USE_OPENSSL
         // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
         if (BIO *bio = Ssl::Bio::Create(fd, type)) {
-            Ssl::Bio::Link(ssl.get(), bio); // cannot fail
+            Ssl::Bio::Link(session.get(), bio); // cannot fail
+#elif USE_GNUTLS
+        errCode = gnutls_credentials_set(session.get(), GNUTLS_CRD_CERTIFICATE, ctx.get());
+        if (errCode == GNUTLS_E_SUCCESS) {
+
+            opts.updateSessionOptions(session);
+
+            // NP: GnuTLS does not yet support the BIO operations
+            //     this does the equivalent of SSL_set_fd() for now.
+            gnutls_transport_set_int(session.get(), fd);
+            gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+#endif
 
-            fd_table[fd].ssl = ssl;
-            fd_table[fd].read_method = &ssl_read_method;
-            fd_table[fd].write_method = &ssl_write_method;
+            debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
+
+            fd_table[fd].ssl = session;
+            fd_table[fd].useBufferedIo(&tls_read_method, &tls_write_method);
             fd_note(fd, squidCtx);
             return true;
         }
+
+#if USE_OPENSSL
         errCode = ERR_get_error();
         errAction = "failed to initialize I/O";
-    } else {
-        errCode = ERR_get_error();
-        errAction = "failed to allocate handle";
-    }
+#elif USE_GNUTLS
+        errAction = "failed to assign credentials";
 #endif
+    }
+
     debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
            ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
+#endif
     return false;
 }
 
 bool
 Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
 {
-    return CreateSession(ctx, c, Security::Io::BIO_TO_SERVER, squidCtx);
+    if (!c || !c->getPeer())
+        return CreateSession(ctx, c, Security::ProxyOutgoingConfig, Security::Io::BIO_TO_SERVER, squidCtx);
+
+    auto *peer = c->getPeer();
+    return CreateSession(ctx, c, peer->secure, Security::Io::BIO_TO_SERVER, squidCtx);
 }
 
 bool
-Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
+Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, Security::PeerOptions &o, const char *squidCtx)
+{
+    return CreateSession(ctx, c, o, Security::Io::BIO_TO_CLIENT, squidCtx);
+}
+
+void
+Security::SessionSendGoodbye(const Security::SessionPointer &s)
 {
-    return CreateSession(ctx, c, Security::Io::BIO_TO_CLIENT, squidCtx);
+    debugs(83, 5, "session=" << (void*)s.get());
+    if (s) {
+#if USE_OPENSSL
+        SSL_shutdown(s.get());
+#elif USE_GNUTLS
+        gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
+#endif
+    }
 }
 
 bool
@@ -142,6 +282,108 @@ isTlsServer()
     return false;
 }
 
+#if USE_OPENSSL
+static int
+store_session_cb(SSL *ssl, SSL_SESSION *session)
+{
+    if (!SessionCache)
+        return 0;
+
+    debugs(83, 5, "Request to store SSL_SESSION");
+
+    SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
+
+    unsigned int idlen;
+    const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
+    // XXX: the other calls [to openForReading()] do not copy the sessionId to a char buffer, does this really have to?
+    unsigned char key[MEMMAP_SLOT_KEY_SIZE];
+    // Session ids are of size 32bytes. They should always fit to a
+    // MemMap::Slot::key
+    assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
+    memset(key, 0, sizeof(key));
+    memcpy(key, id, idlen);
+    int pos;
+    if (auto slotW = SessionCache->openForWriting(static_cast<const cache_key*>(key), pos)) {
+        int lenRequired = i2d_SSL_SESSION(session, nullptr);
+        if (lenRequired <  MEMMAP_SLOT_DATA_SIZE) {
+            unsigned char *p = static_cast<unsigned char *>(slotW->p);
+            lenRequired = i2d_SSL_SESSION(session, &p);
+            slotW->set(key, nullptr, lenRequired, squid_curtime + Config.SSL.session_ttl);
+        }
+        SessionCache->closeForWriting(pos);
+        debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired << " at pos " << pos);
+    }
+    return 0;
+}
+
+static void
+remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
+{
+    if (!SessionCache)
+        return;
+
+    debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
+    int pos;
+    if (SessionCache->openForReading(reinterpret_cast<const cache_key*>(sessionID), pos)) {
+        SessionCache->closeForReading(pos);
+        // TODO:
+        // What if we are not able to remove the session?
+        // Maybe schedule a job to remove it later?
+        // For now we just have an invalid entry in cache until will be expired
+        // The OpenSSL library will reject it when we try to use it
+        SessionCache->free(pos);
+    }
+}
+
+static SSL_SESSION *
+#if SQUID_USE_CONST_SSL_SESSION_CBID
+get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
+#else
+get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
+#endif
+{
+    if (!SessionCache)
+        return nullptr;
+
+    const unsigned int *p = reinterpret_cast<const unsigned int *>(sessionID);
+    debugs(83, 5, "Request to search for SSL_SESSION of len: " <<
+           len << p[0] << ":" << p[1]);
+
+    SSL_SESSION *session = nullptr;
+    int pos;
+    if (const auto slot = SessionCache->openForReading(static_cast<const cache_key*>(sessionID), pos)) {
+        if (slot->expire > squid_curtime) {
+            const unsigned char *ptr = slot->p;
+            session = d2i_SSL_SESSION(nullptr, &ptr, slot->pSize);
+            debugs(83, 5, "SSL_SESSION retrieved from cache at pos " << pos);
+        } else
+            debugs(83, 5, "SSL_SESSION in cache expired");
+        SessionCache->closeForReading(pos);
+    }
+
+    if (!session)
+        debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
+
+    // With the parameter copy the callback can require the SSL engine
+    // to increment the reference count of the SSL_SESSION object, Normally
+    // the reference count is not incremented and therefore the session must
+    // not be explicitly freed with SSL_SESSION_free(3).
+    *copy = 0;
+    return session;
+}
+
+void
+Security::SetSessionCacheCallbacks(Security::ContextPointer &ctx)
+{
+    if (SessionCache) {
+        SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
+        SSL_CTX_sess_set_new_cb(ctx.get(), store_session_cb);
+        SSL_CTX_sess_set_remove_cb(ctx.get(), remove_session_cb);
+        SSL_CTX_sess_set_get_cb(ctx.get(), get_session_cb);
+    }
+}
+#endif /* USE_OPENSSL */
+
 void
 initializeSessionCache()
 {
@@ -153,15 +395,15 @@ initializeSessionCache()
 
     int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
     if (IamWorkerProcess() && configuredItems)
-        Ssl::SessionCache = new Ipc::MemMap(Ssl::SessionCacheName);
+        SessionCache = new Ipc::MemMap(SessionCacheName);
     else {
-        Ssl::SessionCache = nullptr;
+        SessionCache = nullptr;
         return;
     }
 
     for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
         if (s->secure.staticContext)
-            Ssl::SetSessionCallbacks(s->secure.staticContext);
+            Security::SetSessionCacheCallbacks(s->secure.staticContext);
     }
 #endif
 }
@@ -187,8 +429,8 @@ RunnerRegistrationEntry(SharedSessionCacheRr);
 void
 SharedSessionCacheRr::useConfig()
 {
-#if USE_OPENSSL // while Ssl:: bits in use
-    if (Ssl::SessionCache || !isTlsServer()) //no need to configure ssl session cache.
+#if USE_OPENSSL
+    if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
         return;
 
     Ipc::Mem::RegisteredRunner::useConfig();
@@ -199,19 +441,19 @@ SharedSessionCacheRr::useConfig()
 void
 SharedSessionCacheRr::create()
 {
-    if (!isTlsServer()) //no need to configure ssl session cache.
+    if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
         return;
 
-#if USE_OPENSSL // while Ssl:: bits in use
+#if USE_OPENSSL
     if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
-        owner = Ipc::MemMap::Init(Ssl::SessionCacheName, items);
+        owner = Ipc::MemMap::Init(SessionCacheName, items);
 #endif
 }
 
 SharedSessionCacheRr::~SharedSessionCacheRr()
 {
     // XXX: Enable after testing to reduce at-exit memory "leaks".
-    // delete Ssl::SessionCache;
+    // delete SessionCache;
 
     delete owner;
 }