]> granicus.if.org Git - pdns/commitdiff
add ECDSA support to DNSSEC infra via OpenSSL
authorKees Monshouwer <mind04@monshouwer.org>
Mon, 28 Dec 2015 11:10:17 +0000 (12:10 +0100)
committermind04 <mind04@monshouwer.org>
Tue, 29 Dec 2015 21:45:14 +0000 (22:45 +0100)
configure.ac
m4/ax_check_openssl.m4 [new file with mode: 0644]
pdns/Makefile.am
pdns/opensslsigners.cc [new file with mode: 0644]
pdns/opensslsigners.hh [new file with mode: 0644]
pdns/pdnsutil.cc
pdns/receiver.cc

index 77da1915a678304311b2625d9625d582e292c8a3..b205ef7eb3cefdc34f3f65092863fe55a644a338 100644 (file)
@@ -106,6 +106,13 @@ AC_CHECK_HEADERS(
        [have_mmap=no]
 )
 
+AX_CHECK_OPENSSL([
+       AM_CONDITIONAL([OPENSSL], [true])
+       AC_DEFINE(HAVE_OPENSSL, [1], [Define to 1 if you openssl])
+       ],[
+       AM_CONDITIONAL([OPENSSL], [false])
+       ]
+)
 
 PDNS_CHECK_READLINE
 PDNS_CHECK_RAGEL
diff --git a/m4/ax_check_openssl.m4 b/m4/ax_check_openssl.m4
new file mode 100644 (file)
index 0000000..c52fe43
--- /dev/null
@@ -0,0 +1,125 @@
+# ===========================================================================
+#     http://www.gnu.org/software/autoconf-archive/ax_check_openssl.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]])
+#
+# DESCRIPTION
+#
+#   Look for OpenSSL in a number of default spots, or in a user-selected
+#   spot (via --with-openssl).  Sets
+#
+#     OPENSSL_INCLUDES to the include directives required
+#     OPENSSL_LIBS to the -l directives required
+#     OPENSSL_LDFLAGS to the -L or -R flags required
+#
+#   and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately
+#
+#   This macro sets OPENSSL_INCLUDES such that source files should use the
+#   openssl/ directory in include directives:
+#
+#     #include <openssl/hmac.h>
+#
+# LICENSE
+#
+#   Copyright (c) 2009,2010 Zmanda Inc. <http://www.zmanda.com/>
+#   Copyright (c) 2009,2010 Dustin J. Mitchell <dustin@zmanda.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 8
+
+AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL])
+AC_DEFUN([AX_CHECK_OPENSSL], [
+    found=false
+    AC_ARG_WITH([openssl],
+        [AS_HELP_STRING([--with-openssl=DIR],
+            [root of the OpenSSL directory])],
+        [
+            case "$withval" in
+            "" | y | ye | yes | n | no)
+            AC_MSG_ERROR([Invalid --with-openssl value])
+              ;;
+            *) ssldirs="$withval"
+              ;;
+            esac
+        ], [
+            # if pkg-config is installed and openssl has installed a .pc file,
+            # then use that information and don't search ssldirs
+            AC_PATH_PROG([PKG_CONFIG], [pkg-config])
+            if test x"$PKG_CONFIG" != x""; then
+                OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null`
+                if test $? = 0; then
+                    OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null`
+                    OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null`
+                    found=true
+                fi
+            fi
+
+            # no such luck; use some default ssldirs
+            if ! $found; then
+                ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr"
+            fi
+        ]
+        )
+
+
+    # note that we #include <openssl/foo.h>, so the OpenSSL headers have to be in
+    # an 'openssl' subdirectory
+
+    if ! $found; then
+        OPENSSL_INCLUDES=
+        for ssldir in $ssldirs; do
+            AC_MSG_CHECKING([for openssl/ssl.h in $ssldir])
+            if test -f "$ssldir/include/openssl/ssl.h"; then
+                OPENSSL_INCLUDES="-I$ssldir/include"
+                OPENSSL_LDFLAGS="-L$ssldir/lib"
+                OPENSSL_LIBS="-lssl -lcrypto"
+                found=true
+                AC_MSG_RESULT([yes])
+                break
+            else
+                AC_MSG_RESULT([no])
+            fi
+        done
+
+        # if the file wasn't found, well, go ahead and try the link anyway -- maybe
+        # it will just work!
+    fi
+
+    # try the preprocessor and linker with our new flags,
+    # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS
+
+    AC_MSG_CHECKING([whether compiling and linking against OpenSSL works])
+    echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \
+        "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD
+
+    save_LIBS="$LIBS"
+    save_LDFLAGS="$LDFLAGS"
+    save_CPPFLAGS="$CPPFLAGS"
+    LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS"
+    LIBS="$OPENSSL_LIBS $LIBS"
+    CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS"
+    AC_LINK_IFELSE(
+        [AC_LANG_PROGRAM([#include <openssl/ssl.h>], [SSL_new(NULL)])],
+        [
+            AC_MSG_RESULT([yes])
+            $1
+        ], [
+            AC_MSG_RESULT([no])
+            $2
+        ])
+    CPPFLAGS="$save_CPPFLAGS"
+    LDFLAGS="$save_LDFLAGS"
+    LIBS="$save_LIBS"
+
+    AC_SUBST([OPENSSL_INCLUDES])
+    AC_SUBST([OPENSSL_LIBS])
+    AC_SUBST([OPENSSL_LDFLAGS])
+])
+
index a7b3ccbd909833d92730a7f03a13558c56f655ed..662e71534aad120e5a5d142536c1f9df832451a2 100644 (file)
@@ -224,6 +224,11 @@ pdns_server_SOURCES += sodiumsigners.cc
 pdns_server_LDADD += $(LIBSODIUM_LIBS)
 endif
 
+if OPENSSL
+pdns_server_SOURCES += opensslsigners.cc opensslsigners.hh
+pdns_server_LDADD += $(OPENSSL_LIBS)
+endif
+
 if SQLITE3
 pdns_server_SOURCES += ssqlite3.cc ssqlite3.hh
 pdns_server_LDADD += $(SQLITE3_LIBS)
@@ -319,6 +324,11 @@ pdnsutil_SOURCES += sodiumsigners.cc
 pdnsutil_LDADD += $(LIBSODIUM_LIBS)
 endif
 
+if OPENSSL
+pdnsutil_SOURCES += opensslsigners.cc
+pdnsutil_LDADD += $(OPENSSL_LIBS)
+endif
+
 if SQLITE3
 pdnsutil_SOURCES += ssqlite3.cc ssqlite3.hh
 pdnsutil_LDADD += $(SQLITE3_LIBS)
diff --git a/pdns/opensslsigners.cc b/pdns/opensslsigners.cc
new file mode 100644 (file)
index 0000000..9ff23e6
--- /dev/null
@@ -0,0 +1,329 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <openssl/obj_mac.h>
+#include <openssl/ecdsa.h>
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+
+#include "dnssecinfra.hh"
+
+
+class OpenSSLECDSADNSCryptoKeyEngine : public DNSCryptoKeyEngine
+{
+public:
+  explicit OpenSSLECDSADNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo)
+  {RAND_cleanup();
+
+    int ret = RAND_status();
+    if (ret != 1) {
+      throw runtime_error(getName()+" insufficient entropy");
+    }
+
+    d_eckey = EC_KEY_new();
+    if (d_eckey == NULL) {
+      throw runtime_error(getName()+" allocation of key structure failed");
+    }
+
+    if(d_algorithm == 13) {
+      d_ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
+      d_len = 32;
+    } else if (d_algorithm == 14) {
+      d_ecgroup = EC_GROUP_new_by_curve_name(NID_secp384r1);
+      d_len = 48;
+    } else {
+      throw runtime_error(getName()+" unknown algorithm "+std::to_string(d_algorithm));
+    }
+    if (d_ecgroup == NULL) {
+      throw runtime_error(getName()+" allocation of group structure failed");
+    }
+
+    ret = EC_KEY_set_group(d_eckey,d_ecgroup);
+    if (ret != 1) {
+      throw runtime_error(getName()+" setting key group failed");
+    }
+
+  }
+
+  ~OpenSSLECDSADNSCryptoKeyEngine()
+  {
+    EC_KEY_free(d_eckey);
+    EC_GROUP_free(d_ecgroup);
+    BN_CTX_free(d_ctx);
+  }
+
+  string getName() const { return "OpenSSL ECDSA"; }
+  int getBits() const { return d_len << 3; }
+
+  void create(unsigned int bits);
+  storvector_t convertToISCVector() const;
+  std::string hash(const std::string& hash) const;
+  std::string sign(const std::string& hash) const;
+  bool verify(const std::string& hash, const std::string& signature) const;
+  std::string getPubKeyHash() const;
+  std::string getPublicKeyString() const;
+  void fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap);
+  void fromPublicKeyString(const std::string& content);
+
+  static DNSCryptoKeyEngine* maker(unsigned int algorithm)
+  {
+    return new OpenSSLECDSADNSCryptoKeyEngine(algorithm);
+  }
+
+private:
+  unsigned int d_len;
+
+  EC_KEY *d_eckey = NULL;
+  EC_GROUP *d_ecgroup = NULL;
+  BN_CTX *d_ctx = NULL;
+};
+
+
+void OpenSSLECDSADNSCryptoKeyEngine::create(unsigned int bits)
+{
+  if (bits >> 3 != d_len) {
+    throw runtime_error(getName()+" unknown key length of "+std::to_string(bits)+" bits requested");
+  }
+
+  int res = EC_KEY_generate_key(d_eckey);
+  if (res == 0) {
+    throw runtime_error(getName()+" key generation failed");
+  }
+}
+
+
+DNSCryptoKeyEngine::storvector_t OpenSSLECDSADNSCryptoKeyEngine::convertToISCVector() const
+{
+  storvector_t storvect;
+  string algorithm;
+
+  if(d_algorithm == 13)
+    algorithm = "13 (ECDSAP256SHA256)";
+  else if(d_algorithm == 14)
+    algorithm = "14 (ECDSAP384SHA384)";
+  else
+    algorithm = " ? (?)";
+
+  storvect.push_back(make_pair("Algorithm", algorithm));
+
+  const BIGNUM *key = EC_KEY_get0_private_key(d_eckey);
+  if (key == NULL) {
+    throw runtime_error(getName()+" private key not set");
+  }
+
+  unsigned char tmp[BN_num_bytes(key)];
+  int len = BN_bn2bin(key, tmp);
+
+  string prefix;
+  if (d_len - len)
+    prefix.append(d_len - len, 0x00);
+
+  storvect.push_back(make_pair("PrivateKey", prefix + string((char*) tmp, sizeof(tmp))));
+
+  return storvect;
+}
+
+
+std::string OpenSSLECDSADNSCryptoKeyEngine::hash(const std::string& orig) const
+{
+  if(getBits() == 256) {
+    unsigned char hash[SHA256_DIGEST_LENGTH];
+    SHA256((unsigned char*) orig.c_str(), orig.length(), hash);
+    return string((char*) hash, sizeof(hash));
+  }
+  else if(getBits() == 384) {
+    unsigned char hash[SHA384_DIGEST_LENGTH];
+    SHA384((unsigned char*) orig.c_str(), orig.length(), hash);
+    return string((char*) hash, sizeof(hash));
+  }
+
+  throw runtime_error(getName()+" does not support a hash size of "+std::to_string(getBits())+" bits");
+}
+
+
+std::string OpenSSLECDSADNSCryptoKeyEngine::sign(const std::string& msg) const
+{
+  string hash = this->hash(msg);
+
+  ECDSA_SIG *signature = ECDSA_do_sign((unsigned char*) hash.c_str(), hash.length(), d_eckey);
+  if (NULL == signature) {
+    throw runtime_error(getName()+" failed to generate signature");
+  }
+
+  string ret;
+  unsigned char tmp[d_len];
+
+  int len = BN_bn2bin(signature->r, tmp);
+  if (d_len - len)
+    ret.append(d_len - len, 0x00);
+  ret.append(string((char*) tmp, len));
+
+  len = BN_bn2bin(signature->s, tmp);
+  if (d_len - len)
+    ret.append(d_len - len, 0x00);
+  ret.append(string((char*) tmp, len));
+
+  ECDSA_SIG_free(signature);
+
+  return ret;
+}
+
+
+bool OpenSSLECDSADNSCryptoKeyEngine::verify(const std::string& msg, const std::string& signature) const
+{
+  if (signature.length() != (d_len * 2)) {
+    throw runtime_error(getName()+" invalid signature size "+std::to_string(signature.length()));
+  }
+
+  string hash = this->hash(msg);
+
+  ECDSA_SIG *sig;
+  sig = ECDSA_SIG_new();
+  if (sig == NULL) {
+    throw runtime_error(getName()+" allocation of signature structure failed");
+  }
+
+  sig->r = BN_bin2bn((unsigned char*) signature.c_str(), d_len, sig->r);
+  sig->s = BN_bin2bn((unsigned char*) signature.c_str() + d_len, d_len, sig->s);
+  if (!sig->r || !sig->s) {
+    ECDSA_SIG_free(sig);
+    throw runtime_error(getName()+" invalid signature");
+  }
+
+  int ret = ECDSA_do_verify((unsigned char*) hash.c_str(), hash.length(), sig, d_eckey);
+
+  ECDSA_SIG_free(sig);
+
+  if (ret == -1){
+    throw runtime_error(getName()+" verify error");
+  }
+
+  return (ret == 1);
+}
+
+
+std::string OpenSSLECDSADNSCryptoKeyEngine::getPubKeyHash() const
+{
+  string pubKey = getPublicKeyString();
+  unsigned char hash[SHA_DIGEST_LENGTH];
+  SHA1((unsigned char*) pubKey.c_str(), pubKey.length(), hash);
+  return string((char*) hash, sizeof(hash));
+}
+
+
+std::string OpenSSLECDSADNSCryptoKeyEngine::getPublicKeyString() const
+{
+  unsigned char binaryPoint[(d_len * 2) + 1];
+
+  int ret = EC_POINT_point2oct(d_ecgroup, EC_KEY_get0_public_key(d_eckey), POINT_CONVERSION_UNCOMPRESSED, binaryPoint, sizeof(binaryPoint), d_ctx);
+  if (ret == 0) {
+    throw runtime_error(getName()+" exporting point to binary failed");
+  }
+
+  /* we skip the first byte as the other backends use
+     raw field elements, as opposed to the format described in
+     SEC1: "2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion" */
+  return string((const char *)(binaryPoint + 1), sizeof(binaryPoint) - 1);
+}
+
+
+void OpenSSLECDSADNSCryptoKeyEngine::fromISCMap(DNSKEYRecordContent& drc, std::map<std::string, std::string>& stormap)
+{
+  drc.d_algorithm = atoi(stormap["algorithm"].c_str());
+
+  if (drc.d_algorithm != d_algorithm) {
+    throw runtime_error(getName()+" tried to feed an algorithm "+std::to_string(drc.d_algorithm)+" to a "+std::to_string(d_algorithm)+" key");
+  }
+
+  string privateKey = stormap["privatekey"];
+
+  BIGNUM *prv_key = BN_bin2bn((unsigned char*) privateKey.c_str(), privateKey.length(), NULL);
+  if (prv_key == NULL) {
+    throw runtime_error(getName()+" reading private key from binary failed");
+  }
+
+  int ret = EC_KEY_set_private_key(d_eckey, prv_key);
+  if (ret != 1) {
+    BN_free(prv_key);
+    throw runtime_error(getName()+" setting private key failed");
+  }
+
+  EC_POINT *pub_key = EC_POINT_new(d_ecgroup);
+  if (pub_key == NULL) {
+    BN_free(prv_key);
+    throw runtime_error(getName()+" allocation of public key point failed");
+  }
+
+  ret = EC_POINT_mul(d_ecgroup, pub_key, prv_key, NULL, NULL, d_ctx);
+  if (ret != 1) {
+    EC_POINT_free(pub_key);
+    BN_free(prv_key);
+    throw runtime_error(getName()+" computing public key from private failed");
+  }
+
+  BN_free(prv_key);
+
+  ret = EC_KEY_set_public_key(d_eckey, pub_key);
+  if (ret != 1) {
+    EC_POINT_free(pub_key);
+    throw runtime_error(getName()+" setting public key failed");
+  }
+
+  EC_POINT_free(pub_key);
+
+  ret = EC_KEY_check_key(d_eckey);
+  if (ret != 1) {
+    throw runtime_error(getName()+" invalid public key");
+  }
+
+}
+
+
+void OpenSSLECDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& input)
+{
+  /* uncompressed point, from SEC1:
+     "2.3.4 Octet-String-to-Elliptic-Curve-Point Conversion" */
+  string ecdsaPoint= "\x04";
+  ecdsaPoint.append(input);
+
+  EC_POINT *pub_key = EC_POINT_new(d_ecgroup);
+  if (pub_key == NULL) {
+    throw runtime_error(getName()+" allocation of point structure failed");
+  }
+
+  int ret = EC_POINT_oct2point(d_ecgroup, pub_key, (unsigned char*) ecdsaPoint.c_str(), ecdsaPoint.length(), d_ctx);
+  if (ret != 1) {
+    throw runtime_error(getName()+" reading ECP point from binary failed");
+  }
+
+  ret = EC_KEY_set_private_key(d_eckey, NULL);
+  if (ret == 1) {
+    EC_POINT_free(pub_key);
+    throw runtime_error(getName()+" setting private key failed");
+  }
+
+  ret = EC_KEY_set_public_key(d_eckey, pub_key);
+  if (ret != 1) {
+    EC_POINT_free(pub_key);
+    throw runtime_error(getName()+" setting public key failed");
+  }
+
+  EC_POINT_free(pub_key);
+
+  ret = EC_KEY_check_key(d_eckey);
+  if (ret != 1) {
+    throw runtime_error(getName()+" invalid public key");
+  }
+}
+
+
+namespace {
+  struct LoaderStruct
+  {
+    LoaderStruct()
+    {
+      DNSCryptoKeyEngine::report(13, &OpenSSLECDSADNSCryptoKeyEngine::maker);
+      DNSCryptoKeyEngine::report(14, &OpenSSLECDSADNSCryptoKeyEngine::maker);
+    }
+  } loaderOpenSSL;
+}
diff --git a/pdns/opensslsigners.hh b/pdns/opensslsigners.hh
new file mode 100644 (file)
index 0000000..241de4a
--- /dev/null
@@ -0,0 +1,58 @@
+#include <string>
+#include <pthread.h>
+#include <openssl/crypto.h>
+#include <openssl/rand.h>
+
+#include "dns_random.hh"
+
+/* pthread OpemSSL locking */
+
+static pthread_mutex_t *locks;
+
+void openssl_pthreads_locking_callback(int mode, int type, char *file, int line)
+{
+  if (mode & CRYPTO_LOCK)
+    pthread_mutex_lock(&(locks[type]));
+  else
+    pthread_mutex_unlock(&(locks[type]));
+}
+
+unsigned long openssl_pthreads_id_callback()
+{
+  return (unsigned long)pthread_self();
+}
+
+void openssl_thread_setup()
+{
+  locks = (pthread_mutex_t*)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
+
+  for (int i = 0; i < CRYPTO_num_locks(); i++)
+    pthread_mutex_init(&(locks[i++]), NULL);
+
+  CRYPTO_set_id_callback((unsigned long (*)())openssl_pthreads_id_callback);
+  CRYPTO_set_locking_callback((void (*)(int, int, const char*, int))openssl_pthreads_locking_callback);
+}
+
+void openssl_thread_cleanup()
+{
+  CRYPTO_set_locking_callback(NULL);
+
+  for (int i=0; i<CRYPTO_num_locks(); i++)
+    pthread_mutex_destroy(&(locks[i]));
+
+  OPENSSL_free(locks);
+}
+
+void openssl_seed()
+{ 
+  std::string entropy;
+  entropy.reserve(1024);
+  
+  unsigned int r;
+  for(int i=0; i<1024; i+=4) {
+    r=dns_random(0xffffffff);
+    entropy.append((const char*)&r, 4);
+  }
+  
+  RAND_seed((const unsigned char*)entropy.c_str(), 1024);
+}
index fe3cec6e3341e1b076463c8a59f5a85c1b9eafa8..7f02c289c51d5338e583c66aea25fb605112be1a 100644 (file)
@@ -21,6 +21,9 @@
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
 #endif
+#ifdef HAVE_OPENSSL
+#include "opensslsigners.hh"
+#endif
 #ifdef HAVE_SQLITE3
 #include "ssqlite3.hh"
 #include "bind-dnssec.schema.sqlite3.sql.h"
@@ -1474,6 +1477,10 @@ try
     return 0;
   }
 
+loadMainConfig(g_vm["config-dir"].as<string>());
+
+seedRandom(::arg()["entropy-source"]);
+
 #ifdef HAVE_LIBSODIUM
   if (sodium_init() == -1) {
     cerr<<"Unable to initialize sodium crypto library"<<endl;
@@ -1481,6 +1488,10 @@ try
   }
 #endif
 
+#ifdef HAVE_OPENSSL
+  openssl_seed();
+#endif
+
   if (cmds[0] == "test-algorithm") {
     if(cmds.size() != 2) {
       cerr << "Syntax: pdnsutil test-algorithm algonum"<<endl;
@@ -1497,7 +1508,6 @@ try
     return 1;
   }
 
-  loadMainConfig(g_vm["config-dir"].as<string>());
   reportAllTypes();
 
   if(cmds[0] == "create-bind-db") {
@@ -2154,7 +2164,6 @@ try
      }
 
      cerr << "Generating new key with " << klen << " bytes (this can take a while)" << endl;
-     seedRandom(::arg()["entropy-source"]);
      for(size_t i = 0; i < klen; i+=4) {
         *(unsigned int*)(tmpkey+i) = dns_random(0xffffffff);
      }
index 7a2b99b2a7d07223b3da7e7f2c7f324f96e5e5a9..594dfaf9a62c9f3baf6cc06721096b1e8b4e7f14 100644 (file)
@@ -49,6 +49,9 @@
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
 #endif
+#ifdef HAVE_OPENSSL
+#include "opensslsigners.hh"
+#endif
 
 #include "dns.hh"
 #include "dnsbackend.hh"
@@ -505,7 +508,12 @@ int main(int argc, char **argv)
         exit(99);
       }
 #endif
-    
+
+#ifdef HAVE_OPENSSL
+    openssl_thread_setup();
+    openssl_seed();
+#endif
+
     loadModules();
     BackendMakers().launch(::arg()["launch"]); // vrooooom!