From 72cf2339edbb302b8b1dbe14c5475e8d2c3f62b1 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 12 Jul 2017 12:52:06 +0200 Subject: Introduce QHstsStore - the permanent store for HSTS policies The store is using QSettings under the hood. A user can enable/disable storing HSTS policies (via QNAM's setter method) and we take care of the rest - filling QHstsCache from the store, writing updated/observed targets, removing expired policies. Change-Id: I26e4a98761ddfe5005fedd18be56a6303fe7b35a Reviewed-by: Timur Pocheptsov Reviewed-by: Edward Welbourne --- src/network/access/qhsts.cpp | 61 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) (limited to 'src/network/access/qhsts.cpp') diff --git a/src/network/access/qhsts.cpp b/src/network/access/qhsts.cpp index ca9f3b977b..6a731afc2f 100644 --- a/src/network/access/qhsts.cpp +++ b/src/network/access/qhsts.cpp @@ -37,6 +37,7 @@ ** ****************************************************************************/ +#include "qhstsstore_p.h" #include "qhsts_p.h" #include "QtCore/private/qipaddress_p.h" @@ -80,14 +81,24 @@ void QHstsCache::updateFromHeaders(const QList> &h return; QHstsHeaderParser parser; - if (parser.parse(headers)) + if (parser.parse(headers)) { updateKnownHost(url.host(), parser.expirationDate(), parser.includeSubDomains()); + if (hstsStore) + hstsStore->synchronize(); + } } void QHstsCache::updateFromPolicies(const QVector &policies) { for (const auto &policy : policies) updateKnownHost(policy.host(), policy.expiry(), policy.includesSubDomains()); + + if (hstsStore && policies.size()) { + // These policies are coming either from store or from QNAM's setter + // function. As a result we can notice expired or new policies, time + // to sync ... + hstsStore->synchronize(); + } } void QHstsCache::updateKnownHost(const QUrl &url, const QDateTime &expires, @@ -97,6 +108,8 @@ void QHstsCache::updateKnownHost(const QUrl &url, const QDateTime &expires, return; updateKnownHost(url.host(), expires, includeSubDomains); + if (hstsStore) + hstsStore->synchronize(); } void QHstsCache::updateKnownHost(const QString &host, const QDateTime &expires, @@ -124,13 +137,20 @@ void QHstsCache::updateKnownHost(const QString &host, const QDateTime &expires, } knownHosts.insert(pos, hostName, newPolicy); + if (hstsStore) + hstsStore->addToObserved(newPolicy); return; } if (newPolicy.isExpired()) knownHosts.erase(pos); - else + else if (*pos != newPolicy) *pos = std::move(newPolicy); + else + return; + + if (hstsStore) + hstsStore->addToObserved(newPolicy); } bool QHstsCache::isKnownHost(const QUrl &url) const @@ -165,10 +185,15 @@ bool QHstsCache::isKnownHost(const QUrl &url) const while (nameToTest.fragment.size()) { auto const pos = knownHosts.find(nameToTest); if (pos != knownHosts.end()) { - if (pos.value().isExpired()) + if (pos.value().isExpired()) { knownHosts.erase(pos); - else if (!superDomainMatch || pos.value().includesSubDomains()) + if (hstsStore) { + // Inform our store that this policy has expired. + hstsStore->addToObserved(pos.value()); + } + } else if (!superDomainMatch || pos.value().includesSubDomains()) { return true; + } } const int dot = nameToTest.fragment.indexOf(QLatin1Char('.')); @@ -196,6 +221,34 @@ QVector QHstsCache::policies() const return values; } +void QHstsCache::setStore(QHstsStore *store) +{ + // Caller retains ownership of store, which must outlive this cache. + if (store != hstsStore) { + hstsStore = store; + + if (!hstsStore) + return; + + // First we augment our store with the policies we already know about + // (and thus the cached policy takes priority over whatever policy we + // had in the store for the same host, if any). + if (knownHosts.size()) { + const QVector observed(policies()); + for (const auto &policy : observed) + hstsStore->addToObserved(policy); + hstsStore->synchronize(); + } + + // Now we update the cache with anything we have not observed yet, but + // the store knows about (well, it can happen we synchronize again as a + // result if some policies managed to expire or if we add a new one + // from the store to cache): + const QVector restored(store->readPolicies()); + updateFromPolicies(restored); + } +} + // The parser is quite simple: 'nextToken' knowns exactly what kind of tokens // are valid and it will return false if something else was found; then // we immediately stop parsing. 'parseDirective' knows how these tokens can -- cgit v1.2.3