PDNS_ENABLE_VERBOSE_LOGGING
PDNS_ENABLE_PKCS11
+PDNS_ENABLE_GSS_TSIG
AC_SUBST([socketdir])
socketdir="/var/run"
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])]
)
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``.
GSS-ACCEPTOR-PRINCIPAL
----------------------
- .. versionchanged:: 4.4.0
- GSS support was removed
Use this principal for accepting GSS context.
(See :ref:`tsig-gss-tsig`).
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.
--- /dev/null
+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])
+])
../../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 \
$(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 \
AM_CPPFLAGS +=$(LUA_CFLAGS)
endif
+if GSS_TSIG
+AM_CPPFLAGS +=$(GSS_CFLAGS)
+endif
+
if LIBSODIUM
AM_CPPFLAGS +=$(LIBSODIUM_CFLAGS)
endif
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 \
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 \
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 \
pdnsutil_LDADD += $(LUA_LIBS)
endif
+if GSS_TSIG
+pdnsutil_LDADD += $(GSS_LIBS)
+endif
+
zone2sql_SOURCES = \
arguments.cc \
base32.cc \
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 \
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ gss_context.cc gss_context.hh \
iputils.cc \
logger.cc \
misc.cc misc.hh \
saxfr_LDADD += $(P11KIT1_LIBS)
endif
+if GSS_TSIG
+saxfr_LDADD += $(GSS_LIBS)
+endif
+
ixfrdist_SOURCES = \
arguments.cc \
axfr-retriever.cc \
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 \
ixfrdist_LDADD += $(P11KIT1_LIBS)
endif
+if GSS_TSIG
+ixfrdist_LDADD += $(GSS_LIBS)
+endif
+
+
ixplore_SOURCES = \
arguments.cc \
axfr-retriever.cc \
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ gss_context.cc gss_context.hh \
iputils.cc \
ixfr.cc ixfr.hh \
ixfrutils.cc ixfrutils.hh \
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
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ gss_context.cc gss_context.hh \
iputils.cc \
logger.cc \
misc.cc misc.hh \
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)
dnsrecords.cc \
dnssecinfra.cc \
dnswriter.cc dnswriter.hh \
+ gss_context.cc gss_context.hh \
iputils.cc \
logger.cc \
misc.cc misc.hh \
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)
dnsrecords.cc \
dnssecinfra.cc dnssecinfra.hh \
dnswriter.cc dnswriter.hh \
+ gss_context.cc gss_context.hh \
iputils.cc \
logger.cc \
misc.cc misc.hh \
ednsoptions.cc ednsoptions.hh \
ednssubnet.cc \
gettime.cc gettime.hh \
+ gss_context.cc gss_context.hh \
histogram.hh \
ipcipher.cc ipcipher.hh \
iputils.cc \
$(LIBDL) \
$(IPCRYPT_LIBS)
+if GSS_TSIG
+testrunner_LDADD += $(GSS_LIBS)
+endif
+
if PKCS11
testrunner_SOURCES += pkcs11signers.cc pkcs11signers.hh
testrunner_LDADD += $(P11KIT1_LIBS)
#include "dnssecinfra.hh"
#include "base64.hh"
#include "ednssubnet.hh"
+#include "gss_context.hh"
#include "dns_random.hh"
#include "shuffle.hh"
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;
#ifdef HAVE_P11KIT1
#include "pkcs11signers.hh"
#endif
+#include "gss_context.hh"
#include "misc.hh"
using namespace boost::assign;
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
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);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
#include "dnssecinfra.hh"
#include "dns_random.hh"
+#include "gss_context.hh"
#include <boost/multi_index_container.hpp>
#include "axfr-retriever.hh"
#include <fstream>
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;
#include "packetcache.hh"
#include "dnsseckeeper.hh"
#include "lua-auth4.hh"
+#include "gss_context.hh"
#include "namespaces.hh"
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") {
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 \
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 \
--- /dev/null
+../gss_context.cc
\ No newline at end of file
--- /dev/null
+../gss_context.hh
\ No newline at end of file
#include "dns_random.hh"
#include <poll.h>
+#include "gss_context.hh"
#include "namespaces.hh"
using pdns::resolver::parseResult;
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;
+ }
}
}
#include "dnssecinfra.hh"
#include "dns_random.hh"
+#include "gss_context.hh"
StatBag S;
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;
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;
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();
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;
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
}
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)
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();
}
#include "tsigverifier.hh"
#include "dnssecinfra.hh"
+#include "gss_context.hh"
bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
{
"NOTIFY-DNSUPDATE",
"ALSO-NOTIFY",
"AXFR-MASTER-TSIG",
+ "GSS-ALLOW-AXFR-PRINCIPAL",
+ "GSS-ACCEPTOR-PRINCIPAL",
"IXFR",
"LUA-AXFR-SCRIPT",
"NSEC3NARROW",