Jelajahi Sumber

Merge branch 'master' of github.com:tatsuhiro-t/aria2

Tatsuhiro Tsujikawa 11 tahun lalu
induk
melakukan
6c3a33d958

+ 91 - 1
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> eventPoll)
     asyncDNSServers_(nullptr),
 #endif // HAVE_ARES_ADDR_NODE
     dnsCache_(make_unique<DNSCache>()),
-    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<double> mm;
+    for (auto i = 0; i < 10; ++i) {
+      auto c = std::clock();
+      tokenExpected_ = make_unique<HMACResult>(
+          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<HMACResult>(
+        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

+ 16 - 0
src/DownloadEngine.h

@@ -74,6 +74,13 @@ class WebSocketSessionMan;
 } // namespace rpc
 #endif // ENABLE_WEBSOCKET
 
+namespace util {
+  namespace security {
+    class HMAC;
+    class HMACResult;
+  } // namespace security
+} // namespace util
+
 class DownloadEngine {
 private:
   void waitData();
@@ -173,6 +180,13 @@ private:
   // deleted.
   std::deque<std::unique_ptr<Command>> routineCommands_;
   std::deque<std::unique_ptr<Command>> commands_;
+
+  std::unique_ptr<util::security::HMAC> tokenHMAC_;
+  std::unique_ptr<util::security::HMACResult> tokenExpected_;
+  size_t tokenIterations_;
+
+  double tokenAverageDuration_;
+
 public:
   DownloadEngine(std::unique_ptr<EventPoll> eventPoll);
 
@@ -360,6 +374,8 @@ public:
     return webSocketSessionMan_;
   }
 #endif // ENABLE_WEBSOCKET
+
+  bool validateToken(const std::string& token);
 };
 
 } // namespace aria2

+ 2 - 1
src/Makefile.am

@@ -262,7 +262,8 @@ SRCS =  option_processing.cc\
 	ChunkChecksum.cc ChunkChecksum.h\
 	MessageDigest.cc MessageDigest.h\
 	MessageDigestImpl.h\
-	HashFuncEntry.h
+	HashFuncEntry.h \
+	util_security.cc util_security.h
 
 if ANDROID
 SRCS += android/android.c

+ 2 - 4
src/RpcMethod.cc

@@ -85,10 +85,8 @@ void RpcMethod::authorize(RpcRequest& req, DownloadEngine* e)
       }
     }
   }
-  if(e && e->getOption()->defined(PREF_RPC_SECRET)) {
-    if(token != e->getOption()->get(PREF_RPC_SECRET)) {
-      throw DL_ABORT_EX("Unauthorized");
-    }
+  if (!e || !e->validateToken(token)) {
+    throw DL_ABORT_EX("Unauthorized");
   }
 }
 

+ 190 - 0
src/util_security.cc

@@ -0,0 +1,190 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2014 Nils Maier
+ *
+ * This program 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; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+
+#include "util_security.h"
+
+#include "FatalException.h"
+#include "util.h"
+
+namespace {
+using namespace aria2;
+
+static inline size_t getBlockSize(const std::string& algorithm)
+{
+  std::string canon;
+  if (!MessageDigest::supports(algorithm)) {
+    goto err;
+  }
+  canon = MessageDigest::getCanonicalHashType(algorithm);
+
+  // Per RFC 6234
+  if (canon == "sha-1") {
+    return 64;
+  }
+  if (canon == "sha-224") {
+    return 64;
+  }
+  if (canon == "sha-256") {
+    return 64;
+  }
+  if (canon == "sha-384") {
+    return 128;
+  }
+  if (canon == "sha-512") {
+    // RFC 4868
+    return 128;
+  }
+
+err:
+  throw FATAL_EXCEPTION(fmt("HMAC does not support algorithm %s", algorithm.c_str()));
+}
+
+} // namespace
+
+namespace aria2 {
+namespace util {
+namespace security {
+
+bool compare(const unsigned char a, const unsigned char b)
+{
+  unsigned char rv = ~(a ^ b);
+  rv &= rv >> 4;
+  rv &= rv >> 2;
+  rv &= rv >> 1;
+  return rv;
+}
+
+bool compare(const uint8_t *a, const uint8_t *b, size_t length)
+{
+  unsigned char rv = 0;
+  for (size_t i = 0; i < length; ++i) {
+    rv |= a[i] ^ b[i];
+  }
+  return compare(rv, 0);
+}
+
+HMAC::HMAC(const std::string& algorithm, const char* secret, size_t length)
+  : blockSize_(getBlockSize(algorithm)), md_(MessageDigest::create(algorithm)),
+    clean_(false)
+{
+  ipad_.assign(blockSize_, 0x36);
+  opad_.assign(blockSize_, 0x5c);
+
+  if (length > blockSize_) {
+    md_->reset();
+    md_->update(secret, length);
+    auto hash = md_->digest();
+    for (size_t i = 0uL, e = hash.length(); i < e; ++i) {
+      ipad_.replace(i, 1, 1, hash[i]  ^ 0x36);
+      opad_.replace(i, 1, 1, hash[i]  ^ 0x5c);
+    }
+  }
+  else {
+    for (size_t i = 0uL, e = length; i < e; ++i) {
+      ipad_.replace(i, 1, 1, secret[i]  ^ 0x36);
+      opad_.replace(i, 1, 1, secret[i]  ^ 0x5c);
+    }
+  }
+  reset();
+}
+
+std::unique_ptr<HMAC> HMAC::createRandom(const std::string& algorithm)
+{
+  const auto len = MessageDigest::getDigestLength(algorithm);
+  if (len == 0) {
+    return nullptr;
+  }
+  auto buf = make_unique<char[]>(len);
+  generateRandomData((unsigned char*)buf.get(), len);
+  return create(algorithm, buf.get(), len);
+}
+
+bool HMAC::supports(const std::string& algorithm) {
+  if (!MessageDigest::supports(algorithm)) {
+    return false;
+  }
+  const auto canon = MessageDigest::getCanonicalHashType(algorithm);
+  return canon == "sha-1" || canon == "sha-224" || canon == "sha-256" ||
+    canon == "sha-384" ||  canon == "sha-512";
+}
+
+HMACResult PBKDF2(HMAC* hmac, const char* salt, size_t salt_length,
+                  size_t iterations, size_t key_length)
+{
+  if (!hmac) {
+    throw FATAL_EXCEPTION("hmac cannot be null");
+  }
+  const size_t hmac_length = hmac->length();
+  if (key_length == 0) {
+    key_length = hmac_length;
+  }
+  typedef union {
+    uint8_t bytes[4];
+    uint32_t count;
+  } counter_t;
+  counter_t counter, swapped;
+  counter.count = 1;
+
+  auto work = make_unique<char[]>(hmac_length);
+  char* p = work.get();
+  std::string rv;
+
+  hmac->reset();
+
+  while (key_length) {
+    hmac->update(salt, salt_length);
+    swapped.count = htonl(counter.count++);
+    hmac->update((char*)swapped.bytes, sizeof(swapped.bytes));
+
+    auto bytes = hmac->getResult().getBytes();
+    memcpy(p, bytes.data(), bytes.length());
+
+    for (size_t i = 1uL; i < iterations; ++i) {
+      hmac->update(bytes);
+      bytes = hmac->getResult().getBytes();
+      for (size_t j = 0uL; j < hmac_length; ++j) {
+        p[j] ^= bytes[j];
+      }
+    }
+    auto use = std::min(key_length, hmac_length);
+    rv.append(p, use);
+    key_length -= use;
+  }
+  return HMACResult(rv);
+}
+
+} // namespace security
+} // namespace util
+} // namespace aria2

+ 313 - 0
src/util_security.h

@@ -0,0 +1,313 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2014 Nils Maier
+ *
+ * This program 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; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+
+#ifndef D_UTIL_SECURITY_H
+#define D_UTIL_SECURITY_H
+
+#include <string>
+
+#include "common.h"
+#include "a2functional.h"
+#include "MessageDigest.h"
+
+namespace aria2 {
+namespace util {
+namespace security {
+
+/**
+ * Compare to bytes in constant time.
+ *
+ * @param a First byte.
+ * @param b Second byte.
+ * @return True, if both match, false otherwise.
+ */
+bool compare(const uint8_t a, const uint8_t b);
+
+/**
+ * Compare two byte arrays in constant time. The arrays must have the same
+ * length!
+ *
+ * @param a First byte array.
+ * @param b Second byte array.
+ * @return True, if both match, false otherwise.
+ */
+bool compare(const uint8_t *a, const uint8_t *b, size_t length);
+inline bool compare(const char *a, const char *b, size_t length)
+{
+  return compare(
+      reinterpret_cast<const uint8_t*>(a),
+      reinterpret_cast<const uint8_t*>(b),
+      length * sizeof(char)
+      );
+}
+
+/**
+ * HMAC Result wrapper. While it is still possible to get the raw result bytes,
+ * when using this wrapper it is ensured that constant-time comparison is used.
+ * Also, this wrapper makes it an error to compare results of a different
+ * length, helping to prevent logic errors either during development, or
+ * triggering in the wild. Therefore |.getBytes()| use should be avoided.
+ */
+class HMACResult {
+public:
+  HMACResult(const std::string& result)
+    : result_(result), len_(result.length())
+  {}
+
+  HMACResult(const char* result, size_t length)
+    : result_(result, length), len_(length)
+  {}
+
+  HMACResult(const HMACResult& other) :
+    result_(other.result_), len_(other.len_)
+  {}
+
+  HMACResult& operator=(const HMACResult& other) {
+    result_ = other.result_;
+    len_ = other.len_;
+    return *this;
+  }
+
+  bool operator == (const HMACResult& other) const
+  {
+    if (len_ != other.len_) {
+      throw std::domain_error("comparing different hmac is undefined");
+    }
+    return compare(result_.data(), other.result_.data(), len_);
+  }
+
+  bool operator != (const HMACResult& other) const
+  {
+    return !(*this == other);
+  }
+
+  size_t length() const
+  {
+    return len_;
+  }
+
+  const std::string& getBytes() const
+  {
+    return result_;
+  }
+
+private:
+  std::string result_;
+  size_t len_;
+};
+
+/**
+ * Implements HMAC-SHA* per RFC 6234. It supports the same cryptographic hash
+ * algorithms that MessageDigest supports, but at most the SHA-1, SHA-2
+ * algorithms as specified in the RFC.
+ */
+class HMAC {
+public:
+  /**
+   * Constructs a new HMAC. It is recommended to use the |create| or
+   * |createRandom| factory methods instead.
+   *
+   * @see create
+   * @see createRandom
+   */
+  HMAC(const std::string& algorithm, const char* secret, size_t length);
+
+  /**
+   * Creates a new instance using the specified algorithm and secret.
+   */
+  static std::unique_ptr<HMAC> create(
+      const std::string& algorithm, const std::string& secret)
+  {
+    return create(algorithm, secret.data(), secret.length());
+  }
+
+  /**
+   * Creates a new instance using the specified algorithm and secret.
+   */
+  static std::unique_ptr<HMAC> create(
+      const std::string& algorithm, const char* secret, size_t length)
+  {
+    if (!supports(algorithm)) {
+      return nullptr;
+    }
+    return make_unique<HMAC>(algorithm, secret, length);
+  }
+
+  /**
+   * Creates a new instance using sha-1 and the specified secret.
+   */
+  static std::unique_ptr<HMAC> create(const std::string& secret)
+  {
+    return create("sha-1", secret.data(), secret.length());
+  }
+
+  /**
+   * Creates a new instance using sha-1 and the specified secret.
+   */
+  static std::unique_ptr<HMAC> create(const char* secret, size_t length)
+  {
+    return create("sha-1", secret, length);
+  }
+
+  /**
+   * Creates a new instance using the specified algorithm and a random secret.
+   */
+  static std::unique_ptr<HMAC> createRandom(const std::string& algorithm);
+
+  /**
+   * Creates a new instance using sha-1 and a random secret.
+   */
+  static std::unique_ptr<HMAC> createRandom()
+  {
+    return createRandom("sha-1");
+  }
+
+  /**
+   * Tells if this implementation supports a specific hash algorithm.
+   */
+  static bool supports(const std::string& algorithm);
+
+  /**
+   * Tells the length in bytes of the resulting HMAC.
+   */
+  size_t length() const
+  {
+    return md_->getDigestLength();
+  }
+
+  /**
+   * Resets the instance, clearing the internal state. The instance can be
+   * re-used afterwards.
+   */
+  void reset()
+  {
+    if (clean_) {
+      return;
+    }
+    md_->reset();
+    md_->update(ipad_.data(), ipad_.length());
+    clean_ = true;
+  }
+
+  /**
+   * Updates the HMAC with new message data.
+   */
+  void update(const std::string& data)
+  {
+    md_->update(data.data(), data.length());
+    clean_ = false;
+  }
+
+  /**
+   * Updates the HMAC with new message data.
+   */
+  void update(const char* data, size_t length)
+  {
+    md_->update(data, length);
+    clean_ = false;
+  }
+
+  /**
+   * Returns the result. This can only be called once. After the call the
+   * internal state is reset and new HMACs can be computed with the same
+   * instance.
+   */
+  HMACResult getResult()
+  {
+    auto rv = md_->digest();
+    md_->reset();
+    md_->update(opad_.data(), opad_.length());
+    md_->update(rv.data(), rv.length());
+    rv = md_->digest();
+    clean_ = false;
+    reset();
+    return HMACResult(rv);
+  }
+
+  /**
+   * Returns the resulting HMAC of string in one go. You cannot mix call to this
+   * method with calls to update.
+   */
+  HMACResult getResult(const std::string& str)
+  {
+    reset();
+    update(str);
+    return getResult();
+  }
+
+  /**
+   * Returns the resulting HMAC of string in one go. You cannot mix call to this
+   * method with calls to update.
+   */
+  HMACResult getResult(const char* data, size_t len)
+  {
+    reset();
+    update(data, len);
+    return getResult();
+  }
+
+private:
+  const size_t blockSize_;
+  std::unique_ptr<MessageDigest> md_;
+  std::string ipad_, opad_;
+  bool clean_;
+};
+
+/**
+ * Create A PKBDF2-HMAC. See RFC 2898.
+ *
+ * Example:
+ *   result = PBKDF2(HMAC::create("password"), random_salt, salt_len, 1000);
+ */
+HMACResult PBKDF2(HMAC* hmac, const char* salt, size_t salt_length,
+                  size_t iterations, size_t key_length = 0);
+
+/**
+ * Create A PKBDF2-HMAC. See RFC 2898.
+ *
+ * Example:
+ *   result = PBKDF2(HMAC::create("password"), random_salt, 1000);
+ */
+inline HMACResult PBKDF2(HMAC* hmac, const std::string& salt, size_t iterations,
+                  size_t key_length = 0)
+{
+  return PBKDF2(hmac, salt.data(), salt.length(), iterations, key_length);
+}
+
+} // namespace security
+} // namespace util
+} // namespace aria2
+
+#endif // D_UTIL_SECURITY_H

+ 1 - 0
test/Makefile.am

@@ -16,6 +16,7 @@ aria2c_SOURCES = AllTest.cc\
 	DefaultBtProgressInfoFileTest.cc\
 	RequestGroupTest.cc\
 	UtilTest.cc\
+	UtilSecurityTest.cc\
 	UriListParserTest.cc\
 	HttpHeaderProcessorTest.cc\
 	RequestTest.cc\

+ 1 - 1
test/RpcMethodTest.cc

@@ -723,7 +723,7 @@ void RpcMethodTest::testChangeGlobalOption_withNotAllowedOption()
 void RpcMethodTest::testNoSuchMethod()
 {
   NoSuchMethodRpcMethod m;
-  auto res = m.execute(createReq("make.hamburger"), nullptr);
+  auto res = m.execute(createReq("make.hamburger"), e_.get());
   CPPUNIT_ASSERT_EQUAL(1, res.code);
   CPPUNIT_ASSERT_EQUAL(std::string("No such method: make.hamburger"),
                        getString(downcast<Dict>(res.param), "faultString"));

+ 419 - 0
test/UtilSecurityTest.cc

@@ -0,0 +1,419 @@
+#include "util.h"
+#include "util_security.h"
+
+#include <cppunit/extensions/HelperMacros.h>
+
+// Test vectors from RFC 6234
+enum {
+    SHA1HashSize = 20, SHA224HashSize = 28, SHA256HashSize = 32,
+    SHA384HashSize = 48, SHA512HashSize = 64,
+};
+
+static struct hmachash {
+    const char *keyarray[5];
+    int keylength[5];
+    const char *dataarray[5];
+    int datalength[5];
+    const char *resultarray[5];
+    int resultlength[5];
+} hmachashes[] = {
+  { /* 1 */ {
+      "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
+      "\x0b\x0b\x0b\x0b\x0b"
+    }, { 20 }, {
+      "\x48\x69\x20\x54\x68\x65\x72\x65" /* "Hi There" */
+    }, { 8 }, {
+      /* HMAC-SHA-1 */
+      "B617318655057264E28BC0B6FB378C8EF146BE00",
+      /* HMAC-SHA-224 */
+      "896FB1128ABBDF196832107CD49DF33F47B4B1169912BA4F53684B22",
+      /* HMAC-SHA-256 */
+      "B0344C61D8DB38535CA8AFCEAF0BF12B881DC200C9833DA726E9376C2E32"
+      "CFF7",
+      /* HMAC-SHA-384 */
+      "AFD03944D84895626B0825F4AB46907F15F9DADBE4101EC682AA034C7CEB"
+      "C59CFAEA9EA9076EDE7F4AF152E8B2FA9CB6",
+      /* HMAC-SHA-512 */
+      "87AA7CDEA5EF619D4FF0B4241A1D6CB02379F4E2CE4EC2787AD0B30545E1"
+      "7CDEDAA833B7D6B8A702038B274EAEA3F4E4BE9D914EEB61F1702E696C20"
+      "3A126854"
+    }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+      SHA384HashSize, SHA512HashSize }
+  },
+  { /* 2 */ {
+      "\x4a\x65\x66\x65" /* "Jefe" */
+    }, { 4 }, {
+      "\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61\x20\x77\x61\x6e\x74"
+      "\x20\x66\x6f\x72\x20\x6e\x6f\x74\x68\x69\x6e\x67\x3f"
+      /* "what do ya want for nothing?" */
+    }, { 28 }, {
+      /* HMAC-SHA-1 */
+      "EFFCDF6AE5EB2FA2D27416D5F184DF9C259A7C79",
+      /* HMAC-SHA-224 */
+      "A30E01098BC6DBBF45690F3A7E9E6D0F8BBEA2A39E6148008FD05E44",
+      /* HMAC-SHA-256 */
+      "5BDCC146BF60754E6A042426089575C75A003F089D2739839DEC58B964EC"
+      "3843",
+      /* HMAC-SHA-384 */
+      "AF45D2E376484031617F78D2B58A6B1B9C7EF464F5A01B47E42EC3736322"
+      "445E8E2240CA5E69E2C78B3239ECFAB21649",
+      /* HMAC-SHA-512 */
+      "164B7A7BFCF819E2E395FBE73B56E0A387BD64222E831FD610270CD7EA25"
+      "05549758BF75C05A994A6D034F65F8F0E6FDCAEAB1A34D4A6B4B636E070A"
+      "38BCE737"
+    }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+      SHA384HashSize, SHA512HashSize }
+  },
+  { /* 3 */
+    {
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa"
+    }, { 20 }, {
+      "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+      "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+      "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
+      "\xdd\xdd\xdd\xdd\xdd"
+    }, { 50 }, {
+      /* HMAC-SHA-1 */
+      "125D7342B9AC11CD91A39AF48AA17B4F63F175D3",
+      /* HMAC-SHA-224 */
+      "7FB3CB3588C6C1F6FFA9694D7D6AD2649365B0C1F65D69D1EC8333EA",
+      /* HMAC-SHA-256 */
+      "773EA91E36800E46854DB8EBD09181A72959098B3EF8C122D9635514CED5"
+      "65FE",
+      /* HMAC-SHA-384 */
+      "88062608D3E6AD8A0AA2ACE014C8A86F0AA635D947AC9FEBE83EF4E55966"
+      "144B2A5AB39DC13814B94E3AB6E101A34F27",
+      /* HMAC-SHA-512 */
+      "FA73B0089D56A284EFB0F0756C890BE9B1B5DBDD8EE81A3655F83E33B227"
+      "9D39BF3E848279A722C806B485A47E67C807B946A337BEE8942674278859"
+      "E13292FB"
+    }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+      SHA384HashSize, SHA512HashSize }
+  },
+  { /* 4 */ {
+      "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+      "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
+    }, { 25 }, {
+      "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+      "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+      "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
+      "\xcd\xcd\xcd\xcd\xcd"
+    }, { 50 }, {
+      /* HMAC-SHA-1 */
+      "4C9007F4026250C6BC8414F9BF50C86C2D7235DA",
+      /* HMAC-SHA-224 */
+      "6C11506874013CAC6A2ABC1BB382627CEC6A90D86EFC012DE7AFEC5A",
+      /* HMAC-SHA-256 */
+      "82558A389A443C0EA4CC819899F2083A85F0FAA3E578F8077A2E3FF46729"
+      "665B",
+      /* HMAC-SHA-384 */
+      "3E8A69B7783C25851933AB6290AF6CA77A9981480850009CC5577C6E1F57"
+      "3B4E6801DD23C4A7D679CCF8A386C674CFFB",
+      /* HMAC-SHA-512 */
+      "B0BA465637458C6990E5A8C5F61D4AF7E576D97FF94B872DE76F8050361E"
+      "E3DBA91CA5C11AA25EB4D679275CC5788063A5F19741120C4F2DE2ADEBEB"
+      "10A298DD"
+    }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+      SHA384HashSize, SHA512HashSize }
+  },
+  { /* 5 */ {
+      "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
+      "\x0c\x0c\x0c\x0c\x0c"
+    }, { 20 }, {
+      "Test With Truncation"
+    }, { 20 }, {
+      /* HMAC-SHA-1 */
+      "4C1A03424B55E07FE7F27BE1",
+      /* HMAC-SHA-224 */
+      "0E2AEA68A90C8D37C988BCDB9FCA6FA8",
+      /* HMAC-SHA-256 */
+      "A3B6167473100EE06E0C796C2955552B",
+      /* HMAC-SHA-384 */
+      "3ABF34C3503B2A23A46EFC619BAEF897",
+      /* HMAC-SHA-512 */
+      "415FAD6271580A531D4179BC891D87A6"
+    }, { 12, 16, 16, 16, 16 }
+  },
+  { /* 6 */ {
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+    }, { 80, 131 }, {
+      "Test Using Larger Than Block-Size Key - Hash Key First"
+    }, { 54 }, {
+      /* HMAC-SHA-1 */
+      "AA4AE5E15272D00E95705637CE8A3B55ED402112",
+      /* HMAC-SHA-224 */
+      "95E9A0DB962095ADAEBE9B2D6F0DBCE2D499F112F2D2B7273FA6870E",
+      /* HMAC-SHA-256 */
+      "60E431591EE0B67F0D8A26AACBF5B77F8E0BC6213728C5140546040F0EE3"
+      "7F54",
+      /* HMAC-SHA-384 */
+      "4ECE084485813E9088D2C63A041BC5B44F9EF1012A2B588F3CD11F05033A"
+      "C4C60C2EF6AB4030FE8296248DF163F44952",
+      /* HMAC-SHA-512 */
+      "80B24263C7C1A3EBB71493C1DD7BE8B49B46D1F41B4AEEC1121B013783F8"
+      "F3526B56D037E05F2598BD0FD2215D6A1E5295E64F73F63F0AEC8B915A98"
+      "5D786598"
+    }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+      SHA384HashSize, SHA512HashSize }
+  },
+  { /* 7 */ {
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+      "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+    }, { 80, 131 }, {
+      "Test Using Larger Than Block-Size Key and "
+      "Larger Than One Block-Size Data",
+      "\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74\x20"
+      "\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20"
+      "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65"
+      "\x20\x6b\x65\x79\x20\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67"
+      "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73"
+      "\x69\x7a\x65\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b"
+      "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65\x20"
+      "\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62"
+      "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20\x62\x79\x20\x74\x68"
+      "\x65\x20\x48\x4d\x41\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68"
+      "\x6d\x2e"
+      /* "This is a test using a larger than block-size key and a "
+          "larger than block-size data.  The key needs to be hashed "
+          "before being used by the HMAC algorithm." */
+    }, { 73, 152 }, {
+      /* HMAC-SHA-1 */
+      "E8E99D0F45237D786D6BBAA7965C7808BBFF1A91",
+      /* HMAC-SHA-224 */
+      "3A854166AC5D9F023F54D517D0B39DBD946770DB9C2B95C9F6F565D1",
+      /* HMAC-SHA-256 */
+      "9B09FFA71B942FCB27635FBCD5B0E944BFDC63644F0713938A7F51535C3A"
+      "35E2",
+      /* HMAC-SHA-384 */
+      "6617178E941F020D351E2F254E8FD32C602420FEB0B8FB9ADCCEBB82461E"
+      "99C5A678CC31E799176D3860E6110C46523E",
+      /* HMAC-SHA-512 */
+      "E37B6A775DC87DBAA4DFA9F96E5E3FFDDEBD71F8867289865DF5A32D20CD"
+      "C944B6022CAC3C4982B10D5EEB55C3E4DE15134676FB6DE0446065C97440"
+      "FA8C6A58"
+    }, { SHA1HashSize, SHA224HashSize, SHA256HashSize,
+      SHA384HashSize, SHA512HashSize }
+  }
+};
+
+namespace aria2 {
+
+class SecurityTest: public CppUnit::TestFixture {
+
+  CPPUNIT_TEST_SUITE(SecurityTest);
+  CPPUNIT_TEST(testCompareByte);
+  CPPUNIT_TEST(testCompareArray);
+  CPPUNIT_TEST(testHMAC);
+  CPPUNIT_TEST(testHMACRandom);
+  CPPUNIT_TEST(testPBKDF2);
+  CPPUNIT_TEST_SUITE_END();
+
+private:
+
+public:
+  void setUp() { }
+
+  void testCompareByte();
+  void testCompareArray();
+  void testHMAC();
+  void testHMACRandom();
+  void testPBKDF2();
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SecurityTest);
+
+void SecurityTest::testCompareByte()
+{
+  CPPUNIT_ASSERT(util::security::compare('a', 'a'));
+  CPPUNIT_ASSERT(util::security::compare('\0', '\0'));
+  CPPUNIT_ASSERT(util::security::compare(0xff, 0xff));
+
+  CPPUNIT_ASSERT(!util::security::compare('a', 'f'));
+  CPPUNIT_ASSERT(!util::security::compare('\xfe', 'f'));
+  CPPUNIT_ASSERT(!util::security::compare(0, 0xff));
+}
+
+void SecurityTest::testCompareArray()
+{
+  CPPUNIT_ASSERT(util::security::compare("", "", 0));
+  CPPUNIT_ASSERT(util::security::compare("a", "a", 1));
+  CPPUNIT_ASSERT(util::security::compare("a", "ab", 1));
+  CPPUNIT_ASSERT(util::security::compare("a\0b", "a\0b", 3));
+  CPPUNIT_ASSERT(util::security::compare("a\0b", "a\0b", 4)); // implicit null-termination
+  CPPUNIT_ASSERT(util::security::compare("a\0", "a\0b", 2));
+  CPPUNIT_ASSERT(util::security::compare("a\xff", "a\xff", 2));
+
+  CPPUNIT_ASSERT(!util::security::compare("a", "b", 1));
+  CPPUNIT_ASSERT(!util::security::compare("a", "bb", 1));
+  CPPUNIT_ASSERT(!util::security::compare("a\1b", "a\0b", 3));
+  CPPUNIT_ASSERT(!util::security::compare("a\1b", "a\0b", 4)); // implicit null-termination
+  CPPUNIT_ASSERT(!util::security::compare("a\4", "a\0b", 2));
+  CPPUNIT_ASSERT(!util::security::compare("a\0", "a\xff", 2));
+}
+
+static struct {
+  const char* hash;
+  const size_t idx;
+} hmacs[] = {
+  { "sha-1", 0 },
+  { "sha-224", 1 },
+  { "sha-256", 2 },
+  { "sha-384", 3 },
+  { "sha-512", 4 }
+};
+
+void SecurityTest::testHMAC()
+{
+  for (const auto& test : hmachashes) {
+    for (const auto& hmac : hmacs) {
+      if (!util::security::HMAC::supports(hmac.hash)) {
+        continue;
+      }
+#define SELECT(v) test.v[hmac.idx] ? test.v[hmac.idx] : test.v[1] ? test.v[1] : test.v[0]
+      auto key = SELECT(keyarray);
+      auto keylen = SELECT(keylength);
+      auto data = SELECT(dataarray);
+      auto datalen = SELECT(datalength);
+      auto result = SELECT(resultarray);
+      auto resultlen = SELECT(resultlength);
+#undef SELECT
+
+      auto h = util::security::HMAC::create(hmac.hash, key, keylen);
+      auto h2 = util::security::HMAC::create(hmac.hash, key, keylen - 1);
+      auto r = h->getResult(data, datalen);
+      auto hr = util::toUpper(util::toHex(r.getBytes()));
+      CPPUNIT_ASSERT(util::security::compare(hr.data(), result, resultlen));
+      CPPUNIT_ASSERT(r == r);
+      auto r2 = r;
+      CPPUNIT_ASSERT(r == r2);
+      CPPUNIT_ASSERT(r != h2->getResult(data, datalen));
+    }
+  }
+}
+
+void SecurityTest::testHMACRandom()
+{
+  auto h = util::security::HMAC::createRandom();
+  CPPUNIT_ASSERT(h->getResult("abc") == h->getResult("abc"));
+  auto r = h->getResult("def");
+  CPPUNIT_ASSERT(r == h->getResult("def"));
+
+  // Sanity check. At the very least 3 out of ten times 2 random hmacs should
+  // be different from each other and the 0-secret hmac.
+  size_t diff = 0;
+  for (auto i = 0; i < 10; ++i) {
+    auto h1 = util::security::HMAC::createRandom("sha-1");
+    auto h2 = util::security::HMAC::createRandom("sha-1");
+    auto h3 = util::security::HMAC::create("sha-1", "");
+    CPPUNIT_ASSERT(h1->getResult("abc") == h1->getResult("abc"));
+    CPPUNIT_ASSERT(h2->getResult("abc") == h2->getResult("abc"));
+    if (h1->getResult("abc") != h2->getResult("abc") &&
+        h1->getResult("abc") != h3->getResult("abc") &&
+        h2->getResult("abc") != h3->getResult("abc")) {
+      ++diff;
+    }
+  }
+  CPPUNIT_ASSERT(diff > 3);
+}
+
+static struct pbkdf2 {
+  char pass[32];
+  size_t pass_len;
+  char salt[40];
+  size_t salt_len;
+  size_t iterations;
+  unsigned char key[32];
+  size_t key_len;
+} pbkdf2s[] = {
+  {
+    "password", 8,
+    "salt", 4,
+    1,
+    { 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, 0xf3, 0xa9, 0xb5, 0x24,
+      0xaf, 0x60, 0x12, 0x06, 0x2f, 0xe0, 0x37, 0xa6 },
+    20
+  }, {
+    "password", 8,
+    "salt", 4,
+    1,
+    { 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, 0xf3, 0xa9, 0xb5, 0x24,
+      0xaf, 0x60, 0x12, 0x06, 0x2f, 0xe0, 0x37, 0xa6 },
+    0
+  }, {
+    "password", 8,
+    "salt", 4,
+    2,
+    { 0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, 0xcd, 0x1e, 0xd9, 0x2a,
+      0xce, 0x1d, 0x41, 0xf0, 0xd8, 0xde, 0x89, 0x57 },
+    20
+  }, {
+    "password", 8,
+    "salt", 4,
+    4096,
+    { 0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a, 0xbe, 0xad, 0x49, 0xd9,
+      0x26, 0xf7, 0x21, 0xd0, 0x65, 0xa4, 0x29, 0xc1 },
+    20
+  }, {
+    "password", 8,
+    "salt", 4,
+    16777216,
+    { 0xee, 0xfe, 0x3d, 0x61, 0xcd, 0x4d, 0xa4, 0xe4, 0xe9, 0x94, 0x5b, 0x3d,
+      0x6b, 0xa2, 0x15, 0x8c, 0x26, 0x34, 0xe9, 0x84 },
+    20,
+  }, {
+    "passwordPASSWORDpassword", 24,
+    "saltSALTsaltSALTsaltSALTsaltSALTsalt", 36,
+    4096,
+    { 0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b, 0x80, 0xc8, 0xd8, 0x36,
+      0x62, 0xc0, 0xe4, 0x4a, 0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70,
+      0x38 },
+    25,
+  }, {
+    "pass\0word",
+    9,
+    "sa\0lt",
+    5,
+    4096,
+    { 0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d, 0xcc, 0x37, 0xd7, 0xf0,
+      0x34, 0x25, 0xe0, 0xc3 },
+    16
+  }
+};
+
+void SecurityTest::testPBKDF2()
+{
+  for (const auto& test : pbkdf2s) {
+    auto h = util::security::HMAC::create(test.pass, test.pass_len);
+    auto r = util::security::PBKDF2(h.get(), test.salt, test.salt_len,
+                                    test.iterations, test.key_len);
+    const auto kl = test.key_len ? test.key_len : r.length();
+    auto e = util::security::HMACResult( (char*)test.key, kl);
+#if 0
+    printf("actual [%s]  expected [%s]\n",
+        util::toHex(r.getBytes()).c_str(),
+        util::toHex(e.getBytes()).c_str()
+        );
+#endif
+    CPPUNIT_ASSERT(r == e);
+    CPPUNIT_ASSERT(r.getBytes() == std::string((char*)test.key, kl));
+  }
+}
+
+} // namespace aria2