]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Revert of 9385 (which removed GSS_TSIG functionality) and rebase to master
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 7 Dec 2021 16:07:43 +0000 (17:07 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Fri, 2 Sep 2022 12:22:48 +0000 (14:22 +0200)
Reformat according to project settings

We prefer C++ style casts and change NULL into nullptr

Move includes from gss_context.hh to gss_context.cc if possible, remove unused ones and move from boost::shared_ptr to std::shared_ptr.

Protect access to global maps by a mechanic translation to use
LockGuarded. The scopes of the locks should be verified, they might need
to be wider!

23 files changed:
configure.ac
docs/domainmetadata.rst
docs/tsig.rst
m4/pdns_enable_gss_tsig.m4 [new file with mode: 0644]
modules/remotebackend/Makefile.am
pdns/Makefile.am
pdns/dnspacket.cc
pdns/dnssecinfra.cc
pdns/gss_context.cc [new file with mode: 0644]
pdns/gss_context.hh [new file with mode: 0644]
pdns/ixplore.cc
pdns/packethandler.cc
pdns/packethandler.hh
pdns/pdnsutil.cc
pdns/recursordist/Makefile.am
pdns/recursordist/gss_context.cc [new symlink]
pdns/recursordist/gss_context.hh [new symlink]
pdns/resolver.cc
pdns/rfc2136handler.cc
pdns/saxfr.cc
pdns/tkey.cc
pdns/tsigverifier.cc
pdns/ws-auth.cc

index 42c1bd21ea4d36bab959de10308c31244fe977ec..34b74a6e1a95ab61c1943c62c081efd024c6a1c9 100644 (file)
@@ -164,6 +164,7 @@ AC_SUBST([LIBDL], [$lt_cv_dlopen_libs])
 
 PDNS_ENABLE_VERBOSE_LOGGING
 PDNS_ENABLE_PKCS11
+PDNS_ENABLE_GSS_TSIG
 
 AC_SUBST([socketdir])
 socketdir="/var/run"
@@ -415,6 +416,9 @@ AS_IF([test "x$LUAPC" != "x"],
 AS_IF([test "x$enable_experimental_pkcs11" = "xyes"],
   [AC_MSG_NOTICE([PKCS-11: yes])]
 )
+AS_IF([test "x$enable_experimental_gss_tsig" = "xyes"],
+  [AC_MSG_NOTICE([GSS-TSIG: yes])]
+)
 AS_IF([test "x$enable_lua_records" = "xyes"],
   [AC_MSG_NOTICE([LUA records: yes])]
 )
index 382e68c053b77d2a0086d15cfb05dfbe360a6a1e..a021fa6ea494475811fc2ef4220406492d0865c2 100644 (file)
@@ -112,8 +112,6 @@ Use this named TSIG key to retrieve this zone from its master, see :ref:`tsig-pr
 
 GSS-ALLOW-AXFR-PRINCIPAL
 ------------------------
-  .. versionchanged:: 4.3.1
-    GSS support was removed
 
 Allow this GSS principal to perform AXFR retrieval. Most commonly it is
 ``host/something@REALM``, ``DNS/something@REALM`` or ``user@REALM``.
@@ -121,8 +119,6 @@ Allow this GSS principal to perform AXFR retrieval. Most commonly it is
 
 GSS-ACCEPTOR-PRINCIPAL
 ----------------------
-  .. versionchanged:: 4.4.0
-    GSS support was removed
 
 Use this principal for accepting GSS context.
 (See :ref:`tsig-gss-tsig`).
index bd0a12fb6c2fdd37b353be6607c5151d19663b30..1f7941c89f59522768b8291613b8c9890e4831c5 100644 (file)
@@ -119,8 +119,6 @@ the master, not just those about AXFR requests.
 
 GSS-TSIG support
 ----------------
-  .. versionchanged:: 4.4.0
-    GSS support was removed
 
 GSS-TSIG allows authentication and authorization of DNS updates or AXFR
 using Kerberos with TSIG signatures.
diff --git a/m4/pdns_enable_gss_tsig.m4 b/m4/pdns_enable_gss_tsig.m4
new file mode 100644 (file)
index 0000000..9acd756
--- /dev/null
@@ -0,0 +1,24 @@
+AC_DEFUN([PDNS_ENABLE_GSS_TSIG],[
+  AC_MSG_CHECKING([whether to enable experimental GSS-TSIG support])
+  AC_ARG_ENABLE([experimental_gss_tsig],
+    AS_HELP_STRING([--enable-experimental-gss-tsig],
+      [enable experimental GSS-TSIG support @<:@default=no@:>@]
+    ),
+    [enable_experimental_gss_tsig=$enableval],
+    [enable_experimental_gss_tsig=no]
+  )
+
+  AC_MSG_RESULT([$enable_experimental_gss_tsig])
+
+  AM_CONDITIONAL([GSS_TSIG],[test "x$enable_experimental_gss_tsig" != "xno"])
+  AC_SUBST(GSS_TSIG)
+  AS_IF([test "x$enable_experimental_gss_tsig" != "xno"],
+   [PKG_CHECK_MODULES([GSS], [krb5 krb5-gssapi gss],
+      [
+        AC_DEFINE([ENABLE_GSS_TSIG], [1], [Define to 1 if you want to enable GSS-TSIG support])
+        GSS_TSIG=yes
+      ],
+      [AC_MSG_ERROR([Required libraries for GSS-TSIG not found])]
+   )],
+    [GSS_TSIG=no])
+])
index 1b5fc20025856dc2b86aabad2d5f2dc1ff836151..718913dee4bc3e63fd58a250acd387206418208a 100644 (file)
@@ -121,6 +121,7 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/ednscookies.cc \
        ../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \
        ../../pdns/ednssubnet.cc \
+       ../../pdns/gss_context.cc ../../pdns/gss_context.hh \
        ../../pdns/iputils.cc \
        ../../pdns/json.hh ../../pdns/json.cc \
        ../../pdns/logger.cc \
@@ -175,6 +176,13 @@ libtestremotebackend_la_CPPFLAGS += \
        $(P11KIT1_CFLAGS)
 endif
 
+if GSS_TSIG
+libtestremotebackend_la_LIBADD += \
+       $(GSS_LIBS)
+libtestremotebackend_la_CPPFLAGS+= \
+       $(GSS_CFLAGS)
+endif
+
 remotebackend_http_test_SOURCES = \
        test-remotebackend-http.cc \
        test-remotebackend-keys.hh \
index ec93fadc6f3eea8bd49ee1b79a8cf416016c3183..4712a8ec8884a492410c698efed2a0456f43196e 100644 (file)
@@ -35,6 +35,10 @@ if LUA
 AM_CPPFLAGS +=$(LUA_CFLAGS)
 endif
 
+if GSS_TSIG
+AM_CPPFLAGS +=$(GSS_CFLAGS)
+endif
+
 if LIBSODIUM
 AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
 endif
@@ -230,6 +234,7 @@ pdns_server_SOURCES = \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
        histogram.hh \
+        gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
        ixfr.cc ixfr.hh \
        json.cc json.hh \
@@ -328,6 +333,10 @@ if LUA
 pdns_server_LDADD += $(LUA_LIBS)
 endif
 
+if GSS_TSIG
+pdns_server_LDADD += $(GSS_LIBS)
+endif
+
 pdnsutil_SOURCES = \
        arguments.cc \
        auth-caches.cc auth-caches.hh \
@@ -360,6 +369,7 @@ pdnsutil_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
+       gss_context.cc gss_context.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc iputils.hh \
        json.cc \
@@ -429,6 +439,10 @@ if LUA
 pdnsutil_LDADD += $(LUA_LIBS)
 endif
 
+if GSS_TSIG
+pdnsutil_LDADD += $(GSS_LIBS)
+endif
+
 zone2sql_SOURCES = \
        arguments.cc \
        base32.cc \
@@ -525,6 +539,10 @@ zone2ldap_SOURCES = \
 zone2ldap_LDADD = $(LIBCRYPTO_LIBS)
 zone2ldap_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 
+if GSS_TSIG
+zone2ldap_LDADD += $(GSS_LIBS)
+endif
+
 sdig_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
@@ -664,6 +682,7 @@ saxfr_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -685,6 +704,10 @@ saxfr_SOURCES += pkcs11signers.cc pkcs11signers.hh
 saxfr_LDADD += $(P11KIT1_LIBS)
 endif
 
+if GSS_TSIG
+saxfr_LDADD += $(GSS_LIBS)
+endif
+
 ixfrdist_SOURCES = \
        arguments.cc \
        axfr-retriever.cc \
@@ -699,6 +722,7 @@ ixfrdist_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.hh iputils.cc \
        ixfr.cc ixfr.hh \
        ixfrdist-stats.hh ixfrdist-stats.cc \
@@ -746,6 +770,11 @@ ixfrdist_SOURCES += pkcs11signers.cc pkcs11signers.hh
 ixfrdist_LDADD += $(P11KIT1_LIBS)
 endif
 
+if GSS_TSIG
+ixfrdist_LDADD += $(GSS_LIBS)
+endif
+
+
 ixplore_SOURCES = \
        arguments.cc \
        axfr-retriever.cc \
@@ -759,6 +788,7 @@ ixplore_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+        gss_context.cc gss_context.hh  \
        iputils.cc \
        ixfr.cc ixfr.hh \
        ixfrutils.cc ixfrutils.hh \
@@ -779,6 +809,9 @@ ixplore_SOURCES = \
 
 ixplore_LDADD = $(LIBCRYPTO_LIBS)
 ixplore_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
+if GSS_TSIG
+ixplore_LDADD += $(GSS_LIBS)
+endif
 
 if PKCS11
 ixplore_SOURCES += pkcs11signers.cc pkcs11signers.hh
@@ -825,6 +858,7 @@ nsec3dig_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -838,9 +872,14 @@ nsec3dig_SOURCES = \
        svc-records.cc svc-records.hh \
        unix_utility.cc
 
+
 nsec3dig_LDADD = $(LIBCRYPTO_LIBS)
 nsec3dig_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 
+if GSS_TSIG
+nsec3dig_LDADD += $(GSS_LIBS)
+endif
+
 if PKCS11
 nsec3dig_SOURCES += pkcs11signers.cc pkcs11signers.hh
 nsec3dig_LDADD += $(P11KIT1_LIBS)
@@ -902,6 +941,7 @@ tsig_tests_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -921,6 +961,10 @@ tsig_tests_SOURCES = \
 tsig_tests_LDADD = $(LIBCRYPTO_LIBS)
 tsig_tests_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 
+if GSS_TSIG
+toysdig_LDADD += $(GSS_LIBS)
+endif
+
 if PKCS11
 tsig_tests_SOURCES += pkcs11signers.cc pkcs11signers.hh
 tsig_tests_LDADD += $(P11KIT1_LIBS)
@@ -938,6 +982,7 @@ speedtest_SOURCES = \
        dnsrecords.cc \
        dnssecinfra.cc dnssecinfra.hh \
        dnswriter.cc dnswriter.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc \
        logger.cc \
        misc.cc misc.hh \
@@ -1352,6 +1397,7 @@ testrunner_SOURCES = \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
        gettime.cc gettime.hh \
+        gss_context.cc gss_context.hh \
        histogram.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc \
@@ -1437,6 +1483,10 @@ testrunner_LDADD = \
        $(LIBDL) \
        $(IPCRYPT_LIBS)
 
+if GSS_TSIG
+testrunner_LDADD += $(GSS_LIBS)
+endif
+
 if PKCS11
 testrunner_SOURCES += pkcs11signers.cc pkcs11signers.hh
 testrunner_LDADD += $(P11KIT1_LIBS)
index c7af0607cee441a34c3890082d6083e1793b5382..054e3539c63ff9b56944393d832773e7ac71c0fb 100644 (file)
@@ -49,6 +49,7 @@
 #include "dnssecinfra.hh"
 #include "base64.hh"
 #include "ednssubnet.hh"
+#include "gss_context.hh"
 #include "dns_random.hh"
 #include "shuffle.hh"
 
@@ -732,13 +733,15 @@ bool DNSPacket::checkForCorrectTSIG(UeberBackend* B, DNSName* keyname, string* s
   if (tt.algo == DNSName("hmac-md5.sig-alg.reg.int"))
     tt.algo = DNSName("hmac-md5");
 
-  string secret64;
-  if (!B->getTSIGKey(*keyname, tt.algo, secret64)) {
-    g_log << Logger::Error << "Packet for domain '" << this->qdomain << "' denied: can't find TSIG key with name '" << *keyname << "' and algorithm '" << tt.algo << "'" << endl;
-    return false;
+  if (tt.algo != DNSName("gss-tsig")) {
+    string secret64;
+    if(!B->getTSIGKey(*keyname, tt.algo, secret64)) {
+      g_log << Logger::Error << "Packet for domain '" << this->qdomain << "' denied: can't find TSIG key with name '" << *keyname << "' and algorithm '" << tt.algo << "'" << endl;
+      return false;
+    }
+    B64Decode(secret64, *secret);
+    tt.secret = *secret;
   }
-  B64Decode(secret64, *secret);
-  tt.secret = *secret;
 
   bool result;
 
index 43d623f93e18e91ce3d64b4fa2086fe247240eb5..95693838ea63c369faf5e83fac138edd280089d4 100644 (file)
@@ -44,6 +44,7 @@
 #ifdef HAVE_P11KIT1
 #include "pkcs11signers.hh"
 #endif
+#include "gss_context.hh"
 #include "misc.hh"
 
 using namespace boost::assign;
@@ -697,7 +698,9 @@ void addTSIG(DNSPacketWriter& pw, TSIGRecordContent& trc, const DNSName& tsigkey
   string toSign = makeTSIGPayload(tsigprevious, reinterpret_cast<const char*>(pw.getContent().data()), pw.getContent().size(), tsigkeyname, trc, timersonly);
 
   if (algo == TSIG_GSS) {
-    throw PDNSException(string("Unsupported TSIG GSS algorithm ") + trc.d_algoName.toLogString());
+    if (!gss_add_signature(tsigkeyname, toSign, trc.d_mac)) {
+      throw PDNSException(string("Could not add TSIG signature with algorithm 'gss-tsig' and key name '")+tsigkeyname.toLogString()+string("'"));
+    }
   } else {
     trc.d_mac = calculateHMAC(tsigsecret, toSign, algo);
     //  trc.d_mac[0]++; // sabotage
@@ -732,7 +735,10 @@ bool validateTSIG(const std::string& packet, size_t sigPos, const TSIGTriplet& t
   tsigMsg = makeTSIGMessageFromTSIGPacket(packet, sigPos, tt.name, trc, previousMAC, timersOnly, dnsHeaderOffset);
 
   if (algo == TSIG_GSS) {
-    throw std::runtime_error("Unsupported TSIG GSS algorithm " + trc.d_algoName.toLogString());
+    GssContext gssctx(tt.name);
+    if (!gss_verify_signature(tt.name, tsigMsg, theirMAC)) {
+      throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate");
+    }
   } else {
     string ourMac = calculateHMAC(tt.secret, tsigMsg, algo);
 
diff --git a/pdns/gss_context.cc b/pdns/gss_context.cc
new file mode 100644 (file)
index 0000000..818322e
--- /dev/null
@@ -0,0 +1,568 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "gss_context.hh"
+
+#include "lock.hh"
+#include "logger.hh"
+
+#ifndef ENABLE_GSS_TSIG
+
+bool GssContext::supported() { return false; }
+GssContext::GssContext() :
+  d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
+GssContext::GssContext(const DNSName& label) :
+  d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
+void GssContext::setLocalPrincipal(const std::string& name) {}
+bool GssContext::getLocalPrincipal(std::string& name) { return false; }
+void GssContext::setPeerPrincipal(const std::string& name) {}
+bool GssContext::getPeerPrincipal(std::string& name) { return false; }
+void GssContext::generateLabel(const std::string& suffix) {}
+void GssContext::setLabel(const DNSName& label) {}
+bool GssContext::init(const std::string& input, std::string& output) { return false; }
+bool GssContext::accept(const std::string& input, std::string& output) { return false; }
+bool GssContext::destroy() { return false; }
+bool GssContext::expired() { return false; }
+bool GssContext::valid() { return false; }
+bool GssContext::sign(const std::string& input, std::string& output) { return false; }
+bool GssContext::verify(const std::string& input, const std::string& signature) { return false; }
+GssContextError GssContext::getError() { return GSS_CONTEXT_UNSUPPORTED; }
+
+#else
+
+static string gsserror(OM_uint32 status_code)
+{
+  OM_uint32 maj_status;
+  OM_uint32 min_status;
+  OM_uint32 message_context = 0;
+  gss_buffer_desc status_string;
+  std::basic_ostringstream<char> ret;
+  bool first = true;
+  do {
+    if (!first) {
+      ret << '/';
+    } else {
+      first = false;
+    }
+    maj_status = gss_display_status(&min_status,
+                                    status_code,
+                                    GSS_C_GSS_CODE,
+                                    GSS_C_NO_OID,
+                                    &message_context,
+                                    &status_string);
+    if (maj_status == GSS_S_COMPLETE) {
+      ret << string(static_cast<char*>(status_string.value), status_string.length);
+      gss_release_buffer(&min_status, &status_string);
+    } else {
+      // XXX to release or not to release?
+      ret << std::to_string(status_code);
+    }
+  } while (message_context != 0);
+  return ret.str();
+}
+
+class GssCredential : boost::noncopyable
+{
+public:
+  GssCredential(const std::string& name, const gss_cred_usage_t usage) :
+    d_valid(false), d_nameS(name), d_name(GSS_C_NO_NAME), d_cred(GSS_C_NO_CREDENTIAL), d_usage(usage)
+  {
+    gss_buffer_desc buffer;
+
+    if (!name.empty()) {
+      buffer.length = name.size();
+      buffer.value = const_cast<void*>(static_cast<const void*>(name.c_str()));
+      d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
+      if (d_maj != GSS_S_COMPLETE) {
+        d_name = GSS_C_NO_NAME;
+        d_valid = false;
+        return;
+      }
+    }
+
+    renew();
+  };
+
+  ~GssCredential()
+  {
+    OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+    if (d_cred != GSS_C_NO_CREDENTIAL)
+      tmp_maj = gss_release_cred(&tmp_min, &d_cred);
+    if (d_name != GSS_C_NO_NAME)
+      tmp_maj = gss_release_name(&tmp_min, &d_name);
+  };
+
+  bool expired() const
+  {
+    if (d_expires == -1)
+      return false;
+    return time(nullptr) > d_expires;
+  }
+
+  bool renew()
+  {
+    OM_uint32 time_rec, tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+    d_maj = gss_acquire_cred(&d_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, nullptr, &time_rec);
+
+    if (d_maj != GSS_S_COMPLETE) {
+      d_valid = false;
+      tmp_maj = gss_release_name(&tmp_min, &d_name);
+      d_name = GSS_C_NO_NAME;
+      return false;
+    }
+
+    d_valid = true;
+
+    if (time_rec > GSS_C_INDEFINITE) {
+      d_expires = time(nullptr) + time_rec;
+    }
+    else {
+      d_expires = -1;
+    }
+
+    return true;
+  }
+
+  bool valid()
+  {
+    return d_valid && !expired();
+  }
+
+  OM_uint32 d_maj, d_min;
+
+  bool d_valid;
+  int64_t d_expires;
+  std::string d_nameS;
+  gss_name_t d_name;
+  gss_cred_id_t d_cred;
+  gss_cred_usage_t d_usage;
+};
+
+LockGuarded<std::map<std::string, std::shared_ptr<GssCredential>>> s_gss_accept_creds;
+LockGuarded<std::map<std::string, std::shared_ptr<GssCredential>>> s_gss_init_creds;
+
+class GssSecContext : boost::noncopyable
+{
+public:
+  GssSecContext(std::shared_ptr<GssCredential> cred)
+  {
+    if (!cred->valid()) {
+      throw PDNSException("Invalid credential " + cred->d_nameS + ": " + gsserror(cred->d_maj));
+    }
+    d_cred = cred;
+    d_state = GssStateInitial;
+    d_ctx = GSS_C_NO_CONTEXT;
+    d_expires = 0;
+    d_maj = d_min = 0;
+    d_peer_name = GSS_C_NO_NAME;
+    d_type = GSS_CONTEXT_NONE;
+  }
+
+  ~GssSecContext()
+  {
+    OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+    if (d_ctx != GSS_C_NO_CONTEXT) {
+      tmp_maj = gss_delete_sec_context(&tmp_min, &d_ctx, GSS_C_NO_BUFFER);
+    }
+    if (d_peer_name != GSS_C_NO_NAME) {
+      tmp_maj = gss_release_name(&tmp_min, &(d_peer_name));
+    }
+  }
+
+  GssContextType d_type;
+  gss_ctx_id_t d_ctx;
+  gss_name_t d_peer_name;
+  int64_t d_expires;
+  std::shared_ptr<GssCredential> d_cred;
+  OM_uint32 d_maj, d_min;
+
+  enum
+  {
+    GssStateInitial,
+    GssStateNegotiate,
+    GssStateComplete,
+    GssStateError
+  } d_state;
+};
+
+LockGuarded<std::map<DNSName, std::shared_ptr<GssSecContext>>> s_gss_sec_context;
+
+bool GssContext::supported() { return true; }
+
+void GssContext::initialize()
+{
+  d_peerPrincipal = "";
+  d_localPrincipal = "";
+  d_error = GSS_CONTEXT_NO_ERROR;
+  d_type = GSS_CONTEXT_NONE;
+}
+
+GssContext::GssContext()
+{
+  initialize();
+  generateLabel("pdns.tsig.");
+}
+
+GssContext::GssContext(const DNSName& label)
+{
+  initialize();
+  setLabel(label);
+}
+
+void GssContext::generateLabel(const std::string& suffix)
+{
+  std::ostringstream oss;
+  oss << std::hex << time(nullptr) << "." << suffix;
+  setLabel(DNSName(oss.str()));
+}
+
+void GssContext::setLabel(const DNSName& label)
+{
+  d_label = label;
+  auto lock = s_gss_sec_context.lock();
+  if (lock->find(d_label) != lock->end()) {
+    d_ctx = (*lock)[d_label];
+    d_type = d_ctx->d_type;
+  }
+}
+
+bool GssContext::expired()
+{
+  return (!d_ctx || (d_ctx->d_expires > -1 && d_ctx->d_expires < time(nullptr)));
+}
+
+bool GssContext::valid()
+{
+  return (d_ctx && !expired() && d_ctx->d_state == GssSecContext::GssStateComplete);
+}
+
+bool GssContext::init(const std::string& input, std::string& output)
+{
+  OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+  OM_uint32 maj, min;
+  gss_buffer_desc recv_tok, send_tok, buffer;
+  OM_uint32 flags;
+  OM_uint32 expires;
+
+  std::shared_ptr<GssCredential> cred;
+  if (d_label.empty()) {
+    d_error = GSS_CONTEXT_INVALID;
+    return false;
+  }
+
+  d_type = GSS_CONTEXT_INIT;
+
+  {
+    auto lock = s_gss_init_creds.lock();
+    if (lock->find(d_localPrincipal) != lock->end()) {
+      cred = (*lock)[d_localPrincipal];
+    }
+    else {
+      (*lock)[d_localPrincipal] = std::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE);
+      cred = (*lock)[d_localPrincipal];
+    }
+  }
+
+  // see if we can find a context in non-completed state
+  if (d_ctx) {
+    if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
+      d_error = GSS_CONTEXT_INVALID;
+      return false;
+    }
+  }
+  else {
+    // make context
+    auto lock = s_gss_sec_context.lock();
+    (*lock)[d_label] = std::make_shared<GssSecContext>(cred);
+    (*lock)[d_label]->d_type = d_type;
+    d_ctx = (*lock)[d_label];
+    d_ctx->d_state = GssSecContext::GssStateNegotiate;
+  }
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+
+  if (!d_peerPrincipal.empty()) {
+    buffer.value = const_cast<void*>(static_cast<const void*>(d_peerPrincipal.c_str()));
+    buffer.length = d_peerPrincipal.size();
+    maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_ctx->d_peer_name));
+    if (maj != GSS_S_COMPLETE) {
+      processError("gss_import_name", maj, min);
+      return false;
+    }
+  }
+
+  maj = gss_init_sec_context(&min, cred->d_cred, &(d_ctx->d_ctx), d_ctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires);
+
+  if (send_tok.length > 0) {
+    output.assign(static_cast<char*>(send_tok.value), send_tok.length);
+    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
+  }
+
+  if (maj == GSS_S_COMPLETE) {
+    if (expires > GSS_C_INDEFINITE) {
+      d_ctx->d_expires = time(nullptr) + expires;
+    }
+    else {
+      d_ctx->d_expires = -1;
+    }
+    d_ctx->d_state = GssSecContext::GssStateComplete;
+    return true;
+  }
+  else if (maj != GSS_S_CONTINUE_NEEDED) {
+    processError("gss_init_sec_context", maj, min);
+  }
+
+  return (maj == GSS_S_CONTINUE_NEEDED);
+}
+
+bool GssContext::accept(const std::string& input, std::string& output)
+{
+  OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+  OM_uint32 maj, min;
+  gss_buffer_desc recv_tok, send_tok;
+  OM_uint32 flags;
+  OM_uint32 expires;
+
+  std::shared_ptr<GssCredential> cred;
+  if (d_label.empty()) {
+    d_error = GSS_CONTEXT_INVALID;
+    return false;
+  }
+
+  d_type = GSS_CONTEXT_ACCEPT;
+  {
+    auto lock = s_gss_accept_creds.lock();
+    if (lock->find(d_localPrincipal) != lock->end()) {
+      cred = (*lock)[d_localPrincipal];
+    }
+    else {
+      (*lock)[d_localPrincipal] = std::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT);
+      cred = (*lock)[d_localPrincipal];
+    }
+  }
+
+  // see if we can find a context in non-completed state
+  if (d_ctx) {
+    if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
+      d_error = GSS_CONTEXT_INVALID;
+      return false;
+    }
+  }
+  else {
+    // make context
+    auto lock = s_gss_sec_context.lock();
+    (*lock)[d_label] = std::make_shared<GssSecContext>(cred);
+    (*lock)[d_label]->d_type = d_type;
+    d_ctx = (*lock)[d_label];
+    d_ctx->d_state = GssSecContext::GssStateNegotiate;
+  }
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+
+  maj = gss_accept_sec_context(&min, &(d_ctx->d_ctx), cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &(d_ctx->d_peer_name), nullptr, &send_tok, &flags, &expires, nullptr);
+
+  if (send_tok.length > 0) {
+    output.assign(static_cast<char*>(send_tok.value), send_tok.length);
+    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
+  }
+
+  if (maj == GSS_S_COMPLETE) {
+    if (expires > GSS_C_INDEFINITE) {
+      d_ctx->d_expires = time(nullptr) + expires;
+    }
+    else {
+      d_ctx->d_expires = -1;
+    }
+    d_ctx->d_state = GssSecContext::GssStateComplete;
+    return true;
+  }
+  else if (maj != GSS_S_CONTINUE_NEEDED) {
+    processError("gss_accept_sec_context", maj, min);
+  }
+  return (maj == GSS_S_CONTINUE_NEEDED);
+};
+
+bool GssContext::sign(const std::string& input, std::string& output)
+{
+  OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
+  OM_uint32 maj, min;
+
+  gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc send_tok = GSS_C_EMPTY_BUFFER;
+
+  recv_tok.length = input.size();
+  recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+
+  maj = gss_get_mic(&min, d_ctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
+
+  if (send_tok.length > 0) {
+    output.assign(static_cast<char*>(send_tok.value), send_tok.length);
+    tmp_maj = gss_release_buffer(&tmp_min, &send_tok);
+  }
+
+  if (maj != GSS_S_COMPLETE) {
+    processError("gss_get_mic", maj, min);
+  }
+
+  return (maj == GSS_S_COMPLETE);
+}
+
+bool GssContext::verify(const std::string& input, const std::string& signature)
+{
+  OM_uint32 maj, min;
+
+  gss_buffer_desc recv_tok = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc sign_tok = GSS_C_EMPTY_BUFFER;
+
+  recv_tok.length = input.size();
+  recv_tok.value =  const_cast<void*>(static_cast<const void*>(input.c_str()));
+  sign_tok.length = signature.size();
+  sign_tok.value =  const_cast<void*>(static_cast<const void*>(signature.c_str()));
+
+  maj = gss_verify_mic(&min, d_ctx->d_ctx, &recv_tok, &sign_tok, nullptr);
+
+  if (maj != GSS_S_COMPLETE) {
+    processError("gss_get_mic", maj, min);
+  }
+
+  return (maj == GSS_S_COMPLETE);
+}
+
+bool GssContext::destroy()
+{
+  return false;
+}
+
+void GssContext::setLocalPrincipal(const std::string& name)
+{
+  d_localPrincipal = name;
+}
+
+bool GssContext::getLocalPrincipal(std::string& name)
+{
+  name = d_localPrincipal;
+  return name.size() > 0;
+}
+
+void GssContext::setPeerPrincipal(const std::string& name)
+{
+  d_peerPrincipal = name;
+}
+
+bool GssContext::getPeerPrincipal(std::string& name)
+{
+  gss_buffer_desc value;
+  OM_uint32 maj, min;
+
+  if (d_ctx->d_peer_name != GSS_C_NO_NAME) {
+    maj = gss_display_name(&min, d_ctx->d_peer_name, &value, nullptr);
+    if (maj == GSS_S_COMPLETE && value.length > 0) {
+      name.assign(static_cast<char*>(value.value), value.length);
+      maj = gss_release_buffer(&min, &value);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+  else {
+    return false;
+  }
+}
+
+void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min)
+{
+  OM_uint32 tmp_min;
+  gss_buffer_desc msg;
+  OM_uint32 msg_ctx;
+
+  msg_ctx = 0;
+  while (1) {
+    ostringstream oss;
+    gss_display_status(&tmp_min, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
+    oss << method << ": " << msg.value;
+    d_gss_errors.push_back(oss.str());
+    if (!msg_ctx)
+      break;
+  }
+  msg_ctx = 0;
+  while (1) {
+    ostringstream oss;
+    gss_display_status(&tmp_min, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
+    oss << method << ": " << msg.value;
+    d_gss_errors.push_back(oss.str());
+    if (!msg_ctx)
+      break;
+  }
+}
+
+#endif
+
+bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac)
+{
+  string tmp_mac;
+  GssContext gssctx(context);
+  if (!gssctx.valid()) {
+    g_log << Logger::Error << "GSS context '" << context << "' is not valid" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+
+  if (!gssctx.sign(message, tmp_mac)) {
+    g_log << Logger::Error << "Could not sign message using GSS context '" << context << "'" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+  mac = tmp_mac;
+  return true;
+}
+
+bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac)
+{
+  GssContext gssctx(context);
+  if (!gssctx.valid()) {
+    g_log << Logger::Error << "GSS context '" << context << "' is not valid" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+
+  if (!gssctx.verify(message, mac)) {
+    g_log << Logger::Error << "Could not verify message using GSS context '" << context << "'" << endl;
+    for (const string& error : gssctx.getErrorStrings()) {
+      g_log << Logger::Error << "GSS error: " << error << endl;
+      ;
+    }
+    return false;
+  }
+  return true;
+}
diff --git a/pdns/gss_context.hh b/pdns/gss_context.hh
new file mode 100644 (file)
index 0000000..0fcf1be
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "config.h"
+
+#include "namespaces.hh"
+#include "pdnsexception.hh"
+#include "dns.hh"
+
+#ifdef ENABLE_GSS_TSIG
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+#include <gssapi/gssapi_ext.h>
+#endif
+
+
+//! Generic errors
+enum GssContextError
+{
+  GSS_CONTEXT_NO_ERROR,
+  GSS_CONTEXT_UNSUPPORTED,
+  GSS_CONTEXT_NOT_FOUND,
+  GSS_CONTEXT_NOT_INITIALIZED,
+  GSS_CONTEXT_INVALID,
+  GSS_CONTEXT_EXPIRED,
+  GSS_CONTEXT_ALREADY_INITIALIZED
+};
+
+//! GSS context types
+enum GssContextType
+{
+  GSS_CONTEXT_NONE,
+  GSS_CONTEXT_INIT,
+  GSS_CONTEXT_ACCEPT
+};
+
+class GssSecContext;
+
+/*! Class for representing GSS names, such as host/host.domain.com@REALM.
+*/
+class GssName
+{
+public:
+  //! Initialize to empty name
+  GssName()
+  {
+    setName("");
+  };
+
+  //! Initialize using specific name
+  GssName(const std::string& name)
+  {
+    setName(name);
+  };
+
+  //! Parse name into native representation
+  bool setName(const std::string& name)
+  {
+#ifdef ENABLE_GSS_TSIG
+    gss_buffer_desc buffer;
+    d_name = GSS_C_NO_NAME;
+
+    if (!name.empty()) {
+      buffer.length = name.size();
+      buffer.value = (void*)name.c_str();
+      d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
+      return d_maj == GSS_S_COMPLETE;
+    }
+
+    return true;
+#endif
+    return false;
+  };
+
+  ~GssName()
+  {
+#ifdef ENABLE_GSS_TSIG
+    if (d_name != GSS_C_NO_NAME)
+      gss_release_name(&d_min, &d_name);
+#endif
+  };
+
+  //! Compare two Gss Names, if no gss support is compiled in, returns false always
+  //! This is not necessarily same as string comparison between two non-parsed names
+  bool operator==(const GssName& rhs)
+  {
+#ifdef ENABLE_GSS_TSIG
+    OM_uint32 maj, min;
+    int result;
+    maj = gss_compare_name(&min, d_name, rhs.d_name, &result);
+    return (maj == GSS_S_COMPLETE && result != 0);
+#endif
+    return false;
+  }
+
+  //! Compare two Gss Names, if no gss support is compiled in, returns false always
+  //! This is not necessarily same as string comparison between two non-parsed names
+  bool match(const std::string& name)
+  {
+#ifdef ENABLE_GSS_TSIG
+    OM_uint32 maj, min;
+    int result;
+    gss_name_t comp;
+    gss_buffer_desc buffer;
+    buffer.length = name.size();
+    buffer.value = (void*)name.c_str();
+    maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &comp);
+    if (maj != GSS_S_COMPLETE)
+      throw PDNSException("Could not import " + name + ": " + std::to_string(maj) + string(",") + std::to_string(min));
+    // do comparison
+    maj = gss_compare_name(&min, d_name, comp, &result);
+    gss_release_name(&min, &comp);
+    return (maj == GSS_S_COMPLETE && result != 0);
+#else
+    return false;
+#endif
+  };
+
+  //! Check if GSS name was parsed successfully.
+  bool valid()
+  {
+#ifdef ENABLE_GSS_TSIG
+    return d_maj == GSS_S_COMPLETE;
+#else
+    return false;
+#endif
+  }
+
+private:
+#ifdef ENABLE_GSS_TSIG
+  OM_uint32 d_maj, d_min;
+  gss_name_t d_name;
+#endif
+};
+
+class GssContext
+{
+public:
+  static bool supported(); //<! Returns true if GSS is supported in the first place
+  GssContext(); //<! Construct new GSS context with random name
+  GssContext(const DNSName& label); //<! Create or open existing named context
+
+  void setLocalPrincipal(const std::string& name); //<! Set our gss name
+  bool getLocalPrincipal(std::string& name); //<! Get our name
+  void setPeerPrincipal(const std::string& name); //<! Set remote name (do not use after negotiation)
+  bool getPeerPrincipal(std::string& name); //<! Return remote name, returns actual name after negotiation
+
+  void generateLabel(const std::string& suffix); //<! Generate random context name using suffix (such as mydomain.com)
+  void setLabel(const DNSName& label); //<! Set context name to this label
+  const DNSName& getLabel() { return d_label; } //<! Return context name
+
+  bool init(const std::string& input, std::string& output); //<! Perform GSS Initiate Security Context handshake
+  bool accept(const std::string& input, std::string& output); //<! Perform GSS Accept Security Context handshake
+  bool destroy(); //<! Release the cached context
+  bool expired(); //<! Check if context is expired
+  bool valid(); //<! Check if context is valid
+
+  bool sign(const std::string& input, std::string& output); //<! Sign something using gss
+  bool verify(const std::string& input, const std::string& signature); //<! Validate gss signature with something
+
+  GssContextError getError(); //<! Get error
+  const std::vector<std::string> getErrorStrings() { return d_gss_errors; } //<! Get native error texts
+private:
+  void release(); //<! Release context
+  void initialize(); //<! Initialize context
+#ifdef ENABLE_GSS_TSIG
+  void processError(const string& method, OM_uint32 maj, OM_uint32 min); //<! Process and fill error text vector
+#endif
+  DNSName d_label; //<! Context name
+  std::string d_peerPrincipal; //<! Remote name
+  std::string d_localPrincipal; //<! Our name
+  GssContextError d_error; //<! Context error
+  GssContextType d_type; //<! Context type
+  std::vector<std::string> d_gss_errors; //<! Native error string(s)
+  std::shared_ptr<GssSecContext> d_ctx; //<! Attached security context
+};
+
+bool gss_add_signature(const DNSName& context, const std::string& message, std::string& mac); //<! Create signature
+bool gss_verify_signature(const DNSName& context, const std::string& message, const std::string& mac); //<! Validate signature
index 0890aa3d2507b7072b7f9a8236f1be098736f067..2bb262e2a5af136bcfccb3912a5f43aadc7a846c 100644 (file)
@@ -32,6 +32,7 @@
 #include "dnssecinfra.hh"
 
 #include "dns_random.hh"
+#include "gss_context.hh"
 #include <boost/multi_index_container.hpp>
 #include "axfr-retriever.hh"
 #include <fstream>
index ac7f62a6ee7f45bc5027cac034840cb09b26c1de..0ef5e39c7bd8f76c9ab1afb5f51face5e0331b13 100644 (file)
@@ -1376,6 +1376,12 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
       return r;
     } else {
       getTSIGHashEnum(trc.d_algoName, p.d_tsig_algo);
+      if (p.d_tsig_algo == TSIG_GSS) {
+        GssContext gssctx(keyname);
+        if (!gssctx.getPeerPrincipal(p.d_peer_principal)) {
+          g_log<<Logger::Warning<<"Failed to extract peer principal from GSS context with keyname '"<<keyname<<"'"<<endl;
+        }
+      }
     }
     p.setTSIGDetails(trc, keyname, secret, trc.d_mac); // this will get copied by replyPacket()
     noCache=true;
index 3bd0511fc317400e6c13db7e9fd6c7ce8e8dab39..91faa7d344fae36677a5855488ef7da09b677ee3 100644 (file)
@@ -28,6 +28,7 @@
 #include "packetcache.hh"
 #include "dnsseckeeper.hh"
 #include "lua-auth4.hh"
+#include "gss_context.hh"
 
 #include "namespaces.hh"
 
index 4729d5beff1f075705e5704ae6005a15e2d9f8ce..60fe6a8a0e2abf842b8cc8fcd055fbac635d18af 100644 (file)
@@ -3828,7 +3828,7 @@ try
     DNSName zone(cmds.at(1));
     string kind = cmds.at(2);
     static vector<string> multiMetaWhitelist = {"ALLOW-AXFR-FROM", "ALLOW-DNSUPDATE-FROM",
-      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE",
+      "ALSO-NOTIFY", "TSIG-ALLOW-AXFR", "TSIG-ALLOW-DNSUPDATE", "GSS-ALLOW-AXFR-PRINCIPAL",
       "PUBLISH-CDS"};
     bool clobber = true;
     if (cmds.at(0) == "add-meta") {
index 8be3d49b441766904bb6a1b062f509e13b9948cb..2f69dbdf5f61d17500564462f7acb7bc75f35163 100644 (file)
@@ -130,6 +130,7 @@ pdns_recursor_SOURCES = \
        filterpo.cc filterpo.hh \
        fstrm_logger.cc fstrm_logger.hh \
        gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        histogram.hh \
        iputils.hh iputils.cc \
        ixfr.cc ixfr.hh \
@@ -276,6 +277,7 @@ testrunner_SOURCES = \
        ednssubnet.cc ednssubnet.hh \
        filterpo.cc filterpo.hh \
        gettime.cc gettime.hh \
+       gss_context.cc gss_context.hh \
        iputils.cc iputils.hh \
        ixfr.cc ixfr.hh \
        logger.cc logger.hh \
diff --git a/pdns/recursordist/gss_context.cc b/pdns/recursordist/gss_context.cc
new file mode 120000 (symlink)
index 0000000..3ed3e71
--- /dev/null
@@ -0,0 +1 @@
+../gss_context.cc
\ No newline at end of file
diff --git a/pdns/recursordist/gss_context.hh b/pdns/recursordist/gss_context.hh
new file mode 120000 (symlink)
index 0000000..050b795
--- /dev/null
@@ -0,0 +1 @@
+../gss_context.hh
\ No newline at end of file
index d09976108d9fc6c1cf0b6e19f3fe3bdcb17f98de..836c345f051d030eb49876624de279d8a9438e62 100644 (file)
@@ -48,6 +48,7 @@
 
 #include "dns_random.hh"
 #include <poll.h>
+#include "gss_context.hh"
 #include "namespaces.hh"
 
 using pdns::resolver::parseResult;
index 24763517b429ff31346bb4852be51186c1655e4f..ba4a919157f569c88fd887b5fb0935c651e380f6 100644 (file)
@@ -694,10 +694,20 @@ int PacketHandler::processUpdate(DNSPacket& p) {
         return RCode::Refused;
       }
 
-      for(const auto& key: tsigKeys) {
-        if (inputkey == DNSName(key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid.
-          validKey=true;
-          break;
+      if (p.d_tsig_algo == TSIG_GSS) {
+        GssName inputname(p.d_peer_principal); // match against principal since GSS
+        for(const auto& key: tsigKeys) {
+          if (inputname.match(key)) {
+            validKey = true;
+            break;
+          }
+        }
+      } else {
+        for(const auto& key: tsigKeys) {
+          if (inputkey == DNSName(key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid.
+            validKey=true;
+            break;
+          }
         }
       }
 
index ef0761e5edd15012099ac62f223722483bd93048..9470ef44308aaa53f38692b955ef797387afb25a 100644 (file)
@@ -12,6 +12,7 @@
 #include "dnssecinfra.hh"
 
 #include "dns_random.hh"
+#include "gss_context.hh"
 
 StatBag S;
 
@@ -19,13 +20,14 @@ int main(int argc, char** argv)
 try
 {
   if(argc < 4) {
-    cerr<<"Syntax: saxfr IP-address port zone [showdetails] [showflags] [unhash] [tsig:keyname:algo:secret]"<<endl;
+    cerr<<"Syntax: saxfr IP-address port zone [showdetails] [showflags] [unhash] [gss:remote-principal] [tsig:keyname:algo:secret]"<<endl;
     exit(EXIT_FAILURE);
   }
 
   bool showdetails=false;
   bool showflags=false;
   bool unhash=false;
+  bool gss=false;
   bool tsig=false;
   TSIGHashEnum tsig_algo;
   DNSName tsig_key;
@@ -41,6 +43,16 @@ try
         showflags=true;
       if (strcmp(argv[i], "unhash") == 0)
         unhash=true;
+      if (strncmp(argv[i], "gss:",4) == 0) {
+        gss=true;
+        tsig=true;
+        tsig_algo=TSIG_GSS;
+        remote_principal = string(argv[i]+4);
+        if (remote_principal.empty()) {
+          cerr<<"Remote principal is required"<<endl;
+          exit(EXIT_FAILURE);
+        }
+      }
       if (strncmp(argv[i], "tsig:",5) == 0) {
         vector<string> parts;
         tsig=true;
@@ -78,6 +90,75 @@ try
   Socket sock(dest.sin4.sin_family, SOCK_STREAM);
   sock.connect(dest);
 
+  if (gss) {
+#ifndef ENABLE_GSS_TSIG
+    cerr<<"No GSS support compiled in"<<endl;
+    exit(EXIT_FAILURE);
+#else
+    string input,output;
+    GssContext gssctx;
+    gssctx.generateLabel(argv[3]);
+    gssctx.setPeerPrincipal(remote_principal);
+
+    while(gssctx.init(input, output) && gssctx.valid() == false) {
+      input="";
+      DNSPacketWriter pwtkey(packet, gssctx.getLabel(), QType::TKEY, QClass::ANY);
+      TKEYRecordContent tkrc;
+      tkrc.d_algo = DNSName("gss-tsig.");
+      tkrc.d_inception = time((time_t*)NULL);
+      tkrc.d_expiration = tkrc.d_inception+15;
+      tkrc.d_mode = 3;
+      tkrc.d_error = 0;
+      tkrc.d_keysize = output.size();
+      tkrc.d_key = output;
+      tkrc.d_othersize = 0;
+      pwtkey.getHeader()->id = dns_random_uint16();
+      pwtkey.startRecord(gssctx.getLabel(), QType::TKEY, 3600, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+      tkrc.toPacket(pwtkey);
+      pwtkey.commit();
+      for(const string& msg :  gssctx.getErrorStrings()) {
+        cerr<<msg<<endl;
+      }
+
+      len = htons(packet.size());
+      if(sock.write((char *) &len, 2) != 2)
+        throw PDNSException("tcp write failed");
+      sock.writen(string((char*)&packet[0], packet.size()));
+      if(sock.read((char *) &len, 2) != 2)
+        throw PDNSException("tcp read failed");
+
+      len=ntohs(len);
+      std::unique_ptr<char[]> creply(new char[len]);
+      int n=0;
+      int numread;
+      while(n<len) {
+        numread=sock.read(creply.get()+n, len-n);
+        if(numread<0)
+          throw PDNSException("tcp read failed");
+        n+=numread;
+      }
+
+      MOADNSParser mdp(false, string(creply.get(), len));
+       if (mdp.d_header.rcode != 0) {
+         throw PDNSException(string("Remote server refused: ") + std::to_string(mdp.d_header.rcode));
+       }
+       for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
+         if(i->first.d_type != QType::TKEY) continue;
+         // recover TKEY record
+         tkrc = TKEYRecordContent(i->first.d_content->getZoneRepresentation());
+         input = tkrc.d_key;
+       }
+    }
+
+    if (gssctx.valid() == false) {
+      cerr<<"Could not create GSS context"<<endl;
+      exit(EXIT_FAILURE);
+    }
+
+    tsig_key = DNSName(gssctx.getLabel());
+#endif
+  }
+
   DNSPacketWriter pw(packet, DNSName(argv[3]), 252);
 
   pw.getHeader()->id = dns_random_uint16();
index d8bb30b17d67f6d2beab8df539334f7ef30dca9c..7c9e9aa316b4c065b5766f992ec36e443f5afda2 100644 (file)
@@ -7,6 +7,7 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   TKEYRecordContent tkey_in;
   std::shared_ptr<TKEYRecordContent> tkey_out(new TKEYRecordContent());
   DNSName name;
+  bool sign = false;
 
   if (!p.getTKEYRecord(&tkey_in, &name)) {
     g_log<<Logger::Error<<"TKEY request but no TKEY RR found"<<endl;
@@ -21,9 +22,26 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   tkey_out->d_inception = time((time_t*)nullptr);
   tkey_out->d_expiration = tkey_out->d_inception+15;
 
+  GssContext ctx(name);
+
   if (tkey_in.d_mode == 3) { // establish context
     if (tkey_in.d_algo == DNSName("gss-tsig.")) {
-      tkey_out->d_error = 19;
+      std::vector<std::string> meta;
+      DNSName tmpName(name);
+      do {
+        if (B.getDomainMetadata(tmpName, "GSS-ACCEPTOR-PRINCIPAL", meta) && meta.size()>0) {
+          break;
+        }
+      } while(tmpName.chopOff());
+
+      if (meta.size()>0) {
+        ctx.setLocalPrincipal(meta[0]);
+      }
+      // try to get a context
+      if (!ctx.accept(tkey_in.d_key, tkey_out->d_key))
+        tkey_out->d_error = 19;
+      else
+        sign = true;
     } else {
       tkey_out->d_error = 21; // BADALGO
     }
@@ -35,8 +53,10 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
         r->setRcode(RCode::NotAuth);
       return;
     }
-
-    tkey_out->d_error = 20; // BADNAME (because we have no support for anything here)
+    if (ctx.valid())
+      ctx.destroy();
+    else
+      tkey_out->d_error = 20; // BADNAME (because we have no support for anything here)
   } else {
     if (p.d_havetsig == false && tkey_in.d_mode != 2) { // unauthenticated
       if (p.d.opcode == Opcode::Update)
@@ -60,5 +80,20 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   zrr.dr.d_content = tkey_out;
   zrr.dr.d_place = DNSResourceRecord::ANSWER;
   r->addRecord(std::move(zrr));
+
+  if (sign)
+  {
+    TSIGRecordContent trc;
+    trc.d_algoName = DNSName("gss-tsig");
+    trc.d_time = tkey_out->d_inception;
+    trc.d_fudge = 300;
+    trc.d_mac = "";
+    trc.d_origID = p.d.id;
+    trc.d_eRcode = 0;
+    trc.d_otherData = "";
+    // this should cause it to lookup name context
+    r->setTSIGDetails(trc, name, name.toStringNoDot(), "", false);
+  }
+
   r->commitD();
 }
index c379c841afa6b724981b964747e33be0c75ffecb..de0471b73dde899b853c1ac6a9e4bdbda3ff7e78 100644 (file)
@@ -1,6 +1,7 @@
 
 #include "tsigverifier.hh"
 #include "dnssecinfra.hh"
+#include "gss_context.hh"
 
 bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
 {
index 9aca8ca2fb7016990699307750e2ba26b1317227..2e29adb8fc23da161d9a3c24857dcbfe264c82d4 100644 (file)
@@ -863,6 +863,8 @@ static bool isValidMetadataKind(const string& kind, bool readonly) {
     "NOTIFY-DNSUPDATE",
     "ALSO-NOTIFY",
     "AXFR-MASTER-TSIG",
+    "GSS-ALLOW-AXFR-PRINCIPAL",
+    "GSS-ACCEPTOR-PRINCIPAL",
     "IXFR",
     "LUA-AXFR-SCRIPT",
     "NSEC3NARROW",