From: Pieter Lexis Date: Tue, 10 Apr 2018 08:38:50 +0000 (+0200) Subject: Initial EDDSA signer/verifier with OpenSSL X-Git-Tag: dnsdist-1.3.3~70^2~9 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=439974d76b9fee5d2b69bfe5764c74d69052c971;p=thirdparty%2Fpdns.git Initial EDDSA signer/verifier with OpenSSL --- diff --git a/configure.ac b/configure.ac index 7984a4c153..133c894f95 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,7 @@ PDNS_CHECK_LIBCRYPTO([ ] ) PDNS_CHECK_LIBCRYPTO_ECDSA +PDNS_CHECK_LIBCRYPTO_EDDSA PDNS_CHECK_RAGEL([pdns/dnslabeltext.cc], [www.powerdns.com]) PDNS_CHECK_CLOCK_GETTIME @@ -352,11 +353,11 @@ AS_IF([test "x$libcrypto_ecdsa" = "xyes"], [AC_MSG_NOTICE([OpenSSL ecdsa: yes])], [AC_MSG_NOTICE([OpenSSL ecdsa: no])] ) -AS_IF([test "x$LIBSODIUM_LIBS" != "x" || test "x$LIBDECAF_LIBS" != "x"], +AS_IF([test "x$LIBSODIUM_LIBS" != "x" || test "x$LIBDECAF_LIBS" != "x" || test "x$libcrypto_ed25519" = "xyes"], [AC_MSG_NOTICE([ed25519: yes])], [AC_MSG_NOTICE([ed25519: no])] ) -AS_IF([test "x$LIBDECAF_LIBS" != "x"], +AS_IF([test "x$LIBDECAF_LIBS" != "x" || test "x$libcrypto_ed448" = "xyes"], [AC_MSG_NOTICE([ed448: yes])], [AC_MSG_NOTICE([ed448: no])] ) diff --git a/m4/pdns_check_libcrypto_eddsa.m4 b/m4/pdns_check_libcrypto_eddsa.m4 new file mode 100644 index 0000000000..e9f69e47f8 --- /dev/null +++ b/m4/pdns_check_libcrypto_eddsa.m4 @@ -0,0 +1,36 @@ +AC_DEFUN([PDNS_CHECK_LIBCRYPTO_EDDSA], [ + AC_REQUIRE([PDNS_CHECK_LIBCRYPTO]) + + # Set the environment correctly for a possibly non-default OpenSSL path that was found by/supplied to PDNS_CHECK_LIBCRYPTO + save_CPPFLAGS="$CPPFLAGS" + save_LDFLAGS="$LDFLAGS" + save_LIBS="$LIBS" + + CPPFLAGS="$LIBCRYPTO_INCLUDES $CPPFLAGS" + LDFLAGS="$LIBCRYPTO_LDFLAGS $LDFLAGS" + LIBS="$LIBCRYPTO_LIBS $LIBS" + + libcrypto_ed25519=no + libcrypto_ed448=no + AC_CHECK_DECLS([NID_ED25519], [ + libcrypto_ed25519=yes + AC_DEFINE([HAVE_LIBCRYPTO_ED25519], [1], [define to 1 if OpenSSL ed25519 support is available.]) + ], [ : ], + [AC_INCLUDES_DEFAULT + #include <$ssldir/include/openssl/evp.h>]) + AC_CHECK_DECLS([NID_ED448], [ + libcrypto_ed448=yes + AC_DEFINE([HAVE_LIBCRYPTO_ED448], [1], [define to 1 if OpenSSL ed448 support is available.]) + ], [ : ], + [AC_INCLUDES_DEFAULT + #include <$ssldir/include/openssl/evp.h>]) + + AS_IF([test "$libcrypto_ed448" = "yes" -o "$libcrypto_ed448" = "yes"], [ + AC_DEFINE([HAVE_LIBCRYPTO_EDDSA], [1], [define to 1 if OpenSSL EDDSA support is available.]) + ], [ : ]) + + # Restore variables + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" +]) diff --git a/pdns/opensslsigners.cc b/pdns/opensslsigners.cc index daff5a5384..15ac2840f2 100644 --- a/pdns/opensslsigners.cc +++ b/pdns/opensslsigners.cc @@ -26,6 +26,9 @@ #ifdef HAVE_LIBCRYPTO_ECDSA #include #endif +#if defined(HAVE_LIBCRYPTO_ED25519) || defined(HAVE_LIBCRYPTO_ED448) +#include +#endif #include #include #include @@ -910,6 +913,216 @@ void OpenSSLECDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& inpu } #endif +#ifdef HAVE_LIBCRYPTO_EDDSA +class OpenSSLEDDSADNSCryptoKeyEngine : public DNSCryptoKeyEngine +{ +public: + explicit OpenSSLEDDSADNSCryptoKeyEngine(unsigned int algo) : DNSCryptoKeyEngine(algo) + { + + int ret = RAND_status(); + if (ret != 1) { + throw runtime_error(getName()+" insufficient entropy"); + } + + d_edkey = EVP_PKEY_new(); + if (d_edkey == NULL) { + throw runtime_error(getName()+" allocation of key structure failed"); + } + + if(d_algorithm == 15) { + d_len = 32; + d_id = NID_ED25519; + } else if (d_algorithm == 16) { + d_len = 57; + d_id = NID_ED448; + } else { + throw runtime_error(getName()+" unknown algorithm "+std::to_string(d_algorithm)); + } + + d_edctx = EVP_PKEY_CTX_new_id(d_id, NULL); + if (d_edctx == NULL) { + throw runtime_error(getName()+" unable to allocate context"); + } + } + + ~OpenSSLEDDSADNSCryptoKeyEngine() + { + EVP_PKEY_free(d_edkey); + EVP_PKEY_CTX_free(d_edctx); + } + + string getName() const override { return "OpenSSL EDDSA"; } + int getBits() const override { return d_len; } + + void create(unsigned int bits) override; + storvector_t convertToISCVector() const override; + std::string sign(const std::string& hash) const override; + bool verify(const std::string& msg, const std::string& signature) const override; + std::string getPubKeyHash() const override; + std::string getPublicKeyString() const override; + void fromISCMap(DNSKEYRecordContent& drc, std::map& stormap) override; + void fromPublicKeyString(const std::string& content) override; + bool checkKey() const override; + + static std::shared_ptr maker(unsigned int algorithm) + { + return std::make_shared(algorithm); + } + +private: + size_t d_len; + int d_id; + + EVP_PKEY *d_edkey = NULL; + EVP_PKEY_CTX *d_edctx = NULL; +}; + +bool OpenSSLEDDSADNSCryptoKeyEngine::checkKey() const +{ + return (d_edkey != NULL); +} + +void OpenSSLEDDSADNSCryptoKeyEngine::create(unsigned int bits) +{ + if (EVP_PKEY_keygen_init(d_edctx) < 1) { + throw runtime_error(getName()+" context initialization failed"); + } + if (EVP_PKEY_keygen(d_edctx, &d_edkey) < 1) { + throw runtime_error(getName()+" key generation failed"); + } +} + +DNSCryptoKeyEngine::storvector_t OpenSSLEDDSADNSCryptoKeyEngine::convertToISCVector() const +{ + storvector_t storvect; + string algorithm; + + if(d_algorithm == 15) + algorithm = "15 (ED25519)"; + else if(d_algorithm == 16) + algorithm = "16 (ED448)"; + else + algorithm = " ? (?)"; + + storvect.push_back(make_pair("Algorithm", algorithm)); + + string buf; + size_t len = d_len; + buf.resize(len); + EVP_PKEY_get_raw_private_key(d_edkey, reinterpret_cast(&buf.at(0)), &len); + storvect.push_back(make_pair("PrivateKey", buf)); + return storvect; +} + +std::string OpenSSLEDDSADNSCryptoKeyEngine::sign(const std::string& msg) const +{ + auto ctx = EVP_MD_CTX_new(); + auto pctx = d_edctx; + if(EVP_DigestSignInit(ctx, &pctx, NULL, NULL, d_edkey) < 1) { + throw runtime_error(getName()+" unable to initialize signer"); + } + + string signature; + size_t siglen; + string msgToSign = msg; + if (EVP_DigestSign(ctx, + NULL, &siglen, + reinterpret_cast(&msgToSign.at(0)), msgToSign.length()) < 1) { + throw runtime_error(getName()+" could not determine signature size"); + } + + signature.resize(siglen); + + if (EVP_DigestSign(ctx, + reinterpret_cast(&signature.at(0)), &siglen, + reinterpret_cast(&msgToSign.at(0)), msgToSign.length()) < 1) { + throw runtime_error(getName()+" signing error"); + } + return signature; +} + +bool OpenSSLEDDSADNSCryptoKeyEngine::verify(const std::string& msg, const std::string& signature) const +{ + auto ctx = EVP_MD_CTX_new(); + auto pctx = d_edctx; + if(EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, d_edkey) < 1) { + throw runtime_error(getName()+" unable to initialize verifyer"); + } + + string checkSignature = signature; + string checkMsg = msg; + + auto r = EVP_DigestVerify(ctx, + reinterpret_cast(&checkSignature.at(0)), checkSignature.length(), + reinterpret_cast(&checkMsg.at(0)), checkMsg.length()); + if (r < 0) { + throw runtime_error(getName()+" verification failure"); + } + + return (r == 1); +} + +std::string OpenSSLEDDSADNSCryptoKeyEngine::getPubKeyHash() const +{ + return this->getPublicKeyString(); +} + +std::string OpenSSLEDDSADNSCryptoKeyEngine::getPublicKeyString() const +{ + string buf; + size_t len = d_len; + buf.resize(len); + if (EVP_PKEY_get_raw_public_key(d_edkey, reinterpret_cast(&buf.at(0)), &len) < 1) { + throw std::runtime_error("Unable to get public key from key struct"); + } + return buf; +} + +void OpenSSLEDDSADNSCryptoKeyEngine::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"); + } + + d_edkey = EVP_PKEY_new_raw_private_key(d_id, NULL, reinterpret_cast(&stormap["privatekey"].at(0)), stormap["privatekey"].length()); + if (d_edkey == NULL) { + throw std::runtime_error(getName() + " could not create key structure from private key"); + } + // Replace the context for a new one with the key + EVP_PKEY_CTX_free(d_edctx); + d_edctx = EVP_PKEY_CTX_new(d_edkey, NULL); +} + +void OpenSSLEDDSADNSCryptoKeyEngine::fromPublicKeyString(const std::string& content) +{ + const unsigned char* raw = (const unsigned char*)content.c_str(); + const size_t inputLen = content.length(); + + if (inputLen < 1) { + throw runtime_error(getName()+" invalid input size for the public key"); + } + + int type{0}; + + if (inputLen == 32) { + type = NID_ED25519; + } else if (inputLen == 57) { + type = NID_ED448; + } else { + throw runtime_error(getName() + "could not determine EDDSA key type"); + } + + d_edkey = EVP_PKEY_new_raw_public_key(type, NULL, raw, inputLen); + if (d_edkey == NULL) { + throw runtime_error(getName()+" allocation of public key structure failed"); + } + + // Replace the context for a new one with the key + EVP_PKEY_CTX_free(d_edctx); + d_edctx = EVP_PKEY_CTX_new(d_edkey, NULL); +} +#endif // HAVE_LIBCRYPTO_EDDSA namespace { struct LoaderStruct @@ -923,6 +1136,12 @@ namespace { #ifdef HAVE_LIBCRYPTO_ECDSA DNSCryptoKeyEngine::report(13, &OpenSSLECDSADNSCryptoKeyEngine::maker); DNSCryptoKeyEngine::report(14, &OpenSSLECDSADNSCryptoKeyEngine::maker); +#endif +#ifdef HAVE_LIBCRYPTO_ED25519 + DNSCryptoKeyEngine::report(15, &OpenSSLEDDSADNSCryptoKeyEngine::maker); +#endif +#ifdef HAVE_LIBCRYPTO_ED448 + DNSCryptoKeyEngine::report(16, &OpenSSLEDDSADNSCryptoKeyEngine::maker); #endif } } loaderOpenSSL; diff --git a/pdns/test-signers.cc b/pdns/test-signers.cc index 716ae8d72e..e03ab18559 100644 --- a/pdns/test-signers.cc +++ b/pdns/test-signers.cc @@ -99,7 +99,7 @@ static const struct signerParams false }, #endif /* HAVE_LIBCRYPTO_ECDSA */ -#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) +#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519) /* ed25519 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h, also from rfc8080 section 6.1 */ { "Algorithm: 15\n" @@ -121,7 +121,7 @@ static const struct signerParams DNSSECKeeper::ED25519, true }, -#endif /* defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) */ +#endif /* defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519) */ }; static void checkRR(const signerParams& signer) @@ -234,7 +234,7 @@ BOOST_AUTO_TEST_CASE(test_generic_signers) } } -#ifdef HAVE_LIBDECAF +#if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448) BOOST_AUTO_TEST_CASE(test_ed448_signer) { vector > rrs; DNSName qname("example.com."); @@ -275,6 +275,6 @@ BOOST_AUTO_TEST_CASE(test_ed448_signer) { // vector verified from dnskey.py as above, and confirmed with https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935 BOOST_CHECK_EQUAL(b64, "3cPAHkmlnxcDHMyg7vFC34l0blBhuG1qpwLmjInI8w1CMB29FkEAIJUA0amxWndkmnBZ6SKiwZSAxGILn/NBtOXft0+Gj7FSvOKxE/07+4RQvE581N3Aj/JtIyaiYVdnYtyMWbSNyGEY2213WKsJlwEA"); } -#endif +#endif /* defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448) */ BOOST_AUTO_TEST_SUITE_END()