From: Otto Moerbeek Date: Tue, 7 Dec 2021 16:07:43 +0000 (+0100) Subject: Revert of 9385 (which removed GSS_TSIG functionality) and rebase to master X-Git-Tag: rec-4.8.0-alpha1~31^2~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b08f131552e44b34f3482688f87150498a9c281b;p=thirdparty%2Fpdns.git Revert of 9385 (which removed GSS_TSIG functionality) and rebase to master 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! --- diff --git a/configure.ac b/configure.ac index 42c1bd21ea..34b74a6e1a 100644 --- a/configure.ac +++ b/configure.ac @@ -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])] ) diff --git a/docs/domainmetadata.rst b/docs/domainmetadata.rst index 382e68c053..a021fa6ea4 100644 --- a/docs/domainmetadata.rst +++ b/docs/domainmetadata.rst @@ -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`). diff --git a/docs/tsig.rst b/docs/tsig.rst index bd0a12fb6c..1f7941c89f 100644 --- a/docs/tsig.rst +++ b/docs/tsig.rst @@ -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 index 0000000000..9acd756976 --- /dev/null +++ b/m4/pdns_enable_gss_tsig.m4 @@ -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]) +]) diff --git a/modules/remotebackend/Makefile.am b/modules/remotebackend/Makefile.am index 1b5fc20025..718913dee4 100644 --- a/modules/remotebackend/Makefile.am +++ b/modules/remotebackend/Makefile.am @@ -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 \ diff --git a/pdns/Makefile.am b/pdns/Makefile.am index ec93fadc6f..4712a8ec88 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -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) diff --git a/pdns/dnspacket.cc b/pdns/dnspacket.cc index c7af0607ce..054e3539c6 100644 --- a/pdns/dnspacket.cc +++ b/pdns/dnspacket.cc @@ -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; diff --git a/pdns/dnssecinfra.cc b/pdns/dnssecinfra.cc index 43d623f93e..95693838ea 100644 --- a/pdns/dnssecinfra.cc +++ b/pdns/dnssecinfra.cc @@ -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(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 index 0000000000..818322eca9 --- /dev/null +++ b/pdns/gss_context.cc @@ -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 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(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(static_cast(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>> s_gss_accept_creds; +LockGuarded>> s_gss_init_creds; + +class GssSecContext : boost::noncopyable +{ +public: + GssSecContext(std::shared_ptr 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 d_cred; + OM_uint32 d_maj, d_min; + + enum + { + GssStateInitial, + GssStateNegotiate, + GssStateComplete, + GssStateError + } d_state; +}; + +LockGuarded>> 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 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(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(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(static_cast(input.c_str())); + + if (!d_peerPrincipal.empty()) { + buffer.value = const_cast(static_cast(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(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 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(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(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(static_cast(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(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(static_cast(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(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(static_cast(input.c_str())); + sign_tok.length = signature.size(); + sign_tok.value = const_cast(static_cast(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(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 index 0000000000..0fcf1bea7c --- /dev/null +++ b/pdns/gss_context.hh @@ -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 +#include +#include +#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(); // getErrorStrings() { return d_gss_errors; } // d_gss_errors; // d_ctx; // #include "axfr-retriever.hh" #include diff --git a/pdns/packethandler.cc b/pdns/packethandler.cc index ac7f62a6ee..0ef5e39c7b 100644 --- a/pdns/packethandler.cc +++ b/pdns/packethandler.cc @@ -1376,6 +1376,12 @@ std::unique_ptr 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< 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") { diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 8be3d49b44..2f69dbdf5f 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -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 index 0000000000..3ed3e719f9 --- /dev/null +++ b/pdns/recursordist/gss_context.cc @@ -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 index 0000000000..050b795024 --- /dev/null +++ b/pdns/recursordist/gss_context.hh @@ -0,0 +1 @@ +../gss_context.hh \ No newline at end of file diff --git a/pdns/resolver.cc b/pdns/resolver.cc index d09976108d..836c345f05 100644 --- a/pdns/resolver.cc +++ b/pdns/resolver.cc @@ -48,6 +48,7 @@ #include "dns_random.hh" #include +#include "gss_context.hh" #include "namespaces.hh" using pdns::resolver::parseResult; diff --git a/pdns/rfc2136handler.cc b/pdns/rfc2136handler.cc index 24763517b4..ba4a919157 100644 --- a/pdns/rfc2136handler.cc +++ b/pdns/rfc2136handler.cc @@ -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; + } } } diff --git a/pdns/saxfr.cc b/pdns/saxfr.cc index ef0761e5ed..9470ef4430 100644 --- a/pdns/saxfr.cc +++ b/pdns/saxfr.cc @@ -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]"< 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"<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< creply(new char[len]); + int n=0; + int numread; + while(nfirst.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"<id = dns_random_uint16(); diff --git a/pdns/tkey.cc b/pdns/tkey.cc index d8bb30b17d..7c9e9aa316 100644 --- a/pdns/tkey.cc +++ b/pdns/tkey.cc @@ -7,6 +7,7 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr& TKEYRecordContent tkey_in; std::shared_ptr tkey_out(new TKEYRecordContent()); DNSName name; + bool sign = false; if (!p.getTKEYRecord(&tkey_in, &name)) { g_log<& 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 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& 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& 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(); } diff --git a/pdns/tsigverifier.cc b/pdns/tsigverifier.cc index c379c841af..de0471b73d 100644 --- a/pdns/tsigverifier.cc +++ b/pdns/tsigverifier.cc @@ -1,6 +1,7 @@ #include "tsigverifier.hh" #include "dnssecinfra.hh" +#include "gss_context.hh" bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp) { diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 9aca8ca2fb..2e29adb8fc 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -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",