]> granicus.if.org Git - pdns/commitdiff
Add support for encrypting IP addresses #gdpr
authorbert hubert <bert.hubert@netherlabs.nl>
Fri, 2 Feb 2018 10:43:20 +0000 (11:43 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 25 Mar 2019 09:22:39 +0000 (10:22 +0100)
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.

13 files changed:
ext/ipcrypt/LICENSE [new file with mode: 0644]
ext/ipcrypt/ipcrypt.c [new file with mode: 0644]
ext/ipcrypt/ipcrypt.h [new file with mode: 0644]
pdns/Makefile.am
pdns/dnsdist-lua-bindings.cc
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/ext/ipcrypt/LICENSE [new symlink]
pdns/dnsdistdist/ext/ipcrypt/ipcrypt.c [new symlink]
pdns/dnsdistdist/ext/ipcrypt/ipcrypt.h [new symlink]
pdns/ipcrypt.cc [new file with mode: 0644]
pdns/ipcrypt.hh [new file with mode: 0644]
pdns/test-ipcrypt_cc.cc [new file with mode: 0644]

diff --git a/ext/ipcrypt/LICENSE b/ext/ipcrypt/LICENSE
new file mode 100644 (file)
index 0000000..0a199e5
--- /dev/null
@@ -0,0 +1,14 @@
+Copyright (c) 2015-2018, Frank Denis <j at pureftpd dot org>
+
+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 (file)
index 0000000..6ef464a
--- /dev/null
@@ -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 (file)
index 0000000..5d07614
--- /dev/null
@@ -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
index fae32e50b946b7aaafd9d825bde01ab10773c0cb..ae36433cbe3bbe3219d3864b39a77fc50773eca3 100644 (file)
@@ -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 \
index 79e01d2e13792acb0162eab0757f63caea9d4115..5095a71a361b495665581e910d12603854a4cc8b 100644 (file)
@@ -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<ComboAddress(ComboAddress::*)()>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); });
   g_lua.registerFunction<bool(nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); });
 
+  g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipencrypt", [](const ComboAddress& ca, const std::string& key) {
+      return encryptCA(ca, key);
+    });
+  g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipdecrypt", [](const ComboAddress& ca, const std::string& key) {
+      return decryptCA(ca, key);
+    });
+  
   /* DNSName */
   g_lua.registerFunction("isPartOf", &DNSName::isPartOf);
   g_lua.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
index ceeb6d04048aef9f151cb51b60be921855be2a60..b38656ea2c38bc8cce15d192601bec27ba24b761 100644 (file)
@@ -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
index 1013e0d437682a6fe0f703f98b883440ea66df0c..f517885f06779cb24f06d8258fc3ccf5790c8431 100644 (file)
@@ -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 (symlink)
index 0000000..768695e
--- /dev/null
@@ -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 (symlink)
index 0000000..0cc9c14
--- /dev/null
@@ -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 (symlink)
index 0000000..e5042c9
--- /dev/null
@@ -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 (file)
index 0000000..394843d
--- /dev/null
@@ -0,0 +1,83 @@
+#include "ipcrypt.hh"
+#include "ext/ipcrypt/ipcrypt.h"
+#include <openssl/aes.h>
+
+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 (file)
index 0000000..1c9faed
--- /dev/null
@@ -0,0 +1,6 @@
+#pragma once
+#include "iputils.hh"
+#include <string>
+
+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 (file)
index 0000000..b394997
--- /dev/null
@@ -0,0 +1,69 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <boost/test/unit_test.hpp>
+#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<pair<string,string>>  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()