diff options
author | Peter Powell <petpow@saberuk.com> | 2017-10-29 05:58:16 +0000 |
---|---|---|
committer | Peter Powell <petpow@saberuk.com> | 2017-11-13 16:16:31 +0000 |
commit | 4e81454a4a6a4b67d1a5c76576a46b8e02d9044e (patch) | |
tree | 89262f1b38ea55a64e2a4dbbd36138e1379a2d9a | |
parent | d23c030c9a8fd58807438245a004e4aa5b7288ba (diff) |
Add the m_ircv3_sts module which implements the IRCv3 STS spec.
-rw-r--r-- | docs/conf/modules.conf.example | 21 | ||||
-rw-r--r-- | src/modules/m_ircv3_sts.cpp | 181 |
2 files changed, 202 insertions, 0 deletions
diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index cf79dafce..b573de903 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -1014,6 +1014,27 @@ #<module name="ircv3_invitenotify"> #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# IRCv3 Strict Transport Security module: Provides the sts IRCv3.2 +# extension which allows clients connecting insecurely to upgrade their +# connections to TLS. +#<module name="ircv3_sts"> +# +# If using the ircv3_sts module you MUST define a STS policy to send +# to clients using the <sts> tag. This tag takes the following +# attributes: +# +# host - A glob match for the SNI hostname to apply this policy to. +# duration - The amount of time that the policy lasts for. Defaults to +# approximately two months by default. +# port - The port on which TLS connections to the server are being +# accepted. You MUST have a CA-verified certificate on this +# port. Self signed certificates are not acceptable. +# preload - Whether client developers can include your certificate in +# preload lists. +# +# <sts host="*.example.com" duration="60d" port="6697" preload="yes"> + +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # Join flood module: Adds support for join flood protection +j X:Y. # Closes the channel for N seconds if X users join in Y seconds. #<module name="joinflood"> diff --git a/src/modules/m_ircv3_sts.cpp b/src/modules/m_ircv3_sts.cpp new file mode 100644 index 000000000..ee619903a --- /dev/null +++ b/src/modules/m_ircv3_sts.cpp @@ -0,0 +1,181 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2017 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "modules/cap.h" +#include "modules/ssl.h" + +class STSCap : public Cap::Capability +{ + private: + std::string host; + std::string plaintextpolicy; + std::string securepolicy; + + bool OnList(LocalUser* user) CXX11_OVERRIDE + { + // Don't send the cap to clients that only support cap-3.1. + if (GetProtocol(user) == Cap::CAP_LEGACY) + return false; + + // Plaintext listeners have their own policy. + SSLIOHook* sslhook = SSLIOHook::IsSSL(&user->eh); + if (!sslhook) + return true; + + // If no hostname has been provided for the connection, an STS persistence policy SHOULD NOT be advertised. + std::string snihost; + if (!sslhook->GetServerName(snihost)) + return false; + + // Before advertising an STS persistence policy over a secure connection, servers SHOULD verify whether the + // hostname provided by clients, for example, via TLS Server Name Indication (SNI), has been whitelisted by + // administrators in the server configuration. + return InspIRCd::Match(snihost, host, ascii_case_insensitive_map); + } + + bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE + { + // Clients MUST NOT request this capability with CAP REQ. Servers MAY reply with a CAP NAK message if a + // client requests this capability. + return false; + } + + const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE + { + return SSLIOHook::IsSSL(&user->eh) ? &securepolicy : &plaintextpolicy; + } + + public: + STSCap(Module* mod) + : Cap::Capability(mod, "sts") + { + } + + ~STSCap() + { + // TODO: Send duration=0 when STS vanishes. + } + + void SetPolicy(const std::string& newhost, unsigned long duration, unsigned int port, bool preload) + { + // To enforce an STS upgrade policy, servers MUST send this key to insecurely connected clients. Servers + // MAY send this key to securely connected clients, but it will be ignored. + std::string newplaintextpolicy("port="); + newplaintextpolicy.append(ConvToStr(port)); + + // To enforce an STS persistence policy, servers MUST send this key to securely connected clients. Servers + // MAY send this key to all clients, but insecurely connected clients MUST ignore it. + std::string newsecurepolicy("duration="); + newsecurepolicy.append(ConvToStr(duration)); + + // Servers MAY send this key to all clients, but insecurely connected clients MUST ignore it. + if (preload) + newsecurepolicy.append(",preload"); + + // Apply the new policy. + bool changed = false; + if (!irc::equals(host, newhost)) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing STS SNI hostname from \"%s\" to \"%s\"", host.c_str(), newhost.c_str()); + host = newhost; + changed = true; + } + + if (plaintextpolicy != newplaintextpolicy) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing plaintext STS policy from \"%s\" to \"%s\"", plaintextpolicy.c_str(), newplaintextpolicy.c_str()); + plaintextpolicy.swap(newplaintextpolicy); + changed = true; + } + + if (securepolicy != newsecurepolicy) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing secure STS policy from \"%s\" to \"%s\"", securepolicy.c_str(), newsecurepolicy.c_str()); + securepolicy.swap(newsecurepolicy); + changed = true; + } + + // If the policy has changed then notify all clients via cap-notify. + if (changed) + NotifyValueChange(); + } +}; + +class ModuleIRCv3STS : public Module +{ + private: + STSCap cap; + + // The IRCv3 STS specification requires that the server is listening using SSL using a valid certificate. + bool HasValidSSLPort(unsigned int port) + { + for (std::vector<ListenSocket*>::const_iterator iter = ServerInstance->ports.begin(); iter != ServerInstance->ports.end(); ++iter) + { + ListenSocket* ls = *iter; + + // Is this listener on the right port? + unsigned int saport = ls->bind_sa.port(); + if (saport != port) + continue; + + // Is this listener using SSL? + if (ls->bind_tag->getString("ssl").empty()) + continue; + + // TODO: Add a way to check if a listener's TLS cert is CA-verified. + return true; + } + return false; + } + + public: + ModuleIRCv3STS() + : cap(this) + { + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + // TODO: Multiple SNI profiles + ConfigTag* tag = ServerInstance->Config->ConfValue("sts"); + if (tag == ServerInstance->Config->EmptyTag) + throw ModuleException("You must define a STS policy!"); + + const std::string host = tag->getString("host"); + if (host.empty()) + throw ModuleException("<sts:host> must contain a hostname, at " + tag->getTagLocation()); + + int port = tag->getInt("port"); + if (!HasValidSSLPort(port)) + throw ModuleException("<sts:port> must be a TLS port, at " + tag->getTagLocation()); + + unsigned long duration = tag->getDuration("duration", 60*60*24*30*2, 0, LONG_MAX); + bool preload = tag->getBool("preload"); + cap.SetPolicy(host, duration, port, preload); + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Provides IRCv3 Strict Transport Security policy advertisement", VF_OPTCOMMON); + } +}; + +MODULE_INIT(ModuleIRCv3STS) |