From: bert hubert Date: Fri, 2 Feb 2018 10:43:20 +0000 (+0100) Subject: Add support for encrypting IP addresses #gdpr X-Git-Tag: dnsdist-1.4.0-alpha1~27^2~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7d2803422b988ebed885ac25b0c3a6dff56d7e1a;p=thirdparty%2Fpdns.git Add support for encrypting IP addresses #gdpr With this change, PowerDNS core gains ability to encrypt & decrypt IP addresses as described in https://medium.com/@bert.hubert/on-ip-address-encryption-security-analysis-with-respect-for-privacy-dabe1201b476 For IPv4 this uses ipcrypt, for IPv6 it uses a 128-bit AES ECB operation. This CR also hooks up ipencrypt() and ipdecrypt() methods for dnsdist use, specifically to pseudonomyse logging. --- diff --git a/ext/ipcrypt/LICENSE b/ext/ipcrypt/LICENSE new file mode 100644 index 0000000000..0a199e50ae --- /dev/null +++ b/ext/ipcrypt/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2015-2018, Frank Denis + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/ext/ipcrypt/ipcrypt.c b/ext/ipcrypt/ipcrypt.c new file mode 100644 index 0000000000..6ef464aa90 --- /dev/null +++ b/ext/ipcrypt/ipcrypt.c @@ -0,0 +1,87 @@ + +#include "ipcrypt.h" + +#define ROTL(X, R) (X) = (unsigned char) ((X) << (R)) | ((X) >> (8 - (R))) + +static void +arx_fwd(unsigned char state[4]) +{ + state[0] += state[1]; + state[2] += state[3]; + ROTL(state[1], 2); + ROTL(state[3], 5); + state[1] ^= state[0]; + state[3] ^= state[2]; + ROTL(state[0], 4); + state[0] += state[3]; + state[2] += state[1]; + ROTL(state[1], 3); + ROTL(state[3], 7); + state[1] ^= state[2]; + state[3] ^= state[0]; + ROTL(state[2], 4); +} + +static void +arx_bwd(unsigned char state[4]) +{ + ROTL(state[2], 4); + state[1] ^= state[2]; + state[3] ^= state[0]; + ROTL(state[1], 5); + ROTL(state[3], 1); + state[0] -= state[3]; + state[2] -= state[1]; + ROTL(state[0], 4); + state[1] ^= state[0]; + state[3] ^= state[2]; + ROTL(state[1], 6); + ROTL(state[3], 3); + state[0] -= state[1]; + state[2] -= state[3]; +} + +static inline void +xor4(unsigned char *out, const unsigned char *x, const unsigned char *y) +{ + out[0] = x[0] ^ y[0]; + out[1] = x[1] ^ y[1]; + out[2] = x[2] ^ y[2]; + out[3] = x[3] ^ y[3]; +} + +int +ipcrypt_encrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]) +{ + unsigned char state[4]; + + xor4(state, in, key); + arx_fwd(state); + xor4(state, state, key + 4); + arx_fwd(state); + xor4(state, state, key + 8); + arx_fwd(state); + xor4(out, state, key + 12); + + return 0; +} + +int +ipcrypt_decrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]) +{ + unsigned char state[4]; + + xor4(state, in, key + 12); + arx_bwd(state); + xor4(state, state, key + 8); + arx_bwd(state); + xor4(state, state, key + 4); + arx_bwd(state); + xor4(out, state, key); + + return 0; +} diff --git a/ext/ipcrypt/ipcrypt.h b/ext/ipcrypt/ipcrypt.h new file mode 100644 index 0000000000..5d07614a13 --- /dev/null +++ b/ext/ipcrypt/ipcrypt.h @@ -0,0 +1,24 @@ + +#ifndef ipcrypt_H +#define ipcrypt_H + +#define IPCRYPT_BYTES 4 +#define IPCRYPT_KEYBYTES 16 + +#ifdef __cplusplus +extern "C" { +#endif + +int ipcrypt_encrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]); + +int ipcrypt_decrypt(unsigned char out[IPCRYPT_BYTES], + const unsigned char in[IPCRYPT_BYTES], + const unsigned char key[IPCRYPT_KEYBYTES]); + +#ifdef __cplusplus +} /* End of the 'extern "C"' block */ +#endif + +#endif diff --git a/pdns/Makefile.am b/pdns/Makefile.am index fae32e50b9..ae36433cbe 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -1272,6 +1272,7 @@ testrunner_SOURCES = \ ednssubnet.cc \ gettime.cc gettime.hh \ gss_context.cc gss_context.hh \ + ipcrypt.cc ipcrypt.hh ../ext/ipcrypt/ipcrypt.c ../ext/ipcrypt.h \ iputils.cc \ ixfr.cc ixfr.hh \ logger.cc \ @@ -1301,6 +1302,7 @@ testrunner_SOURCES = \ test-dnsparser_cc.cc \ test-dnsparser_hh.cc \ test-dnsrecords_cc.cc \ + test-ipcrypt_cc.cc \ test-iputils_hh.cc \ test-ixfr_cc.cc \ test-lock_hh.cc \ diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc index 79e01d2e13..5095a71a36 100644 --- a/pdns/dnsdist-lua-bindings.cc +++ b/pdns/dnsdist-lua-bindings.cc @@ -31,6 +31,7 @@ #include "dolog.hh" #include "fstrm_logger.hh" #include "remote_logger.hh" +#include "ipcrypt.hh" void setupLuaBindings(bool client) { @@ -166,6 +167,13 @@ void setupLuaBindings(bool client) g_lua.registerFunction("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); }); g_lua.registerFunction("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); }); + g_lua.registerFunction("ipencrypt", [](const ComboAddress& ca, const std::string& key) { + return encryptCA(ca, key); + }); + g_lua.registerFunction("ipdecrypt", [](const ComboAddress& ca, const std::string& key) { + return decryptCA(ca, key); + }); + /* DNSName */ g_lua.registerFunction("isPartOf", &DNSName::isPartOf); g_lua.registerFunction("chopOff", [](DNSName&dn ) { return dn.chopOff(); }); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index ceeb6d0404..b38656ea2c 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -171,14 +171,13 @@ if HAVE_RE2 dnsdist_LDADD += $(RE2_LIBS) endif -if HAVE_DNS_OVER_TLS if HAVE_GNUTLS dnsdist_LDADD += -lgnutls endif if HAVE_LIBSSL dnsdist_LDADD += $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS) -endif +dnsdist_SOURCES += ipcrypt.cc ipcrypt.hh ext/ipcrypt/ipcrypt.c ext/ipcrypt/ipcrypt.h endif if !HAVE_LUA_HPP diff --git a/pdns/dnsdistdist/configure.ac b/pdns/dnsdistdist/configure.ac index 1013e0d437..f517885f06 100644 --- a/pdns/dnsdistdist/configure.ac +++ b/pdns/dnsdistdist/configure.ac @@ -57,13 +57,17 @@ PDNS_CHECK_LUA_HPP AM_CONDITIONAL([HAVE_GNUTLS], [false]) AM_CONDITIONAL([HAVE_LIBSSL], [false]) + +PDNS_CHECK_LIBCRYPTO + DNSDIST_ENABLE_DNS_OVER_TLS + AS_IF([test "x$enable_dns_over_tls" != "xno"], [ DNSDIST_WITH_GNUTLS DNSDIST_WITH_LIBSSL AS_IF([test "$HAVE_LIBSSL" = "1"], [ # we need libcrypto if libssl is enabled - PDNS_CHECK_LIBCRYPTO + ]) AS_IF([test "$HAVE_GNUTLS" = "0" -a "$HAVE_LIBSSL" = "0"], [ AC_MSG_ERROR([DNS over TLS support requested but neither GnuTLS nor OpenSSL are available]) diff --git a/pdns/dnsdistdist/ext/ipcrypt/LICENSE b/pdns/dnsdistdist/ext/ipcrypt/LICENSE new file mode 120000 index 0000000000..768695e8b9 --- /dev/null +++ b/pdns/dnsdistdist/ext/ipcrypt/LICENSE @@ -0,0 +1 @@ +/home/ahu/git/pdns/ext/ipcrypt/LICENSE \ No newline at end of file diff --git a/pdns/dnsdistdist/ext/ipcrypt/ipcrypt.c b/pdns/dnsdistdist/ext/ipcrypt/ipcrypt.c new file mode 120000 index 0000000000..0cc9c14851 --- /dev/null +++ b/pdns/dnsdistdist/ext/ipcrypt/ipcrypt.c @@ -0,0 +1 @@ +/home/ahu/git/pdns/ext/ipcrypt/ipcrypt.c \ No newline at end of file diff --git a/pdns/dnsdistdist/ext/ipcrypt/ipcrypt.h b/pdns/dnsdistdist/ext/ipcrypt/ipcrypt.h new file mode 120000 index 0000000000..e5042c9b44 --- /dev/null +++ b/pdns/dnsdistdist/ext/ipcrypt/ipcrypt.h @@ -0,0 +1 @@ +/home/ahu/git/pdns/ext/ipcrypt/ipcrypt.h \ No newline at end of file diff --git a/pdns/ipcrypt.cc b/pdns/ipcrypt.cc new file mode 100644 index 0000000000..394843d863 --- /dev/null +++ b/pdns/ipcrypt.cc @@ -0,0 +1,83 @@ +#include "ipcrypt.hh" +#include "ext/ipcrypt/ipcrypt.h" +#include + +static ComboAddress encryptCA4(const ComboAddress& ca, const std::string &key) +{ + if(key.size() != 16) + throw std::runtime_error("Need 128 bits of key for ipcrypt"); + + ComboAddress ret=ca; + + // always returns 0, has no failure mode + ipcrypt_encrypt( (unsigned char*)&ret.sin4.sin_addr.s_addr, + (const unsigned char*) &ca.sin4.sin_addr.s_addr, + (const unsigned char*)key.c_str()); + return ret; +} + +static ComboAddress decryptCA4(const ComboAddress& ca, const std::string &key) +{ + if(key.size() != 16) + throw std::runtime_error("Need 128 bits of key for ipcrypt"); + + ComboAddress ret=ca; + + // always returns 0, has no failure mode + ipcrypt_decrypt( (unsigned char*)&ret.sin4.sin_addr.s_addr, + (const unsigned char*) &ca.sin4.sin_addr.s_addr, + (const unsigned char*)key.c_str()); + return ret; +} + + +static ComboAddress encryptCA6(const ComboAddress& ca, const std::string &key) +{ + if(key.size() != 16) + throw std::runtime_error("Need 128 bits of key for ipcrypt"); + + ComboAddress ret=ca; + + AES_KEY wctx; + AES_set_encrypt_key((const unsigned char*)key.c_str(), 128, &wctx); + AES_encrypt((const unsigned char*)&ca.sin6.sin6_addr.s6_addr, + (unsigned char*)&ret.sin6.sin6_addr.s6_addr, &wctx); + + return ret; +} + +static ComboAddress decryptCA6(const ComboAddress& ca, const std::string &key) +{ + if(key.size() != 16) + throw std::runtime_error("Need 128 bits of key for ipcrypt"); + + ComboAddress ret=ca; + AES_KEY wctx; + AES_set_decrypt_key((const unsigned char*)key.c_str(), 128, &wctx); + AES_decrypt((const unsigned char*)&ca.sin6.sin6_addr.s6_addr, + (unsigned char*)&ret.sin6.sin6_addr.s6_addr, &wctx); + + return ret; +} + + +ComboAddress encryptCA(const ComboAddress& ca, const std::string& key) +{ + if(ca.sin4.sin_family == AF_INET) + return encryptCA4(ca, key); + else if(ca.sin4.sin_family == AF_INET6) + return encryptCA6(ca, key); + else + throw std::runtime_error("ipcrypt can't encrypt non-IP addresses"); +} + +ComboAddress decryptCA(const ComboAddress& ca, const std::string& key) +{ + if(ca.sin4.sin_family == AF_INET) + return decryptCA4(ca, key); + else if(ca.sin4.sin_family == AF_INET6) + return decryptCA6(ca, key); + else + throw std::runtime_error("ipcrypt can't decrypt non-IP addresses"); + +} diff --git a/pdns/ipcrypt.hh b/pdns/ipcrypt.hh new file mode 100644 index 0000000000..1c9faedd31 --- /dev/null +++ b/pdns/ipcrypt.hh @@ -0,0 +1,6 @@ +#pragma once +#include "iputils.hh" +#include + +ComboAddress encryptCA(const ComboAddress& ca, const std::string& key); +ComboAddress decryptCA(const ComboAddress& ca, const std::string& key); diff --git a/pdns/test-ipcrypt_cc.cc b/pdns/test-ipcrypt_cc.cc new file mode 100644 index 0000000000..b394997edb --- /dev/null +++ b/pdns/test-ipcrypt_cc.cc @@ -0,0 +1,69 @@ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include "ipcrypt.hh" +#include "misc.hh" + +using namespace boost; + +BOOST_AUTO_TEST_SUITE(test_ipcrypt_hh) + +BOOST_AUTO_TEST_CASE(test_ipcrypt4) +{ + ComboAddress ca("127.0.0.1"); + std::string key="0123456789ABCDEF"; + auto encrypted = encryptCA(ca, key); + + auto decrypted = decryptCA(encrypted, key); + BOOST_CHECK_EQUAL(ca.toString(), decrypted.toString()); +} + +BOOST_AUTO_TEST_CASE(test_ipcrypt4_vector) +{ + vector> tests{ // test vector from https://github.com/veorq/ipcrypt + {{"127.0.0.1"},{"114.62.227.59"}}, + {{"8.8.8.8"}, {"46.48.51.50"}}, + {{"1.2.3.4"}, {"171.238.15.199"}}}; + + std::string key="some 16-byte key"; + + for(const auto& p : tests) { + auto encrypted = encryptCA(ComboAddress(p.first), key); + BOOST_CHECK_EQUAL(encrypted.toString(), p.second); + auto decrypted = decryptCA(encrypted, key); + BOOST_CHECK_EQUAL(decrypted.toString(), p.first); + } + + ComboAddress ip("192.168.69.42"), out, dec; + string key2; + for(int n=0; n<16; ++n) + key2.append(1, (char)n+1); + + for (unsigned int i = 0; i < 100000000UL; i++) { + out=encryptCA(ip, key2); + // dec=decryptCA(out, key2); + // BOOST_CHECK(ip==dec); + ip=out; + } + + ComboAddress expected("93.155.197.186"); + + BOOST_CHECK_EQUAL(ip.toString(), expected.toString()); +} + + +BOOST_AUTO_TEST_CASE(test_ipcrypt6) +{ + ComboAddress ca("::1"); + std::string key="0123456789ABCDEF"; + auto encrypted = encryptCA(ca, key); + + auto decrypted = decryptCA(encrypted, key); + BOOST_CHECK_EQUAL(ca.toString(), decrypted.toString()); +} + + +BOOST_AUTO_TEST_SUITE_END()