Bladeren bron

Internal implementation of DHKeyExchange

Reusing a bignum (well, unsigned very-long) implementation I had lying
around for years and just cleaned up a bit and brought to C++11 land.

It might not be the most performant implementation, but it shoud be fast
enough for our purposes and will go a long way of removing gcrypt,
nettle, gmp, openssl dependencies when using AppleTLS and WinTLS
(upcoming).
Nils Maier 12 jaren geleden
bovenliggende
commit
e854463b2d
6 gewijzigde bestanden met toevoegingen van 528 en 6 verwijderingen
  1. 16 5
      configure.ac
  2. 3 1
      src/DHKeyExchange.h
  3. 121 0
      src/InternalDHKeyExchange.cc
  4. 73 0
      src/InternalDHKeyExchange.h
  5. 4 0
      src/Makefile.am
  6. 311 0
      src/bignum.h

+ 16 - 5
configure.ac

@@ -506,14 +506,25 @@ else
   AM_CONDITIONAL([ENABLE_MESSAGE_DIGEST], false)
 fi
 
-if test "x$have_libnettle" = "xyes" && test "x$have_libgmp" = "xyes" ||
-   test "x$have_libgcrypt" = "xyes" || test "x$have_openssl" = "xyes"; then
-  enable_bignum=yes
+if test "x$have_libgmp" = "xyes" ||
+   test "x$have_libgcrypt" = "xyes" ||
+   test "x$have_openssl" = "xyes"; then
+  AM_CONDITIONAL([USE_INTERNAL_BIGNUM], false)
+else
+  AC_DEFINE([USE_INTERNAL_BIGNUM], [1], [Define to 1 if internal BIGNUM support is enabled.])
+  AM_CONDITIONAL([USE_INTERNAL_BIGNUM], true)
+fi
+
+if test "x$have_libnettle" = "xyes" ||
+   test "x$have_libgcrypt" = "xyes" ||
+   test "x$have_openssl" = "xyes"; then
+  have_rc4=yes
 fi
 
+
 if test "x$enable_bittorrent" = "xyes" &&
-   test "x$enable_message_digest" = "xyes" &&
-   test "x$enable_bignum" = "xyes"; then
+   test "x$have_rc4" = "xyes" &&
+   test "x$enable_message_digest" = "xyes"; then
   AC_DEFINE([ENABLE_BITTORRENT], [1],
             [Define to 1 if BitTorrent support is enabled.])
   AM_CONDITIONAL([ENABLE_BITTORRENT], true)

+ 3 - 1
src/DHKeyExchange.h

@@ -36,7 +36,9 @@
 #define D_DH_KEY_EXCHANGE_H
 
 #include "common.h"
-#ifdef HAVE_LIBGMP
+#ifdef USE_INTERNAL_BIGNUM
+# include "InternalDHKeyExchange.h"
+#elif HAVE_LIBGMP
 # include "LibgmpDHKeyExchange.h"
 #elif HAVE_LIBGCRYPT
 # include "LibgcryptDHKeyExchange.h"

+ 121 - 0
src/InternalDHKeyExchange.cc

@@ -0,0 +1,121 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 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 "InternalDHKeyExchange.h"
+
+#include <cstring>
+
+#include "DlAbortEx.h"
+#include "LogFactory.h"
+#include "fmt.h"
+#include "util.h"
+
+namespace aria2 {
+
+void DHKeyExchange::init(const unsigned char* prime, size_t primeBits,
+                         const unsigned char* generator, size_t privateKeyBits)
+{
+  std::string pr = reinterpret_cast<const char*>(prime);
+  if (pr.length() % 2) {
+    pr = "0" + pr;
+  }
+  pr = util::fromHex(pr.begin(), pr.end());
+  if (pr.empty()) {
+    throw DL_ABORT_EX("No valid prime supplied");
+  }
+  prime_ = n(pr.c_str(), pr.length());
+
+  std::string gen = reinterpret_cast<const char*>(generator);
+  if (gen.length() % 2) {
+    gen = "0" + gen;
+  }
+  gen = util::fromHex(gen.begin(), gen.end());
+  if (gen.empty()) {
+    throw DL_ABORT_EX("No valid generator supplied");
+  }
+  generator_ = n(gen.c_str(), gen.length());
+
+  size_t pbytes = (privateKeyBits + 7) / 8;
+  unsigned char buf[pbytes];
+  util::generateRandomData(buf, pbytes);
+  privateKey_ = n(reinterpret_cast<char*>(buf), pbytes);
+
+  keyLength_ = (primeBits + 7) / 8;
+}
+
+void DHKeyExchange::generatePublicKey()
+{
+  publicKey_ = generator_.mul_mod(privateKey_, prime_);
+}
+
+size_t DHKeyExchange::getPublicKey(unsigned char* out, size_t outLength) const
+{
+  if (outLength < keyLength_) {
+    throw DL_ABORT_EX(fmt("Insufficient buffer for public key. expect:%lu, actual:%lu",
+                          static_cast<unsigned long>(keyLength_),
+                          static_cast<unsigned long>(outLength)));
+  }
+  publicKey_.binary(reinterpret_cast<char*>(out), outLength);
+  return keyLength_;
+}
+
+void DHKeyExchange::generateNonce(unsigned char* out, size_t outLength) const
+{
+  util::generateRandomData(out, outLength);
+}
+
+size_t DHKeyExchange::computeSecret(unsigned char* out, size_t outLength,
+                                    const unsigned char* peerPublicKeyData,
+                                    size_t peerPublicKeyLength) const
+{
+  if (outLength < keyLength_) {
+    throw DL_ABORT_EX(fmt("Insufficient buffer for secret. expect:%lu, actual:%lu",
+                          static_cast<unsigned long>(keyLength_),
+                          static_cast<unsigned long>(outLength)));
+  }
+  if (prime_.length() < peerPublicKeyLength) {
+    throw DL_ABORT_EX(fmt("peer public key overflows bignum. max:%lu, actual:%lu",
+                          static_cast<unsigned long>(prime_.length()),
+                          static_cast<unsigned long>(peerPublicKeyLength)));
+  }
+
+  n peerKey(reinterpret_cast<const char*>(peerPublicKeyData), peerPublicKeyLength);
+  n secret = peerKey.mul_mod(privateKey_, prime_);
+  secret.binary(reinterpret_cast<char*>(out), outLength);
+
+  return outLength;
+}
+
+} // namespace aria2

+ 73 - 0
src/InternalDHKeyExchange.h

@@ -0,0 +1,73 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2013 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_INTERNAL_DH_KEY_EXCHANGE_H
+#define D_INTERNAL_DH_KEY_EXCHANGE_H
+
+#include "common.h"
+#include "bignum.h"
+
+namespace aria2 {
+
+class DHKeyExchange {
+private:
+  typedef bignum::ulong<1024> n; // aka max. 8096 bits
+  size_t keyLength_;
+  n prime_;
+  n generator_;
+  n privateKey_;
+  n publicKey_;
+
+public:
+  DHKeyExchange() : keyLength_(0) {}
+  ~DHKeyExchange() {}
+
+  void init(const unsigned char* prime, size_t primeBits,
+            const unsigned char* generator, size_t privateKeyBits);
+
+  void generatePublicKey();
+
+  size_t getPublicKey(unsigned char* out, size_t outLength) const;
+
+  void generateNonce(unsigned char* out, size_t outLength) const;
+
+  size_t computeSecret(unsigned char* out, size_t outLength,
+                       const unsigned char* peerPublicKeyData,
+                       size_t peerPublicKeyLength) const;
+};
+
+} // namespace aria2
+
+#endif // D_INTERNAL_DH_KEY_EXCHANGE_H

+ 4 - 0
src/Makefile.am

@@ -333,6 +333,10 @@ if USE_WINDOWS_MD
 SRCS += WinMessageDigestImpl.cc
 endif # USE_WINDOWS_MD
 
+if USE_INTERNAL_BIGNUM
+SRCS += InternalDHKeyExchange.cc InternalDHKeyExchange.h bignum.h
+endif
+
 if USE_INTERNAL_MD
 SRCS += InternalMessageDigestImpl.cc sha1.c md5.c
 endif # USE_WINDOWS_MD

+ 311 - 0
src/bignum.h

@@ -0,0 +1,311 @@
+/***
+ * This software was written by Nils Maier. No copyright is claimed, and the
+ * software is hereby placed in the public domain.
+ *
+ * In case this attempt to disclaim copyright and place the software in the
+ * public domain is deemed null and void in your jurisdiction, then the
+ * software is Copyright 2004,2013 Nils Maier and it is hereby released to the
+ * general public under the following terms:
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ */
+
+#ifndef BIGNUM_H
+#define BIGNUM_H
+
+#include <algorithm>
+#include <memory>
+#include <stdint.h>
+
+namespace bignum {
+
+  template<size_t dim>
+  class ulong {
+
+  public:
+    typedef char char_t;
+    typedef std::make_unsigned<char_t>::type uchar_t;
+
+  private:
+    std::unique_ptr<char_t[]> buf_;
+
+  public:
+    inline ulong()
+      : buf_(new char_t[dim]())
+    {
+    }
+    inline ulong(size_t t)
+      : buf_(new char_t[dim]())
+    {
+      memcpy(buf_.get(), (char_t*)&t, sizeof(t));
+    }
+    inline ulong(const ulong<dim>& rhs)
+      : buf_(new char_t[dim]())
+    {
+      memcpy(buf_.get(), rhs.buf_.get(), dim);
+    }
+    explicit inline ulong(const char_t *data, size_t size)
+      : buf_(new char_t[dim]())
+    {
+      if (size > dim) {
+        throw std::bad_alloc();
+      }
+      memcpy(buf_.get(), data, size);
+    }
+
+    virtual ~ulong() {}
+
+    ulong<dim>& operator=(const ulong<dim>& rhs) {
+      memcpy(buf_.get(), rhs.buf_.get(), dim);
+      return *this;
+    }
+
+    bool operator==(const ulong<dim>& rhs) const {
+      return memcmp(buf_.get(), rhs.buf_.get(), dim) == 0;
+    }
+    bool operator!=(const ulong<dim>& rhs) const {
+      return memcmp(buf_.get(), rhs.buf_.get(), dim) != 0;
+    }
+    bool operator>(const ulong<dim>& rhs) const {
+      const auto b1 = buf_.get();
+      const auto b2 = rhs.buf_.get();
+      for (ssize_t i = dim - 1; i >= 0; --i) {
+        for (ssize_t j = 1; j >= 0; --j) {
+          char_t t = ((uchar_t)(b1[i] << 4 * (1 - j))) >> 4;
+          char_t r = ((uchar_t)(b2[i] << 4 * (1 - j))) >> 4;
+          if (t != r) {
+            return t > r;
+          }
+        }
+      }
+      return false;
+    }
+    bool operator>=(const ulong<dim>& rhs) const {
+      return *this == rhs || *this > rhs;
+    }
+    bool operator<(const ulong<dim>& rhs) const {
+      return !(*this >= rhs);
+    }
+    bool operator<=(const ulong<dim>& rhs) const {
+      return *this == rhs || *this < rhs;
+    }
+
+    ulong<dim> operator+(const ulong<dim>& rhs) const {
+      ulong<dim> rv;
+      const auto b1 = buf_.get();
+      const auto b2 = rhs.buf_.get();
+      const auto rb = rv.buf_.get();
+      bool base = false;
+      for (size_t i = 0; i < dim; ++i) {
+        for (ssize_t j = 0; j < 2; ++j) {
+          char_t t = ((uchar_t)(b1[i] << 4 * (1 - j))) >> 4;
+          char_t r = ((uchar_t)(b2[i] << 4 * (1 - j))) >> 4;
+          if (base) {
+            t++;
+          }
+          if (r + t >= 16) {
+            rb[i] += (t + r - 16) << j * 4;
+            base = true;
+          }
+          else {
+            rb[i] += (t + r) << j * 4;
+            base = false;
+          }
+        }
+      }
+      return rv;
+    }
+    ulong<dim>& operator+=(const ulong<dim>& rhs) {
+      *this = *this + rhs;
+      return *this;
+    }
+    ulong<dim>& operator++() {
+      *this = *this + 1;
+      return *this;
+    }
+    ulong<dim> operator++(int) {
+      ulong<dim> tmp = *this;
+      *this = *this + 1;
+      return tmp;
+    }
+
+    ulong<dim> operator-(const ulong<dim>& rhs) const {
+      ulong<dim> rv;
+      const auto b1 = buf_.get();
+      const auto b2 = rhs.buf_.get();
+      const auto rb = rv.buf_.get();
+      bool base = false;
+      for (size_t i = 0; i < dim; ++i) {
+        for (ssize_t j = 0; j < 2; ++j) {
+          char_t t = ((uchar_t)(b1[i] << 4 * (1 - j))) >> 4;
+          char_t r = ((uchar_t)(b2[i] << 4 * (1 - j))) >> 4;
+          if (base) {
+            t--;
+          }
+          if (t >= r) {
+            rb[i] += (t - r) << j * 4;
+            base = false;
+          }
+          else {
+            rb[i] += (t + 16 - r) << j * 4;
+            base = true;
+          }
+        }
+      }
+      return rv;
+    }
+    ulong<dim>& operator-=(const ulong<dim>& rhs) {
+      *this = *this - rhs;
+      return *this;
+    }
+    ulong<dim>& operator--() {
+      *this = *this - 1;
+      return *this;
+    }
+    ulong<dim> operator--(int) {
+      ulong<dim> tmp = *this;
+      *this = *this - 1;
+      return tmp;
+    }
+
+    ulong<dim> operator*(const ulong<dim>& rhs) const {
+      ulong<dim> c = rhs, rv;
+      const ulong<dim> null;
+      size_t cap = c.capacity();
+      while (c != null) {
+        ulong<dim> tmp = *this;
+        tmp.mul(cap - 1);
+        rv += tmp;
+
+        ulong<dim> diff(1);
+        diff.mul(cap - 1);
+        c -= diff;
+
+        cap = c.capacity();
+      }
+      return rv;
+    }
+    ulong<dim>& operator*=(const ulong<dim>& rhs) {
+      *this = *this * rhs;
+      return *this;
+    }
+
+    ulong<dim> operator/(const ulong<dim>& rhs) const {
+      ulong<dim> quotient, remainder;
+      div(rhs, quotient, remainder);
+      return quotient;
+    }
+    ulong<dim>& operator/=(const ulong<dim>& rhs) {
+      *this = *this / rhs;
+      return *this;
+    }
+
+    ulong<dim> operator%(const ulong<dim>& rhs) const {
+      ulong<dim> quotient, remainder;
+      div(rhs, quotient, remainder);
+      return remainder;
+    }
+    ulong<dim>& operator%=(const ulong<dim>& rhs) {
+      *this = *this % rhs;
+      return *this;
+    }
+
+    ulong<dim> mul_mod(const ulong<dim>& mul, const ulong<dim>& mod) const {
+      if (capacity() + mul.capacity() <= dim) {
+        return (*this * mul) % mod;
+      }
+      ulong<dim*2> et(buf_.get(), dim),
+        emul(mul.buf_.get(), dim),
+        emod(mod.buf_.get(), dim),
+        erv = (et * emul) % emod;
+      ulong<dim> rv;
+      erv.binary(rv.buf_.get(), dim);
+      return rv;
+    }
+
+    std::unique_ptr<char_t[]> binary() const {
+      ulong<dim> c = *this;
+      std::unique_ptr<char_t[]> rv;
+      rv.swap(c.buf_);
+      return rv;
+    }
+    void binary(char_t *buf, size_t len) const {
+      memcpy(buf, buf_.get(), std::min(dim, len));
+    }
+
+    size_t length() const {
+      return dim;
+    }
+
+  private:
+    size_t capacity() const {
+      size_t rv = dim * 2;
+      const auto b = buf_.get();
+      for (ssize_t i = dim - 1; i >= 0; --i) {
+        char_t f = b[i] >> 4;
+        char_t s = (b[i] << 4) >> 4;
+        if (!f && !s) {
+          rv -= 2;
+          continue;
+        }
+        if (!f) {
+          --rv;
+        }
+        return rv;
+      }
+      return rv;
+    }
+
+    void mul(size_t digits) {
+      ulong<dim> tmp = *this;
+      auto bt = tmp.buf_.get();
+      auto b = buf_.get();
+      memset(b, 0, dim);
+      const size_t npar = digits % 2;
+      const size_t d2 = digits / 2;
+      for (size_t i = d2; i < dim; ++i) {
+        for (size_t j = 0; j < 2; ++j) {
+          char_t c = ((uchar_t)(bt[(dim - 1) - i] << 4 * (1 - j))) >> 4;
+          char_t r = c << (npar * (1 - j) * 4 + (1 - npar) * j * 4);
+          ssize_t idx = i - d2 - npar * j;
+          if (idx >= 0) {
+            b[(dim - 1) - idx] += r;
+          }
+        }
+      }
+    }
+
+    void div(const ulong<dim>& d, ulong<dim>& q, ulong<dim>& r) const {
+      ulong<dim> tmp = d;
+      r = *this;
+      q = 0;
+      size_t cr = r.capacity();
+      const size_t cd = d.capacity();
+      while (cr > cd) {
+        tmp = d;
+        tmp.mul(cr - cd - 1);
+        ulong<dim> qt(1);
+        qt.mul(cr - cd - 1);
+        ulong<dim> t = tmp;
+        t.mul(1);
+        if (r >= t) {
+          tmp = t;
+          qt.mul(1);
+        }
+        while (r >= tmp) {
+          r -= tmp;
+          q += qt;
+        }
+        cr = r.capacity();
+      }
+      while (r >= d) {
+        r -= d;
+        ++q;
+      }
+    }
+  };
+
+} // namespace bignum
+
+#endif