diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h
index da90095a3..969eac1f7 100644
--- a/code/nel/include/nel/gui/group_html.h
+++ b/code/nel/include/nel/gui/group_html.h
@@ -793,7 +793,7 @@ namespace NLGUI
void doBrowseLocalFile(const std::string &filename);
// load remote content using either GET or POST
- void doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost = false, const SFormFields &formfields = SFormFields());
+ void doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost = false, const SFormFields &formfields = SFormFields());
// render html string as new browser page
bool renderHtmlString(const std::string &html);
@@ -861,7 +861,7 @@ namespace NLGUI
// BnpDownload system
void initBnpDownload();
void checkBnpDownload();
- bool addBnpDownload(const std::string &url, const std::string &action, const std::string &script, const std::string &md5sum);
+ bool addBnpDownload(std::string url, const std::string &action, const std::string &script, const std::string &md5sum);
std::string localBnpName(const std::string &url);
void releaseDownloads();
diff --git a/code/nel/include/nel/gui/http_hsts.h b/code/nel/include/nel/gui/http_hsts.h
new file mode 100644
index 000000000..2693461cd
--- /dev/null
+++ b/code/nel/include/nel/gui/http_hsts.h
@@ -0,0 +1,73 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef CL_HTTP_HSTS_H
+#define CL_HTTP_HSTS_H
+
+#include "nel/misc/types_nl.h"
+
+namespace NLGUI
+{
+ // ********************************************************************************
+ struct SHSTSObject
+ {
+ public:
+ SHSTSObject(uint64 expires = 0, bool includeSubDomains = false)
+ : Expires(expires)
+ , IncludeSubDomains(includeSubDomains)
+ { }
+
+ uint64 Expires;
+ bool IncludeSubDomains;
+ };
+
+ /**
+ * Keeping track of HSTS header
+ * \author Meelis Mägi (nimetu)
+ * \date 2017
+ */
+ class CStrictTransportSecurity
+ {
+ public:
+ typedef std::map THSTSObject;
+
+ static CStrictTransportSecurity* getInstance();
+ static void release();
+
+ public:
+ bool isSecureHost(const std::string &domain) const;
+
+ // ************************************************************************
+ void init(const std::string& fname);
+ void save();
+
+ void erase(const std::string &domain);
+ void set(const std::string &domain, uint64 expires, bool includeSubDomains);
+ bool get(const std::string &domain, SHSTSObject &hsts) const;
+ void setFromHeader(const std::string &domain, const std::string &header);
+
+ void serial(NLMISC::IStream& f);
+ private:
+ static CStrictTransportSecurity* instance;
+
+ ~CStrictTransportSecurity();
+
+ std::string _Filename;
+ THSTSObject _Domains;
+ };
+
+}
+#endif
diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp
index e99149cec..0c082ae58 100644
--- a/code/nel/src/gui/group_html.cpp
+++ b/code/nel/src/gui/group_html.cpp
@@ -46,6 +46,7 @@
#include "nel/misc/big_file.h"
#include "nel/gui/url_parser.h"
#include "nel/gui/http_cache.h"
+#include "nel/gui/http_hsts.h"
#include "nel/gui/curl_certificates.h"
using namespace std;
@@ -71,6 +72,26 @@ namespace NLGUI
CGroupHTML::SWebOptions CGroupHTML::options;
+ // Return URL with https is host is in HSTS list
+ static std::string upgradeInsecureUrl(const std::string &url)
+ {
+ if (toLower(url.substr(0, 7)) != "http://") {
+ return url;
+ }
+
+ CUrlParser uri(url);
+ if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){
+ return url;
+ }
+
+ #ifdef LOG_DL
+ nlwarning("HSTS url : '%s', using https", url.c_str());
+ #endif
+ uri.scheme = "https";
+
+ return uri.toString();
+ }
+
// Active cURL www transfer
class CCurlWWWData
{
@@ -149,6 +170,27 @@ namespace NLGUI
return "";
}
+ bool hasHSTSHeader()
+ {
+ // ignore header if not secure connection
+ if (toLower(Url.substr(0, 8)) != "https://")
+ {
+ return false;
+ }
+
+ return HeadersRecv.count("strict-transport-security") > 0;
+ }
+
+ const std::string getHSTSHeader()
+ {
+ if (hasHSTSHeader())
+ {
+ return HeadersRecv["strict-transport-security"];
+ }
+
+ return "";
+ }
+
public:
CURL *Request;
@@ -406,7 +448,7 @@ namespace NLGUI
// Add a image download request in the multi_curl
void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type)
{
- string finalUrl = getAbsoluteUrl(url);
+ string finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url));
// Search if we are not already downloading this url.
for(uint i = 0; i < Curls.size(); i++)
@@ -421,7 +463,7 @@ namespace NLGUI
}
}
- // use requested url for local name
+ // use requested url for local name (cache)
string dest = localImageName(url);
#ifdef LOG_DL
nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img);
@@ -467,8 +509,10 @@ namespace NLGUI
}
// Add a bnp download request in the multi_curl, return true if already downloaded
- bool CGroupHTML::addBnpDownload(const string &url, const string &action, const string &script, const string &md5sum)
+ bool CGroupHTML::addBnpDownload(string url, const string &action, const string &script, const string &md5sum)
{
+ url = upgradeInsecureUrl(getAbsoluteUrl(url));
+
// Search if we are not already downloading this url.
for(uint i = 0; i < Curls.size(); i++)
{
@@ -577,6 +621,12 @@ namespace NLGUI
#ifdef LOG_DL
nlwarning("(%s) web transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), _CurlWWW->Request, res, code, _CurlWWW->Url.size(), _CurlWWW->Url.c_str());
#endif
+ // save HSTS header from all requests regardless of HTTP code
+ if (res == CURLE_OK && _CurlWWW->hasHSTSHeader())
+ {
+ CUrlParser uri(_CurlWWW->Url);
+ CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader());
+ }
if (res != CURLE_OK)
{
@@ -663,6 +713,12 @@ namespace NLGUI
#endif
curl_multi_remove_handle(MultiCurl, it->data->Request);
+ // save HSTS header from all requests regardless of HTTP code
+ if (res == CURLE_OK && it->data->hasHSTSHeader())
+ {
+ CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, it->data->getHSTSHeader());
+ }
+
string tmpfile = it->dest + ".tmp";
if(res != CURLE_OK || r < 200 || r >= 300 || (!it->md5sum.empty() && (it->md5sum != getMD5(tmpfile).toString())))
{
@@ -5239,7 +5295,7 @@ namespace NLGUI
}
// ***************************************************************************
- void CGroupHTML::doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost, const SFormFields &formfields)
+ void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields)
{
// Stop previous request and remove content
stopBrowse ();
@@ -5253,6 +5309,8 @@ namespace NLGUI
else
setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait"));
+ url = upgradeInsecureUrl(url);
+
#if LOG_DL
nlwarning("(%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d",
_Id.c_str(), (_TrustedDomain ? "true" :"false"), url.c_str(), referer.c_str(), (doPost ? "true" : "false"), formfields.Values.size());
diff --git a/code/nel/src/gui/http_hsts.cpp b/code/nel/src/gui/http_hsts.cpp
new file mode 100644
index 000000000..980bdabda
--- /dev/null
+++ b/code/nel/src/gui/http_hsts.cpp
@@ -0,0 +1,245 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+#include "nel/gui/http_hsts.h"
+
+using namespace std;
+using namespace NLMISC;
+
+#ifdef DEBUG_NEW
+#define new DEBUG_NEW
+#endif
+
+namespace NLGUI {
+ CStrictTransportSecurity* CStrictTransportSecurity::instance = NULL;
+ CStrictTransportSecurity* CStrictTransportSecurity::getInstance()
+ {
+ if (!instance)
+ {
+ instance= new CStrictTransportSecurity();
+ }
+ return instance;
+ }
+
+ void CStrictTransportSecurity::release()
+ {
+ delete instance;
+ instance = NULL;
+ }
+
+ CStrictTransportSecurity::~CStrictTransportSecurity()
+ {
+ save();
+ }
+
+ // ************************************************************************
+ bool CStrictTransportSecurity::isSecureHost(const std::string &domain) const
+ {
+ SHSTSObject hsts;
+ if (get(domain, hsts))
+ {
+ time_t currentTime;
+ time(¤tTime);
+
+ return (hsts.Expires < currentTime);
+ }
+
+ return false;
+ }
+
+ // ************************************************************************
+ void CStrictTransportSecurity::erase(const std::string &domain)
+ {
+ if (_Domains.count(domain) > 0)
+ {
+ _Domains.erase(domain);
+ }
+ }
+
+ void CStrictTransportSecurity::set(const std::string &domain, uint64 expires, bool includeSubDomains)
+ {
+ if (expires == 0)
+ {
+ erase(domain);
+ return;
+ }
+
+ _Domains[domain].Expires = expires;
+ _Domains[domain].IncludeSubDomains = includeSubDomains;
+ }
+
+ bool CStrictTransportSecurity::get(const std::string &domain, SHSTSObject &hsts) const
+ {
+ if (domain.empty() || _Domains.empty())
+ return false;
+
+ if (_Domains.count(domain) > 0)
+ {
+ hsts = _Domains.at(domain);
+ return true;
+ }
+
+ size_t firstOf = domain.find_first_of(".");
+ size_t lastOf = domain.find_last_of(".");
+ while(firstOf != lastOf)
+ {
+ std::string tmp;
+ tmp = domain.substr(firstOf+1);
+ if (_Domains.count(tmp))
+ {
+ if (_Domains.at(tmp).IncludeSubDomains)
+ {
+ hsts = _Domains.at(tmp);
+ return true;
+ }
+
+ return false;
+ }
+
+ firstOf = domain.find_first_of(".", firstOf + 1);
+ }
+
+ return false;
+ }
+
+ void CStrictTransportSecurity::init(const std::string &fname)
+ {
+ _Domains.clear();
+ _Filename = fname;
+
+ if (_Filename.empty() || !CFile::fileExists(_Filename))
+ {
+ return;
+ }
+
+ CIFile in;
+ if (!in.open(_Filename))
+ {
+ nlwarning("Unable to open %s for reading", _Filename.c_str());
+ return;
+ }
+
+ serial(in);
+ }
+
+ void CStrictTransportSecurity::save()
+ {
+ if (_Filename.empty())
+ return;
+
+ if (_Domains.empty())
+ {
+ CFile::deleteFile(_Filename);
+ return;
+ }
+
+ COFile out;
+ if (!out.open(_Filename))
+ {
+ nlwarning("Unable to open %s for writing", _Filename.c_str());
+ return;
+ }
+
+ serial(out);
+ out.close();
+ }
+
+ void CStrictTransportSecurity::serial(NLMISC::IStream& f)
+ {
+ try
+ {
+ f.serialVersion(1);
+ // HSTS
+ f.serialCheck(NELID("STSH"));
+
+ if (f.isReading())
+ {
+ uint32 nbItems;
+ f.serial(nbItems);
+ for(uint32 k = 0; k < nbItems; ++k)
+ {
+ std::string domain;
+ f.serial(domain);
+ f.serial(_Domains[domain].Expires);
+ f.serial(_Domains[domain].IncludeSubDomains);
+ }
+ }
+ else
+ {
+ uint32 nbItems = _Domains.size();
+ f.serial(nbItems);
+ for (THSTSObject::iterator it = _Domains.begin(); it != _Domains.end(); ++it)
+ {
+ std::string domain(it->first);
+ f.serial(domain);
+ f.serial(_Domains[domain].Expires);
+ f.serial(_Domains[domain].IncludeSubDomains);
+ }
+ }
+ }
+ catch (...)
+ {
+ _Domains.clear();
+ nlwarning("Invalid HTST file format (%s)", _Filename.c_str());
+ }
+ }
+
+ // ***************************************************************************
+ void CStrictTransportSecurity::setFromHeader(const std::string &domain, const std::string &header)
+ {
+ // max-age=; includeSubdomains; preload;
+ std::vector elements;
+ NLMISC::splitString(toLower(header), ";", elements);
+ if (elements.empty()) return;
+
+ time_t currentTime;
+ time(¤tTime);
+
+ uint64 expire = 0;
+ bool includeSubDomains = false;
+
+ for(uint i=0; i< elements.size(); ++i)
+ {
+ std::string str(trim(elements[i]));
+ if (str.substr(0, 8) == "max-age=")
+ {
+ uint64 ttl;
+ if (fromString(str.substr(8), ttl))
+ {
+ if (ttl > 0)
+ {
+ expire = currentTime + ttl;
+ }
+ }
+ }
+ else if (str == "includesubdomains")
+ {
+ includeSubDomains = true;
+ }
+ }
+
+ if (expire == 0)
+ {
+ erase(domain);
+ }
+ else
+ {
+ set(domain, expire, includeSubDomains);
+ }
+ }
+
+}
diff --git a/code/ryzom/client/src/init.cpp b/code/ryzom/client/src/init.cpp
index 900f3c2c1..244ef3384 100644
--- a/code/ryzom/client/src/init.cpp
+++ b/code/ryzom/client/src/init.cpp
@@ -69,6 +69,7 @@
#include "interface_v3/sbrick_manager.h"
#include "nel/gui/widget_manager.h"
#include "nel/gui/http_cache.h"
+#include "nel/gui/http_hsts.h"
//
#include "gabarit.h"
#include "hair_set.h"
@@ -1370,6 +1371,8 @@ void prelogInit()
CHttpCache::getInstance()->setCacheIndex("cache/cache.index");
CHttpCache::getInstance()->init();
+ CStrictTransportSecurity::getInstance()->init("save/hsts-list.save");
+
// Register the reflected classes
registerInterfaceElements();
diff --git a/code/ryzom/client/src/release.cpp b/code/ryzom/client/src/release.cpp
index a1f3a29c2..ff569b22a 100644
--- a/code/ryzom/client/src/release.cpp
+++ b/code/ryzom/client/src/release.cpp
@@ -95,6 +95,7 @@
#include "nel/gui/lua_manager.h"
#include "item_group_manager.h"
#include "nel/gui/http_cache.h"
+#include "nel/gui/http_hsts.h"
///////////
// USING //
@@ -688,6 +689,7 @@ void release()
CViewRenderer::release();
CIXml::releaseLibXml();
CHttpCache::release();
+ CStrictTransportSecurity::release();
#if FINAL_VERSION
// openURL ("http://www.ryzomcore.org/exit/");