From: Kees Monshouwer Date: Mon, 28 Dec 2015 11:10:17 +0000 (+0100) Subject: add ECDSA support to DNSSEC infra via OpenSSL X-Git-Tag: dnsdist-1.0.0-alpha2~142^2~1 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7581a35b3ef3526761731b8f60423d4f72ea3af4;p=pdns add ECDSA support to DNSSEC infra via OpenSSL --- diff --git a/configure.ac b/configure.ac index 77da1915a..b205ef7eb 100644 --- a/configure.ac +++ b/configure.ac @@ -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 index 000000000..c52fe43fd --- /dev/null +++ b/m4/ax_check_openssl.m4 @@ -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 +# +# LICENSE +# +# Copyright (c) 2009,2010 Zmanda Inc. +# Copyright (c) 2009,2010 Dustin J. Mitchell +# +# 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 , 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 ], [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]) +]) + diff --git a/pdns/Makefile.am b/pdns/Makefile.am index a7b3ccbd9..662e71534 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -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 index 000000000..9ff23e68f --- /dev/null +++ b/pdns/opensslsigners.cc @@ -0,0 +1,329 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include + +#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& 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& 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 index 000000000..241de4af7 --- /dev/null +++ b/pdns/opensslsigners.hh @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +#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 #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()); + +seedRandom(::arg()["entropy-source"]); + #ifdef HAVE_LIBSODIUM if (sodium_init() == -1) { cerr<<"Unable to initialize sodium crypto library"<()); 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); } diff --git a/pdns/receiver.cc b/pdns/receiver.cc index 7a2b99b2a..594dfaf9a 100644 --- a/pdns/receiver.cc +++ b/pdns/receiver.cc @@ -49,6 +49,9 @@ #ifdef HAVE_LIBSODIUM #include #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!