From 82dad90ff3fe1438ce5d578dcf50bc82e5c96e9b Mon Sep 17 00:00:00 2001 From: Nils Maier Date: Sat, 19 Apr 2014 19:10:06 +0200 Subject: Validate token using PBKDF2-HMAC-SHA1. This change should make token validation more resilient to: - timing attacks (constant time array compare) - brute-force/dictionary attacks (PBKDF2) Closes #220 --- src/DownloadEngine.cc | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) (limited to 'src/DownloadEngine.cc') diff --git a/src/DownloadEngine.cc b/src/DownloadEngine.cc index efa86950..36b67469 100644 --- a/src/DownloadEngine.cc +++ b/src/DownloadEngine.cc @@ -73,6 +73,15 @@ #ifdef ENABLE_WEBSOCKET # include "WebSocketSessionMan.h" #endif // ENABLE_WEBSOCKET +#include "Option.h" +#include "util_security.h" + +// Lower time limit for PBKDF2 operations in validateToken. +static const double kTokenTimeLower = 0.055; +// Upper time limit for PBKDF2 operations in validateToken. +static const double kTokenTimeUpper = 0.120; +// Sweet spot time for PBKDF2 operations in validateToken. +static const double kTokenTimeSweetspot = 0.072; namespace aria2 { @@ -102,7 +111,8 @@ DownloadEngine::DownloadEngine(std::unique_ptr eventPoll) asyncDNSServers_(nullptr), #endif // HAVE_ARES_ADDR_NODE dnsCache_(make_unique()), - option_(nullptr) + option_(nullptr), + tokenIterations_(5000) { unsigned char sessionId[20]; util::generateRandomKey(sessionId); @@ -631,4 +641,84 @@ void DownloadEngine::setWebSocketSessionMan } #endif // ENABLE_WEBSOCKET +bool DownloadEngine::validateToken(const std::string& token) +{ + using namespace util::security; + + if (!option_->defined(PREF_RPC_SECRET)) { + return true; + } + + if (!tokenHMAC_ || tokenAverageDuration_ > kTokenTimeUpper || + tokenAverageDuration_ < kTokenTimeLower) { + + // Setup our stuff. + if (tokenHMAC_) { + A2_LOG_INFO(fmt("Recalculating iterations because avg. duration is %.4f", + tokenAverageDuration_)); + } + + tokenHMAC_ = HMAC::createRandom(); + if (!tokenHMAC_) { + A2_LOG_ERROR("Failed to create HMAC"); + return false; + } + + // This should still be pretty fast on a modern system... Well, too fast + // with the initial 5000 iterations, and that is why we adjust it. + // XXX We should run this setup high priorty, so that other processes on the + // system don't mess up our results and let us underestimate the iterations. + std::deque mm; + for (auto i = 0; i < 10; ++i) { + auto c = std::clock(); + tokenExpected_ = make_unique( + PBKDF2(tokenHMAC_.get(), option_->get(PREF_RPC_SECRET), + tokenIterations_)); + mm.push_back((std::clock() - c) / (double)CLOCKS_PER_SEC); + } + std::sort(mm.begin(), mm.end()); + // Pop outliers. + mm.pop_front(); + mm.pop_back(); + mm.pop_back(); + auto duration = std::accumulate(mm.begin(), mm.end(), 0.0) / mm.size(); + + A2_LOG_INFO(fmt("Took us %.4f secs on average to perform PBKDF2 with %zu " + "iterations during setup", + duration, tokenIterations_)); + + // Adjust iterations so that an op takes about |kTokenTimeSpeetspot| sec, + // which would allow for a couple attempts per second (instead of + // potentially thousands without PBKDF2). + // We might overestimate the performance a bit, but should not perform + // worse than |kTokenTimeUpper| secs per attempt on a normally loaded system + // and no better than |kTokenTimeLower|. If this does not hold true anymore, + // the |tokenAverageDuration_| checks will force a re-calcuation. + tokenIterations_ *= kTokenTimeSweetspot / duration; + + auto c = std::clock(); + tokenExpected_ = make_unique( + PBKDF2(tokenHMAC_.get(), option_->get(PREF_RPC_SECRET), + tokenIterations_)); + duration = (std::clock() - c) / (double)CLOCKS_PER_SEC; + A2_LOG_INFO(fmt("Took us %.4f secs to perform PBKDF2 with %zu iterations", + duration, tokenIterations_)); + + // Seed average duration. + tokenAverageDuration_ = duration; + } + + auto c = std::clock(); + bool rv = *tokenExpected_ == PBKDF2(tokenHMAC_.get(), token, + tokenIterations_); + auto duration = (std::clock() - c) / (double)CLOCKS_PER_SEC; + A2_LOG_DEBUG(fmt("Took us %.4f secs to perform token compare with %zu " + "iterations", + duration, tokenIterations_)); + + // Update rolling hash. + tokenAverageDuration_ = tokenAverageDuration_ * 0.9 + duration * 0.1; + return rv; +} + } // namespace aria2 -- cgit